odoo.define('web.list_tests', function (require) { "use strict"; var AbstractFieldOwl = require('web.AbstractFieldOwl'); var AbstractStorageService = require('web.AbstractStorageService'); var BasicModel = require('web.BasicModel'); var core = require('web.core'); const Domain = require('web.Domain') var basicFields = require('web.basic_fields'); var fieldRegistry = require('web.field_registry'); var fieldRegistryOwl = require('web.field_registry_owl'); var FormView = require('web.FormView'); var ListRenderer = require('web.ListRenderer'); var ListView = require('web.ListView'); var mixins = require('web.mixins'); var NotificationService = require('web.NotificationService'); var RamStorage = require('web.RamStorage'); var testUtils = require('web.test_utils'); var widgetRegistry = require('web.widget_registry'); var Widget = require('web.Widget'); var _t = core._t; const cpHelpers = testUtils.controlPanel; var createActionManager = testUtils.createActionManager; var createView = testUtils.createView; QUnit.module('Views', { beforeEach: function () { this.data = { foo: { fields: { foo: {string: "Foo", type: "char"}, bar: {string: "Bar", type: "boolean"}, date: {string: "Some Date", type: "date"}, int_field: {string: "int_field", type: "integer", sortable: true, group_operator: "sum"}, text: {string: "text field", type: "text"}, qux: {string: "my float", type: "float"}, m2o: {string: "M2O field", type: "many2one", relation: "bar"}, o2m: {string: "O2M field", type: "one2many", relation: "bar"}, m2m: {string: "M2M field", type: "many2many", relation: "bar"}, amount: {string: "Monetary field", type: "monetary"}, currency_id: {string: "Currency", type: "many2one", relation: "res_currency", default: 1}, datetime: {string: "Datetime Field", type: 'datetime'}, reference: {string: "Reference Field", type: 'reference', selection: [ ["bar", "Bar"], ["res_currency", "Currency"], ["event", "Event"]]}, }, records: [ { id: 1, bar: true, foo: "yop", int_field: 10, qux: 0.4, m2o: 1, m2m: [1, 2], amount: 1200, currency_id: 2, date: "2017-01-25", datetime: "2016-12-12 10:55:05", reference: 'bar,1', }, {id: 2, bar: true, foo: "blip", int_field: 9, qux: 13, m2o: 2, m2m: [1, 2, 3], amount: 500, reference: 'res_currency,1'}, {id: 3, bar: true, foo: "gnap", int_field: 17, qux: -3, m2o: 1, m2m: [], amount: 300, reference: 'res_currency,2'}, {id: 4, bar: false, foo: "blip", int_field: -4, qux: 9, m2o: 1, m2m: [1], amount: 0}, ] }, bar: { fields: {}, records: [ {id: 1, display_name: "Value 1"}, {id: 2, display_name: "Value 2"}, {id: 3, display_name: "Value 3"}, ] }, res_currency: { fields: { symbol: {string: "Symbol", type: "char"}, position: { string: "Position", type: "selection", selection: [['after', 'A'], ['before', 'B']], }, }, records: [ {id: 1, display_name: "USD", symbol: '$', position: 'before'}, {id: 2, display_name: "EUR", symbol: '€', position: 'after'}, ], }, event: { fields: { id: {string: "ID", type: "integer"}, name: {string: "name", type: "char"}, }, records: [ {id: "2-20170808020000", name: "virtual"}, ] }, "ir.translation": { fields: { lang_code: {type: "char"}, src: {type: "char"}, value: {type: "char"}, res_id: {type: "integer"}, name: {type: "char"}, lang: {type: "char"}, }, records: [{ id: 99, res_id: 1, value: '', lang_code: 'en_US', lang: 'en_US', name: 'foo,foo' },{ id: 100, res_id: 1, value: '', lang_code: 'fr_BE', lang: 'fr_BE', name: 'foo,foo' }] }, }; } }, function () { QUnit.module('ListView'); QUnit.test('simple readonly list', async function (assert) { assert.expect(10); var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.doesNotHaveClass(list.$el, 'o_cannot_create', "should not have className 'o_cannot_create'"); // 3 th (1 for checkbox, 2 for columns) assert.containsN(list, 'th', 3, "should have 3 columns"); assert.strictEqual(list.$('td:contains(gnap)').length, 1, "should contain gnap"); assert.containsN(list, 'tbody tr', 4, "should have 4 rows"); assert.containsOnce(list, 'th.o_column_sortable', "should have 1 sortable column"); assert.strictEqual(list.$('thead th:nth(2)').css('text-align'), 'right', "header cells of integer fields should be right aligned"); assert.strictEqual(list.$('tbody tr:first td:nth(2)').css('text-align'), 'right', "integer cells should be right aligned"); assert.isVisible(list.$buttons.find('.o_list_button_add')); assert.isNotVisible(list.$buttons.find('.o_list_button_save')); assert.isNotVisible(list.$buttons.find('.o_list_button_discard')); list.destroy(); }); QUnit.test('on_attach_callback is properly called', async function (assert) { assert.expect(3); testUtils.mock.patch(ListRenderer, { on_attach_callback() { assert.step('on_attach_callback'); this._super(...arguments); }, }); const list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.verifySteps(['on_attach_callback']); await list.reload(); assert.verifySteps([]); testUtils.mock.unpatch(ListRenderer); list.destroy(); }); QUnit.test('list with create="0"', async function (assert) { assert.expect(2); var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.hasClass(list.$el,'o_cannot_create', "should have className 'o_cannot_create'"); assert.containsNone(list.$buttons, '.o_list_button_add', "should not have the 'Create' button"); list.destroy(); }); QUnit.test('list with delete="0"', async function (assert) { assert.expect(3); const list = await createView({ View: ListView, model: 'foo', data: this.data, viewOptions: {hasActionMenus: true}, arch: '', }); assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus'); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus .o_dropdown_menu'); list.destroy(); }); QUnit.test('editable list with edit="0"', async function (assert) { assert.expect(2); var list = await createView({ View: ListView, model: 'foo', data: this.data, viewOptions: {hasActionMenus: true}, arch: '', }); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); await testUtils.dom.click(list.$('tr td:not(.o_list_record_selector)').first()); assert.containsNone(list, 'tbody tr.o_selected_row', "should not have editable row"); list.destroy(); }); QUnit.test('export feature in list for users not in base.group_allow_export', async function (assert) { assert.expect(5); const list = await createView({ View: ListView, model: 'foo', data: this.data, viewOptions: { hasActionMenus: true }, arch: '', session: { async user_has_group(group) { if (group === 'base.group_allow_export') { return false; } return this._super(...arguments); }, }, }); assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus'); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); assert.containsNone(list.el, 'div.o_control_panel .o_cp_buttons .o_list_export_xlsx'); await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus'); await cpHelpers.toggleActionMenu(list); assert.deepEqual(cpHelpers.getMenuItemTexts(list), ['Delete'], 'action menu should not contain the Export button'); list.destroy(); }); QUnit.test('list with export button', async function (assert) { assert.expect(5); const list = await createView({ View: ListView, model: 'foo', data: this.data, viewOptions: {hasActionMenus: true}, arch: '', session: { async user_has_group(group) { if (group === 'base.group_allow_export') { return true; } return this._super(...arguments); }, }, }); assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus'); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); assert.containsOnce(list.el, 'div.o_control_panel .o_cp_buttons .o_list_export_xlsx'); await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus'); await cpHelpers.toggleActionMenu(list); assert.deepEqual( cpHelpers.getMenuItemTexts(list), ['Export', 'Delete'], 'action menu should have Export button' ); list.destroy(); }); QUnit.test('export button in list view', async function (assert) { assert.expect(5); const list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '', session: { async user_has_group(group) { if (group === 'base.group_allow_export') { return true; } return this._super(...arguments); }, }, }); assert.containsN(list, '.o_data_row', 4); assert.isVisible(list.$('.o_list_export_xlsx')); await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.isNotVisible(list.$('.o_list_export_xlsx')); assert.containsOnce(list.$('.o_cp_buttons'), '.o_list_selection_box'); await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.isVisible(list.$('.o_list_export_xlsx')); list.destroy(); }); QUnit.test('export button in empty list view', async function (assert) { assert.expect(2); const list = await createView({ View: ListView, model: "foo", data: this.data, arch: '', domain: [["id", "<", 0]], // such that no record matches the domain session: { async user_has_group(group) { if (group === 'base.group_allow_export') { return true; } return this._super(...arguments); }, }, }); assert.isNotVisible(list.el.querySelector('.o_list_export_xlsx')); await list.reload({ domain: [['id', '>', 0]] }); assert.isVisible(list.el.querySelector('.o_list_export_xlsx')); list.destroy(); }); QUnit.test('list view with adjacent buttons', async function (assert) { assert.expect(2); const list = await createView({ View: ListView, model: 'foo', data: this.data, arch: `