diff --git a/addons/base_import/static/src/import_records/import_records.js b/addons/base_import/static/src/import_records/import_records.js
index 7ffa17660fe..6fcf183eb69 100644
--- a/addons/base_import/static/src/import_records/import_records.js
+++ b/addons/base_import/static/src/import_records/import_records.js
@@ -36,10 +36,10 @@ ImportRecords.template = "base_import.ImportRecords";
const importRecordsItem = {
Component: ImportRecords,
groupNumber: 4,
- isDisplayed: ({ isSmall, searchModel }) =>
+ isDisplayed: ({ config, isSmall }) =>
!isSmall &&
- searchModel.action.type === "ir.actions.act_window" &&
- ["kanban", "list"].includes(searchModel.view.type)
+ config.actionType === "ir.actions.act_window" &&
+ ["kanban", "list"].includes(config.viewType)
// TODO: add arch info to searchModel?
// !!JSON.parse(env.view.arch.attrs.import || "1") &&
// !!JSON.parse(env.view.arch.attrs.create || "1"),
diff --git a/addons/board/static/src/add_to_board/add_to_board.js b/addons/board/static/src/add_to_board/add_to_board.js
index 75fecfb88cf..6ca973f78c7 100644
--- a/addons/board/static/src/add_to_board/add_to_board.js
+++ b/addons/board/static/src/add_to_board/add_to_board.js
@@ -24,7 +24,7 @@ export class AddToBoard extends Component {
setup() {
this.notification = useService("notification");
this.rpc = useService("rpc");
- this.state = useState({ name: this.env.searchModel.displayName });
+ this.state = useState({ name: this.env.config.displayName });
useAutofocus();
}
@@ -34,39 +34,19 @@ export class AddToBoard extends Component {
//---------------------------------------------------------------------
async addToBoard() {
- const {
- action,
- displayName,
- domain,
- context,
- groupBy,
- orderedBy,
- view,
- } = this.env.searchModel;
-
- // Retrieves view context
- const fns = this.env.__getContext__.callbacks;
- const viewContext = Object.assign({}, ...fns.map((fn) => fn()));
-
+ const { context, domain } = this.env.searchModel.getIrFilterValues();
const contextToSave = {
...context,
- group_by: groupBy,
- orderedBy,
+ orderedBy: this.env.searchModel.orderBy,
dashboard_merge_domains_contexts: false,
- ...viewContext,
};
- const detailedComparison = this.env.searchModel.getFullComparison();
- if (detailedComparison) {
- contextToSave.comparison = detailedComparison;
- }
-
const result = await this.rpc("/board/add_to_dashboard", {
- action_id: action.id,
+ action_id: this.env.config.actionId,
context_to_save: contextToSave,
domain,
name: this.state.name,
- view_mode: view.type,
+ view_mode: this.env.config.viewType,
});
if (result) {
@@ -77,12 +57,11 @@ export class AddToBoard extends Component {
type: "warning",
}
);
- this.state.name = displayName;
+ this.state.name = this.env.config.displayName;
} else {
- this.notification.add(
- this.env._t("Could not add filter to dashboard"),
- { type: "danger" }
- );
+ this.notification.add(this.env._t("Could not add filter to dashboard"), {
+ type: "danger",
+ });
}
}
@@ -106,7 +85,7 @@ AddToBoard.template = "board.AddToBoard";
const addToBoardItem = {
Component: AddToBoard,
groupNumber: 4,
- isDisplayed: ({ searchModel }) => searchModel.action.type === "ir.actions.act_window",
+ isDisplayed: ({ config }) => config.actionType === "ir.actions.act_window",
};
favoriteMenuRegistry.add("add-to-board", addToBoardItem, { sequence: 10 });
diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js
index b8b4466279a..7d2129d8a7f 100644
--- a/addons/board/static/tests/dashboard_tests.js
+++ b/addons/board/static/tests/dashboard_tests.js
@@ -1,1192 +1,1332 @@
-odoo.define('board.dashboard_tests', function (require) {
-"use strict";
+odoo.define("board.dashboard_tests", function (require) {
+ "use strict";
-var BoardView = require('board.BoardView');
+ var BoardView = require("board.BoardView");
-var ListController = require('web.ListController');
-var testUtils = require('web.test_utils');
-var ListRenderer = require('web.ListRenderer');
-var pyUtils = require('web.py_utils');
-const { registry } = require("@web/core/registry");
-const { makeFakeUserService } = require("@web/../tests/helpers/mock_services");
-const {
- applyFilter,
- applyGroup,
- editConditionValue,
- toggleAddCustomFilter,
- toggleAddCustomGroup,
- toggleFilterMenu,
- toggleGroupByMenu,
- toggleMenuItem,
- toggleMenuItemOption,
- toggleComparisonMenu,
- toggleFavoriteMenu,
-} = require("@web/../tests/search/helpers");
-const LegacyFavoriteMenu = require("web.FavoriteMenu");
-const LegacyAddToBoard = require("board.AddToBoardMenu");
-const { AddToBoard } = require('@board/add_to_board/add_to_board');
+ var ListController = require("web.ListController");
+ var testUtils = require("web.test_utils");
+ var ListRenderer = require("web.ListRenderer");
+ var pyUtils = require("web.py_utils");
+ const { registry } = require("@web/core/registry");
+ const { makeFakeUserService } = require("@web/../tests/helpers/mock_services");
+ const {
+ applyFilter,
+ applyGroup,
+ editConditionValue,
+ toggleAddCustomFilter,
+ toggleAddCustomGroup,
+ toggleFilterMenu,
+ toggleGroupByMenu,
+ toggleMenuItem,
+ toggleMenuItemOption,
+ toggleComparisonMenu,
+ toggleFavoriteMenu,
+ } = require("@web/../tests/search/helpers");
+ const LegacyFavoriteMenu = require("web.FavoriteMenu");
+ const LegacyAddToBoard = require("board.AddToBoardMenu");
+ const { AddToBoard } = require("@board/add_to_board/add_to_board");
-const { createWebClient, doAction } = require("@web/../tests/webclient/helpers");
-var createView = testUtils.createView;
+ const { createWebClient, doAction } = require("@web/../tests/webclient/helpers");
+ var createView = testUtils.createView;
-const patchDate = testUtils.mock.patchDate;
-const favoriteMenuRegistry = registry.category("favoriteMenu");
+ const patchDate = testUtils.mock.patchDate;
+ const favoriteMenuRegistry = registry.category("favoriteMenu");
-let serverData;
-QUnit.module('Dashboard', {
- beforeEach: function () {
- this.data = {
- board: {
- fields: {
+ let serverData;
+ QUnit.module("Dashboard", {
+ beforeEach: function () {
+ this.data = {
+ board: {
+ fields: {},
+ records: [],
},
- records: [
- ]
- },
- partner: {
- fields: {
- display_name: {string: "Displayed name", type: "char", searchable: true},
- foo: {string: "Foo", type: "char", default: "My little Foo Value", searchable: true},
- bar: {string: "Bar", type: "boolean"},
- int_field: {string: "Integer field", type: "integer", group_operator: 'sum'},
+ partner: {
+ fields: {
+ display_name: { string: "Displayed name", type: "char", searchable: true },
+ foo: {
+ string: "Foo",
+ type: "char",
+ default: "My little Foo Value",
+ searchable: true,
+ },
+ bar: { string: "Bar", type: "boolean" },
+ int_field: {
+ string: "Integer field",
+ type: "integer",
+ group_operator: "sum",
+ },
+ },
+ records: [
+ {
+ id: 1,
+ display_name: "first record",
+ foo: "yop",
+ int_field: 3,
+ },
+ {
+ id: 2,
+ display_name: "second record",
+ foo: "lalala",
+ int_field: 5,
+ },
+ {
+ id: 4,
+ display_name: "aaa",
+ foo: "abc",
+ int_field: 2,
+ },
+ ],
},
- records: [{
- id: 1,
- display_name: "first record",
- foo: "yop",
- int_field: 3,
- }, {
- id: 2,
- display_name: "second record",
- foo: "lalala",
- int_field: 5,
- }, {
- id: 4,
- display_name: "aaa",
- foo: "abc",
- int_field: 2,
- }],
- },
- };
-
- LegacyFavoriteMenu.registry.add('add-to-board-menu', LegacyAddToBoard, 10);
- favoriteMenuRegistry.add("add-to-board", {
- Component: AddToBoard,
- groupNumber: 4,
- isDisplayed: ({ searchModel }) => searchModel.action.type === "ir.actions.act_window",
- }, { sequence: 10 });
- serverData = { models: this.data };
- },
-});
-
-QUnit.test('dashboard basic rendering', async function (assert) {
- assert.expect(4);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '
',
- });
-
- assert.doesNotHaveClass(form.renderer.$el, 'o_dashboard',
- "should not have the o_dashboard css class");
-
- form.destroy();
-
- form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- });
-
- assert.hasClass(form.renderer.$el,'o_dashboard',
- "with a dashboard, the renderer should have the proper css class");
- assert.containsOnce(form, '.o_dashboard .o_view_nocontent',
- "should have a no content helper");
- assert.strictEqual(form.getTitle(), "My Dashboard",
- "should have the correct title");
- form.destroy();
-});
-
-QUnit.test('display the no content helper', async function (assert) {
- assert.expect(1);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- viewOptions: {
- action: {
- help: 'click to add a partner
'
- }
- },
- });
-
- assert.containsOnce(form, '.o_dashboard .o_view_nocontent',
- "should have a no content helper with action help");
- form.destroy();
-});
-
-QUnit.test('basic functionality, with one sub action', async function (assert) {
- assert.expect(26);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route, args) {
- if (route === '/web/action/load') {
- assert.step('load action');
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list']],
- });
- }
- if (route === '/web/dataset/search_read') {
- assert.deepEqual(args.domain, [['foo', '!=', 'False']], "the domain should be passed");
- assert.deepEqual(args.context.orderedBy, [{
- 'name': 'foo',
- 'asc': true,
- }],
- 'orderedBy is present in the search read when specified on the custom action'
- );
- }
- if (route === '/web/view/edit_custom') {
- assert.step('edit custom');
- return Promise.resolve(true);
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- },
- });
-
- assert.containsOnce(form, '.oe_dashboard_links',
- "should have rendered a link div");
- assert.containsOnce(form, 'table.oe_dashboard[data-layout="2-1"]',
- "should have rendered a table");
- assert.containsNone(form, 'td.o_list_record_selector',
- "td should not have a list selector");
- assert.strictEqual(form.$('h2 span.oe_header_txt:contains(ABC)').length, 1,
- "should have rendered a header with action string");
- assert.containsN(form, 'tr.o_data_row', 3,
- "should have rendered 3 data rows");
-
- assert.ok(form.$('.oe_content').is(':visible'), "content is visible");
-
- await testUtils.dom.click(form.$('.oe_fold'));
-
- assert.notOk(form.$('.oe_content').is(':visible'), "content is no longer visible");
-
- await testUtils.dom.click(form.$('.oe_fold'));
-
- assert.ok(form.$('.oe_content').is(':visible'), "content is visible again");
- assert.verifySteps(['load action', 'edit custom', 'edit custom']);
-
- assert.strictEqual($('.modal').length, 0, "should have no modal open");
-
- await testUtils.dom.click(form.$('button.oe_dashboard_link_change_layout'));
-
- assert.strictEqual($('.modal').length, 1, "should have opened a modal");
- assert.strictEqual($('.modal li[data-layout="2-1"] i.oe_dashboard_selected_layout').length, 1,
- "should mark currently selected layout");
-
- await testUtils.dom.click($('.modal .oe_dashboard_layout_selector li[data-layout="1-1"]'));
-
- assert.strictEqual($('.modal').length, 0, "should have no modal open");
- assert.containsOnce(form, 'table.oe_dashboard[data-layout="1-1"]',
- "should have rendered a table with correct layout");
-
-
- assert.containsOnce(form, '.oe_action', "should have one displayed action");
- await testUtils.dom.click(form.$('span.oe_close'));
-
- assert.strictEqual($('.modal').length, 1, "should have opened a modal");
-
- // confirm the close operation
- await testUtils.dom.click($('.modal button.btn-primary'));
-
- assert.strictEqual($('.modal').length, 0, "should have no modal open");
- assert.containsNone(form, '.oe_action', "should have no displayed action");
-
- assert.verifySteps(['edit custom', 'edit custom']);
- form.destroy();
-});
-
-QUnit.test('views in the dashboard do not have a control panel', async function (assert) {
- assert.expect(2);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list'], [5, 'form']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- },
- });
-
- assert.containsOnce(form, '.o_action .o_list_view');
- assert.containsNone(form, '.o_action .o_control_panel');
-
- form.destroy();
-});
-
-QUnit.test('can render an action without view_mode attribute', async function (assert) {
- // The view_mode attribute is automatically set to the 'action' nodes when
- // the action is added to the dashboard using the 'Add to dashboard' button
- // in the searchview. However, other dashboard views can be written by hand
- // (see openacademy tutorial), and in this case, we don't want hardcode
- // action's params (like context or domain), as the dashboard can directly
- // retrieve them from the action. Same applies for the view_type, as the
- // first view of the action can be used, by default.
- assert.expect(3);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- archs: {
- 'partner,4,list':
- '',
- },
- mockRPC: function (route, args) {
- if (route === '/board/static/src/img/layout_1-1-1.png') {
- return Promise.resolve();
- }
- if (route === '/web/action/load') {
- return Promise.resolve({
- context: '{"b": 2}',
- domain: '[["foo", "=", "yop"]]',
- res_model: 'partner',
- views: [[4, 'list'], [false, 'form']],
- });
- }
- if (args.method === 'load_views') {
- assert.deepEqual(args.kwargs.context, {a: 1, b: 2},
- "should have mixed both contexts");
- }
- if (route === '/web/dataset/search_read') {
- assert.deepEqual(args.domain, [['foo', '=', 'yop']],
- "should use the domain of the action");
- }
- return this._super.apply(this, arguments);
- },
- });
-
- assert.strictEqual(form.$('.oe_action:contains(ABC) .o_list_view').length, 1,
- "the list view (first view of action) should have been rendered correctly");
-
- form.destroy();
-});
-
-QUnit.test('can sort a sub list', async function (assert) {
- assert.expect(2);
-
- this.data.partner.fields.foo.sortable = true;
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- },
- });
-
- assert.strictEqual($('tr.o_data_row').text(), 'yoplalalaabc',
- "should have correct initial data");
-
- await testUtils.dom.click(form.$('th.o_column_sortable:contains(Foo)'));
-
- assert.strictEqual($('tr.o_data_row').text(), 'abclalalayop',
- "data should have been sorted");
- form.destroy();
-});
-
-QUnit.test('can open a record', async function (assert) {
- assert.expect(1);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- },
- intercepts: {
- do_action: function (event) {
- assert.deepEqual(event.data.action, {
- res_id: 1,
- res_model: 'partner',
- type: 'ir.actions.act_window',
- views: [[false, 'form']],
- }, "should do a do_action with correct parameters");
- },
- },
- });
-
- await testUtils.dom.click(form.$('tr.o_data_row td:contains(yop)'));
- form.destroy();
-});
-
-QUnit.test('can open record using action form view', async function (assert) {
- assert.expect(1);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list'], [5, 'form']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- 'partner,5,form':
- '',
- },
- intercepts: {
- do_action: function (event) {
- assert.deepEqual(event.data.action, {
- res_id: 1,
- res_model: 'partner',
- type: 'ir.actions.act_window',
- views: [[5, 'form']],
- }, "should do a do_action with correct parameters");
- },
- },
- });
-
- await testUtils.dom.click(form.$('tr.o_data_row td:contains(yop)'));
- form.destroy();
-});
-
-QUnit.test('can drag and drop a view', async function (assert) {
- assert.expect(5);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list']],
- });
- }
- if (route === '/web/view/edit_custom') {
- assert.step('edit custom');
- return Promise.resolve(true);
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- },
- });
-
- assert.containsOnce(form, 'td.index_0 .oe_action',
- "initial action is in column 0");
-
- await testUtils.dom.dragAndDrop(form.$('.oe_dashboard_column.index_0 .oe_header'),
- form.$('.oe_dashboard_column.index_1'));
- assert.containsNone(form, 'td.index_0 .oe_action',
- "initial action is not in column 0");
- assert.containsOnce(form, 'td.index_1 .oe_action',
- "initial action is in in column 1");
- assert.verifySteps(['edit custom']);
-
- form.destroy();
-});
-
-QUnit.test('twice the same action in a dashboard', async function (assert) {
- assert.expect(2);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list'],[5, 'kanban']],
- });
- }
- if (route === '/web/view/edit_custom') {
- assert.step('edit custom');
- return Promise.resolve(true);
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '',
- 'partner,5,kanban':
- '' +
- '
' +
- '',
- },
- });
-
- var $firstAction = form.$('.oe_action:contains(ABC)');
- assert.strictEqual($firstAction.find('.o_list_view').length, 1,
- "list view should be displayed in 'ABC' block");
- var $secondAction = form.$('.oe_action:contains(DEF)');
- assert.strictEqual($secondAction.find('.o_kanban_view').length, 1,
- "kanban view should be displayed in 'DEF' block");
-
- form.destroy();
-});
-
-QUnit.test('non-existing action in a dashboard', async function (assert) {
- assert.expect(1);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- intercepts: {
- load_views: function () {
- throw new Error('load_views should not be called');
- }
- },
- mockRPC: function (route) {
- if (route === '/board/static/src/img/layout_1-1-1.png') {
- return Promise.resolve();
- }
- if (route === '/web/action/load') {
- // server answer if the action doesn't exist anymore
- return Promise.resolve(false);
- }
- return this._super.apply(this, arguments);
- },
- });
-
- assert.strictEqual(form.$('.oe_action:contains(ABC)').length, 1,
- "there should be a box for the non-existing action");
-
- form.destroy();
-});
-
-QUnit.test('clicking on a kanban\'s button should trigger the action', async function (assert) {
- assert.expect(2);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- archs: {
- 'partner,false,kanban':
- '' +
- '' +
- '' +
- '
' +
- '' +
- '
' +
- '',
- },
- intercepts: {
- execute_action: function (event) {
- var data = event.data;
- assert.strictEqual(data.env.model, 'partner', "should have correct model");
- assert.strictEqual(data.action_data.name, 'sitting_on_a_park_bench',
- "should call correct method");
- }
- },
-
- mockRPC: function (route) {
- if (route === '/board/static/src/img/layout_1-1-1.png') {
- return Promise.resolve();
- }
- if (route === '/web/action/load') {
- return Promise.resolve({res_model: 'partner', view_mode: 'kanban', views: [[false, 'kanban']]});
- }
- if (route === '/web/dataset/search_read') {
- return Promise.resolve({records: [{foo: 'aqualung'}]});
- }
- return this._super.apply(this, arguments);
- }
- });
-
- await testUtils.dom.click(form.$('.o_kanban_test').find('button:first'));
-
- form.destroy();
-});
-
-QUnit.test('subviews are aware of attach in or detach from the DOM', async function (assert) {
- assert.expect(2);
-
- // patch list renderer `on_attach_callback` for the test only
- testUtils.mock.patch(ListRenderer, {
- on_attach_callback: function () {
- assert.step('subview on_attach_callback');
- }
- });
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '
',
- },
- });
-
- assert.verifySteps(['subview on_attach_callback']);
-
- // restore on_attach_callback of ListRenderer
- testUtils.mock.unpatch(ListRenderer);
-
- form.destroy();
-});
-
-QUnit.test('dashboard intercepts custom events triggered by sub controllers', async function (assert) {
- assert.expect(1);
-
- // we patch the ListController to force it to trigger the custom events that
- // we want the dashboard to intercept (to stop them or to tweak their data)
- testUtils.mock.patch(ListController, {
- start: function () {
- this.trigger_up('update_filters');
- return this._super.apply(this, arguments);
- },
- });
-
- var board = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({res_model: 'partner', views: [[false, 'list']]});
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,false,list': '',
- },
- intercepts: {
- update_filters: assert.step.bind(assert, 'update_filters'),
- },
- });
-
- assert.verifySteps([]);
-
- testUtils.mock.unpatch(ListController);
- board.destroy();
-});
-
-QUnit.test("save actions to dashboard", async function (assert) {
- assert.expect(6);
-
- testUtils.mock.patch(ListController, {
- getOwnedQueryParams: function () {
- var result = this._super.apply(this, arguments);
- result.context = {
- fire: "on the bayou",
};
- return result;
- },
- });
- serverData.models.partner.fields.foo.sortable = true;
-
- serverData.views = {
- "partner,false,list": '
',
- "partner,false,search": "",
- };
-
- const mockRPC = (route, args) => {
- if (route === "/board/add_to_dashboard") {
- assert.deepEqual(
- args.context_to_save.group_by,
- ["foo"],
- "The group_by should have been saved"
+ LegacyFavoriteMenu.registry.add("add-to-board-menu", LegacyAddToBoard, 10);
+ favoriteMenuRegistry.add(
+ "add-to-board",
+ {
+ Component: AddToBoard,
+ groupNumber: 4,
+ isDisplayed: ({ config }) => config.actionType === "ir.actions.act_window",
+ },
+ { sequence: 10 }
);
- assert.deepEqual(
- args.context_to_save.orderedBy,
- [
- {
- name: "foo",
- asc: true,
- },
- ],
- "The orderedBy should have been saved"
- );
- assert.strictEqual(
- args.context_to_save.fire,
- "on the bayou",
- "The context of a controller should be passed and flattened"
- );
- assert.strictEqual(args.action_id, 1, "should save the correct action");
- assert.strictEqual(args.view_mode, "list", "should save the correct view type");
- return Promise.resolve(true);
- }
- };
-
- const webClient = await createWebClient({ serverData, mockRPC });
-
- await doAction(webClient, {
- id: 1,
- res_model: "partner",
- type: "ir.actions.act_window",
- views: [[false, "list"]],
+ serverData = { models: this.data };
+ },
});
- assert.containsOnce(webClient, ".o_list_view", "should display the list view");
+ QUnit.test("dashboard basic rendering", async function (assert) {
+ assert.expect(4);
- // Sort the list
- await testUtils.dom.click($(".o_column_sortable"));
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch: '",
+ });
- // Group It
- await toggleGroupByMenu(webClient);
- await toggleAddCustomGroup(webClient);
- await applyGroup(webClient);
+ assert.doesNotHaveClass(
+ form.renderer.$el,
+ "o_dashboard",
+ "should not have the o_dashboard css class"
+ );
- // add this action to dashboard
- await toggleFavoriteMenu(webClient);
+ form.destroy();
- await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
- await testUtils.fields.editInput($(".o_add_to_board input"), "a name");
- await testUtils.dom.click($(".o_add_to_board .o_dropdown_menu button"));
-
- testUtils.mock.unpatch(ListController);
-});
-
-QUnit.test("save two searches to dashboard", async function (assert) {
- // the second search saved should not be influenced by the first
- assert.expect(2);
-
- serverData.views = {
- "partner,false,list": '
',
- "partner,false,search": "",
- };
-
- const mockRPC = (route, args) => {
- if (route === "/board/add_to_dashboard") {
- if (filter_count === 0) {
- assert.deepEqual(
- args.domain,
- [["display_name", "ilike", "a"]],
- "the correct domain should be sent"
- );
- }
- if (filter_count === 1) {
- assert.deepEqual(
- args.domain,
- [["display_name", "ilike", "b"]],
- "the correct domain should be sent"
- );
- }
-
- filter_count += 1;
- return Promise.resolve(true);
- }
- };
-
- const webClient = await createWebClient({ serverData, mockRPC });
-
- await doAction(webClient, {
- id: 1,
- res_model: "partner",
- type: "ir.actions.act_window",
- views: [[false, "list"]],
- });
-
- var filter_count = 0;
- // Add a first filter
- await toggleFilterMenu(webClient);
- await toggleAddCustomFilter(webClient);
- await editConditionValue(webClient, 0, "a");
- await applyFilter(webClient);
-
- // Add it to dashboard
- await toggleFavoriteMenu(webClient);
- await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
- await testUtils.dom.click($(".o_add_to_board .o_dropdown_menu button"));
-
- // Remove it
- await testUtils.dom.click(webClient.el.querySelector(".o_facet_remove"));
-
- // Add the second filter
- await toggleFilterMenu(webClient);
- await toggleAddCustomFilter(webClient);
- await editConditionValue(webClient, 0, "b");
- await applyFilter(webClient);
- // Add it to dashboard
- await toggleFavoriteMenu(webClient);
- await testUtils.dom.click(webClient.el.querySelector(".o_add_to_board button.o_dropdown_toggler"));
- await testUtils.dom.click(webClient.el.querySelector(".o_add_to_board .o_dropdown_menu button"));
-});
-
-QUnit.test("save a action domain to dashboard", async function (assert) {
- // View domains are to be added to the dashboard domain
- assert.expect(1);
-
- var view_domain = ["display_name", "ilike", "a"];
- var filter_domain = ["display_name", "ilike", "b"];
-
- // The filter domain already contains the view domain, but is always added by dashboard..,
- var expected_domain = ["&", view_domain, "&", view_domain, filter_domain];
-
- serverData.views = {
- "partner,false,list": '
',
- "partner,false,search": "",
- };
-
- const mockRPC = (route, args) => {
- if (route === "/board/add_to_dashboard") {
- assert.deepEqual(args.domain, expected_domain, "the correct domain should be sent");
- return Promise.resolve(true);
- }
- };
-
- const webClient = await createWebClient({ serverData, mockRPC });
-
- await doAction(webClient, {
- id: 1,
- res_model: "partner",
- type: "ir.actions.act_window",
- views: [[false, "list"]],
- domain: [view_domain],
- });
-
- // Add a filter
- await toggleFilterMenu(webClient);
- await toggleAddCustomFilter(webClient);
- await editConditionValue(webClient, 0, "b");
- await applyFilter(webClient);
- // Add it to dashboard
- await toggleFavoriteMenu(webClient);
- await testUtils.dom.click(webClient.el.querySelector(".o_add_to_board button.o_dropdown_toggler"));
- // add
- await testUtils.dom.click(webClient.el.querySelector(".o_add_to_board .o_dropdown_menu button"));
-});
-
-QUnit.test("Views should be loaded in the user's language", async function (assert) {
- assert.expect(2);
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- session: {user_context: {lang: 'fr_FR'}},
- arch: '',
- mockRPC: function (route, args) {
- if (args.method === 'load_views') {
- assert.deepEqual(pyUtils.eval('context', args.kwargs.context), {lang: 'fr_FR'},
- 'The views should be loaded with the correct context');
- }
- if (route === "/web/dataset/search_read") {
- assert.equal(args.context.lang, 'fr_FR',
- 'The data should be loaded with the correct context');
- }
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'list']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '
',
- },
+ "" +
+ "" +
+ "",
+ });
+
+ assert.hasClass(
+ form.renderer.$el,
+ "o_dashboard",
+ "with a dashboard, the renderer should have the proper css class"
+ );
+ assert.containsOnce(
+ form,
+ ".o_dashboard .o_view_nocontent",
+ "should have a no content helper"
+ );
+ assert.strictEqual(form.getTitle(), "My Dashboard", "should have the correct title");
+ form.destroy();
});
- form.destroy();
-});
-
-QUnit.test("Dashboard should use correct groupby", async function (assert) {
- assert.expect(1);
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route, args) {
- if (args.method === 'web_read_group') {
- assert.deepEqual(args.kwargs.groupby, ['bar'],
- 'user defined groupby should have precedence on action groupby');
- }
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- context: {
- group_by: 'some_field',
- },
- views: [[4, 'list']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '
',
- },
- });
-
- form.destroy();
-});
-
-QUnit.test("Dashboard should use correct groupby when defined as a string of one field", async function (assert) {
- assert.expect(1);
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route, args) {
- if (args.method === 'web_read_group') {
- assert.deepEqual(args.kwargs.groupby, ['bar'],
- 'user defined groupby should have precedence on action groupby');
- }
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- context: {
- group_by: 'some_field',
- },
- views: [[4, 'list']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,list':
- '
',
- },
- });
-
- form.destroy();
-});
-
-QUnit.test('click on a cell of pivot view inside dashboard', async function (assert) {
- assert.expect(3);
-
- var form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: '',
- mockRPC: function (route) {
- if (route === '/web/action/load') {
- return Promise.resolve({
- res_model: 'partner',
- views: [[4, 'pivot']],
- });
- }
- return this._super.apply(this, arguments);
- },
- archs: {
- 'partner,4,pivot': '',
- },
- intercepts: {
- do_action: function () {
- assert.step('do action');
- },
- },
- });
-
- assert.verifySteps([]);
-
- await testUtils.dom.click(form.$('.o_legacy_pivot .o_pivot_cell_value'));
-
- assert.verifySteps(['do action']);
-
- form.destroy();
-});
-
-// TODO: The button "Add to my dashboard" is not yet developped on the new control panel search view
-QUnit.skip(
- "correctly save the time ranges of a reporting view in comparison mode",
- async function (assert) {
+ QUnit.test("display the no content helper", async function (assert) {
assert.expect(1);
- const unpatchDate = patchDate(2020, 6, 1, 11, 0, 0);
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ viewOptions: {
+ action: {
+ help: 'click to add a partner
',
+ },
+ },
+ });
- serverData.models.partner.fields.date = {
- string: "Date",
- type: "date",
- sortable: true,
- };
+ assert.containsOnce(
+ form,
+ ".o_dashboard .o_view_nocontent",
+ "should have a no content helper with action help"
+ );
+ form.destroy();
+ });
+
+ QUnit.test("basic functionality, with one sub action", async function (assert) {
+ assert.expect(26);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route, args) {
+ if (route === "/web/action/load") {
+ assert.step("load action");
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "list"]],
+ });
+ }
+ if (route === "/web/dataset/search_read") {
+ assert.deepEqual(
+ args.domain,
+ [["foo", "!=", "False"]],
+ "the domain should be passed"
+ );
+ assert.deepEqual(
+ args.context.orderedBy,
+ [
+ {
+ name: "foo",
+ asc: true,
+ },
+ ],
+ "orderedBy is present in the search read when specified on the custom action"
+ );
+ }
+ if (route === "/web/view/edit_custom") {
+ assert.step("edit custom");
+ return Promise.resolve(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ },
+ });
+
+ assert.containsOnce(form, ".oe_dashboard_links", "should have rendered a link div");
+ assert.containsOnce(
+ form,
+ 'table.oe_dashboard[data-layout="2-1"]',
+ "should have rendered a table"
+ );
+ assert.containsNone(
+ form,
+ "td.o_list_record_selector",
+ "td should not have a list selector"
+ );
+ assert.strictEqual(
+ form.$("h2 span.oe_header_txt:contains(ABC)").length,
+ 1,
+ "should have rendered a header with action string"
+ );
+ assert.containsN(form, "tr.o_data_row", 3, "should have rendered 3 data rows");
+
+ assert.ok(form.$(".oe_content").is(":visible"), "content is visible");
+
+ await testUtils.dom.click(form.$(".oe_fold"));
+
+ assert.notOk(form.$(".oe_content").is(":visible"), "content is no longer visible");
+
+ await testUtils.dom.click(form.$(".oe_fold"));
+
+ assert.ok(form.$(".oe_content").is(":visible"), "content is visible again");
+ assert.verifySteps(["load action", "edit custom", "edit custom"]);
+
+ assert.strictEqual($(".modal").length, 0, "should have no modal open");
+
+ await testUtils.dom.click(form.$("button.oe_dashboard_link_change_layout"));
+
+ assert.strictEqual($(".modal").length, 1, "should have opened a modal");
+ assert.strictEqual(
+ $('.modal li[data-layout="2-1"] i.oe_dashboard_selected_layout').length,
+ 1,
+ "should mark currently selected layout"
+ );
+
+ await testUtils.dom.click($('.modal .oe_dashboard_layout_selector li[data-layout="1-1"]'));
+
+ assert.strictEqual($(".modal").length, 0, "should have no modal open");
+ assert.containsOnce(
+ form,
+ 'table.oe_dashboard[data-layout="1-1"]',
+ "should have rendered a table with correct layout"
+ );
+
+ assert.containsOnce(form, ".oe_action", "should have one displayed action");
+ await testUtils.dom.click(form.$("span.oe_close"));
+
+ assert.strictEqual($(".modal").length, 1, "should have opened a modal");
+
+ // confirm the close operation
+ await testUtils.dom.click($(".modal button.btn-primary"));
+
+ assert.strictEqual($(".modal").length, 0, "should have no modal open");
+ assert.containsNone(form, ".oe_action", "should have no displayed action");
+
+ assert.verifySteps(["edit custom", "edit custom"]);
+ form.destroy();
+ });
+
+ QUnit.test("views in the dashboard do not have a control panel", async function (assert) {
+ assert.expect(2);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ "",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [
+ [4, "list"],
+ [5, "form"],
+ ],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ },
+ });
+
+ assert.containsOnce(form, ".o_action .o_list_view");
+ assert.containsNone(form, ".o_action .o_control_panel");
+
+ form.destroy();
+ });
+
+ QUnit.test("can render an action without view_mode attribute", async function (assert) {
+ // The view_mode attribute is automatically set to the 'action' nodes when
+ // the action is added to the dashboard using the 'Add to dashboard' button
+ // in the searchview. However, other dashboard views can be written by hand
+ // (see openacademy tutorial), and in this case, we don't want hardcode
+ // action's params (like context or domain), as the dashboard can directly
+ // retrieve them from the action. Same applies for the view_type, as the
+ // first view of the action can be used, by default.
+ assert.expect(3);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ archs: {
+ "partner,4,list": '',
+ },
+ mockRPC: function (route, args) {
+ if (route === "/board/static/src/img/layout_1-1-1.png") {
+ return Promise.resolve();
+ }
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ context: '{"b": 2}',
+ domain: '[["foo", "=", "yop"]]',
+ res_model: "partner",
+ views: [
+ [4, "list"],
+ [false, "form"],
+ ],
+ });
+ }
+ if (args.method === "load_views") {
+ assert.deepEqual(
+ args.kwargs.context,
+ { a: 1, b: 2 },
+ "should have mixed both contexts"
+ );
+ }
+ if (route === "/web/dataset/search_read") {
+ assert.deepEqual(
+ args.domain,
+ [["foo", "=", "yop"]],
+ "should use the domain of the action"
+ );
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(
+ form.$(".oe_action:contains(ABC) .o_list_view").length,
+ 1,
+ "the list view (first view of action) should have been rendered correctly"
+ );
+
+ form.destroy();
+ });
+
+ QUnit.test("can sort a sub list", async function (assert) {
+ assert.expect(2);
+
+ this.data.partner.fields.foo.sortable = true;
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "list"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ },
+ });
+
+ assert.strictEqual(
+ $("tr.o_data_row").text(),
+ "yoplalalaabc",
+ "should have correct initial data"
+ );
+
+ await testUtils.dom.click(form.$("th.o_column_sortable:contains(Foo)"));
+
+ assert.strictEqual(
+ $("tr.o_data_row").text(),
+ "abclalalayop",
+ "data should have been sorted"
+ );
+ form.destroy();
+ });
+
+ QUnit.test("can open a record", async function (assert) {
+ assert.expect(1);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "list"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ },
+ intercepts: {
+ do_action: function (event) {
+ assert.deepEqual(
+ event.data.action,
+ {
+ res_id: 1,
+ res_model: "partner",
+ type: "ir.actions.act_window",
+ views: [[false, "form"]],
+ },
+ "should do a do_action with correct parameters"
+ );
+ },
+ },
+ });
+
+ await testUtils.dom.click(form.$("tr.o_data_row td:contains(yop)"));
+ form.destroy();
+ });
+
+ QUnit.test("can open record using action form view", async function (assert) {
+ assert.expect(1);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [
+ [4, "list"],
+ [5, "form"],
+ ],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ "partner,5,form": '',
+ },
+ intercepts: {
+ do_action: function (event) {
+ assert.deepEqual(
+ event.data.action,
+ {
+ res_id: 1,
+ res_model: "partner",
+ type: "ir.actions.act_window",
+ views: [[5, "form"]],
+ },
+ "should do a do_action with correct parameters"
+ );
+ },
+ },
+ });
+
+ await testUtils.dom.click(form.$("tr.o_data_row td:contains(yop)"));
+ form.destroy();
+ });
+
+ QUnit.test("can drag and drop a view", async function (assert) {
+ assert.expect(5);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "list"]],
+ });
+ }
+ if (route === "/web/view/edit_custom") {
+ assert.step("edit custom");
+ return Promise.resolve(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ },
+ });
+
+ assert.containsOnce(form, "td.index_0 .oe_action", "initial action is in column 0");
+
+ await testUtils.dom.dragAndDrop(
+ form.$(".oe_dashboard_column.index_0 .oe_header"),
+ form.$(".oe_dashboard_column.index_1")
+ );
+ assert.containsNone(form, "td.index_0 .oe_action", "initial action is not in column 0");
+ assert.containsOnce(form, "td.index_1 .oe_action", "initial action is in in column 1");
+ assert.verifySteps(["edit custom"]);
+
+ form.destroy();
+ });
+
+ QUnit.test("twice the same action in a dashboard", async function (assert) {
+ assert.expect(2);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [
+ [4, "list"],
+ [5, "kanban"],
+ ],
+ });
+ }
+ if (route === "/web/view/edit_custom") {
+ assert.step("edit custom");
+ return Promise.resolve(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '',
+ "partner,5,kanban":
+ '' +
+ '
' +
+ "",
+ },
+ });
+
+ var $firstAction = form.$(".oe_action:contains(ABC)");
+ assert.strictEqual(
+ $firstAction.find(".o_list_view").length,
+ 1,
+ "list view should be displayed in 'ABC' block"
+ );
+ var $secondAction = form.$(".oe_action:contains(DEF)");
+ assert.strictEqual(
+ $secondAction.find(".o_kanban_view").length,
+ 1,
+ "kanban view should be displayed in 'DEF' block"
+ );
+
+ form.destroy();
+ });
+
+ QUnit.test("non-existing action in a dashboard", async function (assert) {
+ assert.expect(1);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ intercepts: {
+ load_views: function () {
+ throw new Error("load_views should not be called");
+ },
+ },
+ mockRPC: function (route) {
+ if (route === "/board/static/src/img/layout_1-1-1.png") {
+ return Promise.resolve();
+ }
+ if (route === "/web/action/load") {
+ // server answer if the action doesn't exist anymore
+ return Promise.resolve(false);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(
+ form.$(".oe_action:contains(ABC)").length,
+ 1,
+ "there should be a box for the non-existing action"
+ );
+
+ form.destroy();
+ });
+
+ QUnit.test("clicking on a kanban's button should trigger the action", async function (assert) {
+ assert.expect(2);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ archs: {
+ "partner,false,kanban":
+ '' +
+ "" +
+ '' +
+ "
" +
+ '' +
+ "
" +
+ "",
+ },
+ intercepts: {
+ execute_action: function (event) {
+ var data = event.data;
+ assert.strictEqual(data.env.model, "partner", "should have correct model");
+ assert.strictEqual(
+ data.action_data.name,
+ "sitting_on_a_park_bench",
+ "should call correct method"
+ );
+ },
+ },
+
+ mockRPC: function (route) {
+ if (route === "/board/static/src/img/layout_1-1-1.png") {
+ return Promise.resolve();
+ }
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ view_mode: "kanban",
+ views: [[false, "kanban"]],
+ });
+ }
+ if (route === "/web/dataset/search_read") {
+ return Promise.resolve({ records: [{ foo: "aqualung" }] });
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ await testUtils.dom.click(form.$(".o_kanban_test").find("button:first"));
+
+ form.destroy();
+ });
+
+ QUnit.test("subviews are aware of attach in or detach from the DOM", async function (assert) {
+ assert.expect(2);
+
+ // patch list renderer `on_attach_callback` for the test only
+ testUtils.mock.patch(ListRenderer, {
+ on_attach_callback: function () {
+ assert.step("subview on_attach_callback");
+ },
+ });
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "list"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '
',
+ },
+ });
+
+ assert.verifySteps(["subview on_attach_callback"]);
+
+ // restore on_attach_callback of ListRenderer
+ testUtils.mock.unpatch(ListRenderer);
+
+ form.destroy();
+ });
+
+ QUnit.test(
+ "dashboard intercepts custom events triggered by sub controllers",
+ async function (assert) {
+ assert.expect(1);
+
+ // we patch the ListController to force it to trigger the custom events that
+ // we want the dashboard to intercept (to stop them or to tweak their data)
+ testUtils.mock.patch(ListController, {
+ start: function () {
+ this.trigger_up("update_filters");
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ var board = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({ res_model: "partner", views: [[false, "list"]] });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,false,list": '',
+ },
+ intercepts: {
+ update_filters: assert.step.bind(assert, "update_filters"),
+ },
+ });
+
+ assert.verifySteps([]);
+
+ testUtils.mock.unpatch(ListController);
+ board.destroy();
+ }
+ );
+
+ QUnit.test("save actions to dashboard", async function (assert) {
+ assert.expect(6);
+
+ testUtils.mock.patch(ListController, {
+ getOwnedQueryParams: function () {
+ var result = this._super.apply(this, arguments);
+ result.context = {
+ fire: "on the bayou",
+ };
+ return result;
+ },
+ });
+
+ serverData.models.partner.fields.foo.sortable = true;
serverData.views = {
- "partner,false,pivot": '',
- "partner,false,search": '',
+ "partner,false,list": '
',
+ "partner,false,search": "",
};
const mockRPC = (route, args) => {
if (route === "/board/add_to_dashboard") {
- assert.deepEqual(args.context_to_save.comparison, {
- comparisonId: "previous_period",
- fieldName: "date",
- fieldDescription: "Date",
- rangeDescription: "July 2020",
- range: ["&", ["date", ">=", "2020-07-01"], ["date", "<=", "2020-07-31"]],
- comparisonRange: [
- "&",
- ["date", ">=", "2020-06-01"],
- ["date", "<=", "2020-06-30"],
+ assert.deepEqual(
+ args.context_to_save.group_by,
+ ["foo"],
+ "The group_by should have been saved"
+ );
+ assert.deepEqual(
+ args.context_to_save.orderedBy,
+ [
+ {
+ name: "foo",
+ asc: true,
+ },
],
- comparisonRangeDescription: "June 2020",
- });
+ "The orderedBy should have been saved"
+ );
+ assert.strictEqual(
+ args.context_to_save.fire,
+ "on the bayou",
+ "The context of a controller should be passed and flattened"
+ );
+ assert.strictEqual(args.action_id, 1, "should save the correct action");
+ assert.strictEqual(args.view_mode, "list", "should save the correct view type");
return Promise.resolve(true);
}
};
- registry.category("services").add("user", makeFakeUserService());
-
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, {
id: 1,
res_model: "partner",
type: "ir.actions.act_window",
- views: [[false, "pivot"]],
+ views: [[false, "list"]],
});
- // filter on July 2020
- await toggleFilterMenu(webClient);
- await toggleMenuItem(webClient, "Date");
- await toggleMenuItemOption(webClient, "Date", "July");
+ assert.containsOnce(webClient, ".o_list_view", "should display the list view");
- // compare July 2020 to June 2020
- await toggleComparisonMenu(webClient);
- await toggleMenuItem(webClient, 0);
+ // Sort the list
+ await testUtils.dom.click($(".o_column_sortable"));
- // add the view to the dashboard
+ // Group It
+ await toggleGroupByMenu(webClient);
+ await toggleAddCustomGroup(webClient);
+ await applyGroup(webClient);
+
+ // add this action to dashboard
await toggleFavoriteMenu(webClient);
await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
await testUtils.fields.editInput($(".o_add_to_board input"), "a name");
- await testUtils.dom.click($(".o_add_to_board div button"));
+ await testUtils.dom.click($(".o_add_to_board .o_dropdown_menu button"));
- unpatchDate();
- }
-);
+ testUtils.mock.unpatch(ListController);
+ });
-QUnit.test('correctly display the time range descriptions of a reporting view in comparison mode', async function (assert) {
- assert.expect(1);
+ QUnit.test("save two searches to dashboard", async function (assert) {
+ // the second search saved should not be influenced by the first
+ assert.expect(2);
- this.data.partner.fields.date = { string: 'Date', type: 'date', sortable: true };
- this.data.partner.records[0].date = '2020-07-15';
+ serverData.views = {
+ "partner,false,list": '
',
+ "partner,false,search": "",
+ };
- const form = await createView({
- View: BoardView,
- model: 'board',
- data: this.data,
- arch: `",
+ mockRPC: function (route, args) {
+ if (args.method === "load_views") {
+ assert.deepEqual(
+ pyUtils.eval("context", args.kwargs.context),
+ { lang: "fr_FR" },
+ "The views should be loaded with the correct context"
+ );
+ }
+ if (route === "/web/dataset/search_read") {
+ assert.equal(
+ args.context.lang,
+ "fr_FR",
+ "The data should be loaded with the correct context"
+ );
+ }
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "list"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '
',
+ },
+ });
+
+ form.destroy();
+ });
+
+ QUnit.test("Dashboard should use correct groupby", async function (assert) {
+ assert.expect(1);
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route, args) {
+ if (args.method === "web_read_group") {
+ assert.deepEqual(
+ args.kwargs.groupby,
+ ["bar"],
+ "user defined groupby should have precedence on action groupby"
+ );
+ }
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ context: {
+ group_by: "some_field",
+ },
+ views: [[4, "list"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '
',
+ },
+ });
+
+ form.destroy();
+ });
+
+ QUnit.test(
+ "Dashboard should use correct groupby when defined as a string of one field",
+ async function (assert) {
+ assert.expect(1);
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ '",
+ mockRPC: function (route, args) {
+ if (args.method === "web_read_group") {
+ assert.deepEqual(
+ args.kwargs.groupby,
+ ["bar"],
+ "user defined groupby should have precedence on action groupby"
+ );
+ }
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ context: {
+ group_by: "some_field",
+ },
+ views: [[4, "list"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,list": '
',
+ },
+ });
+
+ form.destroy();
+ }
+ );
+
+ QUnit.test("click on a cell of pivot view inside dashboard", async function (assert) {
+ assert.expect(3);
+
+ var form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch:
+ "",
+ mockRPC: function (route) {
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ res_model: "partner",
+ views: [[4, "pivot"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "partner,4,pivot": '',
+ },
+ intercepts: {
+ do_action: function () {
+ assert.step("do action");
+ },
+ },
+ });
+
+ assert.verifySteps([]);
+
+ await testUtils.dom.click(form.$(".o_legacy_pivot .o_pivot_cell_value"));
+
+ assert.verifySteps(["do action"]);
+
+ form.destroy();
+ });
+
+ // TODO: The button "Add to my dashboard" is not yet developped on the new control panel search view
+ QUnit.skip(
+ "correctly save the time ranges of a reporting view in comparison mode",
+ async function (assert) {
+ assert.expect(1);
+
+ const unpatchDate = patchDate(2020, 6, 1, 11, 0, 0);
+
+ serverData.models.partner.fields.date = {
+ string: "Date",
+ type: "date",
+ sortable: true,
+ };
+
+ serverData.views = {
+ "partner,false,pivot": '',
+ "partner,false,search": '',
+ };
+
+ const mockRPC = (route, args) => {
+ if (route === "/board/add_to_dashboard") {
+ assert.deepEqual(args.context_to_save.comparison, {
+ comparisonId: "previous_period",
+ fieldName: "date",
+ fieldDescription: "Date",
+ rangeDescription: "July 2020",
+ range: ["&", ["date", ">=", "2020-07-01"], ["date", "<=", "2020-07-31"]],
+ comparisonRange: [
+ "&",
+ ["date", ">=", "2020-06-01"],
+ ["date", "<=", "2020-06-30"],
+ ],
+ comparisonRangeDescription: "June 2020",
+ });
+ return Promise.resolve(true);
+ }
+ };
+
+ registry.category("services").add("user", makeFakeUserService());
+
+ const webClient = await createWebClient({ serverData, mockRPC });
+
+ await doAction(webClient, {
+ id: 1,
+ res_model: "partner",
+ type: "ir.actions.act_window",
+ views: [[false, "pivot"]],
+ });
+
+ // filter on July 2020
+ await toggleFilterMenu(webClient);
+ await toggleMenuItem(webClient, "Date");
+ await toggleMenuItemOption(webClient, "Date", "July");
+
+ // compare July 2020 to June 2020
+ await toggleComparisonMenu(webClient);
+ await toggleMenuItem(webClient, 0);
+
+ // add the view to the dashboard
+ await toggleFavoriteMenu(webClient);
+
+ await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
+ await testUtils.fields.editInput($(".o_add_to_board input"), "a name");
+ await testUtils.dom.click($(".o_add_to_board div button"));
+
+ unpatchDate();
+ }
+ );
+
+ QUnit.test(
+ "correctly display the time range descriptions of a reporting view in comparison mode",
+ async function (assert) {
+ assert.expect(1);
+
+ this.data.partner.fields.date = { string: "Date", type: "date", sortable: true };
+ this.data.partner.records[0].date = "2020-07-15";
+
+ const form = await createView({
+ View: BoardView,
+ model: "board",
+ data: this.data,
+ arch: ``,
- archs: {
- 'partner,1,pivot':
- '',
- },
- mockRPC: function (route, args) {
- if (route === '/board/static/src/img/layout_1-1-1.png') {
- return Promise.resolve();
- }
- if (route === '/web/action/load') {
- return Promise.resolve({
- context: JSON.stringify({ comparison: {
- comparisonId: "previous_period",
- fieldName: "date",
- fieldDescription: "Date",
- rangeDescription: "July 2020",
- range: ["&",["date", ">=", "2020-07-01"], ["date", "<=", "2020-07-31"]],
- comparisonRange: ["&", ["date", ">=", "2020-06-01"], ["date", "<=", "2020-06-30"]],
- comparisonRangeDescription: "June 2020",
- }}),
- domain: '[]',
- res_model: 'partner',
- views: [[1, 'pivot']],
- });
- }
- return this._super.apply(this, arguments);
- },
- });
+ archs: {
+ "partner,1,pivot": '',
+ },
+ mockRPC: function (route, args) {
+ if (route === "/board/static/src/img/layout_1-1-1.png") {
+ return Promise.resolve();
+ }
+ if (route === "/web/action/load") {
+ return Promise.resolve({
+ context: JSON.stringify({
+ comparison: {
+ comparisonId: "previous_period",
+ fieldName: "date",
+ fieldDescription: "Date",
+ rangeDescription: "July 2020",
+ range: [
+ "&",
+ ["date", ">=", "2020-07-01"],
+ ["date", "<=", "2020-07-31"],
+ ],
+ comparisonRange: [
+ "&",
+ ["date", ">=", "2020-06-01"],
+ ["date", "<=", "2020-06-30"],
+ ],
+ comparisonRangeDescription: "June 2020",
+ },
+ }),
+ domain: "[]",
+ res_model: "partner",
+ views: [[1, "pivot"]],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
- assert.deepEqual(
- [...form.el.querySelectorAll('div.o_legacy_pivot th.o_pivot_origin_row')].map(el => el.innerText),
- ['June 2020', 'July 2020', 'Variation']
+ assert.deepEqual(
+ [...form.el.querySelectorAll("div.o_legacy_pivot th.o_pivot_origin_row")].map(
+ (el) => el.innerText
+ ),
+ ["June 2020", "July 2020", "Variation"]
+ );
+
+ form.destroy();
+ }
);
-
- form.destroy();
-});
});
diff --git a/addons/crm/static/tests/forecast_view_tests.js b/addons/crm/static/tests/forecast_view_tests.js
index b3a05e29150..7d2992142e7 100644
--- a/addons/crm/static/tests/forecast_view_tests.js
+++ b/addons/crm/static/tests/forecast_view_tests.js
@@ -1,24 +1,23 @@
/** @odoo-module **/
-import { _lt } from "@web/core/l10n/translation";
-import AbstractView from "web.AbstractView";
-import AbstractModel from "web.AbstractModel";
-import { controlPanel as cpHelpers } from "web.test_utils";
-import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
-import { dialogService } from "@web/core/dialog/dialog_service";
import { legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
-import legacyViewRegistry from "web.view_registry";
-import { makeView } from "@web/../tests/views/helpers";
-import { mock } from "web.test_utils";
-import { registry } from "@web/core/registry";
import {
setupControlPanelServiceRegistry,
switchView,
toggleFilterMenu,
toggleGroupByMenu,
toggleMenuItem,
- toggleMenuItemOption,
+ toggleMenuItemOption
} from "@web/../tests/search/helpers";
+import { makeView } from "@web/../tests/views/helpers";
+import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
+import { dialogService } from "@web/core/dialog/dialog_service";
+import { _lt } from "@web/core/l10n/translation";
+import { registry } from "@web/core/registry";
+import AbstractModel from "web.AbstractModel";
+import AbstractView from "web.AbstractView";
+import { controlPanel as cpHelpers, mock } from "web.test_utils";
+import legacyViewRegistry from "web.view_registry";
const patchDate = mock.patchDate;
@@ -88,7 +87,7 @@ QUnit.module("Views", (hooks) => {
const forecastGraph = await makeView({
resModel: "foo",
- type: "forecast_graph",
+ type: "graph",
serverData,
searchViewId: false,
context: {
@@ -133,7 +132,7 @@ QUnit.module("Views", (hooks) => {
await makeView({
resModel: "foo",
- type: "forecast_graph",
+ type: "graph",
serverData,
searchViewId: false,
context: {
diff --git a/addons/google_spreadsheet/static/src/add_to_google_spreadsheet/add_to_google_spreadsheet.js b/addons/google_spreadsheet/static/src/add_to_google_spreadsheet/add_to_google_spreadsheet.js
index 49c0e988001..970b6e83ff1 100644
--- a/addons/google_spreadsheet/static/src/add_to_google_spreadsheet/add_to_google_spreadsheet.js
+++ b/addons/google_spreadsheet/static/src/add_to_google_spreadsheet/add_to_google_spreadsheet.js
@@ -45,7 +45,7 @@ AddToGoogleSpreadsheet.template = "google_spreadsheet.AddToGoogleSpreadsheet";
const addToGoogleSpreadsheetItem = {
Component: AddToGoogleSpreadsheet,
groupNumber: 4,
- isDisplayed: ({ searchModel }) => searchModel.action.type === "ir.actions.act_window",
+ isDisplayed: ({ config }) => config.actionType === "ir.actions.act_window",
};
favoriteMenuRegistry.add("add-to-google-spreadsheet", addToGoogleSpreadsheetItem, { sequence: 20 });
diff --git a/addons/project/static/src/js/project_graph_view.js b/addons/project/static/src/js/project_graph_view.js
index 40cf831393f..066193f4b15 100644
--- a/addons/project/static/src/js/project_graph_view.js
+++ b/addons/project/static/src/js/project_graph_view.js
@@ -1,12 +1,12 @@
/** @odoo-module **/
-import { GraphView } from "@web/views/graph/graph_view";
import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel";
import { registry } from "@web/core/registry";
+import { GraphView } from "@web/views/graph/graph_view";
const viewRegistry = registry.category("views");
class ProjectGraphView extends GraphView {}
-ProjectGraphView.components = { ...GraphView.components, ControlPanel: ProjectControlPanel };
+ProjectGraphView.ControlPanel = ProjectControlPanel;
viewRegistry.add("project_graph", ProjectGraphView);
diff --git a/addons/project/static/src/js/project_pivot_view.js b/addons/project/static/src/js/project_pivot_view.js
index 2a28361e861..a221d933405 100644
--- a/addons/project/static/src/js/project_pivot_view.js
+++ b/addons/project/static/src/js/project_pivot_view.js
@@ -1,12 +1,12 @@
/** @odoo-module **/
-import { PivotView } from "@web/views/pivot/pivot_view";
import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel";
import { registry } from "@web/core/registry";
+import { PivotView } from "@web/views/pivot/pivot_view";
const viewRegistry = registry.category("views");
class ProjectPivotView extends PivotView {}
-ProjectPivotView.components = { ...PivotView.components, ControlPanel: ProjectControlPanel };
+ProjectPivotView.ControlPanel = ProjectControlPanel;
viewRegistry.add("project_pivot", ProjectPivotView);
diff --git a/addons/project/static/src/js/project_rating_graph_view.js b/addons/project/static/src/js/project_rating_graph_view.js
index 4c98d62a001..380ef3ffb2b 100644
--- a/addons/project/static/src/js/project_rating_graph_view.js
+++ b/addons/project/static/src/js/project_rating_graph_view.js
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { _lt } from "@web/core/l10n/translation";
-import { GraphView } from "@web/views/graph/graph_view";
-import { GraphArchParser } from "@web/views/graph/graph_arch_parser";
import { registry } from "@web/core/registry";
+import { GraphArchParser } from "@web/views/graph/graph_arch_parser";
+import { GraphView } from "@web/views/graph/graph_view";
const viewRegistry = registry.category("views");
@@ -29,6 +29,6 @@ class ProjectRatingArchParser extends GraphArchParser {
// Would it be not better achiedved by using a proper arch directly?
class ProjectRatingGraphView extends GraphView {}
-ProjectRatingGraphView.archParser = ProjectRatingArchParser;
+ProjectRatingGraphView.ArchParser = ProjectRatingArchParser;
viewRegistry.add("project_rating_graph", ProjectRatingGraphView);
diff --git a/addons/project/static/src/js/project_rating_pivot_view.js b/addons/project/static/src/js/project_rating_pivot_view.js
index 5d54afceb19..e5dae9a110a 100644
--- a/addons/project/static/src/js/project_rating_pivot_view.js
+++ b/addons/project/static/src/js/project_rating_pivot_view.js
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { _lt } from "@web/core/l10n/translation";
-import { PivotView } from "@web/views/pivot/pivot_view";
-import { PivotArchParser } from "@web/views/pivot/pivot_arch_parser";
import { registry } from "@web/core/registry";
+import { PivotArchParser } from "@web/views/pivot/pivot_arch_parser";
+import { PivotView } from "@web/views/pivot/pivot_view";
const viewRegistry = registry.category("views");
@@ -29,6 +29,6 @@ class ProjectRatingArchParser extends PivotArchParser {
// Would it be not better achiedved by using a proper arch directly?
class ProjectRatingPivotView extends PivotView {}
-ProjectRatingPivotView.archParser = ProjectRatingArchParser;
+ProjectRatingPivotView.ArchParser = ProjectRatingArchParser;
viewRegistry.add("project_rating_pivot", ProjectRatingPivotView);
diff --git a/addons/project/static/src/project_control_panel/project_control_panel.js b/addons/project/static/src/project_control_panel/project_control_panel.js
index 6d415a547ff..29e12668102 100644
--- a/addons/project/static/src/project_control_panel/project_control_panel.js
+++ b/addons/project/static/src/project_control_panel/project_control_panel.js
@@ -9,7 +9,7 @@ export class ProjectControlPanel extends ControlPanel {
this.orm = useService("orm");
this.user = useService("user");
const { active_id, show_project_update } = this.env.searchModel.globalContext;
- this.showProjectUpdate = this.env.searchModel.view.type === "form" || show_project_update;
+ this.showProjectUpdate = this.env.config.viewType === "form" || show_project_update;
this.projectId = this.showProjectUpdate ? active_id : false;
}
@@ -48,4 +48,5 @@ export class ProjectControlPanel extends ControlPanel {
});
}
}
+
ProjectControlPanel.template = "project.ProjectControlPanel";