From b20a6f60ae67ed1c8e33509ee4460934d8378f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 5 Dec 2017 10:25:08 +0100 Subject: [PATCH] [IMP] web: add some action_manager tests --- .../tests/chrome/action_manager_tests.js | 540 +++++++++++++++++- 1 file changed, 539 insertions(+), 1 deletion(-) diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 8efc341c968..445cd19c128 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -15,6 +15,7 @@ QUnit.module('ActionManager', { partner: { fields: { foo: {string: "Foo", type: "char"}, + bar: {string: "Bar", type: "many2one", relation: 'partner'}, }, records: [ {id: 1, display_name: "First record", foo: "yop"}, @@ -24,6 +25,16 @@ QUnit.module('ActionManager', { {id: 5, display_name: "Fifth record", foo: "zoup"}, ], }, + pony: { + fields: { + name: {string: 'Name', type: 'char'}, + }, + records: [ + {id: 4, name: 'Twilight Sparkle'}, + {id: 6, name: 'Applejack'}, + {id: 9, name: 'Fluttershy'} + ], + }, }; this.actions = [{ @@ -70,17 +81,24 @@ QUnit.module('ActionManager', { 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']], }]; this.archs = { // kanban views 'partner,1,kanban': '' + - '
' + + '
' + '
', // list views 'partner,false,list': '', 'partner,2,list': '', + 'pony,false,list': '', // form views 'partner,false,form': '
' + @@ -93,14 +111,81 @@ QUnit.module('ActionManager', { '' + '' + '', + 'pony,false,form': '
' + + '' + + '', // search views 'partner,false,search': '', + 'pony,false,search': '', }; }, }, function () { QUnit.module('Misc'); + QUnit.test('breadcrumbs and actions with target inline', function (assert) { + assert.expect(3); + + this.actions[3].views = [[false, 'form']]; + this.actions[3].target = 'inline'; + this.actions[3].view_mode = 'form'; + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + actionManager.doAction(4); + assert.ok(!$('.o_control_panel').is(':visible'), + "control panel should not be visible"); + + 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', function (assert) { + assert.expect(1); + + var delta = 0; + testUtils.patch(Widget, { + init: function () { + delta++; + this._super.apply(this, arguments); + }, + destroy: function () { + delta--; + this._super.apply(this, arguments); + }, + }); + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + actionManager.doAction(8); + + var n = delta; + actionManager.doAction(4); + // kanban view is loaded, switch to list view + $('.o_control_panel .o_cp_switch_list').click(); + // open a record in form view + actionManager.$('.o_list_view .o_data_row:first').click(); + // go back to action 7 in breadcrumbs + $('.o_control_panel .breadcrumb a:first').click(); + + assert.strictEqual(delta, n, + "should have properly destroyed all other widgets"); + actionManager.destroy(); + testUtils.unpatch(Widget); + }); + QUnit.test('no widget memory leaks when executing actions in dialog', function (assert) { assert.expect(1); @@ -259,6 +344,116 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); + QUnit.module('Push State'); + + QUnit.test('properly push state', 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 = 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"); + }, + }, + }); + actionManager.doAction(4); + actionManager.doAction(8); + actionManager.$('tr.o_data_row:first').click(); + + actionManager.destroy(); + }); + + QUnit.test('push state after action is loaded, not before', function (assert) { + assert.expect(5); + + var actionManager = 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); + }, + }); + 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', function (assert) { + assert.expect(3); + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + intercepts: { + push_state: function () { + assert.step('push_state'); + }, + }, + }); + actionManager.doAction(4); + assert.verifySteps(['push_state']); + actionManager.doAction(5); + assert.verifySteps(['push_state']); + + actionManager.destroy(); + }); + + QUnit.test('do not push state when action fails', function (assert) { + assert.expect(4); + + var actionManager = 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 $.Deferred().reject(); + } + return this._super.apply(this, arguments); + }, + }); + actionManager.doAction(8); + assert.verifySteps(['push_state']); + actionManager.$('tr.o_data_row:first').click(); + assert.verifySteps(['push_state']); + // we make sure here that the list view is still in the dom + assert.strictEqual(actionManager.$('.o_list_view').length, 1, + "there should still be a list view in dom"); + + actionManager.destroy(); + }); + QUnit.module('Load State'); QUnit.test('should not crash on invalid state', function (assert) { @@ -581,6 +776,160 @@ QUnit.module('ActionManager', { QUnit.module('Concurrency management'); + QUnit.test('drop previous actions if possible', function (assert) { + assert.expect(6); + + var def = $.Deferred(); + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route) { + var result = this._super.apply(this, arguments); + assert.step(route); + if (route === '/web/action/load') { + return def.then(_.constant(result)); + } + return result; + }, + }); + actionManager.doAction(4); + actionManager.doAction(8); + + def.resolve(); + + // action 4 loads a kanban view first, 6 loads a list view. We want a list + assert.strictEqual(actionManager.$('.o_list_view').length, 1, + 'there should be a list view in DOM'); + + assert.verifySteps([ + '/web/action/load', // load action 4 + '/web/action/load', // load action 6 + '/web/dataset/call_kw/pony', // load views for action 6 + '/web/dataset/search_read', // search read for list view action 6 + ]); + + actionManager.destroy(); + }); + + QUnit.test('handle switching view and switching back on slow network', function (assert) { + assert.expect(8); + + var def = $.Deferred(); + var defs = [$.when(), def, $.when()]; + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route) { + assert.step(route); + var result = this._super.apply(this, arguments); + if (route === '/web/dataset/search_read') { + var def = defs.shift(); + return def.then(_.constant(result)); + } + return result; + }, + }); + actionManager.doAction(4); + + // kanban view is loaded, switch to list view + $('.o_control_panel .o_cp_switch_list').click(); + + // here, list view is not ready yet, because def is not resolved + // switch back to kanban view + $('.o_control_panel .o_cp_switch_kanban').click(); + + // here, we want the kanban view to reload itself, regardless of list view + assert.verifySteps([ + "/web/action/load", // initial load action + "/web/dataset/call_kw/partner", // load views + "/web/dataset/search_read", // search_read for kanban view + "/web/dataset/search_read", // search_read for list view (not resolved yet) + "/web/dataset/search_read" // search_read for kanban view reload (not resolved yet) + ]); + + // we resolve def => list view is now ready (but we want to ignore it) + def.resolve(); + + assert.strictEqual(actionManager.$('.o_kanban_view').length, 1, + "there should be a kanban view in dom"); + assert.strictEqual(actionManager.$('.o_list_view').length, 0, + "there should not be a list view in dom"); + + actionManager.destroy(); + }); + + QUnit.test('when an server action takes too much time...', function (assert) { + assert.expect(1); + + var def = $.Deferred(); + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route) { + if (route === '/web/action/run') { + return def.then(_.constant(1)); + } + return this._super.apply(this, arguments); + }, + }); + + actionManager.doAction(2); + actionManager.doAction(4); + + def.resolve(); + + assert.strictEqual($('.o_control_panel .breadcrumb li.active').text(), 'Partners Action 4', + 'action 4 should be loaded'); + + actionManager.destroy(); + }); + + QUnit.test('clicking quickly on breadcrumbs...', function (assert) { + assert.expect(1); + + var def = $.when(); + + var actionManager = 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 def.then(_.constant(result)); + } + return result; + }, + }); + + // create a situation with 3 breadcrumbs: kanban/form/list + actionManager.doAction(4); + actionManager.$('.o_kanban_record:first').click(); + actionManager.doAction(8); + + // now, the next read operations will be deferred (this is the read + // operation for the form view reload) + def = $.Deferred(); + + // click on the breadcrumbs for the form view, then on the kanban view + // before the form view is fully reloaded + $('.o_control_panel .breadcrumb li:eq(1)').click(); + $('.o_control_panel .breadcrumb li:eq(0)').click(); + + // resolve the form view read + def.resolve(); + + assert.strictEqual($('.o_control_panel .breadcrumb li.active').text(), 'Partners Action 4', + 'action 4 should be loaded and visible'); + + actionManager.destroy(); + }); + QUnit.test('execute a new action while loading a lazy-loaded controller', function (assert) { assert.expect(15); @@ -1558,6 +1907,195 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); + QUnit.test('restore previous view state when switching back', function (assert) { + assert.expect(5); + + this.actions[2].views.unshift([false, 'graph']); + this.archs['partner,false,graph'] = ''; + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + actionManager.doAction(3); + + assert.ok($('.o_control_panel .fa-bar-chart-o').hasClass('active'), + "bar chart button is active"); + assert.ok(!$('.o_control_panel .fa-line-chart').hasClass('active'), + "line chart button is not active"); + + // display line chart + $('.o_control_panel .fa-line-chart').click(); + assert.ok($('.o_control_panel .fa-line-chart').hasClass('active'), + "line chart button is now active"); + + // switch to kanban and back to graph view + $('.o_control_panel .o_cp_switch_kanban').click(); + assert.strictEqual($('.o_control_panel .fa-line-chart').length, 0, + "graph buttons are no longer in control panel"); + + $('.o_control_panel .o_cp_switch_graph').click(); + assert.ok($('.o_control_panel .fa-line-chart').hasClass('active'), + "line chart button is still active"); + actionManager.destroy(); + }); + + QUnit.test('view switcher is properly highlighted in graph view', function (assert) { + assert.expect(4); + + // note: this test should be moved to graph tests ? + + this.actions[2].views.splice(1, 1, [false, 'graph']); + this.archs['partner,false,graph'] = ''; + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + actionManager.doAction(3); + + assert.ok($('.o_control_panel .o_cp_switch_list').hasClass('active'), + "list button in control panel is active"); + assert.ok(!$('.o_control_panel .o_cp_switch_graph').hasClass('active'), + "graph button in control panel is not active"); + + // switch to graph view + $('.o_control_panel .o_cp_switch_graph').click(); + assert.ok(!$('.o_control_panel .o_cp_switch_list').hasClass('active'), + "list button in control panel is not active"); + assert.ok($('.o_control_panel .o_cp_switch_graph').hasClass('active'), + "graph button in control panel is active"); + actionManager.destroy(); + }); + + QUnit.test('can interact with search view', function (assert) { + assert.expect(2); + + this.archs['partner,false,search'] = ''+ + ''+ + '' + + ''+ + ''; + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + actionManager.doAction(3); + + assert.ok(!actionManager.$('.o_list_view').hasClass('o_list_view_grouped'), + "list view is not grouped"); + // open search view sub menus + $('.o_control_panel .o_searchview_more').click(); + + // open group by dropdown + $('.o_control_panel .o_cp_right button:contains(Group By)').click(); + + // click on first link + $('.o_control_panel .o_group_by_menu a:first').click(); + + assert.ok(actionManager.$('.o_list_view').hasClass('o_list_view_grouped'), + 'list view is now grouped'); + + actionManager.destroy(); + }); + + QUnit.test('can open a many2one external window', function (assert) { + // AAB: this test could be merged with 'many2ones in form views' in relational_fields_tests.js + assert.expect(8); + + this.data.partner.records[0].bar = 2; + this.archs['partner,false,search'] = ''+ + ''+ + '' + + ''+ + ''; + this.archs['partner,false,form'] = '
' + + '' + + '' + + '' + + '' + + '
'; + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + assert.step(route); + if (args.method === "get_formview_id") { + return $.when(false); + } + return this._super.apply(this, arguments); + }, + }); + actionManager.doAction(3); + + // open first record in form view + actionManager.$('.o_data_row:first').click(); + // click on edit + $('.o_control_panel .o_form_button_edit').click(); + + // click on external button for m2o + actionManager.$('.o_external_button').click(); + assert.verifySteps([ + '/web/action/load', // initial load action + '/web/dataset/call_kw/partner', // load views + '/web/dataset/search_read', // read list view data + '/web/dataset/call_kw/partner/read', // read form view data + '/web/dataset/call_kw/partner/get_formview_id', // get form view id + '/web/dataset/call_kw/partner', // load form view for modal + '/web/dataset/call_kw/partner/read' // read data for m2o record + ]); + actionManager.destroy(); + }); + + QUnit.test('ask for confirmation when leaving a "dirty" view', function (assert) { + assert.expect(4); + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + actionManager.doAction(4); + + // open record in form view + actionManager.$('.o_kanban_record:first').click(); + + // edit record + $('.o_control_panel button.o_form_button_edit').click(); + actionManager.$('input[name="foo"]').val('pinkypie').trigger('input'); + + // go back to kanban view + $('.o_control_panel .breadcrumb li:first a').click(); + + assert.strictEqual($('.modal .modal-body').text(), + "The record has been modified, your changes will be discarded. Do you want to proceed?", + "should display a modal dialog to confirm discard action"); + + // cancel + $('.modal .modal-footer button.btn-default').click(); + + assert.strictEqual(actionManager.$('.o_form_view').length, 1, + "should still be in form view"); + + // go back again to kanban view + $('.o_control_panel .breadcrumb li:first a').click(); + + // confirm discard + $('.modal .modal-footer button.btn-primary').click(); + + assert.strictEqual(actionManager.$('.o_form_view').length, 0, + "should no longer be in form view"); + assert.strictEqual(actionManager.$('.o_kanban_view').length, 1, + "should be in kanban view"); + + actionManager.destroy(); + }); + QUnit.test('limit set in action is passed to each created controller', function (assert) { assert.expect(2);