flectra.define('web.list_tests', function (require) { "use strict"; var config = require('web.config'); var basicFields = require('web.basic_fields'); var FormView = require('web.FormView'); var ListView = require('web.ListView'); var mixins = require('web.mixins'); var testUtils = require('web.test_utils'); var widgetRegistry = require('web.widget_registry'); var Widget = require('web.Widget'); 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}, 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"}, ] }, }; } }, function () { QUnit.module('ListView'); QUnit.test('simple readonly list', function (assert) { assert.expect(10); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.notOk(list.$el.hasClass('o_cannot_create'), "should not have className 'o_cannot_create'"); // 3 th (1 for checkbox, 2 for columns) assert.strictEqual(list.$('th').length, 3, "should have 3 columns"); assert.strictEqual(list.$('td:contains(gnap)').length, 1, "should contain gnap"); assert.strictEqual(list.$('tbody tr').length, 4, "should have 4 rows"); assert.strictEqual(list.$('th.o_column_sortable').length, 1, "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.ok(list.$buttons.find('.o_list_button_add').is(':visible'), "should have a visible Create button"); assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), "should not have a visible save button"); assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), "should not have a visible save button"); list.destroy(); }); QUnit.test('list with create="0"', function (assert) { assert.expect(2); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.ok(list.$el.hasClass('o_cannot_create'), "should have className 'o_cannot_create'"); assert.strictEqual(list.$buttons.find('.o_list_button_add').length, 0, "should not have the 'Create' button"); list.destroy(); }); QUnit.test('list with delete="0"', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, viewOptions: {sidebar: true}, arch: '', }); assert.ok(list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); list.$('tbody td.o_list_record_selector:first input').click(); assert.ok(!list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); assert.notOk(list.sidebar.$('a:contains(Delete)').length, 'sidebar should not have Delete button'); list.destroy(); }); QUnit.test('simple editable rendering', function (assert) { assert.expect(12); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.strictEqual(list.$('th').length, 3, "should have 2 th"); assert.strictEqual(list.$('th').length, 3, "should have 3 th"); assert.strictEqual(list.$('td:contains(yop)').length, 1, "should contain yop"); assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), "should have a visible Create button"); assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), "should not have a visible save button"); assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), "should not have a visible discard button"); list.$('td:not(.o_list_record_selector)').first().click(); assert.ok(!list.$buttons.find('.o_list_button_add').is(':visible'), "should not have a visible Create button"); assert.ok(list.$buttons.find('.o_list_button_save').is(':visible'), "should have a visible save button"); assert.ok(list.$buttons.find('.o_list_button_discard').is(':visible'), "should have a visible discard button"); list.$buttons.find('.o_list_button_save').click(); assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), "should have a visible Create button"); assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), "should not have a visible save button"); assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), "should not have a visible discard button"); list.destroy(); }); QUnit.test('invisible columns are not displayed', function (assert) { assert.expect(1); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '', }); // 1 th for checkbox, 1 for 1 visible column assert.strictEqual(list.$('th').length, 2, "should have 2 th"); list.destroy(); }); QUnit.test('record-depending invisible lines are correctly aligned', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '' + '', }); assert.strictEqual(list.$('tbody tr:first td').length, 4, "there should be 4 cells in the first row"); assert.strictEqual(list.$('tbody td.o_invisible_modifier').length, 1, "there should be 1 invisible bar cell"); assert.ok(list.$('tbody tr:first td:eq(2)').hasClass('o_invisible_modifier'), "the 3rd cell should be invisible"); assert.strictEqual(list.$('tbody tr:eq(0) td:visible').length, list.$('tbody tr:eq(1) td:visible').length, "there should be the same number of visible cells in different rows"); list.destroy(); }); QUnit.test('do not perform extra RPC to read invisible many2one fields', function (assert) { assert.expect(3); this.data.foo.fields.m2o.default = 2; var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '', mockRPC: function (route, args) { assert.step(_.last(route.split('/'))); return this._super.apply(this, arguments); }, }); list.$buttons.find('.o_list_button_add').click(); assert.verifySteps(['search_read', 'default_get'], "no nameget should be done"); list.destroy(); }); QUnit.test('at least 4 rows are rendered, even if less data', function (assert) { assert.expect(1); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', domain: [['bar', '=', true]], }); assert.strictEqual(list.$('tbody tr').length, 4, "should have 4 rows"); list.destroy(); }); QUnit.test('basic grouped list rendering', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', groupBy: ['bar'], }); assert.strictEqual(list.$('th:contains(Foo)').length, 1, "should contain Foo"); assert.strictEqual(list.$('th:contains(Bar)').length, 1, "should contain Bar"); assert.strictEqual(list.$('tr.o_group_header').length, 2, "should have 2 .o_group_header"); assert.strictEqual(list.$('th.o_group_name').length, 2, "should have 2 .o_group_name"); list.destroy(); }); QUnit.test('many2one field rendering', function (assert) { assert.expect(1); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.ok(list.$('td:contains(Value 1)').length, "should have the display_name of the many2one"); list.destroy(); }); QUnit.test('grouped list view, with 1 open group', function (assert) { assert.expect(6); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', groupBy: ['foo'], }); list.$('th.o_group_name').get(1).click(); assert.strictEqual(list.$('tbody:eq(1) tr').length, 2, "open group should contain 2 records"); assert.strictEqual(list.$('tbody').length, 3, "should contain 3 tbody"); assert.strictEqual(list.$('td:contains(9)').length, 1, "should contain 9"); assert.strictEqual(list.$('td:contains(-4)').length, 1, "should contain -4"); assert.strictEqual(list.$('td:contains(10)').length, 1, "should contain 10"); assert.strictEqual(list.$('tr.o_group_header td:contains(10)').length, 1, "but 10 should be in a header"); list.destroy(); }); QUnit.test('opening records when clicking on record', function (assert) { assert.expect(3); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); testUtils.intercept(list, "open_record", function () { assert.ok("list view should trigger 'open_record' event"); }); list.$('tr td:not(.o_list_record_selector)').first().click(); list.update({groupBy: ['foo']}); assert.strictEqual(list.$('tr.o_group_header').length, 3, "list should be grouped"); list.$('th.o_group_name').first().click(); list.$('tr:not(.o_group_header) td:not(.o_list_record_selector)').first().click(); list.destroy(); }); QUnit.test('editable list view: readonly fields cannot be edited', function (assert) { assert.expect(4); this.data.foo.fields.foo.readonly = true; var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '' + '', }); var $td = list.$('td:not(.o_list_record_selector)').first(); var $second_td = list.$('td:not(.o_list_record_selector)').eq(1); var $third_td = list.$('td:not(.o_list_record_selector)').eq(2); $td.click(); assert.ok($td.parent().hasClass('o_selected_row'), "row should be in edit mode"); assert.ok($td.hasClass('o_readonly_modifier'), "foo cell should be readonly in edit mode"); assert.ok(!$second_td.hasClass('o_readonly_modifier'), "bar cell should be editable"); assert.ok($third_td.hasClass('o_readonly_modifier'), "int_field cell should be readonly in edit mode"); list.destroy(); }); QUnit.test('basic operations for editable list renderer', function (assert) { assert.expect(2); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); var $td = list.$('td:not(.o_list_record_selector)').first(); assert.ok(!$td.parent().hasClass('o_selected_row'), "td should not be in edit mode"); $td.click(); assert.ok($td.parent().hasClass('o_selected_row'), "td should be in edit mode"); list.destroy(); }); QUnit.test('editable list: add a line and discard', function (assert) { assert.expect(11); testUtils.patch(basicFields.FieldChar, { destroy: function () { assert.step('destroy'); this._super.apply(this, arguments); }, }); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', domain: [['foo', '=', 'yop']], }); assert.strictEqual(list.$('tbody tr').length, 4, "list should contain 4 rows"); assert.strictEqual(list.$('.o_data_row').length, 1, "list should contain one record (and thus 3 empty rows)"); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', "pager should be correct"); list.$buttons.find('.o_list_button_add').click(); assert.strictEqual(list.$('tbody tr').length, 4, "list should still contain 4 rows"); assert.strictEqual(list.$('.o_data_row').length, 2, "list should contain two record (and thus 2 empty rows)"); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-2', "pager should be correct"); list.$buttons.find('.o_list_button_discard').click(); assert.strictEqual(list.$('tbody tr').length, 4, "list should still contain 4 rows"); assert.strictEqual(list.$('.o_data_row').length, 1, "list should contain one record (and thus 3 empty rows)"); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', "pager should be correct"); assert.verifySteps(['destroy'], "should have destroyed the widget of the removed line"); testUtils.unpatch(basicFields.FieldChar); list.destroy(); }); QUnit.test('field changes are triggered correctly', function (assert) { assert.expect(2); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); var $td = list.$('td:not(.o_list_record_selector)').first(); var n = 0; testUtils.intercept(list, "field_changed", function () { n += 1; }); $td.click(); $td.find('input').val('abc').trigger('input'); assert.strictEqual(n, 1, "field_changed should have been triggered"); list.$('td:not(.o_list_record_selector)').eq(2).click(); assert.strictEqual(n, 1, "field_changed should not have been triggered"); list.destroy(); }); QUnit.test('editable list view: basic char field edition', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); var $td = list.$('td:not(.o_list_record_selector)').first(); $td.click(); $td.find('input').val('abc').trigger('input'); assert.strictEqual($td.find('input').val(), 'abc', "char field has been edited correctly"); var $next_row_td = list.$('tbody tr:eq(1) td:not(.o_list_record_selector)').first(); $next_row_td.click(); // should trigger the save of the previous row assert.strictEqual(list.$('td:not(.o_list_record_selector)').first().text(), 'abc', 'changes should be saved correctly'); assert.ok(!list.$('tbody tr').first().hasClass('o_selected_row'), 'saved row should be in readonly mode'); assert.strictEqual(this.data.foo.records[0].foo, 'abc', "the edition should have been properly saved"); list.destroy(); }); QUnit.test('editable list view: save data when list sorting in edit mode', function (assert) { assert.expect(3); this.data.foo.fields.foo.sortable = true; var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', mockRPC: function (route, args) { if (args.method === 'write') { assert.deepEqual(args.args, [[1], {foo: 'xyz'}], "should correctly save the edited record"); } return this._super.apply(this, arguments); } }); list.$('.o_data_cell:first').click(); list.$('input[name="foo"]').val('xyz').trigger('input'); list.$('.o_column_sortable').click(); assert.ok(list.$('.o_data_row:first').hasClass('o_selected_row'), "first row should still be in edition"); list.$buttons.find('.o_list_button_save').click(); assert.ok(!list.$buttons.hasClass('o-editing'), "list buttons should be back to their readonly mode"); list.destroy(); }); QUnit.test('selection changes are triggered correctly', function (assert) { assert.expect(8); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); var $tbody_selector = list.$('tbody .o_list_record_selector input').first(); var $thead_selector = list.$('thead .o_list_record_selector input'); var n = 0; testUtils.intercept(list, "selection_changed", function () { n += 1; }); // tbody checkbox click $tbody_selector.click(); assert.strictEqual(n, 1, "selection_changed should have been triggered"); assert.ok($tbody_selector.is(':checked'), "selection checkbox should be checked"); $tbody_selector.click(); assert.strictEqual(n, 2, "selection_changed should have been triggered"); assert.ok(!$tbody_selector.is(':checked'), "selection checkbox shouldn't be checked"); // head checkbox click $thead_selector.click(); assert.strictEqual(n, 3, "selection_changed should have been triggered"); assert.strictEqual(list.$('tbody .o_list_record_selector input:checked').length, list.$('tbody tr').length, "all selection checkboxes should be checked"); $thead_selector.click(); assert.strictEqual(n, 4, "selection_changed should have been triggered"); assert.strictEqual(list.$('tbody .o_list_record_selector input:checked').length, 0, "no selection checkbox should be checked"); list.destroy(); }); QUnit.test('selection is reset on reload', function (assert) { assert.expect(5); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '', }); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', "total should be 32 (no record selected)"); // select first record var $firstRowSelector = list.$('tbody .o_list_record_selector input').first(); $firstRowSelector.click(); assert.ok($firstRowSelector.is(':checked'), "first row should be selected"); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '10', "total should be 10 (first record selected)"); // reload list.reload(); $firstRowSelector = list.$('tbody .o_list_record_selector input').first(); assert.notOk($firstRowSelector.is(':checked'), "first row should no longer be selected"); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', "total should be 32 (no more record selected)"); list.destroy(); }); QUnit.test('aggregates are computed correctly', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); var $tbody_selectors = list.$('tbody .o_list_record_selector input'); var $thead_selector = list.$('thead .o_list_record_selector input'); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "32", "total should be 32"); $tbody_selectors.first().click(); $tbody_selectors.last().click(); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "6", "total should be 6 as first and last records are selected"); $thead_selector.click(); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "32", "total should be 32 as all records are selected"); // Let's update the view to dislay NO records list.update({domain: ['&', ['bar', '=', false], ['int_field', '>', 0]]}); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "0", "total should have been recomputed to 0"); list.destroy(); }); QUnit.test('aggregates are computed correctly in grouped lists', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, groupBy: ['m2o'], arch: '', }); var $groupHeader1 = list.$('.o_group_header').filter(function (index, el) { return $(el).data('group').res_id === 1; }); var $groupHeader2 = list.$('.o_group_header').filter(function (index, el) { return $(el).data('group').res_id === 2; }); assert.strictEqual($groupHeader1.find('td:nth(1)').text(), "23", "first group total should be 23"); assert.strictEqual($groupHeader2.find('td:nth(1)').text(), "9", "second group total should be 9"); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "32", "total should be 32"); $groupHeader1.click(); list.$('tbody .o_list_record_selector input').first().click(); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "10", "total should be 10 as first record of first group is selected"); list.destroy(); }); QUnit.test('aggregates are updated when a line is edited', function (assert) { assert.expect(2); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); assert.strictEqual(list.$('td[title="Sum"]').text(), "32", "current total should be 32"); list.$('tr.o_data_row td.o_data_cell').first().click(); list.$('td.o_data_cell input').val("15").trigger("input"); assert.strictEqual(list.$('td[title="Sum"]').text(), "37", "current total should now be 37"); list.destroy(); }); QUnit.test('aggregates are formatted according to field widget', function (assert) { assert.expect(1); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '', }); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '19:24', "total should be formatted as a float_time"); list.destroy(); }); QUnit.test('groups can be sorted on aggregates', function (assert) { assert.expect(10); var list = createView({ View: ListView, model: 'foo', data: this.data, groupBy: ['foo'], arch: '', mockRPC: function (route, args) { if (args.method === 'read_group') { assert.step(args.kwargs.orderby || 'default order'); } return this._super.apply(this, arguments); }, }); assert.strictEqual(list.$('tbody .o_list_number').text(), '10517', "initial order should be 10, 5, 17"); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', "total should be 32"); list.$('.o_column_sortable').click(); // sort (int_field ASC) assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', "total should still be 32"); assert.strictEqual(list.$('tbody .o_list_number').text(), '51017', "order should be 5, 10, 17"); list.$('.o_column_sortable').click(); // sort (int_field DESC) assert.strictEqual(list.$('tbody .o_list_number').text(), '17105', "initial order should be 17, 10, 5"); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', "total should still be 32"); assert.verifySteps(['default order', 'int_field ASC', 'int_field DESC']); list.destroy(); }); QUnit.test('properly apply onchange in simple case', function (assert) { assert.expect(2); this.data.foo.onchanges = { foo: function (obj) { obj.int_field = obj.foo.length + 1000; }, }; var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', }); var $foo_td = list.$('td:not(.o_list_record_selector)').first(); var $int_field_td = list.$('td:not(.o_list_record_selector)').eq(1); assert.strictEqual($int_field_td.text(), '10', "should contain initial value"); $foo_td.click(); $foo_td.find('input').val('tralala').trigger('input'); assert.strictEqual($int_field_td.find('input').val(), "1007", "should contain input with onchange applied"); list.destroy(); }); QUnit.test('column width should not change when switching mode', function (assert) { assert.expect(10); // Warning: this test is css dependant var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '' + '' + '' + '', }); var startWidths = _.pluck(list.$('thead th'), 'offsetWidth'); // start edition of first row list.$('td:not(.o_list_record_selector)').first().click(); var editionWidths = _.pluck(list.$('thead th'), 'offsetWidth'); // leave edition list.$buttons.find('.o_list_button_save').click(); var readonlyWidths = _.pluck(list.$('thead th'), 'offsetWidth'); for (var i = 0; i < startWidths.length; i++) { assert.strictEqual(startWidths[i], editionWidths[i], 'width of columns should remain unchanged which switching from readonly to edit mode'); assert.strictEqual(editionWidths[i], readonlyWidths[i], 'width of columns should remain unchanged which switching from edit to readonly mode'); } list.destroy(); }); QUnit.test('deleting one record', function (assert) { assert.expect(5); var list = createView({ View: ListView, model: 'foo', data: this.data, viewOptions: {sidebar: true}, arch: '', }); assert.ok(list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 4, "should have 4 records"); list.$('tbody td.o_list_record_selector:first input').click(); assert.ok(!list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); list.sidebar.$('a:contains(Delete)').click(); assert.ok($('body').hasClass('modal-open'), 'body should have modal-open clsss'); $('body .modal-dialog button span:contains(Ok)').click(); assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 3, "should have 3 records"); list.destroy(); }); QUnit.test('archiving one record', function (assert) { assert.expect(9); // add active field on foo model and make all records active this.data.foo.fields.active = {string: 'Active', type: 'boolean', default: true}; var list = createView({ View: ListView, model: 'foo', data: this.data, viewOptions: {sidebar: true}, arch: '', mockRPC: function (route) { if (route === '/web/dataset/call_kw/ir.attachment/search_read') { return $.when([]); } assert.step(route); return this._super.apply(this, arguments); }, }); assert.ok(list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 4, "should have 4 records"); list.$('tbody td.o_list_record_selector:first input').click(); assert.ok(!list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); assert.verifySteps(['/web/dataset/search_read']); list.sidebar.$('a:contains(Archive)').click(); assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 3, "should have 3 records"); assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/foo/write', '/web/dataset/search_read']); list.destroy(); }); QUnit.test('pager (ungrouped and grouped mode), default limit', function (assert) { assert.expect(4); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.strictEqual(args.limit, 80, "default limit should be 80 in List"); } return this._super.apply(this, arguments); }, }); assert.ok(!list.pager.$el.hasClass('o_hidden'), "pager should be visible"); assert.strictEqual(list.pager.state.size, 4, "pager's size should be 4"); list.update({ groupBy: ['bar']}); assert.ok(list.pager.$el.hasClass('o_hidden'), "pager should be invisible"); list.destroy(); }); QUnit.test('can sort records when clicking on header', function (assert) { assert.expect(9); this.data.foo.fields.foo.sortable = true; var nbSearchRead = 0; var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', mockRPC: function (route) { if (route === '/web/dataset/search_read') { nbSearchRead++; } return this._super.apply(this, arguments); }, }); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(yop)').length, "record 1 should be first"); assert.ok(list.$('tbody tr:eq(3) td:contains(blip)').length, "record 3 should be first"); nbSearchRead = 0; list.$('thead th:contains(Foo)').click(); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(blip)').length, "record 3 should be first"); assert.ok(list.$('tbody tr:eq(3) td:contains(yop)').length, "record 1 should be first"); nbSearchRead = 0; list.$('thead th:contains(Foo)').click(); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(yop)').length, "record 3 should be first"); assert.ok(list.$('tbody tr:eq(3) td:contains(blip)').length, "record 1 should be first"); list.destroy(); }); QUnit.test('use default_order', function (assert) { assert.expect(3); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '', mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.strictEqual(args.sort, 'foo ASC', "should correctly set the sort attribute"); } return this._super.apply(this, arguments); }, }); assert.ok(list.$('tbody tr:first td:contains(blip)').length, "record 3 should be first"); assert.ok(list.$('tbody tr:eq(3) td:contains(yop)').length, "record 1 should be first"); list.destroy(); }); QUnit.test('use more complex default_order', function (assert) { assert.expect(3); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '', mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.strictEqual(args.sort, 'foo ASC, bar DESC, int_field ASC', "should correctly set the sort attribute"); } return this._super.apply(this, arguments); }, }); assert.ok(list.$('tbody tr:first td:contains(blip)').length, "record 3 should be first"); assert.ok(list.$('tbody tr:eq(3) td:contains(yop)').length, "record 1 should be first"); list.destroy(); }); QUnit.test('use default_order on editable tree: sort on save', function (assert) { assert.expect(8); this.data.foo.records[0].o2m = [1, 3]; var form = createView({ View: FormView, model: 'foo', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '
', res_id: 1, }); form.$buttons.find('.o_form_button_edit').click(); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); var $o2m = form.$('.o_field_widget[name=o2m]'); form.$('.o_field_x2many_list_row_add a').click(); $o2m.find('.o_field_widget').val("Value 2").trigger('input'); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); form.$buttons.find('.o_form_button_save').click(); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 2)').length, "Value 2 should be second (should be sorted after saving)"); assert.ok(form.$('tbody tr:eq(2) td:contains(Value 3)').length, "Value 3 should be third"); form.destroy(); }); QUnit.test('use default_order on editable tree: sort on demand', function (assert) { assert.expect(11); this.data.foo.records[0].o2m = [1, 3]; this.data.bar.fields = {name: {string: "Name", type: "char", sortable: true}}; this.data.bar.records[0].name = "Value 1"; this.data.bar.records[2].name = "Value 3"; var form = createView({ View: FormView, model: 'foo', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '
', res_id: 1, }); form.$buttons.find('.o_form_button_edit').click(); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); var $o2m = form.$('.o_field_widget[name=o2m]'); form.$('.o_field_x2many_list_row_add a').click(); $o2m.find('.o_field_widget').val("Value 2").trigger('input'); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); form.$('.o_form_sheet_bg').click(); // validate the row before sorting $o2m.find('.o_column_sortable').click(); // resort list after edition assert.strictEqual(form.$('tbody tr:first').text(), 'Value 1', "Value 1 should be first"); assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2', "Value 2 should be second (should be sorted after saving)"); assert.strictEqual(form.$('tbody tr:eq(2)').text(), 'Value 3', "Value 3 should be third"); $o2m.find('.o_column_sortable').click(); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 3', "Value 3 should be first"); assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2', "Value 2 should be second (should be sorted after saving)"); assert.strictEqual(form.$('tbody tr:eq(2)').text(), 'Value 1', "Value 1 should be third"); form.destroy(); }); QUnit.test('use default_order on editable tree: sort on demand in page', function (assert) { assert.expect(4); this.data.bar.fields = {name: {string: "Name", type: "char", sortable: true}}; var ids = []; for (var i=0; i<45; i++) { var id = 4 + i; ids.push(id); this.data.bar.records.push({ id: id, name: "Value " + (id < 10 ? '0' : '') + id, }); } this.data.foo.records[0].o2m = ids; var form = createView({ View: FormView, model: 'foo', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '
', res_id: 1, }); // Change page form.$('.o_pager_next').click(); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 44', "record 44 should be first"); assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 48', "record 48 should be last"); form.$('.o_column_sortable').click(); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 08', "record 48 should be first"); assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 04', "record 44 should be first"); form.destroy(); }); QUnit.test('can display button in edit mode', function (assert) { assert.expect(2); var list = createView({ View: ListView, model: 'foo', data: this.data, arch: '' + '' + '