summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/views/graph_tests.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/tests/views/graph_tests.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/tests/views/graph_tests.js')
-rw-r--r--addons/web/static/tests/views/graph_tests.js2048
1 files changed, 2048 insertions, 0 deletions
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
new file mode 100644
index 00000000..fda4c5cb
--- /dev/null
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -0,0 +1,2048 @@
+odoo.define('web.graph_view_tests', function (require) {
+"use strict";
+
+var searchUtils = require('web.searchUtils');
+var GraphView = require('web.GraphView');
+var testUtils = require('web.test_utils');
+const { sortBy } = require('web.utils');
+
+const cpHelpers = testUtils.controlPanel;
+var createView = testUtils.createView;
+var patchDate = testUtils.mock.patchDate;
+
+const { INTERVAL_OPTIONS, PERIOD_OPTIONS, COMPARISON_OPTIONS } = searchUtils;
+
+var INTERVAL_OPTION_IDS = Object.keys(INTERVAL_OPTIONS);
+
+const yearIds = [];
+const otherIds = [];
+for (const id of Object.keys(PERIOD_OPTIONS)) {
+ const option = PERIOD_OPTIONS[id];
+ if (option.granularity === 'year') {
+ yearIds.push(id);
+ } else {
+ otherIds.push(id);
+ }
+}
+const BASIC_DOMAIN_IDS = [];
+for (const yearId of yearIds) {
+ BASIC_DOMAIN_IDS.push(yearId);
+ for (const id of otherIds) {
+ BASIC_DOMAIN_IDS.push(`${yearId}__${id}`);
+ }
+}
+const GENERATOR_INDEXES = {};
+let index = 0;
+for (const id of Object.keys(PERIOD_OPTIONS)) {
+ GENERATOR_INDEXES[id] = index++;
+}
+
+const COMPARISON_OPTION_IDS = Object.keys(COMPARISON_OPTIONS);
+const COMPARISON_OPTION_INDEXES = {};
+index = 0;
+for (const comparisonOptionId of COMPARISON_OPTION_IDS) {
+ COMPARISON_OPTION_INDEXES[comparisonOptionId] = index++;
+}
+
+var f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
+var cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
+
+var COMBINATIONS = cartesian(COMPARISON_OPTION_IDS, BASIC_DOMAIN_IDS);
+var COMBINATIONS_WITH_DATE = cartesian(COMPARISON_OPTION_IDS, BASIC_DOMAIN_IDS, INTERVAL_OPTION_IDS);
+
+QUnit.assert.checkDatasets = function (graph, keys, expectedDatasets) {
+ keys = keys instanceof Array ? keys : [keys];
+ expectedDatasets = expectedDatasets instanceof Array ?
+ expectedDatasets :
+ [expectedDatasets];
+ var datasets = graph.renderer.chart.data.datasets;
+ var actualValues = datasets.map(dataset => _.pick(dataset, keys));
+ this.pushResult({
+ result: _.isEqual(actualValues, expectedDatasets),
+ actual: actualValues,
+ expected: expectedDatasets,
+ });
+};
+
+QUnit.assert.checkLabels = function (graph, expectedLabels) {
+ var labels = graph.renderer.chart.data.labels;
+ this.pushResult({
+ result: _.isEqual(labels, expectedLabels),
+ actual: labels,
+ expected: expectedLabels,
+ });
+};
+
+QUnit.assert.checkLegend = function (graph, expectedLegendLabels) {
+ expectedLegendLabels = expectedLegendLabels instanceof Array ?
+ expectedLegendLabels :
+ [expectedLegendLabels];
+ var chart = graph.renderer.chart;
+ var actualLegendLabels = chart.config.options.legend.labels.generateLabels(chart).map(o => o.text);
+
+ this.pushResult({
+ result: _.isEqual(actualLegendLabels, expectedLegendLabels),
+ actual: actualLegendLabels,
+ expected: expectedLegendLabels,
+ });
+};
+
+QUnit.module('Views', {
+ beforeEach: function () {
+ this.data = {
+ foo: {
+ fields: {
+ foo: {string: "Foo", type: "integer", store: true},
+ bar: {string: "bar", type: "boolean"},
+ product_id: {string: "Product", type: "many2one", relation: 'product', store: true},
+ color_id: {string: "Color", type: "many2one", relation: 'color'},
+ date: {string: "Date", type: 'date', store: true, sortable: true},
+ revenue: {string: "Revenue", type: 'integer', store: true},
+ },
+ records: [
+ {id: 1, foo: 3, bar: true, product_id: 37, date: "2016-01-01", revenue: 1},
+ {id: 2, foo: 53, bar: true, product_id: 37, color_id: 7, date: "2016-01-03", revenue: 2},
+ {id: 3, foo: 2, bar: true, product_id: 37, date: "2016-03-04", revenue: 3},
+ {id: 4, foo: 24, bar: false, product_id: 37, date: "2016-03-07", revenue: 4},
+ {id: 5, foo: 4, bar: false, product_id: 41, date: "2016-05-01", revenue: 5},
+ {id: 6, foo: 63, bar: false, product_id: 41},
+ {id: 7, foo: 42, bar: false, product_id: 41},
+ {id: 8, foo: 48, bar: false, product_id: 41, date: "2016-04-01", revenue: 8},
+ ]
+ },
+ product: {
+ fields: {
+ name: {string: "Product Name", type: "char"}
+ },
+ records: [{
+ id: 37,
+ display_name: "xphone",
+ }, {
+ id: 41,
+ display_name: "xpad",
+ }]
+ },
+ color: {
+ fields: {
+ name: {string: "Color", type: "char"}
+ },
+ records: [{
+ id: 7,
+ display_name: "red",
+ }, {
+ id: 14,
+ display_name: "black",
+ }]
+ },
+ };
+ }
+}, function () {
+
+ QUnit.module('GraphView');
+
+ QUnit.test('simple graph rendering', async function (assert) {
+ assert.expect(5);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas',
+ "should contain a div with a canvas element");
+ assert.strictEqual(graph.renderer.state.mode, "bar",
+ "should be in bar chart mode by default");
+ assert.checkLabels(graph, [[true], [false]]);
+ assert.checkDatasets(graph,
+ ['backgroundColor', 'data', 'label', 'originIndex', 'stack'],
+ {
+ backgroundColor: "#1f77b4",
+ data: [3,5],
+ label: "Count",
+ originIndex: 0,
+ stack: "",
+ }
+ );
+ assert.checkLegend(graph, 'Count');
+
+ graph.destroy();
+ });
+
+ QUnit.test('default type attribute', async function (assert) {
+ assert.expect(1);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="pie">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.strictEqual(graph.renderer.state.mode, "pie", "should be in pie chart mode by default");
+
+ graph.destroy();
+ });
+
+ QUnit.test('title attribute', async function (assert) {
+ assert.expect(1);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph title="Partners" type="pie">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.strictEqual(graph.$('.o_graph_renderer label').text(), "Partners",
+ "should have 'Partners as title'");
+
+ graph.destroy();
+ });
+
+ QUnit.test('field id not in groupBy', async function (assert) {
+ assert.expect(1);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="id"/>' +
+ '</graph>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read_group') {
+ assert.deepEqual(args.kwargs.groupby, [],
+ 'groupby should not contain id field');
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ graph.destroy();
+ });
+
+ QUnit.test('switching mode', async function (assert) {
+ assert.expect(6);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="line">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode by default");
+ assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="bar"]'), 'active',
+ 'bar type button should not be active');
+ assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active',
+ 'line type button should be active');
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-mode="bar"]'));
+ assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode by default");
+ assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="line"]'), 'active',
+ 'line type button should not be active');
+ assert.hasClass(graph.$buttons.find('button[data-mode="bar"]'),'active',
+ 'bar type button should be active');
+
+ graph.destroy();
+ });
+
+ QUnit.test('displaying line chart with only 1 data point', async function (assert) {
+ assert.expect(1);
+ // this test makes sure the line chart does not crash when only one data
+ // point is displayed.
+ this.data.foo.records = this.data.foo.records.slice(0,1);
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="line">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.containsOnce(graph, 'canvas', "should have a canvas");
+
+ graph.destroy();
+ });
+
+ QUnit.test('displaying chart data with multiple groupbys', async function (assert) {
+ // this test makes sure the line chart shows all data labels (X axis) when
+ // it is grouped by several fields
+ assert.expect(6);
+
+ var graph = await createView({
+ View: GraphView,
+ model: 'foo',
+ data: this.data,
+ arch: '<graph type="bar"><field name="foo" /></graph>',
+ groupBy: ['product_id', 'bar', 'color_id'],
+ });
+
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, ['true/Undefined', 'true/red', 'false/Undefined']);
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-mode="line"]'));
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, ['true/Undefined', 'true/red', 'false/Undefined']);
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-mode="pie"]'));
+ assert.checkLabels(graph, [
+ ["xphone", true, "Undefined"],
+ ["xphone", true,"red"],
+ ["xphone", false, "Undefined"],
+ ["xpad", false, "Undefined"]
+ ]);
+ assert.checkLegend(graph, [
+ 'xphone/true/Undefined',
+ 'xphone/true/red',
+ 'xphone/false/Undefined',
+ 'xpad/false/Undefined'
+ ]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('switching measures', async function (assert) {
+ assert.expect(2);
+
+ var rpcCount = 0;
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Gloups">' +
+ '<field name="product_id"/>' +
+ '</graph>',
+ mockRPC: function (route, args) {
+ rpcCount++;
+ return this._super(route, args);
+ },
+ });
+ await cpHelpers.toggleMenu(graph, "Measures");
+ await cpHelpers.toggleMenuItem(graph, "Foo");
+
+ assert.checkLegend(graph, 'Foo');
+ assert.strictEqual(rpcCount, 2, "should have done 2 rpcs (2 readgroups)");
+
+ graph.destroy();
+ });
+
+ QUnit.test('no content helper (bar chart)', async function (assert) {
+ assert.expect(3);
+ this.data.foo.records = [];
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph string="Gloups">
+ <field name="product_id"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">This helper should not be displayed in graph views</p>'
+ }
+ },
+ });
+
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
+
+ graph.destroy();
+ });
+
+ QUnit.test('no content helper (pie chart)', async function (assert) {
+ assert.expect(3);
+ this.data.foo.records = [];
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph type="pie">
+ <field name="product_id"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">This helper should not be displayed in graph views</p>'
+ }
+ },
+ });
+
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
+
+ graph.destroy();
+ });
+
+ QUnit.test('render pie chart in comparison mode', async function (assert) {
+ assert.expect(2);
+
+ const unpatchDate = patchDate(2020, 4, 19, 1, 0, 0);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ context: { search_default_date_filter: 1, },
+ arch: '<graph type="pie">' +
+ '<field name="product_id"/>' +
+ '</graph>',
+ archs: {
+ 'foo,false,search': `
+ <search>
+ <filter name="date_filter" domain="[]" date="date" default_period="third_quarter"/>
+ </search>
+ `,
+ },
+ });
+
+ await cpHelpers.toggleComparisonMenu(graph);
+ await cpHelpers.toggleMenuItem(graph, 'Date: Previous period');
+
+ assert.containsNone(graph, 'div.o_view_nocontent',
+ "should not display the no content helper");
+ assert.checkLegend(graph, 'No data');
+
+ unpatchDate();
+ graph.destroy();
+ });
+
+ QUnit.test('no content helper after update', async function (assert) {
+ assert.expect(6);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph string="Gloups">
+ <field name="product_id"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">This helper should not be displayed in graph views</p>'
+ }
+ },
+ });
+
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
+
+ await testUtils.graph.reload(graph, {domain: [['product_id', '<', 0]]});
+
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
+
+ graph.destroy();
+ });
+
+ QUnit.test('can reload with other group by', async function (assert) {
+ assert.expect(2);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Gloups">' +
+ '<field name="product_id"/>' +
+ '</graph>',
+ });
+
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+
+ await testUtils.graph.reload(graph, {groupBy: ['color_id']});
+ assert.checkLabels(graph, [['Undefined'], ['red']]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('getOwnedQueryParams correctly returns mode, measure, and groupbys', async function (assert) {
+ assert.expect(4);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Gloups">' +
+ '<field name="product_id"/>' +
+ '</graph>',
+ });
+
+ assert.deepEqual(graph.getOwnedQueryParams(), {
+ context: {
+ graph_mode: 'bar',
+ graph_measure: '__count__',
+ graph_groupbys: ['product_id'],
+ }
+ }, "context should be correct");
+
+ await cpHelpers.toggleMenu(graph, "Measures");
+ await cpHelpers.toggleMenuItem(graph, "Foo");
+
+ assert.deepEqual(graph.getOwnedQueryParams(), {
+ context: {
+ graph_mode: 'bar',
+ graph_measure: 'foo',
+ graph_groupbys: ['product_id'],
+ },
+ }, "context should be correct");
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-mode="line"]'));
+ assert.deepEqual(graph.getOwnedQueryParams(), {
+ context: {
+ graph_mode: 'line',
+ graph_measure: 'foo',
+ graph_groupbys: ['product_id'],
+ },
+ }, "context should be correct");
+
+ await testUtils.graph.reload(graph, {groupBy: ['product_id', 'color_id']}); // change groupbys
+ assert.deepEqual(graph.getOwnedQueryParams(), {
+ context: {
+ graph_mode: 'line',
+ graph_measure: 'foo',
+ graph_groupbys: ['product_id', 'color_id'],
+ },
+ }, "context should be correct");
+
+ graph.destroy();
+ });
+
+ QUnit.test('correctly uses graph_ keys from the context', async function (assert) {
+ assert.expect(5);
+
+ var lastOne = _.last(this.data.foo.records);
+ lastOne.color_id = 14;
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph><field name="product_id"/></graph>',
+ viewOptions: {
+ context: {
+ graph_measure: 'foo',
+ graph_mode: 'line',
+ graph_groupbys: ['color_id'],
+ },
+ },
+ });
+ // check measure name is present in legend
+ assert.checkLegend(graph, 'Foo');
+ // check mode
+ assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode");
+ assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="bar"]'), 'active',
+ 'bar chart button should not be active');
+ assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active',
+ 'line chart button should be active');
+ // check groupby values ('Undefined' is rejected in line chart) are in labels
+ assert.checkLabels(graph, [['red'], ['black']]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('correctly use group_by key from the context', async function (assert) {
+ assert.expect(1);
+
+ var lastOne = _.last(this.data.foo.records);
+ lastOne.color_id = 14;
+
+ var graph = await createView({
+ View: GraphView,
+ model: 'foo',
+ data: this.data,
+ arch: '<graph><field name="product_id" /></graph>',
+ groupBy: ['color_id'],
+ viewOptions: {
+ context: {
+ graph_measure: 'foo',
+ graph_mode: 'line',
+ },
+ },
+ });
+ // check groupby values ('Undefined' is rejected in line chart) are in labels
+ assert.checkLabels(graph, [['red'], ['black']]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('correctly uses graph_ keys from the context (at reload)', async function (assert) {
+ assert.expect(7);
+
+ var lastOne = _.last(this.data.foo.records);
+ lastOne.color_id = 14;
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph><field name="product_id"/></graph>',
+ });
+
+ assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode");
+ assert.hasClass(graph.$buttons.find('button[data-mode="bar"]'),'active',
+ 'bar chart button should be active');
+
+ var reloadParams = {
+ context: {
+ graph_measure: 'foo',
+ graph_mode: 'line',
+ graph_groupbys: ['color_id'],
+ },
+ };
+ await testUtils.graph.reload(graph, reloadParams);
+
+ // check measure
+ assert.checkLegend(graph, 'Foo');
+ // check mode
+ assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode");
+ assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="bar"]'), 'active',
+ 'bar chart button should not be active');
+ assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active',
+ 'line chart button should be active');
+ // check groupby values ('Undefined' is rejected in line chart) are in labels
+ assert.checkLabels(graph, [['red'], ['black']]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('reload graph with correct fields', async function (assert) {
+ assert.expect(2);
+
+ var graph = await createView({
+ View: GraphView,
+ model: 'foo',
+ data: this.data,
+ arch: '<graph>' +
+ '<field name="product_id" type="row"/>' +
+ '<field name="foo" type="measure"/>' +
+ '</graph>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read_group') {
+ assert.deepEqual(args.kwargs.fields, ['product_id', 'foo'],
+ "should read the correct fields");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ await testUtils.graph.reload(graph, {groupBy: []});
+
+ graph.destroy();
+ });
+
+ QUnit.test('initial groupby is kept when reloading', async function (assert) {
+ assert.expect(8);
+
+ var graph = await createView({
+ View: GraphView,
+ model: 'foo',
+ data: this.data,
+ arch: '<graph>' +
+ '<field name="product_id" type="row"/>' +
+ '<field name="foo" type="measure"/>' +
+ '</graph>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read_group') {
+ assert.deepEqual(args.kwargs.groupby, ['product_id'],
+ "should group by the correct field");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, 'Foo');
+ assert.checkDatasets(graph, 'data', {data: [82, 157]});
+
+ await testUtils.graph.reload(graph, {groupBy: []});
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, 'Foo');
+ assert.checkDatasets(graph, 'data', {data: [82, 157]});
+
+ graph.destroy();
+ });
+
+ QUnit.test('use a many2one as a measure should work (without groupBy)', async function (assert) {
+ assert.expect(4);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="product_id" type="measure"/>' +
+ '</graph>',
+ });
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas',
+ "should contain a div with a canvas element");
+ assert.checkLabels(graph, [[]]);
+ assert.checkLegend(graph, 'Product');
+ assert.checkDatasets(graph, 'data', {data: [2]});
+
+ graph.destroy();
+ });
+
+ QUnit.test('use a many2one as a measure should work (with groupBy)', async function (assert) {
+ assert.expect(5);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="bar" type="row"/>' +
+ '<field name="product_id" type="measure"/>' +
+ '</graph>',
+ });
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas',
+ "should contain a div with a canvas element");
+
+ assert.strictEqual(graph.renderer.state.mode, "bar",
+ "should be in bar chart mode by default");
+ assert.checkLabels(graph, [[true], [false]]);
+ assert.checkLegend(graph, 'Product');
+ assert.checkDatasets(graph, 'data', {data: [1, 2]});
+
+ graph.destroy();
+ });
+
+ QUnit.test('use a many2one as a measure and as a groupby should work', async function (assert) {
+ assert.expect(3);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="product_id" type="row"/>' +
+ '</graph>',
+ viewOptions: {
+ additionalMeasures: ['product_id'],
+ },
+ });
+
+ // need to set the measure this way because it cannot be set in the
+ // arch.
+ await cpHelpers.toggleMenu(graph, "Measures");
+ await cpHelpers.toggleMenuItem(graph, "Product");
+
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, 'Product');
+ assert.checkDatasets(graph, 'data', {data: [1, 1]});
+
+ graph.destroy();
+ });
+
+ QUnit.test('not use a many2one as a measure by default', async function (assert) {
+ assert.expect(1);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="product_id"/>' +
+ '</graph>',
+ });
+ assert.notOk(graph.measures.product_id,
+ "should not have product_id as measure");
+ graph.destroy();
+ });
+
+ QUnit.test('use a many2one as a measure if set as additional fields', async function (assert) {
+ assert.expect(1);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners">' +
+ '<field name="product_id"/>' +
+ '</graph>',
+ viewOptions: {
+ additionalMeasures: ['product_id'],
+ },
+ });
+
+ assert.ok(graph.measures.find(m => m.fieldName === 'product_id'),
+ "should have product_id as measure");
+
+ graph.destroy();
+ });
+
+ QUnit.test('measure dropdown consistency', async function (assert) {
+ assert.expect(2);
+
+ const actionManager = await testUtils.createActionManager({
+ archs: {
+ 'foo,false,graph': `
+ <graph string="Partners" type="bar">
+ <field name="foo" type="measure"/>
+ </graph>`,
+ 'foo,false,search': `<search/>`,
+ 'foo,false,kanban': `
+ <kanban>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="foo"/>
+ </div>
+ </templates>
+ </kanban>`,
+ },
+ data: this.data,
+ });
+ await actionManager.doAction({
+ res_model: 'foo',
+ type: 'ir.actions.act_window',
+ views: [[false, 'graph'], [false, 'kanban']],
+ flags: {
+ graph: {
+ additionalMeasures: ['product_id'],
+ }
+ },
+ });
+
+ assert.containsOnce(actionManager, '.o_control_panel .o_graph_measures_list',
+ "Measures dropdown is present at init"
+ );
+
+ await cpHelpers.switchView(actionManager, 'kanban');
+ await cpHelpers.switchView(actionManager, 'graph');
+
+ assert.containsOnce(actionManager, '.o_control_panel .o_graph_measures_list',
+ "Measures dropdown is present after reload"
+ );
+
+ actionManager.destroy();
+ });
+
+ QUnit.test('graph view crash when moving from search view using Down key', async function (assert) {
+ assert.expect(1);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="pie">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+ graph._giveFocus();
+ assert.ok(true,"should not generate any error");
+ graph.destroy();
+ });
+
+ QUnit.test('graph measures should be alphabetically sorted', async function (assert) {
+ assert.expect(2);
+
+ var data = this.data;
+ data.foo.fields.bouh = {string: "bouh", type: "integer"};
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: data,
+ arch: '<graph string="Partners">' +
+ '<field name="foo" type="measure"/>' +
+ '<field name="bouh" type="measure"/>' +
+ '</graph>',
+ });
+
+ await cpHelpers.toggleMenu(graph, "Measures");
+ assert.strictEqual(graph.$buttons.find('.o_graph_measures_list .dropdown-item:first').text(), 'bouh',
+ "Bouh should be the first measure");
+ assert.strictEqual(graph.$buttons.find('.o_graph_measures_list .dropdown-item:last').text(), 'Count',
+ "Count should be the last measure");
+
+ graph.destroy();
+ });
+
+ QUnit.test('Undefined should appear in bar, pie graph but not in line graph', async function (assert) {
+ assert.expect(3);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ groupBy:['date'],
+ data: this.data,
+ arch: '<graph string="Partners" type="line">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ function _indexOf (label) {
+ return graph.renderer._indexOf(graph.renderer.chart.data.labels, label);
+ }
+
+ assert.strictEqual(_indexOf(['Undefined']), -1);
+
+ await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=bar]'));
+ assert.ok(_indexOf(['Undefined']) >= 0);
+
+ await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=pie]'));
+ assert.ok(_indexOf(['Undefined']) >= 0);
+
+ graph.destroy();
+ });
+
+ QUnit.test('Undefined should appear in bar, pie graph but not in line graph with multiple groupbys', async function (assert) {
+ assert.expect(4);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ groupBy:['date', 'color_id'],
+ data: this.data,
+ arch: '<graph string="Partners" type="line">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ function _indexOf (label) {
+ return graph.renderer._indexOf(graph.renderer.chart.data.labels, label);
+ }
+
+ assert.strictEqual(_indexOf(['Undefined']), -1);
+
+ await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=bar]'));
+ assert.ok(_indexOf(['Undefined']) >= 0);
+
+ await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=pie]'));
+ var labels = graph.renderer.chart.data.labels;
+ assert.ok(labels.filter(label => /Undefined/.test(label.join(''))).length >= 1);
+
+ // Undefined should not appear after switching back to line chart
+ await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=line]'));
+ assert.strictEqual(_indexOf(['Undefined']), -1);
+
+ graph.destroy();
+ });
+
+ QUnit.test('no comparison and no groupby', async function (assert) {
+ assert.expect(9);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="bar">' +
+ '<field name="foo" type="measure"/>' +
+ '</graph>',
+ });
+
+
+ assert.checkLabels(graph, [[]]);
+ assert.checkLegend(graph, 'Foo');
+ assert.checkDatasets(graph, 'data', {data: [239]});
+
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=line]'));
+ // the labels in line chart is translated in this case to avoid to have a single
+ // point at the left of the screen and chart to seem empty.
+ assert.checkLabels(graph, [[''], [], ['']]);
+ assert.checkLegend(graph, 'Foo');
+ assert.checkDatasets(graph, 'data', {data: [undefined, 239]});
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=pie]'));
+ assert.checkLabels(graph, [[]]);
+ assert.checkLegend(graph, 'Total');
+ assert.checkDatasets(graph, 'data', {data: [239]});
+
+ graph.destroy();
+ });
+
+ QUnit.test('no comparison and one groupby', async function (assert) {
+ assert.expect(9);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="bar">' +
+ '<field name="foo" type="measure"/>' +
+ '<field name="bar" type="row"/>' +
+ '</graph>',
+ });
+
+ assert.checkLabels(graph, [[true], [false]]);
+ assert.checkLegend(graph, 'Foo');
+ assert.checkDatasets(graph, 'data', {data: [58, 181]});
+
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=line]'));
+ assert.checkLabels(graph, [[true], [false]]);
+ assert.checkLegend(graph, 'Foo');
+ assert.checkDatasets(graph, 'data', {data: [58, 181]});
+
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=pie]'));
+
+ assert.checkLabels(graph, [[true], [false]]);
+ assert.checkLegend(graph, ['true', 'false']);
+ assert.checkDatasets(graph, 'data', {data: [58, 181]});
+
+ graph.destroy();
+ });
+ QUnit.test('no comparison and two groupby', async function (assert) {
+ assert.expect(9);
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="bar">' +
+ '<field name="foo" type="measure"/>' +
+ '</graph>',
+ groupBy: ['product_id', 'color_id'],
+ });
+
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, ['Undefined', 'red']);
+ assert.checkDatasets(graph, ['label', 'data'], [
+ {
+ label: 'Undefined',
+ data: [29, 157],
+ },
+ {
+ label: 'red',
+ data: [53, 0],
+ }
+ ]);
+
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=line]'));
+ assert.checkLabels(graph, [['xphone'], ['xpad']]);
+ assert.checkLegend(graph, ['Undefined', 'red']);
+ assert.checkDatasets(graph, ['label', 'data'], [
+ {
+ label: 'Undefined',
+ data: [29, 157],
+ },
+ {
+ label: 'red',
+ data: [53, 0],
+ }
+ ]);
+
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=pie]'));
+ assert.checkLabels(graph, [['xphone', 'Undefined'], ['xphone', 'red'], ['xpad', 'Undefined']]);
+ assert.checkLegend(graph, ['xphone/Undefined', 'xphone/red', 'xpad/Undefined']);
+ assert.checkDatasets(graph, ['label', 'data'], {
+ label: '',
+ data: [29, 53, 157],
+ });
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view only keeps finer groupby filter option for a given groupby', async function (assert) {
+ assert.expect(3);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ groupBy:['date:year','product_id', 'date', 'date:quarter'],
+ data: this.data,
+ arch: '<graph string="Partners" type="line">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.checkLabels(graph, [["January 2016"], ["March 2016"], ["May 2016"], ["April 2016"]]);
+ // mockReadGroup does not always sort groups -> May 2016 is before April 2016 for that reason.
+ assert.checkLegend(graph, ["xphone","xpad"]);
+ assert.checkDatasets(graph, ['label', 'data'], [
+ {
+ label: 'xphone',
+ data: [2, 2, 0, 0],
+ }, {
+ label: 'xpad',
+ data: [0, 0, 1, 1],
+ }
+ ]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('clicking on bar and pie charts triggers a do_action', async function (assert) {
+ assert.expect(5);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Foo Analysis"><field name="bar"/></graph>',
+ intercepts: {
+ do_action: function (ev) {
+ assert.deepEqual(ev.data.action, {
+ context: {},
+ domain: [["bar", "=", true]],
+ name: "Foo Analysis",
+ res_model: "foo",
+ target: 'current',
+ type: 'ir.actions.act_window',
+ view_mode: 'list',
+ views: [[false, 'list'], [false, 'form']],
+ }, "should trigger do_action with correct action parameter");
+ }
+ },
+ });
+ await testUtils.nextTick(); // wait for the graph to be rendered
+
+ // bar mode
+ assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode");
+ assert.checkDatasets(graph, ['domain'], {
+ domain: [[["bar", "=", true]], [["bar", "=", false]]],
+ });
+
+ let myChart = graph.renderer.chart;
+ let meta = myChart.getDatasetMeta(0);
+ let rectangle = myChart.canvas.getBoundingClientRect();
+ let point = meta.data[0].getCenterPoint();
+ await testUtils.dom.triggerEvent(myChart.canvas, 'click', {
+ pageX: rectangle.left + point.x,
+ pageY: rectangle.top + point.y
+ });
+
+ // pie mode
+ await testUtils.dom.click(graph.$('.o_graph_button[data-mode=pie]'));
+ assert.strictEqual(graph.renderer.state.mode, "pie", "should be in pie chart mode");
+
+ myChart = graph.renderer.chart;
+ meta = myChart.getDatasetMeta(0);
+ rectangle = myChart.canvas.getBoundingClientRect();
+ point = meta.data[0].getCenterPoint();
+ await testUtils.dom.triggerEvent(myChart.canvas, 'click', {
+ pageX: rectangle.left + point.x,
+ pageY: rectangle.top + point.y
+ });
+
+ graph.destroy();
+ });
+
+ QUnit.test('clicking charts trigger a do_action with correct views', async function (assert) {
+ assert.expect(3);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Foo Analysis"><field name="bar"/></graph>',
+ intercepts: {
+ do_action: function (ev) {
+ assert.deepEqual(ev.data.action, {
+ context: {},
+ domain: [["bar", "=", true]],
+ name: "Foo Analysis",
+ res_model: "foo",
+ target: 'current',
+ type: 'ir.actions.act_window',
+ view_mode: 'list',
+ views: [[364, 'list'], [29, 'form']],
+ }, "should trigger do_action with correct action parameter");
+ }
+ },
+ viewOptions: {
+ actionViews: [{
+ type: 'list',
+ viewID: 364,
+ }, {
+ type: 'form',
+ viewID: 29,
+ }],
+ },
+ });
+ await testUtils.nextTick(); // wait for the graph to be rendered
+
+ assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode");
+ assert.checkDatasets(graph, ['domain'], {
+ domain: [[["bar", "=", true]], [["bar", "=", false]]],
+ });
+
+ let myChart = graph.renderer.chart;
+ let meta = myChart.getDatasetMeta(0);
+ let rectangle = myChart.canvas.getBoundingClientRect();
+ let point = meta.data[0].getCenterPoint();
+ await testUtils.dom.triggerEvent(myChart.canvas, 'click', {
+ pageX: rectangle.left + point.x,
+ pageY: rectangle.top + point.y
+ });
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view with attribute disable_linking="True"', async function (assert) {
+ assert.expect(2);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph disable_linking="1"><field name="bar"/></graph>',
+ intercepts: {
+ do_action: function () {
+ throw new Error('Should not perform a do_action');
+ },
+ },
+ });
+ await testUtils.nextTick(); // wait for the graph to be rendered
+
+ assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode");
+ assert.checkDatasets(graph, ['domain'], {
+ domain: [[["bar", "=", true]], [["bar", "=", false]]],
+ });
+
+ let myChart = graph.renderer.chart;
+ let meta = myChart.getDatasetMeta(0);
+ let rectangle = myChart.canvas.getBoundingClientRect();
+ let point = meta.data[0].getCenterPoint();
+ await testUtils.dom.triggerEvent(myChart.canvas, 'click', {
+ pageX: rectangle.left + point.x,
+ pageY: rectangle.top + point.y
+ });
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view without invisible attribute on field', async function (assert) {
+ assert.expect(4);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `<graph string="Partners"></graph>`,
+ });
+
+ await testUtils.dom.click(graph.$('.btn-group:first button'));
+ assert.containsN(graph, 'li.o_menu_item', 3,
+ "there should be three menu item in the measures dropdown (count, revenue and foo)");
+ assert.containsOnce(graph, 'li.o_menu_item a:contains("Revenue")');
+ assert.containsOnce(graph, 'li.o_menu_item a:contains("Foo")');
+ assert.containsOnce(graph, 'li.o_menu_item a:contains("Count")');
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view with invisible attribute on field', async function (assert) {
+ assert.expect(2);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph string="Partners">
+ <field name="revenue" invisible="1"/>
+ </graph>`,
+ });
+
+ await testUtils.dom.click(graph.$('.btn-group:first button'));
+ assert.containsN(graph, 'li.o_menu_item', 2,
+ "there should be only two menu item in the measures dropdown (count and foo)");
+ assert.containsNone(graph, 'li.o_menu_item a:contains("Revenue")');
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view sort by measure', async function (assert) {
+ assert.expect(18);
+
+ // change first record from foo as there are 4 records count for each product
+ this.data.product.records.push({ id: 38, display_name: "zphone"});
+ this.data.foo.records[7].product_id = 38;
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `<graph string="Partners" order="desc">
+ <field name="product_id"/>
+ </graph>`,
+ });
+
+ assert.containsN(graph, 'button[data-order]', 2,
+ "there should be two order buttons for sorting axis labels in bar mode");
+ assert.checkLegend(graph, 'Count', 'measure should be by count');
+ assert.hasClass(graph.$('button[data-order="desc"]'), 'active',
+ 'sorting should be applie on descending order by default when sorting="desc"');
+ assert.checkDatasets(graph, 'data', {data: [4, 3, 1]});
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="asc"]'));
+ assert.hasClass(graph.$('button[data-order="asc"]'), 'active',
+ "ascending order should be applied");
+ assert.checkDatasets(graph, 'data', {data: [1, 3, 4]});
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.hasClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order button should be active");
+ assert.checkDatasets(graph, 'data', { data: [4, 3, 1] });
+
+ // again click on descending button to deactivate order button
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.doesNotHaveClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order button should not be active");
+ assert.checkDatasets(graph, 'data', {data: [4, 3, 1]});
+
+ // set line mode
+ await testUtils.dom.click(graph.$buttons.find('button[data-mode="line"]'));
+ assert.containsN(graph, 'button[data-order]', 2,
+ "there should be two order buttons for sorting axis labels in line mode");
+ assert.checkLegend(graph, 'Count', 'measure should be by count');
+ assert.doesNotHaveClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order should be applied");
+ assert.checkDatasets(graph, 'data', {data: [4, 3, 1]});
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="asc"]'));
+ assert.hasClass(graph.$('button[data-order="asc"]'), 'active',
+ "ascending order button should be active");
+ assert.checkDatasets(graph, 'data', { data: [1, 3, 4] });
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.hasClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order button should be active");
+ assert.checkDatasets(graph, 'data', { data: [4, 3, 1] });
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view sort by measure for grouped data', async function (assert) {
+ assert.expect(9);
+
+ // change first record from foo as there are 4 records count for each product
+ this.data.product.records.push({ id: 38, display_name: "zphone", });
+ this.data.foo.records[7].product_id = 38;
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `<graph string="Partners">
+ <field name="product_id"/>
+ <field name="bar"/>
+ </graph>`,
+ });
+
+ assert.checkLegend(graph, ["true","false"], 'measure should be by count');
+ assert.containsN(graph, 'button[data-order]', 2,
+ "there should be two order buttons for sorting axis labels");
+ assert.checkDatasets(graph, 'data', [{data: [3, 0, 0]}, {data: [1, 3, 1]}]);
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="asc"]'));
+ assert.hasClass(graph.$('button[data-order="asc"]'), 'active',
+ "ascending order should be applied by default");
+ assert.checkDatasets(graph, 'data', [{ data: [1, 3, 1] }, { data: [0, 0, 3] }]);
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.hasClass(graph.$('button[data-order="desc"]'), 'active',
+ "ascending order button should be active");
+ assert.checkDatasets(graph, 'data', [{data: [1, 3, 1]}, {data: [3, 0, 0]}]);
+
+ // again click on descending button to deactivate order button
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.doesNotHaveClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order button should not be active");
+ assert.checkDatasets(graph, 'data', [{ data: [3, 0, 0] }, { data: [1, 3, 1] }]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('graph view sort by measure for multiple grouped data', async function (assert) {
+ assert.expect(9);
+
+ // change first record from foo as there are 4 records count for each product
+ this.data.product.records.push({ id: 38, display_name: "zphone" });
+ this.data.foo.records[7].product_id = 38;
+
+ // add few more records to data to have grouped data date wise
+ const data = [
+ {id: 9, foo: 48, bar: false, product_id: 41, date: "2016-04-01"},
+ {id: 10, foo: 49, bar: false, product_id: 41, date: "2016-04-01"},
+ {id: 11, foo: 50, bar: true, product_id: 37, date: "2016-01-03"},
+ {id: 12, foo: 50, bar: true, product_id: 41, date: "2016-01-03"},
+ ];
+
+ Object.assign(this.data.foo.records, data);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `<graph string="Partners">
+ <field name="product_id"/>
+ <field name="date"/>
+ </graph>`,
+ groupBy: ['date', 'product_id']
+ });
+
+ assert.checkLegend(graph, ["xpad","xphone","zphone"], 'measure should be by count');
+ assert.containsN(graph, 'button[data-order]', 2,
+ "there should be two order buttons for sorting axis labels");
+ assert.checkDatasets(graph, 'data', [{data: [2, 1, 1, 2]}, {data: [0, 1, 0, 0]}, {data: [1, 0, 0, 0]}]);
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="asc"]'));
+ assert.hasClass(graph.$('button[data-order="asc"]'), 'active',
+ "ascending order should be applied by default");
+ assert.checkDatasets(graph, 'data', [{ data: [1, 1, 2, 2] }, { data: [0, 1, 0, 0] }, { data: [0, 0, 0, 1] }]);
+
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.hasClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order button should be active");
+ assert.checkDatasets(graph, 'data', [{data: [1, 0, 0, 0]}, {data: [2, 2, 1, 1]}, {data: [0, 0, 1, 0]}]);
+
+ // again click on descending button to deactivate order button
+ await testUtils.dom.click(graph.$buttons.find('button[data-order="desc"]'));
+ assert.doesNotHaveClass(graph.$('button[data-order="desc"]'), 'active',
+ "descending order button should not be active");
+ assert.checkDatasets(graph, 'data', [{ data: [2, 1, 1, 2] }, { data: [0, 1, 0, 0] }, { data: [1, 0, 0, 0] }]);
+
+ graph.destroy();
+ });
+
+ QUnit.test('empty graph view with sample data', async function (assert) {
+ assert.expect(8);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph sample="1">
+ <field name="product_id"/>
+ <field name="date"/>
+ </graph>`,
+ domain: [['id', '<', 0]],
+ viewOptions: {
+ action: {
+ help: '<p class="abc">click to add a foo</p>'
+ }
+ },
+ });
+
+ assert.hasClass(graph.el, 'o_view_sample_data');
+ assert.containsOnce(graph, '.o_view_nocontent');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.hasClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+
+ await graph.reload({ domain: [] });
+
+ assert.doesNotHaveClass(graph.el, 'o_view_sample_data');
+ assert.containsNone(graph, '.o_view_nocontent');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.doesNotHaveClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+
+ graph.destroy();
+ });
+
+ QUnit.test('non empty graph view with sample data', async function (assert) {
+ assert.expect(8);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph sample="1">
+ <field name="product_id"/>
+ <field name="date"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">click to add a foo</p>'
+ }
+ },
+ })
+
+ assert.doesNotHaveClass(graph.el, 'o_view_sample_data');
+ assert.containsNone(graph, '.o_view_nocontent');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.doesNotHaveClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+
+ await graph.reload({ domain: [['id', '<', 0]] });
+
+ assert.doesNotHaveClass(graph.el, 'o_view_sample_data');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.doesNotHaveClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+ assert.containsNone(graph, '.o_view_nocontent');
+
+ graph.destroy();
+ });
+
+ QUnit.module('GraphView: comparison mode', {
+ beforeEach: async function () {
+ this.data.foo.records[0].date = '2016-12-15';
+ this.data.foo.records[1].date = '2016-12-17';
+ this.data.foo.records[2].date = '2016-11-22';
+ this.data.foo.records[3].date = '2016-11-03';
+ this.data.foo.records[4].date = '2016-12-20';
+ this.data.foo.records[5].date = '2016-12-19';
+ this.data.foo.records[6].date = '2016-12-15';
+ this.data.foo.records[7].date = undefined;
+
+ this.data.foo.records.push({id: 9, foo: 48, bar: false, product_id: 41, color_id: 7, date: "2016-12-01"});
+ this.data.foo.records.push({id: 10, foo: 17, bar: true, product_id: 41, color_id: 7, date: "2016-12-01"});
+ this.data.foo.records.push({id: 11, foo: 45, bar: true, product_id: 37, color_id: 14, date: "2016-12-01"});
+ this.data.foo.records.push({id: 12, foo: 48, bar: false, product_id: 37, color_id: 7, date: "2016-12-10"});
+ this.data.foo.records.push({id: 13, foo: 48, bar: false, product_id: undefined, color_id: 14, date: "2016-11-30"});
+ this.data.foo.records.push({id: 14, foo: -50, bar: true, product_id: 41, color_id: 14, date: "2016-12-15"});
+ this.data.foo.records.push({id: 15, foo: 53, bar: false, product_id: 41, color_id: 14, date: "2016-11-01"});
+ this.data.foo.records.push({id: 16, foo: 53, bar: true, product_id: undefined, color_id: 7, date: "2016-09-01"});
+ this.data.foo.records.push({id: 17, foo: 48, bar: false, product_id: 41, color_id: undefined, date: "2016-08-01"});
+ this.data.foo.records.push({id: 18, foo: -156, bar: false, product_id: 37, color_id: undefined, date: "2016-07-15"});
+ this.data.foo.records.push({id: 19, foo: 31, bar: false, product_id: 41, color_id: 14, date: "2016-12-15"});
+ this.data.foo.records.push({id: 20, foo: 109, bar: true, product_id: 41, color_id: 7, date: "2015-06-01"});
+
+ this.data.foo.records = sortBy(this.data.foo.records, 'date');
+
+ this.unpatchDate = patchDate(2016, 11, 20, 1, 0, 0);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph string="Partners" type="bar">
+ <field name="foo" type="measure"/>
+ </graph>
+ `,
+ archs: {
+ 'foo,false,search': `
+ <search>
+ <filter name="date" string="Date" context="{'group_by': 'date'}"/>
+ <filter name="date_filter" string="Date Filter" date="date"/>
+ <filter name="bar" string="Bar" context="{'group_by': 'bar'}"/>
+ <filter name="product_id" string="Product" context="{'group_by': 'product_id'}"/>
+ <filter name="color_id" string="Color" context="{'group_by': 'color_id'}"/>
+ </search>
+ `,
+ },
+ viewOptions: {
+ additionalMeasures: ['product_id'],
+ },
+ });
+
+ this.graph = graph;
+
+ var checkOnlyToCheck = true;
+ var exhaustiveTest = false || checkOnlyToCheck;
+
+ var self = this;
+ async function* graphGenerator(combinations) {
+ var i = 0;
+ while (i < combinations.length) {
+ var combination = combinations[i];
+ if (!checkOnlyToCheck || combination.toString() in self.combinationsToCheck) {
+ await self.setConfig(combination);
+ }
+ if (exhaustiveTest) {
+ i++;
+ } else {
+ i += Math.floor(1 + Math.random() * 20);
+ }
+ yield combination;
+ }
+ }
+
+ this.combinationsToCheck = {};
+ this.testCombinations = async function (combinations, assert) {
+ for await (var combination of graphGenerator(combinations)) {
+ // we can check particular combinations here
+ if (combination.toString() in self.combinationsToCheck) {
+ if (self.combinationsToCheck[combination].errorMessage) {
+ assert.strictEqual(
+ graph.$('.o_nocontent_help p').eq(1).text().trim(),
+ self.combinationsToCheck[combination].errorMessage
+ );
+ } else {
+ assert.checkLabels(graph, self.combinationsToCheck[combination].labels);
+ assert.checkLegend(graph, self.combinationsToCheck[combination].legend);
+ assert.checkDatasets(graph, ['label', 'data'], self.combinationsToCheck[combination].datasets);
+ }
+ }
+ }
+ };
+
+ const GROUPBY_NAMES = ['Date', 'Bar', 'Product', 'Color'];
+
+ this.selectTimeRanges = async function (comparisonOptionId, basicDomainId) {
+ const facetEls = graph.el.querySelectorAll('.o_searchview_facet');
+ const facetIndex = [...facetEls].findIndex(el => !!el.querySelector('span.fa-filter'));
+ if (facetIndex > -1) {
+ await cpHelpers.removeFacet(graph, facetIndex);
+ }
+ const [yearId, otherId] = basicDomainId.split('__');
+ await cpHelpers.toggleFilterMenu(graph);
+ await cpHelpers.toggleMenuItem(graph, 'Date Filter');
+ await cpHelpers.toggleMenuItemOption(graph, 'Date Filter', GENERATOR_INDEXES[yearId]);
+ if (otherId) {
+ await cpHelpers.toggleMenuItemOption(graph, 'Date Filter', GENERATOR_INDEXES[otherId]);
+ }
+ const itemIndex = COMPARISON_OPTION_INDEXES[comparisonOptionId];
+ await cpHelpers.toggleComparisonMenu(graph);
+ await cpHelpers.toggleMenuItem(graph, itemIndex);
+ };
+
+ // groupby menu is assumed to be closed
+ this.selectDateIntervalOption = async function (intervalOption) {
+ intervalOption = intervalOption || 'month';
+ const optionIndex = INTERVAL_OPTION_IDS.indexOf(intervalOption);
+
+ await cpHelpers.toggleGroupByMenu(graph);
+ let wasSelected = false;
+ if (this.keepFirst) {
+ if (cpHelpers.isItemSelected(graph, 2)) {
+ wasSelected = true;
+ await cpHelpers.toggleMenuItem(graph, 2);
+ }
+ }
+ await cpHelpers.toggleMenuItem(graph, 0);
+ if (!cpHelpers.isOptionSelected(graph, 0, optionIndex)) {
+ await cpHelpers.toggleMenuItemOption(graph, 0, optionIndex);
+ }
+ for (let i = 0; i < INTERVAL_OPTION_IDS.length; i++) {
+ const oId = INTERVAL_OPTION_IDS[i];
+ if (oId !== intervalOption && cpHelpers.isOptionSelected(graph, 0, i)) {
+ await cpHelpers.toggleMenuItemOption(graph, 0, i);
+ }
+ }
+
+ if (this.keepFirst) {
+ if (wasSelected && !cpHelpers.isItemSelected(graph, 2)) {
+ await cpHelpers.toggleMenuItem(graph, 2);
+ }
+ }
+ await cpHelpers.toggleGroupByMenu(graph);
+
+ };
+
+ // groupby menu is assumed to be closed
+ this.selectGroupBy = async function (groupByName) {
+ await cpHelpers.toggleGroupByMenu(graph);
+ const index = GROUPBY_NAMES.indexOf(groupByName);
+ if (!cpHelpers.isItemSelected(graph, index)) {
+ await cpHelpers.toggleMenuItem(graph, index);
+ }
+ await cpHelpers.toggleGroupByMenu(graph);
+ };
+
+ this.setConfig = async function (combination) {
+ await this.selectTimeRanges(combination[0], combination[1]);
+ if (combination.length === 3) {
+ await self.selectDateIntervalOption(combination[2]);
+ }
+ };
+
+ this.setMode = async function (mode) {
+ await testUtils.dom.click($(`.o_control_panel .o_graph_button[data-mode="${mode}"]`));
+ };
+
+ },
+ afterEach: function () {
+ this.unpatchDate();
+ this.graph.destroy();
+ },
+ }, function () {
+ QUnit.test('comparison with one groupby equal to comparison date field', async function (assert) {
+ assert.expect(11);
+
+ this.combinationsToCheck = {
+ 'previous_period,this_year__this_month,day': {
+ labels: [...Array(6).keys()].map(x => [x]),
+ legend: ["December 2016", "November 2016"],
+ datasets: [
+ {
+ data: [110, 48, 26, 53, 63, 4],
+ label: "December 2016",
+ },
+ {
+ data: [53, 24, 2, 48],
+ label: "November 2016",
+ }
+ ],
+ }
+ };
+
+ var combinations = COMBINATIONS_WITH_DATE;
+ await this.testCombinations(combinations, assert);
+ await this.setMode('line');
+ await this.testCombinations(combinations, assert);
+ this.combinationsToCheck['previous_period,this_year__this_month,day'] = {
+ labels: [...Array(6).keys()].map(x => [x]),
+ legend: [
+ "2016-12-01,2016-11-01",
+ "2016-12-10,2016-11-03",
+ "2016-12-15,2016-11-22",
+ "2016-12-17,2016-11-30",
+ "2016-12-19",
+ "2016-12-20"
+ ],
+ datasets: [
+ {
+ data: [ 110, 48, 26, 53, 63, 4],
+ label: "December 2016",
+ },
+ {
+ data: [ 53, 24, 2, 48, 0, 0],
+ label: "November 2016",
+ }
+ ],
+ };
+ await this.setMode('pie');
+ await this.testCombinations(combinations, assert);
+
+ // isNotVisible can not have two elements so checking visibility of first element
+ assert.isNotVisible(this.graph.$('button[data-order]:first'),
+ "there should not be order button in comparison mode");
+ assert.ok(true, "No combination causes a crash");
+ });
+
+ QUnit.test('comparison with no groupby', async function (assert) {
+ assert.expect(10);
+
+ this.combinationsToCheck = {
+ 'previous_period,this_year__this_month': {
+ labels: [[]],
+ legend: ["December 2016", "November 2016"],
+ datasets: [
+ {
+ data: [304],
+ label: "December 2016",
+ },
+ {
+ data: [127],
+ label: "November 2016",
+ }
+ ],
+ }
+ };
+
+ var combinations = COMBINATIONS;
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_period,this_year__this_month'] = {
+ labels: [[''], [], ['']],
+ legend: ["December 2016", "November 2016"],
+ datasets: [
+ {
+ data: [undefined, 304],
+ label: "December 2016",
+ },
+ {
+ data: [undefined, 127],
+ label: "November 2016",
+ }
+ ],
+ };
+ await this.setMode('line');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_period,this_year__this_month'] = {
+ labels: [[]],
+ legend: ["Total"],
+ datasets: [
+ {
+ data: [304],
+ label: "December 2016",
+ },
+ {
+ data: [127],
+ label: "November 2016",
+ }
+ ],
+ };
+ await this.setMode('pie');
+ await this.testCombinations(combinations, assert);
+
+ assert.ok(true, "No combination causes a crash");
+ });
+
+ QUnit.test('comparison with one groupby different from comparison date field', async function (assert) {
+ assert.expect(10);
+
+ this.combinationsToCheck = {
+ 'previous_period,this_year__this_month': {
+ labels: [["xpad"], ["xphone"],["Undefined"]],
+ legend: ["December 2016", "November 2016"],
+ datasets: [
+ {
+ data: [ 155, 149, 0],
+ label: "December 2016",
+ },
+ {
+ data: [ 53, 26, 48],
+ label: "November 2016",
+ }
+ ],
+ }
+ };
+
+ var combinations = COMBINATIONS;
+ await this.selectGroupBy('Product');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_period,this_year__this_month'] = {
+ labels: [["xpad"], ["xphone"]],
+ legend: ["December 2016", "November 2016"],
+ datasets: [
+ {
+ data: [155, 149],
+ label: "December 2016",
+ },
+ {
+ data: [53, 26],
+ label: "November 2016",
+ }
+ ],
+ };
+ await this.setMode('line');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_period,this_year__this_month'] = {
+ labels: [["xpad"], ["xphone"], ["Undefined"]],
+ legend: ["xpad", "xphone", "Undefined"],
+ datasets: [
+ {
+ data: [ 155, 149, 0],
+ label: "December 2016",
+ },
+ {
+ data: [ 53, 26, 48],
+ label: "November 2016",
+ }
+ ],
+ };
+ await this.setMode('pie');
+ await this.testCombinations(combinations, assert);
+
+ assert.ok(true, "No combination causes a crash");
+ });
+
+ QUnit.test('comparison with two groupby with first groupby equal to comparison date field', async function (assert) {
+ assert.expect(10);
+
+ this.keepFirst = true;
+ this.combinationsToCheck = {
+ 'previous_period,this_year__this_month,day': {
+ labels: [...Array(6).keys()].map(x => [x]),
+ legend: [
+ "December 2016/xpad",
+ "December 2016/xphone",
+ "November 2016/xpad",
+ "November 2016/xphone",
+ "November 2016/Undefined"
+ ],
+ datasets: [
+ {
+ data: [ 65, 0, 23, 0, 63, 4],
+ label: "December 2016/xpad"
+ },
+ {
+ data: [ 45, 48, 3, 53, 0, 0],
+ label: "December 2016/xphone"
+ },
+ {
+ data: [ 53, 0, 0, 0],
+ label: "November 2016/xpad"
+ },
+ {
+ data: [ 0, 24, 2, 0],
+ label: "November 2016/xphone"
+ },
+ {
+ data: [ 0, 0, 0, 48],
+ label: "November 2016/Undefined"
+ }
+ ]
+ }
+ };
+
+ var combinations = COMBINATIONS_WITH_DATE;
+ await this.selectGroupBy('Product');
+ await this.testCombinations(combinations, assert);
+ await this.setMode('line');
+ await this.testCombinations(combinations, assert);
+
+
+ this.combinationsToCheck['previous_period,this_year__this_month,day'] = {
+ labels: [[0, "xpad"], [0, "xphone"], [1, "xphone"], [2, "xphone"], [2, "xpad"], [3, "xphone"], [4, "xpad"], [5, "xpad"], [3, "Undefined"]],
+ legend: [
+ "2016-12-01,2016-11-01/xpad",
+ "2016-12-01,2016-11-01/xphone",
+ "2016-12-10,2016-11-03/xphone",
+ "2016-12-15,2016-11-22/xphone",
+ "2016-12-15,2016-11-22/xpad",
+ "2016-12-17,2016-11-30/xphone",
+ "2016-12-19/xpad",
+ "2016-12-20/xpad",
+ "2016-12-17,2016-11-30/Undefine..."
+ ],
+ datasets: [
+ {
+ "data": [ 65, 45, 48, 3, 23, 53, 63, 4, 0],
+ "label": "December 2016"
+ },
+ {
+ "data": [ 53, 0, 24, 2, 0, 0, 0, 0, 48],
+ "label": "November 2016"
+ }
+ ],
+ };
+
+ await this.setMode('pie');
+ await this.testCombinations(combinations, assert);
+
+ assert.ok(true, "No combination causes a crash");
+
+ this.keepFirst = false;
+ });
+
+ QUnit.test('comparison with two groupby with second groupby equal to comparison date field', async function (assert) {
+ assert.expect(8);
+
+ this.combinationsToCheck = {
+ 'previous_period,this_year,quarter': {
+ labels: [["xphone"], ["xpad"],["Undefined"]],
+ legend: [
+ "2016/Q3 2016",
+ "2016/Q4 2016",
+ "2015/Q2 2015"
+ ],
+ datasets: [
+ {
+ data: [-156, 48, 53],
+ label: "2016/Q3 2016",
+ },
+ {
+ data: [175, 208, 48],
+ label: "2016/Q4 2016",
+ },
+ {
+ data: [0, 109, 0],
+ label: "2015/Q2 2015",
+ },
+ ]
+ }
+ };
+
+ const combinations = COMBINATIONS_WITH_DATE;
+ await this.selectGroupBy('Product');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_period,this_year,quarter'] = {
+ labels: [["xphone"], ["xpad"]],
+ legend: [
+ "2016/Q3 2016",
+ "2016/Q4 2016",
+ "2015/Q2 2015"
+ ],
+ datasets: [
+ {
+ data: [-156, 48],
+ label: "2016/Q3 2016",
+ },
+ {
+ data: [175, 208],
+ label: "2016/Q4 2016",
+ },
+ {
+ data: [0, 109],
+ label: "2015/Q2 2015",
+ },
+ ]
+ };
+ await this.setMode('line');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_period,this_year,quarter'] = {
+ errorMessage: 'Pie chart cannot mix positive and negative numbers. ' +
+ 'Try to change your domain to only display positive results'
+ };
+ await this.setMode('pie');
+ await this.testCombinations(combinations, assert);
+
+ assert.ok(true, "No combination causes a crash");
+ });
+ QUnit.test('comparison with two groupby with no groupby equal to comparison date field', async function (assert) {
+ assert.expect(10);
+
+ this.combinationsToCheck = {
+ 'previous_year,this_year__last_month': {
+ labels: [["xpad"], ["xphone"],["Undefined"] ],
+ legend: ["November 2016/false", "November 2016/true"],
+ datasets: [
+ {
+ data: [53, 24, 48],
+ label: "November 2016/false",
+ },
+ {
+ data: [0, 2, 0],
+ label: "November 2016/true",
+ }
+ ],
+ }
+ };
+
+ var combinations = COMBINATIONS;
+ await this.selectGroupBy('Product');
+ await this.selectGroupBy('Bar');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_year,this_year__last_month'] = {
+ labels: [["xpad"], ["xphone"] ],
+ legend: ["November 2016/false", "November 2016/true"],
+ datasets: [
+ {
+ data: [53, 24],
+ label: "November 2016/false",
+ },
+ {
+ data: [0, 2],
+ label: "November 2016/true",
+ }
+ ],
+ };
+ await this.setMode('line');
+ await this.testCombinations(combinations, assert);
+
+ this.combinationsToCheck['previous_year,this_year__last_month'] = {
+ labels:
+ [["xpad", false], ["xphone", false], ["xphone", true], ["Undefined", false], ["No data"]],
+ legend: [
+ "xpad/false",
+ "xphone/false",
+ "xphone/true",
+ "Undefined/false",
+ "No data"
+ ],
+ datasets: [
+ {
+ "data": [ 53, 24, 2, 48],
+ "label": "November 2016"
+ },
+ {
+ "data": [ undefined, undefined, undefined, undefined, 1],
+ "label": "November 2015"
+ }
+ ],
+ };
+ await this.setMode('pie');
+ await this.testCombinations(combinations, assert);
+
+ assert.ok(true, "No combination causes a crash");
+ });
+ });
+});
+});