From 6506cfd775ee5cfd9726505c8bbc1578ee4d52e1 Mon Sep 17 00:00:00 2001 From: Pierre Paridans Date: Mon, 11 Mar 2019 14:22:54 +0000 Subject: [PATCH 1/2] [FIX] lunch: remove unused code --- .../lunch/static/src/js/lunch_kanban_controller.js | 14 -------------- addons/lunch/static/src/js/lunch_kanban_widget.js | 13 ------------- addons/lunch/static/src/js/lunch_payment_dialog.js | 6 +----- 3 files changed, 1 insertion(+), 32 deletions(-) diff --git a/addons/lunch/static/src/js/lunch_kanban_controller.js b/addons/lunch/static/src/js/lunch_kanban_controller.js index 8055f876d2e..6e1092a51f3 100644 --- a/addons/lunch/static/src/js/lunch_kanban_controller.js +++ b/addons/lunch/static/src/js/lunch_kanban_controller.js @@ -18,11 +18,9 @@ var LunchKanbanController = KanbanController.extend({ add_product: '_onAddProduct', change_location: '_onLocationChanged', change_user: '_onUserChanged', - edit_order: '_onEditOrder', open_wizard: '_onOpenWizard', order_now: '_onOrderNow', remove_product: '_onRemoveProduct', - save_order: '_onSaveOrder', unlink_order: '_onUnlinkOrder', }), @@ -134,12 +132,6 @@ var LunchKanbanController = KanbanController.extend({ self.reload(); }); }, - _onEditOrder: function (ev) { - ev.stopPropagation(); - - this.editMode = true; - this.reload(); - }, _onLocationChanged: function (ev) { var self = this; @@ -208,12 +200,6 @@ var LunchKanbanController = KanbanController.extend({ self.reload(); }); }, - _onSaveOrder: function (ev) { - ev.stopPropagation(); - - this.editMode = false; - this.reload(); - }, _onUserChanged: function (ev) { ev.stopPropagation(); diff --git a/addons/lunch/static/src/js/lunch_kanban_widget.js b/addons/lunch/static/src/js/lunch_kanban_widget.js index 73721c6def1..7bad4168b06 100644 --- a/addons/lunch/static/src/js/lunch_kanban_widget.js +++ b/addons/lunch/static/src/js/lunch_kanban_widget.js @@ -23,11 +23,9 @@ var LunchKanbanWidget = Widget.extend({ field_changed: '_onFieldChanged', }, events: { - 'click .o_add_money': '_onAddMoney', 'click .o_add_product': '_onAddProduct', 'click .o_lunch_widget_order_button': '_onOrderNow', 'click .o_remove_product': '_onRemoveProduct', - 'click .o_lunch_widget_save': '_onSaveOrder', 'click .o_lunch_widget_unlink': '_onUnlinkOrder', 'click .o_lunch_open_wizard': '_onLunchOpenWizard', }, @@ -124,11 +122,6 @@ var LunchKanbanWidget = Widget.extend({ // Handlers //-------------------------------------------------------------------------- - _onAddMoney: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - this.trigger_up('add_money', {}); - }, _onAddProduct: function (ev) { ev.preventDefault(); ev.stopPropagation(); @@ -163,12 +156,6 @@ var LunchKanbanWidget = Widget.extend({ this.trigger_up('remove_product', {lineId: $(ev.currentTarget).data('id')}); }, - _onSaveOrder: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - this.trigger_up('save_order', {}); - }, _onUnlinkOrder: function (ev) { ev.preventDefault(); ev.stopPropagation(); diff --git a/addons/lunch/static/src/js/lunch_payment_dialog.js b/addons/lunch/static/src/js/lunch_payment_dialog.js index 00e49299189..ae624403f12 100644 --- a/addons/lunch/static/src/js/lunch_payment_dialog.js +++ b/addons/lunch/static/src/js/lunch_payment_dialog.js @@ -1,19 +1,15 @@ odoo.define('lunch.LunchPaymentDialog', function (require) { "use strict"; -var core = require('web.core'); var Dialog = require('web.Dialog'); -var qweb = core.qweb; - var LunchPaymentDialog = Dialog.extend({ template: 'lunch.LunchPaymentDialog', init: function (parent, options) { - var self = this; this._super.apply(this, arguments); - options = (options || {}); + options = options || {}; this.message = options.message || ''; }, From bdc0b4b892dc17c0a51e454740633edc26f3de6d Mon Sep 17 00:00:00 2001 From: Pierre Paridans Date: Wed, 27 Feb 2019 11:53:47 +0000 Subject: [PATCH 2/2] [FIX] lunch: make it work on mobile This module didn't worked on mobile: user cannot select the products or see their cart. This commit reworks the LunchKanbanWidget template and introduces a dedicated template for mobile. This template introduces a button at the bottom of the screen to toggle the "cart" widget on mobile (hiding it by default). It tries to make the best use of the existing Bootstrap classes allowing to have a more responsive layout and reducing the custom CSS required by this module (both in desktop and in mobile). This commit adds a decent JS tests-suite (mobile & desktop) to this module as the existing one left substential parts of the UI untested (opening wizard when clicking on a kanban record, cart's lines content, clear cart button, widget structure and content based on the state...). Those tests are also less based on DOM nodes order/tag names and use instead dedicated classnames. It also modifies the LunchKanbanRecord click handling to had a dedicated event handler instead of overriding the default _onGlobalClick() and make it more testable. Task ID: 1945032 --- .../static/src/js/lunch_kanban_controller.js | 33 +- .../static/src/js/lunch_kanban_mobile.js | 74 ++ .../static/src/js/lunch_kanban_record.js | 46 +- .../lunch/static/src/js/lunch_kanban_view.js | 7 - .../lunch/static/src/scss/lunch_kanban.scss | 63 +- addons/lunch/static/src/xml/lunch_kanban.xml | 146 ++-- .../static/tests/lunch_kanban_mobile_tests.js | 208 +++++ .../lunch/static/tests/lunch_kanban_tests.js | 821 ++++++++++++++++++ addons/lunch/static/tests/lunch_test_utils.js | 58 ++ .../lunch/static/tests/test_lunch_kanban.js | 259 ------ addons/lunch/views/lunch_templates.xml | 13 +- .../static/src/scss/search_view_mobile.scss | 3 +- 12 files changed, 1334 insertions(+), 397 deletions(-) create mode 100644 addons/lunch/static/src/js/lunch_kanban_mobile.js create mode 100644 addons/lunch/static/tests/lunch_kanban_mobile_tests.js create mode 100644 addons/lunch/static/tests/lunch_kanban_tests.js create mode 100644 addons/lunch/static/tests/lunch_test_utils.js delete mode 100644 addons/lunch/static/tests/test_lunch_kanban.js diff --git a/addons/lunch/static/src/js/lunch_kanban_controller.js b/addons/lunch/static/src/js/lunch_kanban_controller.js index 6e1092a51f3..5545d926be3 100644 --- a/addons/lunch/static/src/js/lunch_kanban_controller.js +++ b/addons/lunch/static/src/js/lunch_kanban_controller.js @@ -67,7 +67,23 @@ var LunchKanbanController = KanbanController.extend({ }, }).then(function (data) { self.widgetData = data; - self.model._updateLocation(data.user_location[0]); + return self.model._updateLocation(data.user_location[0]); + }); + }, + /** + * Renders and appends the lunch banner widget. + * + * @private + */ + _renderLunchKanbanWidget: function () { + var self = this; + if (this.widget) { + this.widget.destroy(); + } + this.widgetData.wallet = parseFloat(this.widgetData.wallet).toFixed(2); + this.widget = new LunchKanbanWidget(this, _.extend(this.widgetData, {edit: this.editMode})); + return this.widget.appendTo(document.createDocumentFragment()).then(function () { + self.$('.o_lunch_kanban').prepend(self.widget.$el); }); }, _showPaymentDialog: function (title) { @@ -89,19 +105,8 @@ var LunchKanbanController = KanbanController.extend({ * @private */ _update: function () { - var self = this; - - var def = this._fetchWidgetData().then(function () { - if (self.widget) { - self.widget.destroy(); - } - self.widgetData.wallet = parseFloat(self.widgetData.wallet).toFixed(2); - self.widget = new LunchKanbanWidget(self, _.extend(self.widgetData, {edit: self.editMode})); - return self.widget.appendTo(document.createDocumentFragment()).then(function () { - self.$('.o_lunch_kanban').prepend(self.widget.$el); - }); - }); - return $.when(def, this._super.apply(self, arguments)); + var def = this._fetchWidgetData().then(this._renderLunchKanbanWidget.bind(this)); + return $.when(def, this._super.apply(this, arguments)); }, /** * Override to add the location domain (coming from the lunchKanbanWidget) diff --git a/addons/lunch/static/src/js/lunch_kanban_mobile.js b/addons/lunch/static/src/js/lunch_kanban_mobile.js new file mode 100644 index 00000000000..eddd22ac351 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_kanban_mobile.js @@ -0,0 +1,74 @@ +odoo.define('lunch.LunchKanbanMobile', function (require) { +"use strict"; + +var config = require('web.config'); +var LunchKanbanWidget = require('lunch.LunchKanbanWidget'); +var LunchKanbanController = require('lunch.LunchKanbanController'); + +if (!config.device.isMobile) { + return; +} + +LunchKanbanWidget.include({ + template: "LunchKanbanWidgetMobile", + + /** + * Override to set the toggle state allowing initially open it. + * + * @override + */ + init: function (parent, params) { + this._super.apply(this, arguments); + this.keepOpen = params.keepOpen || undefined; + }, +}); + +LunchKanbanController.include({ + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + this.openWidget = false; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Override to add the widget's toggle state to its data. + * + * @override + * @private + */ + _renderLunchKanbanWidget: function () { + this.widgetData.keepOpen = this.openWidget; + this.openWidget = false; + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _onAddProduct: function () { + this.openWidget = true; + this._super.apply(this, arguments); + }, + + /** + * @override + * @private + */ + _onRemoveProduct: function () { + this.openWidget = true; + this._super.apply(this, arguments); + }, +}); + +}); diff --git a/addons/lunch/static/src/js/lunch_kanban_record.js b/addons/lunch/static/src/js/lunch_kanban_record.js index a0d60588fae..ce9272b34f0 100644 --- a/addons/lunch/static/src/js/lunch_kanban_record.js +++ b/addons/lunch/static/src/js/lunch_kanban_record.js @@ -1,22 +1,36 @@ odoo.define('lunch.LunchKanbanRecord', function (require) { -"use strict"; + "use strict"; -/** - * This file defines the KanbanRecord for the Lunch Kanban view. - */ + /** + * This file defines the KanbanRecord for the Lunch Kanban view. + */ -var KanbanRecord = require('web.KanbanRecord'); + var KanbanRecord = require('web.KanbanRecord'); -var LunchKanbanRecord = KanbanRecord.extend({ - _onGlobalClick: function (ev) { - ev.preventDefault(); - // ignore clicks on oe_kanban_action elements - if (!$(ev.target).hasClass('oe_kanban_action')) { - this.trigger_up('open_wizard', {productId: this.recordData.product_id}); - } - }, -}); + var LunchKanbanRecord = KanbanRecord.extend({ + events: _.extend({}, KanbanRecord.prototype.events, { + 'click': '_onSelectRecord', + }), -return LunchKanbanRecord; + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- -}); + /** + * Open the add product wizard + * + * @private + * @param {MouseEvent} ev Click event + */ + _onSelectRecord: function (ev) { + ev.preventDefault(); + // ignore clicks on oe_kanban_action elements + if (!$(ev.target).hasClass('oe_kanban_action')) { + this.trigger_up('open_wizard', {productId: this.recordData.product_id}); + } + }, + }); + + return LunchKanbanRecord; + + }); diff --git a/addons/lunch/static/src/js/lunch_kanban_view.js b/addons/lunch/static/src/js/lunch_kanban_view.js index 72b30634e87..a0cd6d3f3e0 100644 --- a/addons/lunch/static/src/js/lunch_kanban_view.js +++ b/addons/lunch/static/src/js/lunch_kanban_view.js @@ -5,19 +5,12 @@ var LunchKanbanController = require('lunch.LunchKanbanController'); var LunchKanbanModel = require('lunch.LunchKanbanModel'); var LunchKanbanRenderer = require('lunch.LunchKanbanRenderer'); -var config = require('web.config'); var core = require('web.core'); var KanbanView = require('web.KanbanView'); var view_registry = require('web.view_registry'); var _lt = core._lt; -if (config.device.isMobile) { - // use the classical KanbanView in mobile - view_registry.add('lunch_kanban', KanbanView); - return; -} - var LunchKanbanView = KanbanView.extend({ config: _.extend({}, KanbanView.prototype.config, { Controller: LunchKanbanController, diff --git a/addons/lunch/static/src/scss/lunch_kanban.scss b/addons/lunch/static/src/scss/lunch_kanban.scss index 7c50ae36358..51acfd2b3f8 100644 --- a/addons/lunch/static/src/scss/lunch_kanban.scss +++ b/addons/lunch/static/src/scss/lunch_kanban.scss @@ -6,6 +6,8 @@ height: 100%; .o_lunch_kanban_banner { flex: 0 0 auto; + border-bottom: 1px solid #CED4DA; + background-color: white; } .o_kanban_view { flex: 1 1 100%; @@ -19,52 +21,47 @@ } .o_lunch_widget { - display: flex; - border-bottom: 1px solid #CED4DA; - background-color: white; min-height: 90px; max-height: 33vh; overflow-y: auto; - .o_lunch_widget_info { - padding: 5px 12px; - margin: 4px 8px; - width: 300px; - flex: 1 1 auto; - overflow-y: auto; - - .o_lunch_ordered { - color: white; - background-color: #F0D970; - padding: 1px 3px; - border-radius: .25em; + .o_lunch_widget_info.card { + &, .card-title, .card-body { + color: $o-main-text-color; + background-color: inherit !important; } - .o_lunch_confirmed { - color: white; - background-color: #00BA4E; - padding: 1px 3px; - border-radius: .25em; - } - - .o_lunch_widget_title { + .card-title { font-weight: bold; - font-size: initial; + margin-bottom: 0; } - .o_lunch_widget_order_button { - display: block; - width: 100%; - margin-bottom: 4px; - margin-top: 12px; + .card-body { + padding: 0.5rem 1rem; } - .o_lunch_open_wizard { - cursor: pointer; - &:hover { - font-weight: bolder; + .btn-link { + padding: 0; + &.o_lunch_open_wizard { + color: $o-main-text-color; + font-weight: normal; } } } } } + +@include media-breakpoint-down(sm) { + .o_lunch_kanban { + details summary { + // Hide the caret. For details see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary + list-style-type: none; + &::-webkit-details-marker { + display: none + } + } + .o_lunch_widget { + max-height: 100% + } + } +} diff --git a/addons/lunch/static/src/xml/lunch_kanban.xml b/addons/lunch/static/src/xml/lunch_kanban.xml index 62a2edd2900..3b3b319ce95 100644 --- a/addons/lunch/static/src/xml/lunch_kanban.xml +++ b/addons/lunch/static/src/xml/lunch_kanban.xml @@ -1,84 +1,88 @@ -
-
-
-
- -
-
-
-
-
- Your Account - - - - +
+
+
+
+
+ +
+
+
+
+
+
+ Your Account + + + + +
+
-
+
- + - + -
-
- Your order - -
-
-
- -
+
-
-
- Total -
-
+
+

+ Total -

+ +
-
@@ -89,7 +93,7 @@
- + ctx.value = _.str.sprintf('%.2f', parseFloat(ctx.value)); @@ -104,9 +108,23 @@ - +
+ + +
+ + + Your cart + ( + + + ) + + +
+
diff --git a/addons/lunch/static/tests/lunch_kanban_mobile_tests.js b/addons/lunch/static/tests/lunch_kanban_mobile_tests.js new file mode 100644 index 00000000000..216d830bb47 --- /dev/null +++ b/addons/lunch/static/tests/lunch_kanban_mobile_tests.js @@ -0,0 +1,208 @@ +odoo.define('lunch.lunchKanbanMobileTests', function (require) { +"use strict"; + +const LunchKanbanView = require('lunch.LunchKanbanView'); + +const testUtils = require('web.test_utils'); +const {createLunchKanbanView, mockLunchRPC} = require('lunch.test_utils'); + +QUnit.module('Views'); + +QUnit.module('LunchKanbanView Mobile', { + beforeEach() { + const PORTAL_GROUP_ID = 1234; + + this.data = { + 'product': { + fields: { + is_available_at: {string: 'Product Availability', type: 'many2one', relation: 'lunch.location'}, + category_id: {string: 'Product Category', type: 'many2one', relation: 'lunch.product.category'}, + supplier_id: {string: 'Vendor', type: 'many2one', relation: 'lunch.supplier'}, + }, + records: [ + {id: 1, name: 'Tuna sandwich', is_available_at: 1}, + ], + }, + 'lunch.order': { + fields: {}, + update_quantity() { + return $.when(); + }, + }, + 'ir.model.data': { + fields: {}, + xmlid_to_res_id() { + return $.when(PORTAL_GROUP_ID); + }, + }, + 'lunch.location': { + fields: { + name: {string: 'Name', type: 'char'}, + }, + records: [ + {id: 1, name: "Office 1"}, + {id: 2, name: "Office 2"}, + ], + }, + }; + this.regularInfos = { + user_location: [2, "Office 2"], + }; + }, +}, function () { + QUnit.test('basic rendering', function (assert) { + assert.expect(9); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: this.regularInfos, + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + assert.containsOnce(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', + "should have 1 records in the renderer"); + + // check view layout + assert.containsOnce(kanban, '.o_content > div', + "should have 1 column"); + assert.containsNone(kanban, '.o_content > div.o_search_panel', + "shouldn't have a 'lunch filters' column"); + assert.containsOnce(kanban, '.o_content > .o_lunch_kanban', + "should have a 'kanban lunch wrapper' column"); + assert.containsOnce(kanban, '.o_lunch_kanban > .o_kanban_view', + "should have a 'classical kanban view' column"); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_lunch_kanban_view', + "should have classname 'o_lunch_kanban_view'"); + assert.containsOnce($('.o_lunch_kanban'), '> details', + "should have a 'lunch kanban' details/summary discolure panel"); + assert.hasClass($('.o_lunch_kanban > details'), 'fixed-bottom', + "should have classname 'fixed-bottom'"); + assert.isNotVisible($('.o_lunch_kanban > details .o_lunch_kanban_banner'), + "shouldn't have a visible 'lunch kanban' banner"); + + kanban.destroy(); + }); + + QUnit.module('LunchKanbanWidget', function () { + QUnit.test('toggle', function (assert) { + assert.expect(6); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + total: "3.00", + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $details = $('.o_lunch_kanban > details'); + assert.isNotVisible($details.find('.o_lunch_kanban_banner'), + "shouldn't have a visible 'lunch kanban' banner"); + assert.isVisible($details.find('> summary'), + "should hava a visible cart toggle button"); + assert.containsOnce($details, '> summary:contains(Your cart)', + "should have 'Your cart' in the button text"); + assert.containsOnce($details, '> summary:contains(3.00)', + "should have '3.00' in the button text"); + + testUtils.dom.click($details.find('> summary')); + assert.isVisible($details.find('.o_lunch_kanban_banner'), + "should have a visible 'lunch kanban' banner"); + + testUtils.dom.click($details.find('> summary')); + assert.isNotVisible($details.find('.o_lunch_kanban_banner'), + "shouldn't have a visible 'lunch kanban' banner"); + + kanban.destroy(); + }); + + QUnit.test('keep open when adding quantities', function (assert) { + assert.expect(6); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + lines: [ + { + id: 6, + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + quantity: 1.0, + }, + ], + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $details = $('.o_lunch_kanban > details'); + assert.isNotVisible($details.find('.o_lunch_kanban_banner'), + "shouldn't have a visible 'lunch kanban' banner"); + assert.isVisible($details.find('> summary'), + "should hava a visible cart toggle button"); + + testUtils.dom.click($details.find('> summary')); + assert.isVisible($details.find('.o_lunch_kanban_banner'), + "should have a visible 'lunch kanban' banner"); + + const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', + "should have 1 order line"); + + let $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); + + testUtils.dom.click($firstLine.find('button.o_add_product')); + assert.isVisible($('.o_lunch_kanban > details .o_lunch_kanban_banner'), + "add quantity should keep 'lunch kanban' banner open"); + + $firstLine = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1) .o_lunch_widget_lines > li:first'); + + testUtils.dom.click($firstLine.find('button.o_remove_product')); + assert.isVisible($('.o_lunch_kanban > details .o_lunch_kanban_banner'), + "remove quantity should keep 'lunch kanban' banner open"); + + kanban.destroy(); + }); + }); +}); + +}); diff --git a/addons/lunch/static/tests/lunch_kanban_tests.js b/addons/lunch/static/tests/lunch_kanban_tests.js new file mode 100644 index 00000000000..571b11ed52e --- /dev/null +++ b/addons/lunch/static/tests/lunch_kanban_tests.js @@ -0,0 +1,821 @@ +odoo.define('lunch.lunchKanbanTests', function (require) { +"use strict"; + +const LunchKanbanView = require('lunch.LunchKanbanView'); + +const testUtils = require('web.test_utils'); +const {createLunchKanbanView, mockLunchRPC} = require('lunch.test_utils'); + +QUnit.module('Views'); + +QUnit.module('LunchKanbanView', { + beforeEach() { + const PORTAL_GROUP_ID = 1234; + + this.data = { + 'product': { + fields: { + is_available_at: {string: 'Product Availability', type: 'many2one', relation: 'lunch.location'}, + category_id: {string: 'Product Category', type: 'many2one', relation: 'lunch.product.category'}, + supplier_id: {string: 'Vendor', type: 'many2one', relation: 'lunch.supplier'}, + }, + records: [ + {id: 1, name: 'Tuna sandwich', is_available_at: 1}, + ], + }, + 'lunch.order': { + fields: {}, + update_quantity() { + return $.when(); + }, + }, + 'lunch.product.category': { + fields: {}, + records: [], + }, + 'lunch.supplier': { + fields: {}, + records: [], + }, + 'ir.model.data': { + fields: {}, + xmlid_to_res_id() { + return $.when(PORTAL_GROUP_ID); + }, + }, + 'lunch.location': { + fields: { + name: {string: 'Name', type: 'char'}, + }, + records: [ + {id: 1, name: "Office 1"}, + {id: 2, name: "Office 2"}, + ], + }, + 'res.users': { + fields: { + name: {string: 'Name', type: 'char'}, + groups_id: {string: 'Groups', type: 'many2many'}, + }, + records: [ + {id: 1, name: "Mitchell Admin", groups_id: []}, + {id: 2, name: "Marc Demo", groups_id: []}, + {id: 3, name: "Jean-Luc Portal", groups_id: [PORTAL_GROUP_ID]}, + ], + }, + }; + this.regularInfos = { + username: "Marc Demo", + wallet: 36.5, + is_manager: false, + currency: { + symbol: "\u20ac", + position: "after" + }, + user_location: [2, "Office 2"], + }; + this.managerInfos = { + username: "Mitchell Admin", + wallet: 47.6, + is_manager: true, + currency: { + symbol: "\u20ac", + position: "after" + }, + user_location: [2, "Office 2"], + }; + }, +}, function () { + QUnit.test('basic rendering', function (assert) { + assert.expect(7); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: this.regularInfos, + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + assert.containsOnce(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', + "should have 1 records in the renderer"); + + // check view layout + assert.containsN(kanban, '.o_content > div', 2, + "should have 2 columns"); + assert.containsOnce(kanban, '.o_content > div.o_search_panel', + "should have a 'lunch filters' column"); + assert.containsOnce(kanban, '.o_content > .o_lunch_kanban', + "should have a 'kanban lunch wrapper' column"); + assert.containsOnce(kanban, '.o_lunch_kanban > .o_kanban_view', + "should have a 'classical kanban view' column"); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_lunch_kanban_view', + "should have classname 'o_lunch_kanban_view'"); + assert.containsOnce(kanban, '.o_lunch_kanban > .o_lunch_kanban_banner', + "should have a 'lunch kanban' banner"); + + kanban.destroy(); + }); + + QUnit.module('LunchKanbanWidget', function () { + + QUnit.test('empty cart', function (assert) { + assert.expect(3); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: this.regularInfos, + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $kanbanWidget = kanban.$('.o_lunch_widget'); + + assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, + "should have 3 columns"); + assert.isVisible($kanbanWidget.find('> .o_lunch_widget_info:first'), + "should have the first column visible"); + assert.strictEqual($kanbanWidget.find('> .o_lunch_widget_info:not(:first)').html().trim(), "", + "all columns but the first should be empty"); + + kanban.destroy(); + }); + + QUnit.test('non-empty cart', function (assert) { + assert.expect(17); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + total: "3.00", + lines: [ + { + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + quantity: 1.0, + }, + ], + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $kanbanWidget = kanban.$('.o_lunch_widget'); + + assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, + "should have 3 columns"); + + assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(1)', + "should have a second column"); + + const $widgetSecondColumn = $kanbanWidget.find('.o_lunch_widget_info:eq(1)'); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_unlink', + "should have a button to clear the order"); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', + "should have 1 order line"); + + const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); + assert.containsOnce($firstLine, 'button.o_remove_product', + "should have a button to remove a product quantity on each line"); + assert.containsOnce($firstLine, 'button.o_add_product', + "should have a button to add a product quantity on each line"); + assert.containsOnce($firstLine, '.o_lunch_product_quantity > :eq(1)', + "should have the line's quantity"); + assert.strictEqual($firstLine.find('.o_lunch_product_quantity > :eq(1)').text().trim(), "1", + "should have 1 as the line's quantity"); + assert.containsOnce($firstLine, '.o_lunch_open_wizard', + "should have the line's product name to open the wizard"); + assert.strictEqual($firstLine.find('.o_lunch_open_wizard').text().trim(), "Tuna sandwich", + "should have 'Tuna sandwich' as the line's product name"); + assert.containsOnce($firstLine, '.o_field_monetary', + "should have the line's amount"); + assert.strictEqual($firstLine.find('.o_field_monetary').text().trim(), "3.00€", + "should have '3.00€' as the line's amount"); + + assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(2)', + "should have a third column"); + + const $widgetThirdColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(2)'); + + assert.containsOnce($widgetThirdColumn, '.o_field_monetary', + "should have an account balance"); + assert.strictEqual($widgetThirdColumn.find('.o_field_monetary').text().trim(), "3.00€", + "should have '3.00€' in the account balance"); + assert.containsOnce($widgetThirdColumn, '.o_lunch_widget_order_button', + "should have a button to validate the order"); + assert.strictEqual($widgetThirdColumn.find('.o_lunch_widget_order_button').text().trim(), "Order now", + "should have 'Order now' as the validate order button text"); + + kanban.destroy(); + }); + + QUnit.test('ordered cart', function (assert) { + assert.expect(15); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + raw_state: "ordered", + state: "Ordered", + lines: [ + { + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + quantity: 1.0, + }, + ], + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $kanbanWidget = kanban.$('.o_lunch_widget'); + + assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, + "should have 3 columns"); + + assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(1)', + "should have a second column"); + + const $widgetSecondColumn = $kanbanWidget.find('.o_lunch_widget_info:eq(1)'); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_unlink', + "should have a button to clear the order"); + assert.containsOnce($widgetSecondColumn, '.badge.badge-warning.o_lunch_ordered', + "should have an ordered state badge"); + assert.strictEqual($widgetSecondColumn.find('.o_lunch_ordered').text().trim(), "Ordered", + "should have 'Ordered' in the state badge"); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', + "should have 1 order line"); + + const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); + assert.containsOnce($firstLine, 'button.o_remove_product', + "should have a button to remove a product quantity on each line"); + assert.containsOnce($firstLine, 'button.o_add_product', + "should have a button to add a product quantity on each line"); + assert.containsOnce($firstLine, '.o_lunch_product_quantity > :eq(1)', + "should have the line's quantity"); + assert.strictEqual($firstLine.find('.o_lunch_product_quantity > :eq(1)').text().trim(), "1", + "should have 1 as the line's quantity"); + assert.containsOnce($firstLine, '.o_lunch_open_wizard', + "should have the line's product name to open the wizard"); + assert.strictEqual($firstLine.find('.o_lunch_open_wizard').text().trim(), "Tuna sandwich", + "should have 'Tuna sandwich' as the line's product name"); + assert.containsOnce($firstLine, '.o_field_monetary', + "should have the line's amount"); + assert.strictEqual($firstLine.find('.o_field_monetary').text().trim(), "3.00€", + "should have '3.00€' as the line's amount"); + + assert.strictEqual($kanbanWidget.find('> .o_lunch_widget_info:eq(2)').html().trim(), "", + "third column should be empty"); + + kanban.destroy(); + }); + + QUnit.test('confirmed cart', function (assert) { + assert.expect(15); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + raw_state: "confirmed", + state: "Received", + lines: [ + { + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + quantity: 1.0, + }, + ], + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $kanbanWidget = kanban.$('.o_lunch_widget'); + + assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, + "should have 3 columns"); + + assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(1)', + "should have a second column"); + + const $widgetSecondColumn = $kanbanWidget.find('.o_lunch_widget_info:eq(1)'); + + assert.containsNone($widgetSecondColumn, '.o_lunch_widget_unlink', + "shouldn't have a button to clear the order"); + assert.containsOnce($widgetSecondColumn, '.badge.badge-success.o_lunch_confirmed', + "should have a confirmed state badge"); + assert.strictEqual($widgetSecondColumn.find('.o_lunch_confirmed').text().trim(), "Received", + "should have 'Received' in the state badge"); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', + "should have 1 order line"); + + const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); + assert.containsNone($firstLine, 'button.o_remove_product', + "shouldn't have a button to remove a product quantity on each line"); + assert.containsNone($firstLine, 'button.o_add_product', + "shouldn't have a button to add a product quantity on each line"); + assert.containsOnce($firstLine, '.o_lunch_product_quantity', + "should have the line's quantity"); + assert.strictEqual($firstLine.find('.o_lunch_product_quantity').text().trim(), "1", + "should have 1 as the line's quantity"); + assert.containsOnce($firstLine, '.o_lunch_open_wizard', + "should have the line's product name to open the wizard"); + assert.strictEqual($firstLine.find('.o_lunch_open_wizard').text().trim(), "Tuna sandwich", + "should have 'Tuna sandwich' as the line's product name"); + assert.containsOnce($firstLine, '.o_field_monetary', + "should have the line's amount"); + assert.strictEqual($firstLine.find('.o_field_monetary').text().trim(), "3.00€", + "should have '3.00€' as the line's amount"); + + assert.strictEqual($kanbanWidget.find('> .o_lunch_widget_info:eq(2)').html().trim(), "", + "third column should be empty"); + + kanban.destroy(); + }); + + QUnit.test('regular user', function (assert) { + assert.expect(11); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: this.regularInfos, + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $kanbanWidget = kanban.$('.o_lunch_widget'); + + assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:first', + "should have a first column"); + + const $widgetFirstColumn = $kanbanWidget.find('.o_lunch_widget_info:first'); + + assert.containsOnce($widgetFirstColumn, 'img.rounded-circle', + "should have a rounded avatar image"); + + assert.containsOnce($widgetFirstColumn, '.o_lunch_user_field', + "should have a user field"); + assert.containsNone($widgetFirstColumn, '.o_lunch_user_field > .o_field_widget', + "shouldn't have a field widget in the user field"); + assert.strictEqual($widgetFirstColumn.find('.o_lunch_user_field').text().trim(), "Marc Demo", + "should have 'Marc Demo' in the user field"); + + assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field', + "should have a location field"); + assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field > .o_field_many2one[name="locations"]', + "should have a many2one in the location field"); + + testUtils.fields.many2one.clickOpenDropdown('locations'); + const $input = $widgetFirstColumn.find('.o_field_many2one[name="locations"] input'); + assert.containsN($input.autocomplete('widget'), 'li', 2, + "autocomplete dropdown should have 2 entries"); + assert.strictEqual($input.val(), "Office 2", + "locations input should have 'Office 2' as value"); + + assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field + div', + "should have an account balance"); + assert.strictEqual($widgetFirstColumn.find('.o_lunch_location_field + div .o_field_monetary').text().trim(), "36.50€", + "should have '36.50€' in the account balance"); + + kanban.destroy(); + }); + + QUnit.test('manager user', function (assert) { + assert.expect(12); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: this.managerInfos, + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $kanbanWidget = kanban.$('.o_lunch_widget'); + + assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:first', + "should have a first column"); + + const $widgetFirstColumn = $kanbanWidget.find('.o_lunch_widget_info:first'); + + assert.containsOnce($widgetFirstColumn, 'img.rounded-circle', + "should have a rounded avatar image"); + + assert.containsOnce($widgetFirstColumn, '.o_lunch_user_field', + "should have a user field"); + assert.containsOnce($widgetFirstColumn, '.o_lunch_user_field > .o_field_many2one[name="users"]', + "shouldn't have a field widget in the user field"); + + testUtils.fields.many2one.clickOpenDropdown('users'); + const $userInput = $widgetFirstColumn.find('.o_field_many2one[name="users"] input'); + assert.containsN($userInput.autocomplete('widget'), 'li', 2, + "users autocomplete dropdown should have 2 entries"); + assert.strictEqual($userInput.val(), "Mitchell Admin", + "should have 'Mitchell Admin' as value in user field"); + + assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field', + "should have a location field"); + assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field > .o_field_many2one[name="locations"]', + "should have a many2one in the location field"); + + testUtils.fields.many2one.clickOpenDropdown('locations'); + const $locationInput = $widgetFirstColumn.find('.o_field_many2one[name="locations"] input'); + assert.containsN($locationInput.autocomplete('widget'), 'li', 2, + "locations autocomplete dropdown should have 2 entries"); + assert.strictEqual($locationInput.val(), "Office 2", + "should have 'Office 2' as value"); + + assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field + div', + "should have an account balance"); + assert.strictEqual($widgetFirstColumn.find('.o_lunch_location_field + div .o_field_monetary').text().trim(), "47.60€", + "should have '47.60€' in the account balance"); + + kanban.destroy(); + }); + + QUnit.test('add a product', function (assert) { + assert.expect(1); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: this.regularInfos, + userLocation: this.data['lunch.location'].records[0].id, + }), + intercepts: { + do_action: function (ev) { + assert.deepEqual(ev.data.action, { + res_model: 'lunch.order.temp', + type: 'ir.actions.act_window', + views: [[false, 'form']], + target: 'new', + context: { + default_product_id: undefined, + line_id: false, + }, + }, + "should open the wizard"); + }, + }, + }); + + testUtils.dom.click(kanban.$('.o_kanban_record:first')); + + kanban.destroy(); + }); + + QUnit.test('add product quantity', function (assert) { + assert.expect(3); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: Object.assign({}, this.data, { + 'lunch.order': { + fields: {}, + update_quantity([lineIds, increment]) { + assert.deepEqual(lineIds, [6], "should have [6] as lineId to update quantity"); + assert.strictEqual(increment, 1, "should have +1 as increment to update quantity"); + return $.when(); + }, + }, + }), + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + lines: [ + { + id: 6, + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + quantity: 1.0, + }, + ], + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', + "should have 1 order line"); + + const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); + + testUtils.dom.click($firstLine.find('button.o_add_product')); + + kanban.destroy(); + }); + + QUnit.test('remove product quantity', function (assert) { + assert.expect(3); + + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: Object.assign({}, this.data, { + 'lunch.order': { + fields: {}, + update_quantity([lineIds, increment]) { + assert.deepEqual(lineIds, [6], "should have [6] as lineId to update quantity"); + assert.strictEqual(increment, -1, "should have -1 as increment to update quantity"); + return $.when(); + }, + }, + }), + arch: ` + + + +
+
+
+
+ `, + mockRPC: mockLunchRPC({ + infos: Object.assign({}, this.regularInfos, { + lines: [ + { + id: 6, + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + quantity: 1.0, + }, + ], + }), + userLocation: this.data['lunch.location'].records[0].id, + }), + }); + + const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); + + assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', + "should have 1 order line"); + + const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); + + testUtils.dom.click($firstLine.find('button.o_remove_product')); + + kanban.destroy(); + }); + + QUnit.test('clear order', function (assert) { + assert.expect(1); + + const self = this; + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: function (route) { + if (route.startsWith('/lunch')) { + if (route === '/lunch/trash') { + assert.ok('should perform clear order RPC call'); + return $.when(); + } + return mockLunchRPC({ + infos: Object.assign({}, self.regularInfos, { + lines: [ + { + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + }, + ], + }), + userLocation: self.data['lunch.location'].records[0].id, + }).apply(this, arguments); + } + return this._super.apply(this, arguments); + }, + }); + + const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); + + testUtils.dom.click($widgetSecondColumn.find('button.o_lunch_widget_unlink')); + + kanban.destroy(); + }); + + QUnit.test('validate order: success', function (assert) { + assert.expect(1); + + const self = this; + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: function (route) { + if (route.startsWith('/lunch')) { + if (route === '/lunch/pay') { + assert.ok("should perform pay order RPC call"); + return $.when(true); + } + return mockLunchRPC({ + infos: Object.assign({}, self.regularInfos, { + lines: [ + { + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + }, + ], + }), + userLocation: self.data['lunch.location'].records[0].id, + }).apply(this, arguments); + } + return this._super.apply(this, arguments); + }, + }); + + const $widgetThirdColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(2)'); + + testUtils.dom.click($widgetThirdColumn.find('button.o_lunch_widget_order_button')); + + kanban.destroy(); + }); + + QUnit.test('validate order: failure', function (assert) { + assert.expect(5); + + const self = this; + const kanban = createLunchKanbanView({ + View: LunchKanbanView, + model: 'product', + data: this.data, + arch: ` + + + +
+
+
+
+ `, + mockRPC: function (route) { + if (route.startsWith('/lunch')) { + if (route === '/lunch/pay') { + assert.ok('should perform pay order RPC call'); + return $.when(false); + } + if (route === '/lunch/payment_message') { + assert.ok('should perform payment message RPC call'); + return $.when({ message: 'This is a payment message.'}); + } + return mockLunchRPC({ + infos: Object.assign({}, self.regularInfos, { + lines: [ + { + product: [1, "Tuna sandwich", "3.00"], + toppings: [], + }, + ], + }), + userLocation: self.data['lunch.location'].records[0].id, + }).apply(this, arguments); + } + return this._super.apply(this, arguments); + }, + }); + + const $widgetThirdColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(2)'); + + testUtils.dom.click($widgetThirdColumn.find('button.o_lunch_widget_order_button')); + + assert.containsOnce(document.body, '.modal', "should open a Dialog box"); + assert.strictEqual($('.modal-title').text().trim(), + "Not enough money in your wallet", "should have a Dialog's title"); + assert.strictEqual($('.modal-body').text().trim(), + "This is a payment message.", "should have a Dialog's message"); + + kanban.destroy(); + }); + + }); +}); + +}); diff --git a/addons/lunch/static/tests/lunch_test_utils.js b/addons/lunch/static/tests/lunch_test_utils.js new file mode 100644 index 00000000000..3c8b52d18e3 --- /dev/null +++ b/addons/lunch/static/tests/lunch_test_utils.js @@ -0,0 +1,58 @@ +odoo.define('lunch.test_utils', function (require) { +"use strict"; + +const AbstractStorageService = require('web.AbstractStorageService'); +const RamStorage = require('web.RamStorage'); +const {createView} = require('web.test_utils'); + +/** + * Helper to create a lunch kanban view with searchpanel + * + * @param {object} params + */ +function createLunchKanbanView(params) { + const archPieces = params.arch.split(''); + params.arch = ` + ${archPieces[0]} + + + + + ${archPieces[1]} + `; + if (!params.services || !params.services.local_storage) { + // the searchPanel uses the localStorage to store/retrieve default + // active category value + params.services = params.services || {}; + const RamStorageService = AbstractStorageService.extend({ + storage: new RamStorage(), + }); + params.services.local_storage = RamStorageService; + } + return createView(params); +} + +/** + * Helper to generate a mockRPC function for the mandatory lunch routes (prefixed by '/lunch') + * + * @param {object} infos + * @param {integer} userLocation + */ +function mockLunchRPC({infos, userLocation}) { + return function (route) { + if (route === '/lunch/infos') { + return $.when(infos); + } + if (route === '/lunch/user_location_get') { + return $.when(userLocation); + } + return this._super.apply(this, arguments); + }; +} + +return { + createLunchKanbanView, + mockLunchRPC, +}; + +}); diff --git a/addons/lunch/static/tests/test_lunch_kanban.js b/addons/lunch/static/tests/test_lunch_kanban.js deleted file mode 100644 index 020c3748150..00000000000 --- a/addons/lunch/static/tests/test_lunch_kanban.js +++ /dev/null @@ -1,259 +0,0 @@ -odoo.define('lunch.lunchKanbanTests', function (require) { -"use strict"; - -var LunchKanbanView = require('lunch.LunchKanbanView'); -var LunchKanbanWidget = require('lunch.LunchKanbanWidget'); -var testUtils = require('web.test_utils'); - -var createView = testUtils.createView; - -QUnit.module('Views', { - beforeEach: function () { - this.data = { - product: { - fields: { - id: {string: 'ID', type: 'integer'}, - is_available_at: {string: 'available_location_ids', type: 'integer'}, - }, - records: [ - {id: 1, name: 'Tuna sandwich', price: 3.0, is_available_at: 1}, - ] - }, - }; - }, -}, function () { - QUnit.module('LunchKanbanView'); - - QUnit.test('Simple rendering of LunchKanbanView', function (assert) { - assert.expect(7); - - var lunchKanban = createView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: '' + - '
' + - '
' + - '
', - mockRPC: function(route, args) { - if (route === '/lunch/infos') { - return $.when({ - order: 1, - wallet: 20, - username: 'Marc Demo', - userimage: '', - is_manager: false, - users: [ - {id: 1, name: 'Mitchell Admin'}, - {id: 2, name: 'Marc Demo'}, - ], - total: 7.4, - state: 'new', - lines: [ - {id: 1, product: ['Pizza Italiana', 7.4], toppings: [], quantity: 1.0, price: 7.4} - ], - alerts: [], - locations: ['hello', 'hallo'], - user_location: [1, 'hello'], - }); - } else if (route === '/lunch/user_location_get') { - return $.when(1); - } else if (route === '/web/dataset/call_kw/ir.model.data/xmlid_to_res_id') { - return $.when(); - } else if (route.startsWith('data:image/png;base64,')) { - return $.when(); - } - return this._super(route, args); - }, - }); - - var $section = $(lunchKanban.$('.o_lunch_widget_info')[0]); - // username - assert.strictEqual($section.find('div:eq(1) div:eq(0)').text().trim(), 'Marc Demo', 'Username should have been Marc Demo'); - // your order section - $section = $(lunchKanban.$('.o_lunch_widget_info')[1]); - // only one line - assert.strictEqual($section.find('div:eq(2) div.d-flex').length, 1, 'There should be only one line'); - // quantity = 1 - assert.strictEqual($section.find('div:eq(2) div:eq(0) span:eq(0)').text().trim(), '1', 'The line should contain only one product'); - // buttons to remove and to add product - assert.strictEqual($section.find('.o_remove_product').length, 1, 'There should be a remove product button'); - assert.strictEqual($section.find('.o_add_product').length, 1, 'There should be a add product button'); - - $section = $(lunchKanban.$('.o_lunch_widget_info')[2]); - // total - assert.strictEqual($section.find('div:eq(0) div:eq(1)').text().trim(), '7.40', 'total should be of 7.40'); - // order now button - assert.strictEqual($section.find('button').length, 1, 'order now button should be available'); - - lunchKanban.destroy(); - }); - - QUnit.test('User interactions', function (assert) { - assert.expect(9); - - var state = 'new'; - - var lunchKanban = createView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: '' + - '
' + - '
' + - '
', - mockRPC: function(route, args) { - if (route === '/lunch/infos') { - return $.when({ - order: 1, - wallet: 20, - username: 'Marc Demo', - userimage: '', - is_manager: false, - users: [ - {id: 1, name: 'Mitchell Admin'}, - {id: 2, name: 'Marc Demo'}, - ], - total: 7.4, - raw_state: state, - state: 'New', - lines: [ - {id: 1, product: ['Pizza Italiana', 7.4], toppings: [], quantity: 1.0, price: 7.4} - ], - alerts: [], - locations: ['hello', 'hallo'], - user_location: [1, 'hello'], - }); - } else if (route === '/lunch/payment_message') { - return $.when({message: 'Hello'}); - } else if (route === '/lunch/pay') { - return $.when(true); - } else if (route === '/lunch/user_location_get') { - return $.when(1); - } else if (args.method === 'update_quantity') { - assert.step(args.args); - return $.when(); - } else if (route === '/web/dataset/call_kw/ir.model.data/xmlid_to_res_id') { - return $.when(); - } else if (route.startsWith('data:image/png;base64,')) { - return $.when(); - } - return this._super(route, args); - }, - }); - - lunchKanban.$('.o_add_product').click(); - lunchKanban.$('.o_remove_product').click(); - - state = 'ordered'; - lunchKanban.$('.o_lunch_widget_order_button').click(); - // state is shown - assert.strictEqual(lunchKanban.$('.o_lunch_ordered').length, 1, 'state should be shown as ordered'); - // buttons - assert.strictEqual(lunchKanban.$('.o_remove_product').length, 1, 'button to remove product should be shown'); - assert.strictEqual(lunchKanban.$('.o_add_product').length, 1, 'button to add product should be shown'); - state = 'confirmed'; - lunchKanban.reload(); - // state is updated - assert.strictEqual(lunchKanban.$('.o_lunch_confirmed').length, 1, 'state should be shown as confirmed'); - // Buttons not shown anymore - assert.strictEqual(lunchKanban.$('.o_remove_product').length, 0, 'button to remove product should not be shown'); - assert.strictEqual(lunchKanban.$('.o_add_product').length, 0, 'button to add product should not be shown'); - - - assert.verifySteps([ - [[1], 1], - [[1], -1], - ]); - - lunchKanban.destroy(); - }); - - QUnit.test('Manager interactions', function (assert) { - assert.expect(1); - - var lunchKanban = createView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: '' + - '
' + - '
' + - '
', - mockRPC: function(route, args) { - if (route === '/lunch/infos') { - return $.when({ - order: 1, - wallet: 20, - username: 'Marc Demo', - userimage: '', - is_manager: true, - users: [ - {id: 1, name: 'Mitchell Admin'}, - {id: 2, name: 'Marc Demo'}, - ], - total: 7.4, - raw_state: 'new', - state: 'New', - lines: [ - {id: 1, product: ['Pizza Italiana', 7.4], toppings: [], quantity: 1.0, price: 7.4} - ], - alerts: [], - locations: ['hello', 'hallo'], - user_location: [1, 'hello'], - }); - } else if (route === '/lunch/user_location_get') { - return $.when(1); - } else if (route === '/web/dataset/call_kw/ir.model.data/xmlid_to_res_id') { - return $.when(); - } else if (route.startsWith('data:image/png;base64,')) { - return $.when(); - } - return this._super(route, args); - }, - }); - - var select = lunchKanban.$('div.o_lunch_user_field div.o_field_widget.o_field_many2one'); - - assert.strictEqual(select.length, 1, 'There should be a user selection field'); - - lunchKanban.destroy(); - }); - - QUnit.module('LunchKanbanWidget'); - - QUnit.test('Rpc calls should be performed after the init()', function (assert) { - assert.expect(2); - - var parent = testUtils.createParent({}); - - var widget = new LunchKanbanWidget(parent, { - user_location: [1, 'hello'], - }); - - testUtils.mock.addMockEnvironment(widget, { - data: {}, - mockRPC: function (route, args) { - if (route === '/web/dataset/call_kw/ir.model.data/xmlid_to_res_id') { - assert.ok('should perform xmlid_to_res_id RPC call in the willStart()'); - assert.deepEqual(args, { - args: [], - kwargs: { - xmlid: "base.group_portal", - }, - method: "xmlid_to_res_id", - model: "ir.model.data", - }, 'should have the right parameters for xmlid_to_res_id RPC call'); - return $.when(); - } - return this._super.apply(this, arguments); - }, - }); - - widget.appendTo($("#qunit-fixture")); - - parent.destroy(); - }); -}); - -}); diff --git a/addons/lunch/views/lunch_templates.xml b/addons/lunch/views/lunch_templates.xml index a076bbb2261..40c57da0b79 100644 --- a/addons/lunch/views/lunch_templates.xml +++ b/addons/lunch/views/lunch_templates.xml @@ -11,16 +11,25 @@ + + + diff --git a/addons/web/static/src/scss/search_view_mobile.scss b/addons/web/static/src/scss/search_view_mobile.scss index c3c264649ea..dd72aaae83b 100644 --- a/addons/web/static/src/scss/search_view_mobile.scss +++ b/addons/web/static/src/scss/search_view_mobile.scss @@ -7,7 +7,7 @@ padding: 0; width: 100%; background-color: white; - z-index: 1000; + z-index: $zindex-modal; overflow: auto; .o_mobile_search_header { height: 46px; @@ -89,7 +89,6 @@ right: 0; padding: 15px; font-size: 17px; - z-index: 1000; } } }