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 = $('