From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- .../static/tests/chrome/action_manager_tests.js | 4682 ++++++++++++++++++++ .../chrome/keyboard_navigation_mixin_tests.js | 88 + addons/web/static/tests/chrome/menu_tests.js | 47 + addons/web/static/tests/chrome/systray_tests.js | 42 + addons/web/static/tests/chrome/user_menu_tests.js | 32 + 5 files changed, 4891 insertions(+) create mode 100644 addons/web/static/tests/chrome/action_manager_tests.js create mode 100644 addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js create mode 100644 addons/web/static/tests/chrome/menu_tests.js create mode 100644 addons/web/static/tests/chrome/systray_tests.js create mode 100644 addons/web/static/tests/chrome/user_menu_tests.js (limited to 'addons/web/static/tests/chrome') diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js new file mode 100644 index 00000000..8511a8bf --- /dev/null +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -0,0 +1,4682 @@ +odoo.define('web.action_manager_tests', function (require) { +"use strict"; + +var ActionManager = require('web.ActionManager'); +var ReportClientAction = require('report.client_action'); +var Notification = require('web.Notification'); +var NotificationService = require('web.NotificationService'); +var AbstractAction = require('web.AbstractAction'); +var AbstractStorageService = require('web.AbstractStorageService'); +var BasicFields = require('web.basic_fields'); +var core = require('web.core'); +var ListController = require('web.ListController'); +var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin'); +var RamStorage = require('web.RamStorage'); +var ReportService = require('web.ReportService'); +var SessionStorageService = require('web.SessionStorageService'); +var testUtils = require('web.test_utils'); +var WarningDialog = require('web.CrashManager').WarningDialog; +var Widget = require('web.Widget'); + +var createActionManager = testUtils.createActionManager; +const cpHelpers = testUtils.controlPanel; +const { xml } = owl.tags; + +QUnit.module('ActionManager', { + beforeEach: function () { + this.data = { + partner: { + fields: { + foo: {string: "Foo", type: "char"}, + bar: {string: "Bar", type: "many2one", relation: 'partner'}, + o2m: {string: "One2Many", type: "one2many", relation: 'partner', relation_field: 'bar'}, + }, + records: [ + {id: 1, display_name: "First record", foo: "yop", bar: 2, o2m: [2, 3]}, + {id: 2, display_name: "Second record", foo: "blip", bar: 1, o2m: [1, 4, 5]}, + {id: 3, display_name: "Third record", foo: "gnap", bar: 1, o2m: []}, + {id: 4, display_name: "Fourth record", foo: "plop", bar: 2, o2m: []}, + {id: 5, display_name: "Fifth record", foo: "zoup", bar: 2, o2m: []}, + ], + }, + pony: { + fields: { + name: {string: 'Name', type: 'char'}, + }, + records: [ + {id: 4, name: 'Twilight Sparkle'}, + {id: 6, name: 'Applejack'}, + {id: 9, name: 'Fluttershy'} + ], + }, + }; + + this.actions = [{ + id: 1, + name: 'Partners Action 1', + res_model: 'partner', + type: 'ir.actions.act_window', + views: [[1, 'kanban']], + }, { + id: 2, + type: 'ir.actions.server', + }, { + id: 3, + name: 'Partners', + res_model: 'partner', + type: 'ir.actions.act_window', + views: [[false, 'list'], [1, 'kanban'], [false, 'form']], + }, { + id: 4, + name: 'Partners Action 4', + res_model: 'partner', + type: 'ir.actions.act_window', + views: [[1, 'kanban'], [2, 'list'], [false, 'form']], + }, { + id: 5, + name: 'Create a Partner', + res_model: 'partner', + target: 'new', + type: 'ir.actions.act_window', + views: [[false, 'form']], + }, { + id: 6, + name: 'Partner', + res_id: 2, + res_model: 'partner', + target: 'inline', + type: 'ir.actions.act_window', + views: [[false, 'form']], + }, { + id: 7, + name: "Some Report", + report_name: 'some_report', + report_type: 'qweb-pdf', + type: 'ir.actions.report', + }, { + id: 8, + name: 'Favorite Ponies', + res_model: 'pony', + type: 'ir.actions.act_window', + views: [[false, 'list'], [false, 'form']], + }, { + id: 9, + name: 'A Client Action', + tag: 'ClientAction', + type: 'ir.actions.client', + }, { + id: 10, + type: 'ir.actions.act_window_close', + }, { + id: 11, + name: "Another Report", + report_name: 'another_report', + report_type: 'qweb-pdf', + type: 'ir.actions.report', + close_on_report_download: true, + }, { + id: 12, + name: "Some HTML Report", + report_name: 'some_report', + report_type: 'qweb-html', + type: 'ir.actions.report', + }]; + + this.archs = { + // kanban views + 'partner,1,kanban': '' + + '
' + + '
', + + // list views + 'partner,false,list': '', + 'partner,2,list': '', + 'pony,false,list': '', + + // form views + 'partner,false,form': '
' + + '
' + + '
' + + '' + + '' + + '' + + '' + + '
', + 'pony,false,form': '
' + + '' + + '', + + // search views + 'partner,false,search': '', + 'partner,1,search': '' + + '' + + '', + 'pony,false,search': '', + }; + }, +}, function () { + QUnit.module('Misc'); + + QUnit.test('breadcrumbs and actions with target inline', async function (assert) { + assert.expect(3); + + this.actions[3].views = [[false, 'form']]; + this.actions[3].target = 'inline'; + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + await actionManager.doAction(4); + assert.ok(!$('.o_control_panel').is(':visible'), + "control panel should not be visible"); + + await actionManager.doAction(1, {clear_breadcrumbs: true}); + assert.ok($('.o_control_panel').is(':visible'), + "control panel should now be visible"); + assert.strictEqual($('.o_control_panel .breadcrumb').text(), "Partners Action 1", + "should have only one current action visible in breadcrumbs"); + + actionManager.destroy(); + }); + + QUnit.test('no widget memory leaks when doing some action stuff', async function (assert) { + assert.expect(1); + + var delta = 0; + testUtils.mock.patch(Widget, { + init: function () { + delta++; + this._super.apply(this, arguments); + }, + destroy: function () { + delta--; + this._super.apply(this, arguments); + }, + }); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + await actionManager.doAction(8); + + var n = delta; + await actionManager.doAction(4); + + // kanban view is loaded, switch to list view + await cpHelpers.switchView(actionManager, 'list'); + // open a record in form view + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + // go back to action 7 in breadcrumbs + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + + assert.strictEqual(delta, n, + "should have properly destroyed all other widgets"); + actionManager.destroy(); + testUtils.mock.unpatch(Widget); + }); + + QUnit.test('no widget memory leaks when executing actions in dialog', async function (assert) { + assert.expect(1); + + var delta = 0; + testUtils.mock.patch(Widget, { + init: function () { + delta++; + this._super.apply(this, arguments); + }, + destroy: function () { + if (!this.isDestroyed()) { + delta--; + } + this._super.apply(this, arguments); + }, + }); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + var n = delta; + + await actionManager.doAction(5); + await actionManager.doAction({type: 'ir.actions.act_window_close'}); + + assert.strictEqual(delta, n, + "should have properly destroyed all widgets"); + + actionManager.destroy(); + testUtils.mock.unpatch(Widget); + }); + + QUnit.test('no memory leaks when executing an action while switching view', async function (assert) { + assert.expect(1); + + var def; + var delta = 0; + testUtils.mock.patch(Widget, { + init: function () { + delta += 1; + this._super.apply(this, arguments); + }, + destroy: function () { + delta -= 1; + this._super.apply(this, arguments); + }, + }); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'read') { + return Promise.resolve(def).then(_.constant(result)); + } + return result; + }, + }); + + await actionManager.doAction(4); + var n = delta; + + await actionManager.doAction(3, {clear_breadcrumbs: true}); + + // switch to the form view (this request is blocked) + def = testUtils.makeTestPromise(); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + + // execute another action meanwhile (don't block this request) + await actionManager.doAction(4, {clear_breadcrumbs: true}); + + // unblock the switch to the form view in action 3 + def.resolve(); + await testUtils.nextTick(); + + assert.strictEqual(n, delta, + "all widgets of action 3 should have been destroyed"); + + actionManager.destroy(); + testUtils.mock.unpatch(Widget); + }); + + QUnit.test('no memory leaks when executing an action while loading views', async function (assert) { + assert.expect(1); + + var def; + var delta = 0; + testUtils.mock.patch(Widget, { + init: function () { + delta += 1; + this._super.apply(this, arguments); + }, + destroy: function () { + delta -= 1; + this._super.apply(this, arguments); + }, + }); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'load_views') { + return Promise.resolve(def).then(_.constant(result)); + } + return result; + }, + }); + + // execute action 4 to know the number of widgets it instantiates + await actionManager.doAction(4); + var n = delta; + + // execute a first action (its 'load_views' RPC is blocked) + def = testUtils.makeTestPromise(); + actionManager.doAction(3, {clear_breadcrumbs: true}); + + // execute another action meanwhile (and unlock the RPC) + actionManager.doAction(4, {clear_breadcrumbs: true}); + def.resolve(); + await testUtils.nextTick(); + + assert.strictEqual(n, delta, + "all widgets of action 3 should have been destroyed"); + + actionManager.destroy(); + testUtils.mock.unpatch(Widget); + }); + + QUnit.test('no memory leaks when executing an action while loading data of default view', async function (assert) { + assert.expect(1); + + var def; + var delta = 0; + testUtils.mock.patch(Widget, { + init: function () { + delta += 1; + this._super.apply(this, arguments); + }, + destroy: function () { + delta -= 1; + this._super.apply(this, arguments); + }, + }); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route) { + var result = this._super.apply(this, arguments); + if (route === '/web/dataset/search_read') { + return Promise.resolve(def).then(_.constant(result)); + } + return result; + }, + }); + + // execute action 4 to know the number of widgets it instantiates + await actionManager.doAction(4); + var n = delta; + + // execute a first action (its 'search_read' RPC is blocked) + def = testUtils.makeTestPromise(); + actionManager.doAction(3, {clear_breadcrumbs: true}); + + // execute another action meanwhile (and unlock the RPC) + actionManager.doAction(4, {clear_breadcrumbs: true}); + def.resolve(); + await testUtils.nextTick(); + + assert.strictEqual(n, delta, + "all widgets of action 3 should have been destroyed"); + + actionManager.destroy(); + testUtils.mock.unpatch(Widget); + }); + + QUnit.test('action with "no_breadcrumbs" set to true', async function (assert) { + assert.expect(2); + + _.findWhere(this.actions, {id: 4}).context = {no_breadcrumbs: true}; + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + await actionManager.doAction(3); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, + "there should be one controller in the breadcrumbs"); + + // push another action flagged with 'no_breadcrumbs=true' + await actionManager.doAction(4); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 0, + "the breadcrumbs should be empty"); + + actionManager.destroy(); + }); + + QUnit.test('on_reverse_breadcrumb handler is correctly called', async function (assert) { + assert.expect(3); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + // execute action 3 and open a record in form view + await actionManager.doAction(3); + testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + + // execute action 4 without 'on_reverse_breadcrumb' handler, then go back + await actionManager.doAction(4); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + assert.verifySteps([]); + + // execute action 4 with an 'on_reverse_breadcrumb' handler, then go back + await actionManager.doAction(4, { + on_reverse_breadcrumb: function () { + assert.step('on_reverse_breadcrumb'); + } + }); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + assert.verifySteps(['on_reverse_breadcrumb']); + + actionManager.destroy(); + }); + + QUnit.test('handles "history_back" event', async function (assert) { + assert.expect(2); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + await actionManager.doAction(4); + await actionManager.doAction(3); + actionManager.trigger_up('history_back'); + + await testUtils.nextTick(); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, + "there should be one controller in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners Action 4', + "breadcrumbs should display the display_name of the action"); + + actionManager.destroy(); + }); + + QUnit.test('stores and restores scroll position', async function (assert) { + assert.expect(7); + + var left; + var top; + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + getScrollPosition: function (ev) { + assert.step('getScrollPosition'); + ev.data.callback({left: left, top: top}); + }, + scrollTo: function (ev) { + assert.step('scrollTo left ' + ev.data.left + ', top ' + ev.data.top); + }, + }, + }); + + // execute a first action and simulate a scroll + assert.step('execute action 3'); + await actionManager.doAction(3); + left = 50; + top = 100; + + // execute a second action (in which we don't scroll) + assert.step('execute action 4'); + await actionManager.doAction(4); + + // go back using the breadcrumbs + assert.step('go back to action 3'); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); + + assert.verifySteps([ + 'execute action 3', + 'execute action 4', + 'getScrollPosition', // of action 3, before leaving it + 'go back to action 3', + 'getScrollPosition', // of action 4, before leaving it + 'scrollTo left 50, top 100', // restore scroll position of action 3 + ]); + + actionManager.destroy(); + }); + + QUnit.test('executing an action with target != "new" closes all dialogs', async function (assert) { + assert.expect(4); + + this.archs['partner,false,form'] = '
' + + '' + + '' + + '' + + '' + + ''; + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + await actionManager.doAction(3); + assert.containsOnce(actionManager, '.o_list_view'); + + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + assert.containsOnce(actionManager, '.o_form_view'); + + await testUtils.dom.click(actionManager.$('.o_form_view .o_data_row:first')); + assert.containsOnce(document.body, '.modal .o_form_view'); + + await actionManager.doAction(1); // target != 'new' + assert.containsNone(document.body, '.modal'); + + actionManager.destroy(); + }); + + QUnit.test('executing an action with target "new" does not close dialogs', async function (assert) { + assert.expect(4); + + this.archs['partner,false,form'] = '
' + + '' + + '' + + '' + + '' + + ''; + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + await actionManager.doAction(3); + assert.containsOnce(actionManager, '.o_list_view'); + + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + assert.containsOnce(actionManager, '.o_form_view'); + + await testUtils.dom.click(actionManager.$('.o_form_view .o_data_row:first')); + assert.containsOnce(document.body, '.modal .o_form_view'); + + await actionManager.doAction(5); // target 'new' + assert.containsN(document.body, '.modal .o_form_view', 2); + + actionManager.destroy(); + }); + + QUnit.test('executing a window action with onchange warning does not hide it', async function (assert) { + assert.expect(2); + + this.archs['partner,false,form'] = ` +
+ + `; + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + return Promise.resolve({ + value: {}, + warning: { + title: "Warning", + message: "Everything is alright", + type: 'dialog', + }, + }); + } + return this._super.apply(this, arguments); + }, + intercepts: { + warning: function (event) { + new WarningDialog(actionManager, { + title: event.data.title, + }, event.data).open(); + }, + }, + }); + + await actionManager.doAction(3); + + await testUtils.dom.click(actionManager.$('.o_list_button_add')); + assert.containsOnce( + $, + '.modal.o_technical_modal.show', + "Warning modal should be opened"); + + await testUtils.dom.click($('.modal.o_technical_modal.show button.close')); + assert.containsNone( + $, + '.modal.o_technical_modal.show', + "Warning modal should be closed"); + + actionManager.destroy(); + }); + + QUnit.module('Push State'); + + QUnit.test('properly push state', async function (assert) { + assert.expect(3); + + var stateDescriptions = [ + {action: 4, model: "partner", title: "Partners Action 4", view_type: "kanban"}, + {action: 8, model: "pony", title: "Favorite Ponies", view_type: "list"}, + {action: 8, id: 4, model: "pony", title: "Twilight Sparkle", view_type: "form"}, + ]; + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + push_state: function (event) { + var descr = stateDescriptions.shift(); + assert.deepEqual(_.extend({}, event.data.state), descr, + "should notify the environment of new state"); + }, + }, + }); + await actionManager.doAction(4); + await actionManager.doAction(8); + await testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + + actionManager.destroy(); + }); + + QUnit.test('push state after action is loaded, not before', async function (assert) { + assert.expect(5); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + push_state: function () { + assert.step('push_state'); + }, + }, + mockRPC: function (route) { + assert.step(route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.doAction(4); + assert.verifySteps([ + '/web/action/load', + '/web/dataset/call_kw/partner', + '/web/dataset/search_read', + 'push_state' + ]); + + actionManager.destroy(); + }); + + QUnit.test('do not push state for actions in target=new', async function (assert) { + assert.expect(3); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + push_state: function () { + assert.step('push_state'); + }, + }, + }); + await actionManager.doAction(4); + assert.verifySteps(['push_state']); + await actionManager.doAction(5); + assert.verifySteps([]); + + actionManager.destroy(); + }); + + QUnit.test('do not push state when action fails', async function (assert) { + assert.expect(4); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + push_state: function () { + assert.step('push_state'); + }, + }, + mockRPC: function (route, args) { + if (args.method === 'read') { + // this is the rpc to load form view + return Promise.reject(); + } + return this._super.apply(this, arguments); + }, + }); + await actionManager.doAction(8); + assert.verifySteps(['push_state']); + await testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + assert.verifySteps([]); + // we make sure here that the list view is still in the dom + assert.containsOnce(actionManager, '.o_list_view', + "there should still be a list view in dom"); + + actionManager.destroy(); + }); + + QUnit.module('Load State'); + + QUnit.test('should not crash on invalid state', async function (assert) { + assert.expect(2); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + res_model: 'partner', // the valid key for the model is 'model', not 'res_model' + }); + + assert.strictEqual(actionManager.$el.text(), '', "should display nothing"); + assert.verifySteps([]); + + actionManager.destroy(); + }); + + QUnit.test('properly load client actions', async function (assert) { + assert.expect(2); + + var ClientAction = AbstractAction.extend({ + start: function () { + this.$el.text('Hello World'); + this.$el.addClass('o_client_action_test'); + }, + }); + core.action_registry.add('HelloWorldTest', ClientAction); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + action: 'HelloWorldTest', + }); + + assert.strictEqual(actionManager.$('.o_client_action_test').text(), + 'Hello World', "should have correctly rendered the client action"); + + assert.verifySteps([]); + + actionManager.destroy(); + delete core.action_registry.map.HelloWorldTest; + }); + + QUnit.test('properly load act window actions', async function (assert) { + assert.expect(6); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + action: 1, + }); + + assert.strictEqual($('.o_control_panel').length, 1, + "should have rendered a control panel"); + assert.containsOnce(actionManager, '.o_kanban_view', + "should have rendered a kanban view"); + + assert.verifySteps([ + '/web/action/load', + 'load_views', + '/web/dataset/search_read', + ]); + + actionManager.destroy(); + }); + + QUnit.test('properly load records', async function (assert) { + assert.expect(5); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + id: 2, + model: 'partner', + }); + + assert.containsOnce(actionManager, '.o_form_view', + "should have rendered a form view"); + assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Second record', + "should have opened the second record"); + + assert.verifySteps([ + 'load_views', + 'read', + ]); + + actionManager.destroy(); + }); + + QUnit.test('properly load default record', async function (assert) { + assert.expect(5); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + action: 3, + id: "", // might happen with bbq and id=& in URL + model: 'partner', + view_type: 'form', + }); + + assert.containsOnce(actionManager, '.o_form_view', + "should have rendered a form view"); + + assert.verifySteps([ + '/web/action/load', + 'load_views', + 'onchange', + ]); + + actionManager.destroy(); + }); + + QUnit.test('load requested view for act window actions', async function (assert) { + assert.expect(6); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + action: 3, + view_type: 'kanban', + }); + + assert.containsNone(actionManager, '.o_list_view', + "should not have rendered a list view"); + assert.containsOnce(actionManager, '.o_kanban_view', + "should have rendered a kanban view"); + + assert.verifySteps([ + '/web/action/load', + 'load_views', + '/web/dataset/search_read', + ]); + + actionManager.destroy(); + }); + + QUnit.test('lazy load multi record view if mono record one is requested', async function (assert) { + assert.expect(11); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.loadState({ + action: 3, + id: 2, + view_type: 'form', + }); + assert.containsNone(actionManager, '.o_list_view', + "should not have rendered a list view"); + assert.containsOnce(actionManager, '.o_form_view', + "should have rendered a form view"); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, + "there should be two controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Second record', + "breadcrumbs should contain the display_name of the opened record"); + + // go back to Lst + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); + assert.containsOnce(actionManager, '.o_list_view', + "should now display the list view"); + assert.containsNone(actionManager, '.o_form_view', + "should not display the form view anymore"); + + assert.verifySteps([ + '/web/action/load', + 'load_views', + 'read', // read the opened record + '/web/dataset/search_read', // search read when coming back to List + ]); + + actionManager.destroy(); + }); + + QUnit.test('lazy load multi record view with previous action', async function (assert) { + assert.expect(6); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + await actionManager.doAction(4); + + assert.strictEqual($('.o_control_panel .breadcrumb li').length, 1, + "there should be one controller in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4', + "breadcrumbs should contain the display_name of the opened record"); + + await actionManager.doAction(3, { + resID: 2, + viewType: 'form', + }); + + assert.strictEqual($('.o_control_panel .breadcrumb li').length, 3, + "there should be three controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4PartnersSecond record', + "the breadcrumb elements should be correctly ordered"); + + // go back to List + await testUtils.dom.click($('.o_control_panel .breadcrumb a:last')); + + assert.strictEqual($('.o_control_panel .breadcrumb li').length, 2, + "there should be two controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4Partners', + "the breadcrumb elements should be correctly ordered"); + + actionManager.destroy(); + }); + + QUnit.test('lazy loaded multi record view with failing mono record one', async function (assert) { + assert.expect(4); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'read') { + return Promise.reject(); + } + return this._super.apply(this, arguments); + }, + }); + + await actionManager.loadState({ + action: 3, + id: 2, + view_type: 'form', + }).then(function () { + assert.ok(false, 'should not resolve the deferred'); + }).catch(function () { + assert.ok(true, 'should reject the deferred'); + }); + + assert.containsNone(actionManager, '.o_form_view'); + assert.containsNone(actionManager, '.o_list_view'); + + await actionManager.doAction(1); + + assert.containsOnce(actionManager, '.o_kanban_view'); + + actionManager.destroy(); + }); + + QUnit.test('change the viewType of the current action', async function (assert) { + assert.expect(13); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + await actionManager.doAction(3); + + assert.containsOnce(actionManager, '.o_list_view', + "should have rendered a list view"); + + // switch to kanban view + await actionManager.loadState({ + action: 3, + view_type: 'kanban', + }); + + assert.containsNone(actionManager, '.o_list_view', + "should not display the list view anymore"); + assert.containsOnce(actionManager, '.o_kanban_view', + "should have switched to the kanban view"); + + // switch to form view, open record 4 + await actionManager.loadState({ + action: 3, + id: 4, + view_type: 'form', + }); + + assert.containsNone(actionManager, '.o_kanban_view', + "should not display the kanban view anymore"); + assert.containsOnce(actionManager, '.o_form_view', + "should have switched to the form view"); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, + "there should be two controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Fourth record', + "should have opened the requested record"); + + // verify steps to ensure that the whole action hasn't been re-executed + // (if it would have been, /web/action/load and load_views would appear + // several times) + assert.verifySteps([ + '/web/action/load', + 'load_views', + '/web/dataset/search_read', // list view + '/web/dataset/search_read', // kanban view + 'read', // form view + ]); + + actionManager.destroy(); + }); + + QUnit.test('change the id of the current action', async function (assert) { + assert.expect(11); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method || route); + return this._super.apply(this, arguments); + }, + }); + + // execute action 3 and open the first record in a form view + await actionManager.doAction(3); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + + assert.containsOnce(actionManager, '.o_form_view', + "should have rendered a form view"); + assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', + "should have opened the first record"); + + // switch to record 4 + await actionManager.loadState({ + action: 3, + id: 4, + view_type: 'form', + }); + + assert.containsOnce(actionManager, '.o_form_view', + "should still display the form view"); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, + "there should be two controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Fourth record', + "should have switched to the requested record"); + + // verify steps to ensure that the whole action hasn't been re-executed + // (if it would have been, /web/action/load and load_views would appear + // twice) + assert.verifySteps([ + '/web/action/load', + 'load_views', + '/web/dataset/search_read', // list view + 'read', // form view, record 1 + 'read', // form view, record 4 + ]); + + actionManager.destroy(); + }); + + QUnit.test('should not push a loaded state', async function (assert) { + assert.expect(3); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + push_state: function () { + assert.step('push_state'); + }, + }, + }); + await actionManager.loadState({action: 3}); + + assert.verifySteps([], "should not push the loaded state"); + + await testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + + assert.verifySteps(['push_state'], + "should push the state of it changes afterwards"); + + actionManager.destroy(); + }); + + QUnit.test('should not push a loaded state of a client action', async function (assert) { + assert.expect(4); + + var ClientAction = AbstractAction.extend({ + init: function (parent, action, options) { + this._super.apply(this, arguments); + this.controllerID = options.controllerID; + }, + start: function () { + var self = this; + var $button = $('