Files
odoo_source/addons/web/static/tests/views/pivot_tests.js
T
40dd121938 [REF] web: move ControlPanel inside controllers/actions
This branch introduces a large-scale reorganization of the
component tree generated by the web client.  The short version is
that now, the control panel is a child of the view controller and
no longer a sibling. Graphically (and simplified), we go from
this:

                  webClient
                 /    |    \
               ...   ...  actionManager
                           /         \
                   controlPanel   viewController
                                    /       \

to this:

           	  webClient
                 /    |    \
               ...   ...  actionManager
                               |
                          viewController
                            /   |   \
          ControlPanelController
              /           \
         CPRenderer     CPModel

The motivation is that this work moves the code where it should be.
Before this commit, it was kind of weird to have code in the
controllers to render buttons outside of their root node (in
renderButtons).  Also, the action manager had to take care of
coordinating search view states between view transitions.

So, this work simplifies the code.  It also makes it easier to
extend. We see day after day that Odoo needs to take care of more
complex UI needs, and in many cases, these needs were quite
difficult to implement (we prefer spaghettis in our plates, not in
our code).  The changes in this branch should open the way to
implement these features.

For example,
- it will now be easy to add the possibility of views (for example,
  the search view or a new ControlPanel view) to add custom buttons
  (of type action or object) in the control panel.
- Another need will be to serialize/ restore the state of the
  search view across action boundaries (needed by the dashboard).
- Another example is the possibility for views to customize easily
  the presence/absence of sub menus (filters/groupbys/favorites/
  time range/...)
- Another need is an easier way for views/client action to customize
  their control panel.

This commit also contains a large rewrite of the search view (so it
is more inline with our architecture and easier to maintain).

Part of task 1893568

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: Martin Geubelle <mge@odoo.com>
2018-12-07 13:16:06 +00:00

1575 lines
58 KiB
JavaScript

odoo.define('web.pivot_tests', function (require) {
"use strict";
var core = require('web.core');
var PivotView = require('web.PivotView');
var testUtils = require('web.test_utils');
var createActionManager = testUtils.createActionManager;
var patchDate = testUtils.mock.patchDate;
var _t = core._t;
var createView = testUtils.createView;
QUnit.module('Views', {
beforeEach: function () {
this.data = {
partner: {
fields: {
foo: {string: "Foo", type: "integer"},
bar: {string: "bar", type: "boolean"},
date: {string: "Date", type: "date", store: true, sortable: true},
product_id: {string: "Product", type: "many2one", relation: 'product', store: true},
other_product_id: {string: "Other Product", type: "many2one", relation: 'product', store: true},
non_stored_m2o: {string: "Non Stored M2O", type: "many2one", relation: 'product'},
customer: {string: "Customer", type: "many2one", relation: 'customer', store: true},
computed_field: {string: "Computed and not stored", compute:true},
},
records: [
{
id: 1,
foo: 12,
bar: true,
date: '2016-12-14',
product_id: 37,
customer: 1,
computed_field: 19,
}, {
id: 2,
foo: 1,
bar: true,
date: '2016-10-26',
product_id: 41,
customer: 2,
computed_field: 23,
}, {
id: 3,
foo: 17,
bar: true,
date: '2016-12-15',
product_id: 41,
customer: 2,
computed_field: 26,
}, {id: 4,
foo: 2,
bar: false,
date: '2016-04-11',
product_id: 41,
customer: 1,
computed_field: 19,
},
]
},
product: {
fields: {
name: {string: "Product Name", type: "char"}
},
records: [{
id: 37,
display_name: "xphone",
}, {
id: 41,
display_name: "xpad",
}]
},
customer: {
fields: {
name: {string: "Customer Name", type: "char"}
},
records: [{
id: 1,
display_name: "First",
}, {
id: 2,
display_name: "Second",
}]
},
};
}
}, function () {
QUnit.module('PivotView');
QUnit.test('simple pivot rendering', function (assert) {
assert.expect(3);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners">' +
'<field name="foo" type="measure"/>' +
'</pivot>',
mockRPC: function (route, args) {
assert.strictEqual(args.kwargs.lazy, false,
"the read_group should be done with the lazy=false option");
return this._super.apply(this, arguments);
},
});
assert.hasClass(pivot.$el,'o_enable_linking',
"root node should have classname 'o_enable_linking'");
assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(32)').length, 1,
"should contain a pivot cell with the sum of all records");
pivot.destroy();
});
QUnit.test('pivot rendering with widget', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners">' +
'<field name="foo" type="measure" widget="float_time"/>' +
'</pivot>',
});
assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(32:00)').length, 1,
"should contain a pivot cell with the sum of all records");
pivot.destroy();
});
QUnit.test('pivot rendering with string attribute on field', function (assert) {
assert.expect(1);
this.data.partner.fields.foo = {string: "Foo", type: "integer", store: true};
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners">' +
'<field name="foo" string="BAR" type="measure"/>' +
'</pivot>',
});
assert.strictEqual(pivot.$('.o_pivot_measure_row').text(), "BAR",
"the displayed name should be the one set in the string attribute");
pivot.destroy();
});
QUnit.test('pivot rendering with invisible attribute on field', function (assert) {
assert.expect(2);
// when invisible, a field should neither be an active measure,
// nor be a selectable measure.
_.extend(this.data.partner.fields, {
foo: {string: "Foo", type: "integer", store: true},
foo2: {string: "Foo2", type: "integer", store: true}
});
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners">' +
'<field name="foo" type="measure"/>' +
'<field name="foo2" type="measure" invisible="True"/>' +
'</pivot>',
});
// there should be only one displayed measure as the other one is invisible
assert.containsOnce(pivot, '.o_pivot_measure_row');
// there should be only one measure besides count, as the other one is invisible
assert.containsN(pivot, '.o_cp_left .dropdown-item', 2);
pivot.destroy();
});
QUnit.test('pivot view without "string" attribute', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
// this is important for export functionality.
assert.strictEqual(pivot.title, _t("Untitled"), "should have a valid title");
pivot.destroy();
});
QUnit.test('pivot view add computed fields explicitly defined as measure', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="computed_field" type="measure"/>' +
'</pivot>',
});
assert.ok(pivot.measures.computed_field, "measures contains the field 'computed_field'");
pivot.destroy();
});
QUnit.test('clicking on a cell triggers a do_action', function (assert) {
assert.expect(2);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
intercepts: {
do_action: function (ev) {
assert.deepEqual(ev.data.action, {
context: {someKey: true, userContextKey: true},
domain: [['product_id', '=', 37]],
name: 'Partners',
res_model: 'partner',
target: 'current',
type: 'ir.actions.act_window',
view_mode: 'list',
view_type: 'list',
views: [[false, 'list'], [2, 'form']],
}, "should trigger do_action with the correct args");
},
},
session: {
user_context: {userContextKey: true},
},
viewOptions: {
action: {
views: [
{ viewID: 2, type: 'form' },
{ viewID: 5, type: 'kanban' },
{ viewID: false, type: 'list' },
{ viewID: false, type: 'pivot' },
],
},
context: {someKey: true, search_default_test: 3},
title: 'Partners',
}
});
assert.hasClass(pivot.$el,'o_enable_linking',
"root node should have classname 'o_enable_linking'");
testUtils.dom.click(pivot.$('.o_pivot_cell_value:contains(12)')); // should trigger a do_action
pivot.destroy();
});
QUnit.test('pivot view with disable_linking="True"', function (assert) {
assert.expect(2);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot disable_linking="True">' +
'<field name="foo" type="measure"/>' +
'</pivot>',
intercepts: {
do_action: function () {
assert.ok(false, "should not trigger do_action");
},
},
});
assert.doesNotHaveClass(pivot.$el, 'o_enable_linking',
"root node should not have classname 'o_enable_linking'");
assert.containsOnce(pivot, '.o_pivot_cell_value',
"should have one cell");
testUtils.dom.click(pivot.$('.o_pivot_cell_value')); // should not trigger a do_action
pivot.destroy();
});
QUnit.test('pivot view grouped by date field', function (assert) {
assert.expect(2);
var data = this.data;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="date" interval="month" type="col"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
mockRPC: function (route, params) {
var wrong_fields = _.filter(params.kwargs.fields, function (field) {
return !(field in data.partner.fields);
});
assert.ok(!wrong_fields.length, 'fields given to read_group should exist on the model');
return this._super.apply(this, arguments);
},
});
pivot.destroy();
});
QUnit.test('without measures, pivot view uses __count by default', function (assert) {
assert.expect(2);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot></pivot>',
mockRPC: function (route, args) {
if (args.method === 'read_group') {
assert.deepEqual(args.kwargs.fields, ['__count'],
"should make a read_group with no valid fields");
}
return this._super(route, args);
}
});
var $countMeasure = pivot.$buttons.find('.dropdown-item[data-field=__count]:first');
assert.hasClass($countMeasure,'selected', "The count measure should be activated");
pivot.destroy();
});
QUnit.test('pivot view can be reloaded', function (assert) {
assert.expect(4);
var readGroupCount = 0;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot></pivot>',
mockRPC: function (route, args) {
if (args.method === 'read_group') {
readGroupCount++;
}
return this._super(route, args);
}
});
assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(4)').length, 1,
"should contain a pivot cell with the number of all records");
assert.strictEqual(readGroupCount, 1, "should have done 1 rpc");
testUtils.pivot.reload(pivot, {domain: [['foo', '>', 10]]});
assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(2)').length, 1,
"should contain a pivot cell with the number of remaining records");
assert.strictEqual(readGroupCount, 2, "should have done 2 rpcs");
pivot.destroy();
});
QUnit.test('pivot view grouped by many2one field', function (assert) {
assert.expect(3);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
assert.containsOnce(pivot, '.o_pivot_header_cell_opened',
"should have one opened header");
assert.strictEqual(pivot.$('.o_pivot_header_cell_closed:contains(xphone)').length, 1,
"should display one header with 'xphone'");
assert.strictEqual(pivot.$('.o_pivot_header_cell_closed:contains(xpad)').length, 1,
"should display one header with 'xpad'");
pivot.destroy();
});
QUnit.test('basic folding/unfolding', function (assert) {
assert.expect(7);
var rpcCount = 0;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
mockRPC: function () {
rpcCount++;
return this._super.apply(this, arguments);
},
});
assert.containsN(pivot, 'tbody tr', 3,
"should have 3 rows: 1 for the opened header, and 2 for data");
// click on the opened header to close it
testUtils.dom.click(pivot.$('.o_pivot_header_cell_opened'));
assert.containsOnce(pivot, 'tbody tr', "should have 1 row");
// click on closed header to open dropdown
testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed'));
assert.containsOnce(pivot, '.o_pivot_field_menu .dropdown-item[data-field="date"]:first',
"should have the date field as proposition");
assert.containsOnce(pivot, '.o_field_selection .dropdown-item[data-field="product_id"]:first',
"should have the product_id field as proposition");
assert.containsNone(pivot, '.o_field_selection .dropdown-item[data-field="non_stored_m2o"]:first',
"should not have the non_stored_m2o field as proposition");
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"]:first'));
assert.containsN(pivot, 'tbody tr', 4,
"should have 4 rows: one for header, 3 for data");
assert.strictEqual(rpcCount, 3,
"should have done 3 rpcs (initial load) + open header with different groupbys");
pivot.destroy();
});
QUnit.test('more folding/unfolding', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
// open dropdown to zoom into first row
testUtils.dom.clickFirst(pivot.$('tbody .o_pivot_header_cell_closed'));
// click on date by day
pivot.$('.dropdown-menu.show .o_inline_dropdown .dropdown-menu').toggle(); // unfold inline dropdown
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"][data-interval="day"]'));
// open dropdown to zoom into second row
testUtils.dom.clickLast(pivot.$('tbody td.o_pivot_header_cell_closed'));
assert.containsN(pivot, 'tbody tr', 7,
"should have 7 rows (1 for total, 1 for xphone, 1 for xpad, 4 for data)");
pivot.destroy();
});
QUnit.test('pivot view can be flipped', function (assert) {
assert.expect(3);
var rpcCount = 0;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'</pivot>',
mockRPC: function () {
rpcCount++;
return this._super.apply(this, arguments);
},
});
assert.containsN(pivot, 'tbody tr', 3,
"should have 3 rows: 1 for the open header, and 2 for data");
rpcCount = 0;
testUtils.dom.click(pivot.$buttons.find('.o_pivot_flip_button'));
assert.strictEqual(rpcCount, 0, "should not have done any rpc");
assert.containsOnce(pivot, 'tbody tr',
"should have 1 rows: 1 for the main header");
pivot.destroy();
});
QUnit.test('can toggle extra measure', function (assert) {
assert.expect(8);
var rpcCount = 0;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
mockRPC: function () {
rpcCount++;
return this._super.apply(this, arguments);
},
});
assert.containsN(pivot, '.o_pivot_cell_value', 3,
"should have 3 cells: 1 for the open header, and 2 for data");
assert.doesNotHaveClass(pivot.$buttons.find('.dropdown-item[data-field=__count]:first'), 'selected',
"the __count measure should not be selected");
rpcCount = 0;
testUtils.pivot.toggleMeasuresDropdown(pivot);
testUtils.pivot.clickMeasure(pivot, '__count');
assert.hasClass(pivot.$buttons.find('.dropdown-item[data-field=__count]:first'),'selected',
"the __count measure should be selected");
assert.containsN(pivot, '.o_pivot_cell_value', 6,
"should have 6 cells: 2 for the open header, and 4 for data");
assert.strictEqual(rpcCount, 2,
"should have done 2 rpcs to reload data");
testUtils.pivot.clickMeasure(pivot, '__count');
assert.doesNotHaveClass(pivot.$buttons.find('.dropdown-item[data-field=__count]:first'), 'selected',
"the __count measure should not be selected");
assert.containsN(pivot, '.o_pivot_cell_value', 3,
"should have 3 cells: 1 for the open header, and 2 for data");
assert.strictEqual(rpcCount, 2,
"should not have done any extra rpcs");
pivot.destroy();
});
QUnit.test('no content helper when no active measure', function (assert) {
assert.expect(4);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners">' +
'</pivot>',
});
assert.containsNone(pivot, '.o_view_nocontent',
"should not have a no_content_helper");
assert.containsOnce(pivot, 'table',
"should have a table in DOM");
testUtils.pivot.toggleMeasuresDropdown(pivot);
testUtils.pivot.clickMeasure(pivot, '__count');
assert.containsOnce(pivot, '.o_view_nocontent',
"should have a no_content_helper");
assert.containsNone(pivot, 'table',
"should not have a table in DOM");
pivot.destroy();
});
QUnit.test('no content helper when no data', function (assert) {
assert.expect(4);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners">' +
'</pivot>',
});
assert.containsNone(pivot, '.o_view_nocontent',
"should not have a no_content_helper");
assert.containsOnce(pivot, 'table',
"should have a table in DOM");
testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]});
assert.containsOnce(pivot, '.o_view_nocontent',
"should have a no_content_helper");
assert.containsNone(pivot, 'table',
"should not have a table in DOM");
pivot.destroy();
});
QUnit.test('no content helper when no data, part 2', function (assert) {
assert.expect(1);
this.data.partner.records = [];
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners"></pivot>',
});
assert.containsOnce(pivot, '.o_view_nocontent',
"should have a no_content_helper");
pivot.destroy();
});
QUnit.test('no content helper when no data, part 3', function (assert) {
assert.expect(4);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot string="Partners"></pivot>',
viewOptions: {
domain: [['foo', '=', 12345]]
},
});
assert.containsOnce(pivot, '.o_view_nocontent',
"should have a no_content_helper");
testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]});
assert.containsOnce(pivot, '.o_view_nocontent',
"should still have a no_content_helper");
testUtils.pivot.reload(pivot, {domain: []});
assert.containsNone(pivot, '.o_view_nocontent',
"should not have a no_content_helper");
// tries to open a field selection menu, to make sure it was not
// removed from the dom.
testUtils.dom.clickFirst(pivot.$('.o_pivot_header_cell_closed'));
assert.containsOnce(pivot, '.o_pivot_field_menu',
"the field selector menu exists");
pivot.destroy();
});
QUnit.test('tries to restore previous state after domain change', function (assert) {
assert.expect(5);
var rpcCount = 0;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
mockRPC: function () {
rpcCount++;
return this._super.apply(this, arguments);
},
});
assert.containsN(pivot, '.o_pivot_cell_value', 3,
"should have 3 cells: 1 for the open header, and 2 for data");
assert.strictEqual(pivot.$('.o_pivot_measure_row:contains(Foo)').length, 1,
"should have 1 row for measure Foo");
testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]});
rpcCount = 0;
testUtils.pivot.reload(pivot, {domain: []});
assert.equal(rpcCount, 2, "should have reloaded data");
assert.containsN(pivot, '.o_pivot_cell_value', 3,
"should still have 3 cells: 1 for the open header, and 2 for data");
assert.strictEqual(pivot.$('.o_pivot_measure_row:contains(Foo)').length, 1,
"should still have 1 row for measure Foo");
pivot.destroy();
});
QUnit.test('can be grouped with the update function', function (assert) {
assert.expect(4);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
assert.containsOnce(pivot, '.o_pivot_cell_value',
"should have only 1 cell");
assert.containsOnce(pivot, 'tbody tr',
"should have 1 rows");
testUtils.pivot.reload(pivot, {groupBy: ['product_id']});
assert.containsN(pivot, '.o_pivot_cell_value', 3,
"should have 3 cells");
assert.containsN(pivot, 'tbody tr', 3,
"should have 3 rows");
pivot.destroy();
});
QUnit.test('can sort data in a column by clicking on header', function (assert) {
assert.expect(3);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'<field name="product_id" type="row"/>' +
'</pivot>',
});
assert.strictEqual($('td.o_pivot_cell_value').text(), "321220",
"should have proper values in cells (total, result 1, result 2");
testUtils.dom.click(pivot.$('th.o_pivot_measure_row'));
assert.strictEqual($('td.o_pivot_cell_value').text(), "322012",
"should have proper values in cells (total, result 2, result 1");
testUtils.dom.click(pivot.$('th.o_pivot_measure_row'));
assert.strictEqual($('td.o_pivot_cell_value').text(), "321220",
"should have proper values in cells (total, result 1, result 2");
pivot.destroy();
});
QUnit.test('can expand all rows', function (assert) {
assert.expect(7);
var nbReadGroups = 0;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'<field name="product_id" type="row"/>' +
'</pivot>',
mockRPC: function (route, args) {
if (args.method === 'read_group') {
nbReadGroups++;
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(nbReadGroups, 2, "should have done 2 read_group RPCS");
assert.strictEqual(pivot.$('td.o_pivot_cell_value').text(), "321220",
"should have proper values in cells (total, result 1, result 2)");
// expand on date:days, product
nbReadGroups = 0;
testUtils.pivot.reload(pivot, {groupBy: ['date:days', 'product_id']});
assert.strictEqual(nbReadGroups, 3, "should have done 3 read_group RPCS");
assert.containsN(pivot, 'tbody tr', 8,
"should have 7 rows (total + 3 for December and 2 for October and April)");
// collapse the last two rows
testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened'));
testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened'));
assert.containsN(pivot, 'tbody tr', 6,
"should have 6 rows now");
// expand all
nbReadGroups = 0;
testUtils.dom.click(pivot.$buttons.find('.o_pivot_expand_button'));
assert.strictEqual(nbReadGroups, 3, "should have done 3 read_group RPCS");
assert.containsN(pivot, 'tbody tr', 8,
"should have 8 rows again");
pivot.destroy();
});
QUnit.test('expand all with a delay', function (assert) {
assert.expect(3);
var def;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'<field name="product_id" type="row"/>' +
'</pivot>',
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'read_group') {
return $.when(def).then(_.constant(result));
}
return result;
},
});
// expand on date:days, product
testUtils.pivot.reload(pivot, {groupBy: ['date:days', 'product_id']});
assert.containsN(pivot, 'tbody tr', 8,
"should have 7 rows (total + 3 for December and 2 for October and April)");
// collapse the last two rows
testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened'));
testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened'));
assert.containsN(pivot, 'tbody tr', 6,
"should have 6 rows now");
// expand all
def = $.Deferred();
testUtils.dom.click(pivot.$buttons.find('.o_pivot_expand_button'));
def.resolve();
assert.containsN(pivot, 'tbody tr', 8,
"should have 8 rows again");
pivot.destroy();
});
QUnit.test('can download a file', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="date" interval="month" type="col"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
session: {
get_file: function (args) {
assert.strictEqual(args.url, '/web/pivot/export_xls',
"should call get_file with correct parameters");
args.complete();
},
},
});
testUtils.dom.click(pivot.$buttons.find('.o_pivot_download'));
pivot.destroy();
});
QUnit.test('download button is disabled when there is no data', function (assert) {
assert.expect(1);
this.data.partner.records = [];
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="date" interval="month" type="col"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
assert.hasAttrValue(pivot.$buttons.find('.o_pivot_download'), 'disabled', 'disabled',
"download button should be disabled");
pivot.destroy();
});
QUnit.test('getOwnedQueryParams correctly returns measures and groupbys', function (assert) {
assert.expect(3);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="date" interval="day" type="col"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
pivot_column_groupby: ['date:day'],
pivot_measures: ['foo'],
pivot_row_groupby: [],
},
}, "context should be correct");
// expand header on field customer
testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed:nth(1)'));
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="customer"]:first'));
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
pivot_column_groupby: ['date:day', 'customer'],
pivot_measures: ['foo'],
pivot_row_groupby: [],
},
}, "context should be correct");
// expand row on field product_id
testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed'));
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first'));
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
pivot_column_groupby: ['date:day', 'customer'],
pivot_measures: ['foo'],
pivot_row_groupby: ['product_id'],
},
}, "context should be correct");
pivot.destroy();
});
QUnit.test('correctly uses pivot_ keys from the context', function (assert) {
assert.expect(7);
this.data.partner.fields.amount = {string: "Amount", type: "float"};
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="date" interval="day" type="col"/>' +
'<field name="amount" type="measure"/>' +
'</pivot>',
viewOptions: {
context: {
pivot_measures: ['foo'],
pivot_column_groupby: ['customer'],
pivot_row_groupby: ['product_id'],
},
},
});
assert.containsOnce(pivot, 'thead .o_pivot_header_cell_opened',
"column: should have one opened header");
assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(First)').length, 1,
"column: should display one closed header with 'First'");
assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(Second)').length, 1,
"column: should display one closed header with 'Second'");
assert.containsOnce(pivot, 'tbody .o_pivot_header_cell_opened',
"row: should have one opened header");
assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xphone)').length, 1,
"row: should display one closed header with 'xphone'");
assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xpad)').length, 1,
"row: should display one closed header with 'xpad'");
assert.strictEqual(pivot.$('tbody tr:first td:nth(3)').text(), '32',
"selected measure should be foo, with total 32");
pivot.destroy();
});
QUnit.test('correctly uses pivot_ keys from the context (at reload)', function (assert) {
assert.expect(8);
this.data.partner.fields.amount = {string: "Amount", type: "float"};
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="date" interval="day" type="col"/>' +
'<field name="amount" type="measure"/>' +
'</pivot>',
});
assert.strictEqual(pivot.$('tbody tr:first td.o_pivot_cell_value:last').text(), '0.00',
"the active measure should be amount");
var reloadParams = {
context: {
pivot_measures: ['foo'],
pivot_column_groupby: ['customer'],
pivot_row_groupby: ['product_id'],
},
};
testUtils.pivot.reload(pivot, reloadParams);
assert.containsOnce(pivot, 'thead .o_pivot_header_cell_opened',
"column: should have one opened header");
assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(First)').length, 1,
"column: should display one closed header with 'First'");
assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(Second)').length, 1,
"column: should display one closed header with 'Second'");
assert.containsOnce(pivot, 'tbody .o_pivot_header_cell_opened',
"row: should have one opened header");
assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xphone)').length, 1,
"row: should display one closed header with 'xphone'");
assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xpad)').length, 1,
"row: should display one closed header with 'xpad'");
assert.strictEqual(pivot.$('tbody tr:first td:nth(3)').text(), '32',
"selected measure should be foo, with total 32");
pivot.destroy();
});
QUnit.test('correctly use group_by key from the context', function (assert) {
assert.expect(7);
var pivot = createView({
View: PivotView,
model: 'partner',
data: this.data,
arch: '<pivot>' +
'<field name="customer" type="col" />' +
'<field name="foo" type="measure" />' +
'</pivot>',
groupBy: ['product_id'],
});
assert.containsOnce(pivot, 'thead .o_pivot_header_cell_opened',
'column: should have one opened header');
assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(First)').length, 1,
'column: should display one closed header with "First"');
assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(Second)').length, 1,
'column: should display one closed header with "Second"');
assert.containsOnce(pivot, 'tbody .o_pivot_header_cell_opened',
'row: should have one opened header');
assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xphone)').length, 1,
'row: should display one closed header with "xphone"');
assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xpad)').length, 1,
'row: should display one closed header with "xpad"');
assert.strictEqual(pivot.$('tbody tr:first td:nth(3)').text(), '32',
'selected measure should be foo, with total 32');
pivot.destroy();
});
QUnit.test('pivot still handles __count__ measure', function (assert) {
// for retro-compatibility reasons, the pivot view still handles
// '__count__' measure.
assert.expect(2);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot></pivot>',
mockRPC: function (route, args) {
if (args.method === 'read_group') {
assert.deepEqual(args.kwargs.fields, ['__count'],
"should make a read_group with field __count");
}
return this._super(route, args);
},
viewOptions: {
context: {
pivot_measures: ['__count__'],
},
},
});
var $countMeasure = pivot.$buttons.find('.dropdown-item[data-field=__count]:first');
assert.hasClass($countMeasure,'selected', "The count measure should be activated");
pivot.destroy();
});
QUnit.test('not use a many2one as a measure by default', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id"/>' +
'<field name="date" interval="month" type="col"/>' +
'</pivot>',
});
assert.notOk(pivot.measures.product_id,
"should not have product_id as measure");
pivot.destroy();
});
QUnit.test('use a many2one as a measure with specified additional measure', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id"/>' +
'<field name="date" interval="month" type="col"/>' +
'</pivot>',
viewOptions: {
additionalMeasures: ['product_id'],
},
});
assert.ok(pivot.measures.product_id,
"should have product_id as measure");
pivot.destroy();
});
QUnit.test('pivot view with many2one field as a measure', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="measure"/>' +
'<field name="date" interval="month" type="col"/>' +
'</pivot>',
});
assert.strictEqual(pivot.$('table tbody tr').text().trim(), "Total2112",
"should display product_id count as measure");
pivot.destroy();
});
QUnit.test('m2o as measure, drilling down into data', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="measure"/>' +
'</pivot>',
});
testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed'));
// click on date by month
pivot.$('.dropdown-menu.show .o_inline_dropdown .dropdown-menu').toggle(); // unfold inline dropdown
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"][data-interval="month"]'));
assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '2211',
'should have loaded the proper data');
pivot.destroy();
});
QUnit.test('pivot view with same many2one field as a measure and grouped by', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="row"/>' +
'</pivot>',
viewOptions: {
additionalMeasures: ['product_id'],
},
});
testUtils.pivot.toggleMeasuresDropdown(pivot);
testUtils.pivot.clickMeasure(pivot, 'product_id');
assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '421131',
'should have loaded the proper data');
pivot.destroy();
});
QUnit.test('pivot view with same many2one field as a measure and grouped by (and drill down)', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="product_id" type="measure"/>' +
'</pivot>',
});
testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed'));
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first'));
assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '211',
'should have loaded the proper data');
pivot.destroy();
});
QUnit.test('Row and column groupbys plus a domain', function (assert) {
assert.expect(3);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
// Set a column groupby
testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
testUtils.dom.click(pivot.$('.o_field_selection .dropdown-item[data-field=customer]:first'));
// Set a Row groupby
testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed'));
testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field=product_id]:first'));
// Set a domain
testUtils.pivot.reload(pivot, {domain: [['product_id', '=', 41]]});
var expectedContext = {
context: {
pivot_column_groupby: ['customer'],
pivot_measures: ['foo'],
pivot_row_groupby: ['product_id'],
},
};
// Mock 'save as favorite'
assert.deepEqual(pivot.getOwnedQueryParams(), expectedContext,
'The pivot view should have the right context');
var $xpadHeader = pivot.$('tbody .o_pivot_header_cell_closed[data-original-title=Product]');
assert.equal($xpadHeader.length, 1,
'There should be only one product line because of the domain');
assert.equal($xpadHeader.text(), 'xpad',
'The product should be the right one');
pivot.destroy();
});
QUnit.test('parallel data loading should discard all but the last one', function (assert) {
assert.expect(2);
var def;
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'read_group') {
return $.when(def).then(_.constant(result));
}
return result;
},
});
def = $.Deferred();
testUtils.pivot.reload(pivot, {groupBy: ['product_id']});
testUtils.pivot.reload(pivot, {groupBy: ['product_id', 'customer']});
def.resolve();
assert.containsN(pivot, '.o_pivot_cell_value', 6,
"should have 6 cells");
assert.containsN(pivot, 'tbody tr', 6,
"should have 6 rows");
pivot.destroy();
});
QUnit.test('pivot measures should be alphabetically sorted', function (assert) {
assert.expect(2);
var data = this.data;
// It's important to compare capitalized and lowercased words
// to be sure the sorting is effective with both of them
data.partner.fields.bouh = {string: "bouh", type: "integer"};
data.partner.fields.modd = {string: "modd", type: "integer"};
data.partner.fields.zip = {string: "Zip", type: "integer"};
var pivot = createView({
View: PivotView,
model: "partner",
data: data,
arch: '<pivot>' +
'<field name="zip" type="measure"/>' +
'<field name="foo" type="measure"/>' +
'<field name="bouh" type="measure"/>' +
'<field name="modd" type="measure"/>' +
'</pivot>',
});
assert.strictEqual(pivot.$buttons.find('.o_pivot_measures_list .dropdown-item:first').data('field'), 'bouh',
"Bouh should be the first measure");
assert.strictEqual(pivot.$buttons.find('.o_pivot_measures_list .dropdown-item:last').data('field'), '__count',
"Count should be the last measure");
pivot.destroy();
});
QUnit.test('pivot view should use default order for auto sorting', function (assert) {
assert.expect(1);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot default_order="foo asc">' +
'<field name="foo" type="measure"/>' +
'</pivot>',
});
assert.hasClass(pivot.$('thead tr:last th:last'),'o_pivot_measure_row_sorted_asc',
"Last thead should be sorted in ascending order");
pivot.destroy();
});
QUnit.test('rendering of pivot view with comparison', function (assert) {
assert.expect(91);
this.data.partner.records[0].date = '2016-12-15';
this.data.partner.records[1].date = '2016-12-17';
this.data.partner.records[2].date = '2016-11-22';
this.data.partner.records[3].date = '2016-11-03';
this.data.partner.fields.company_type = {string: "Company Type", type: "selection", selection: [["company", "Company"], ["individual", "Individual"]], searchable: true, store: true, sortable: true};
this.data.partner.records[0].company_type = 'company';
this.data.partner.records[1].company_type = 'individual';
this.data.partner.records[2].company_type = 'company';
this.data.partner.records[3].company_type = 'individual';
var unpatchDate = patchDate(2016, 11, 20, 1, 0, 0);
var results, i, length;
function checkCellValues (results) {
length = results.length;
for (i = 0; i < length; i++) {
assert.strictEqual($('.o_pivot .o_pivot_cell_value div').eq(i).text().trim(), results.shift());
}
}
// create an action manager to test the interactions with the search view
var actionManager = createActionManager({
data: this.data,
archs: {
'partner,false,pivot': '<pivot>' +
'<field name="date" interval="month" type="col"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
'partner,false,search': '<search></search>',
},
});
actionManager.doAction({
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'pivot']],
flags: {
pivot: {
additionalMeasures: ['product_id'],
}
}
});
// with no data
testUtils.dom.click($('.o_time_range_menu_button'));
testUtils.dom.click($('.o_time_range_menu .o_comparison_checkbox'));
$('.o_time_range_selector').val('today');
testUtils.dom.click($('.o_time_range_menu .o_apply_range'));
assert.strictEqual($('.o_pivot p.o_view_nocontent_empty_folder').length, 1);
// with data, no row groupby
testUtils.dom.click($('.o_time_range_menu_button'));
$('.o_time_range_selector').val('this_month');
testUtils.dom.click($('.o_time_range_menu .o_apply_range'));
results = [
"13", "0", "100%", "0", "19", "-100%", "13", "19", "-31.58%"
];
checkCellValues(results);
// with data, with row groupby
testUtils.dom.click($('.o_pivot .o_pivot_header_cell_closed').eq(2));
testUtils.dom.click($('.o_pivot .o_field_selection a[data-field="product_id"]'));
results = [
"13", "0", "100%", "0", "19", "-100%", "13", "19", "-31.58%" ,
"12", "0", "100%", "12", "0" , "100%" ,
"1" , "0", "100%", "0", "19", "-100%", "1" , "19" , "-94.74%"
];
checkCellValues(results);
testUtils.pivot.toggleMeasuresDropdown(actionManager.getCurrentController().widget);
testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="foo"]'));
testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="product_id"]'));
results = [
"2", "0", "100%", "0", "1", "-100%", "2", "1", "100%" ,
"1", "0", "100%", "1", "0" , "100%" ,
"1" , "0", "100%", "0", "1", "-100%", "1" , "1" , "100%"
];
checkCellValues(results);
testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="__count"]'));
testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="product_id"]'));
results = [
"2", "0", "100%", "0", "2", "-100%", "2", "2", "0%" ,
"1", "0", "100%", "1", "0" , "100%" ,
"1" , "0", "100%", "0", "2", "-100%", "1" , "2" , "-50%"
];
checkCellValues(results);
testUtils.dom.clickFirst($('.o_pivot .o_pivot_header_cell_opened'));
results = [
"2", "2", "0%" ,
"1", "0", "100%" ,
"1", "2", "-50%"
];
checkCellValues(results);
unpatchDate();
actionManager.destroy();
});
QUnit.test('export data in excel with comparison', function (assert) {
assert.expect(10);
this.data.partner.records[0].date = '2016-12-15';
this.data.partner.records[1].date = '2016-12-17';
this.data.partner.records[2].date = '2016-11-22';
this.data.partner.records[3].date = '2016-11-03';
var unpatchDate = patchDate(2016, 11, 20, 1, 0, 0);
// create an action manager to test the interactions with the search view
var actionManager = createActionManager({
data: this.data,
archs: {
'partner,false,pivot': '<pivot>' +
'<field name="date" interval="month" type="col"/>' +
'<field name="foo" type="measure"/>' +
'</pivot>',
'partner,false,search': '<search></search>',
},
session: {
get_file: function (args) {
var data = JSON.parse(args.data.data);
_.each(data.headers, function (l) {
assert.step(l.map(function (o) {return o.title;}));
});
assert.step(data.measure_row.map(function (o) {return o.measure;}));
assert.step(data.nbr_measures);
assert.step(data.rows.map(function (o) {return o.values.length;}));
assert.strictEqual(args.url, '/web/pivot/export_xls',
"should call get_file with correct parameters");
args.complete();
},
},
});
actionManager.doAction({
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'pivot']],
});
// open time range menu
testUtils.dom.click($('.o_control_panel .o_time_range_menu_button'));
// select 'Today' as range
$('.o_control_panel .o_time_range_selector').val('today');
// check checkbox 'Compare To'
testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox'));
// Click on 'Apply' button
testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range'));
// the time range menu configuration is by now: Date, Today, checkbox checked, Previous Period
// With the data above, the time ranges contain no record.
assert.strictEqual($('.o_pivot p.o_view_nocontent_empty_folder').length, 1, "there should be no data");
// export data should be impossible since the pivot buttons
// are deactivated (exception: the 'Measures' button).
assert.ok($('.o_control_panel button.o_pivot_download').prop('disabled'));
// open time range menu
testUtils.dom.click($('.o_control_panel .o_time_range_menu_button'));
// select 'This Month' as date range
$('.o_control_panel .o_time_range_selector').val('this_month');
// Click on 'Apply' button
testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range'));
// the time range menu configuration is by now: Date, This Month, checkbox checked, Previous Period
// With the data above, the time ranges contain some records.
// export data. Should execute 'get_file'
testUtils.dom.click($('.o_control_panel button.o_pivot_download'));
assert.verifySteps([
// Headers
["Total", ""],
["December 2016" , "November 2016"],
["Foo", "Foo", "Foo"],
[
"This Month", "Previous Period", "Variation",
"This Month", "Previous Period", "Variation",
"This Month", "Previous Period", "Variation"
],
// number of 'measures'
3,
// rows values length
[9]
]);
unpatchDate();
actionManager.destroy();
});
QUnit.test('rendering of pivot view with comparison and count measure', function (assert) {
assert.expect(10);
var mockMock = false;
var nbReadGroup = 0;
this.data.partner.records[0].date = '2016-12-15';
this.data.partner.records[1].date = '2016-12-17';
this.data.partner.records[2].date = '2016-12-22';
this.data.partner.records[3].date = '2016-12-03';
var unpatchDate = patchDate(2016, 11, 20, 1, 0, 0);
// create an action manager to test the interactions with the search view
var actionManager = createActionManager({
data: this.data,
archs: {
'partner,false,pivot': '<pivot>' +
'<field name="customer" type="row"/>' +
'</pivot>',
'partner,false,search': '<search></search>',
},
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'read_group' && mockMock) {
nbReadGroup++;
if (nbReadGroup === 4) {
// this modification is necessary because mockReadGroup does not
// properly reflect the server response when there is no record
// and a groupby list of length at least one.
return $.when([{}]);
}
}
return result;
},
});
actionManager.doAction({
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'pivot']],
});
mockMock = true;
// activate 'This Month' and 'Previous Period' in time range menu
testUtils.dom.click($('.o_control_panel .o_time_range_menu_button'));
$('.o_control_panel .o_time_range_selector').val('this_month');
testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox'));
testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range'));
var results = [
"4", "0", "100%",
"2", "0", "100%",
"2", "0", "100%"
];
for (var i = 0; i < 9; i++) {
assert.strictEqual($('.o_pivot .o_pivot_cell_value div').eq(i).text().trim(), results.shift());
}
assert.strictEqual($('.o_pivot_header_cell_closed').length, 3, "there should be exactly three closed header ('Total','First', 'Second')");
unpatchDate();
actionManager.destroy();
});
});
});