/** @odoo-module **/ import { click, getFixture, makeDeferred, nextTick, patchDate, triggerEvent, findChildren, } from "@web/../tests/helpers/utils"; import { editFavoriteName, saveFavorite, switchView, toggleMenu, toggleMenuItem, toggleMenuItemOption, toggleSaveFavorite, toggleSearchBarMenu, validateSearch, } from "@web/../tests/search/helpers"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { createWebClient, doAction } from "@web/../tests/webclient/helpers"; import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; import { getBorderWhite, DEFAULT_BG, getColors, hexToRGBA } from "@web/core/colors/colors"; import { GraphArchParser } from "@web/views/graph/graph_arch_parser"; import { GraphRenderer } from "@web/views/graph/graph_renderer"; import { onRendered } from "@odoo/owl"; import { patchWithCleanup } from "../helpers/utils"; import { Domain } from "@web/core/domain"; import { SampleServer } from "@web/model/sample_server"; import { GraphModel } from "@web/views/graph/graph_model"; const serviceRegistry = registry.category("services"); function getGraphModelMetaData(graph) { return graph.model.metaData; } export function getGraphRenderer(graph) { const layoutNode = findChildren(graph); return Object.values(layoutNode.children) .map((c) => c.component) .find((c) => c.chart); } function getChart(graph) { return getGraphRenderer(graph).chart; } function checkDatasets(assert, graph, keys, expectedDatasets) { keys = keys instanceof Array ? keys : [keys]; expectedDatasets = expectedDatasets instanceof Array ? expectedDatasets : [expectedDatasets]; const datasets = getChart(graph).data.datasets; const actualValues = []; for (const dataset of datasets) { const partialDataset = {}; for (const key of keys) { partialDataset[key] = dataset[key]; } actualValues.push(partialDataset); } assert.deepEqual(actualValues, expectedDatasets); } export function checkLabels(assert, graph, expectedLabels) { const labels = getChart(graph).data.labels.map((l) => l.toString()); assert.deepEqual(labels, expectedLabels); } export function checkYTicks(assert, graph, expectedLabels) { const labels = getChart(graph).scales.y.ticks.map((l) => l.label); assert.deepEqual(labels, expectedLabels); } export function checkLegend(assert, graph, expectedLegendLabels) { expectedLegendLabels = expectedLegendLabels instanceof Array ? expectedLegendLabels : [expectedLegendLabels]; const chart = getChart(graph); const actualLegendLabels = chart.config.options.plugins.legend.labels .generateLabels(chart) .map((o) => o.text); assert.deepEqual(actualLegendLabels, expectedLegendLabels); } function checkTooltip(assert, graph, expectedTooltipContent, index, datasetIndex) { // If the tooltip options are changed, this helper should change: we construct the dataPoints // similarly to Chart.js according to the values set for the tooltips options 'mode' and 'intersect'. const { datasets } = getChart(graph).data; const dataPoints = []; for (let i = 0; i < datasets.length; i++) { const dataset = datasets[i]; const raw = dataset.data[index]; if (raw !== undefined && (datasetIndex === undefined || datasetIndex === i)) { dataPoints.push({ datasetIndex: i, dataIndex: index, raw, }); } } const tooltipModel = { opacity: 1, x: 1, y: 1, dataPoints }; getChart(graph).config.options.plugins.tooltip.external({ tooltip: tooltipModel }); const { title, lines } = expectedTooltipContent; const lineLabels = []; const lineValues = []; for (const line of lines) { lineLabels.push(line.label); lineValues.push(`${line.value}`); } assert.containsOnce(target, "div.o_graph_custom_tooltip"); const tooltipTitle = target.querySelector("table thead tr th.o_measure"); assert.strictEqual(tooltipTitle.innerText, title || "Count", `Tooltip title`); assert.deepEqual( [...target.querySelectorAll("table tbody tr td span.o_label")].map((td) => td.innerText), lineLabels, `Tooltip line labels` ); assert.deepEqual( [...target.querySelectorAll("table tbody tr td.o_value")].map((td) => td.innerText), lineValues, `Tooltip line values` ); } function getModeButton(el, mode) { return el.querySelector(`.o_graph_button[data-mode="${mode}"`); } export async function selectMode(el, mode) { await click(getModeButton(el, mode)); } function checkModeIs(assert, graph, mode) { assert.strictEqual(getGraphModelMetaData(graph).mode, mode); assert.strictEqual(getChart(graph).config.type, mode); assert.hasClass(getModeButton(target, mode), "active"); } function getScaleY(graph) { return getChart(graph).config.options.scales.y; } function getXAxeLabel(graph) { return getChart(graph).config.options.scales.x.title.text; } function getYAxeLabel(graph) { return getChart(graph).config.options.scales.y.title.text; } export async function clickOnDataset(graph) { const chart = getChart(graph); const meta = chart.getDatasetMeta(0); const rectangle = chart.canvas.getBoundingClientRect(); const point = meta.data[0].getCenterPoint(); await triggerEvent(chart.canvas, null, "click", { pageX: rectangle.left + point.x, pageY: rectangle.top + point.y, }); } export async function clickOnLegend(graph, text) { const chart = getChart(graph); const index = chart.legend.legendItems.findIndex((e) => e.text === text); const { left, top, width, height } = chart.legend.legendHitBoxes[index]; const rectangle = chart.canvas.getBoundingClientRect(); const middle = { x: left + width / 2, y: top + height / 2, }; await triggerEvent(chart.canvas, null, "click", { pageX: rectangle.left + middle.x, pageY: rectangle.top + middle.y, }); } let serverData; let target; QUnit.module("Views", (hooks) => { hooks.beforeEach(async () => { serverData = { models: { foo: { fields: { id: { string: "Id", type: "integer" }, foo: { string: "Foo", type: "integer", store: true, group_operator: "sum", sortable: true, }, bar: { string: "bar", type: "boolean", store: true, sortable: true }, product_id: { string: "Product", type: "many2one", relation: "product", store: true, sortable: true, }, color_id: { string: "Color", type: "many2one", relation: "color", store: true, sortable: true, }, date: { string: "Date", type: "date", store: true, sortable: true }, revenue: { string: "Revenue", type: "float", store: true, group_operator: "sum", sortable: true, }, color_ids: { string: "Colors", type: "many2many", relation: "color", store: true, }, }, records: [ { id: 1, foo: 3, bar: true, product_id: 37, date: "2016-01-01", revenue: 1, color_ids: [7], }, { id: 2, foo: 53, bar: true, product_id: 37, color_id: 7, date: "2016-01-03", revenue: 2, color_ids: [14], }, { id: 3, foo: 2, bar: true, product_id: 37, date: "2016-03-04", revenue: 3, color_ids: [7, 14], }, { id: 4, foo: 24, bar: false, product_id: 37, date: "2016-03-07", revenue: 4, color_ids: [7], }, { id: 5, foo: 4, bar: false, product_id: 41, date: "2016-05-01", revenue: 5, color_ids: [7, 14], }, { 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: { id: { string: "Id", type: "integer" }, name: { string: "Product Name", type: "char" }, }, records: [ { id: 37, display_name: "xphone", }, { id: 41, display_name: "xpad", }, ], }, color: { fields: { id: { string: "Id", type: "integer" }, name: { string: "Color", type: "char" }, }, records: [ { id: 7, display_name: "red", }, { id: 14, display_name: "black", }, ], }, }, views: { "foo,false,graph": ``, "foo,false,search": ` `, }, }; setupViewRegistries(); patchWithCleanup(browser, { setTimeout: (fn) => fn() }); target = getFixture(); }); QUnit.module("GraphView"); QUnit.test("simple bar chart rendering", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo" }); const { measure, mode, order, stacked } = getGraphModelMetaData(graph); assert.hasClass(target.querySelector(".o_graph_view"), "o_view_controller"); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); assert.strictEqual(measure, "__count", `the active measure should be "__count" by default`); assert.strictEqual(mode, "bar", "should be in bar chart mode by default"); assert.strictEqual(order, null, "should not be ordered by default"); assert.strictEqual(stacked, true, "bar charts should be stacked by default"); checkLabels(assert, graph, ["Total"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label", "stack"], { backgroundColor: "#1f77b4", borderColor: undefined, data: [8], label: "Count", stack: "", }); checkLegend(assert, graph, "Count"); checkTooltip(assert, graph, { lines: [{ label: "Total", value: "8" }] }, 0); }); QUnit.test("simple bar chart rendering with no data", async function (assert) { assert.expect(4); serverData.models.foo.records = []; const graph = await makeView({ serverData, type: "graph", resModel: "foo" }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); assert.containsNone(target, ".o_nocontent_help"); checkLabels(assert, graph, []); checkDatasets(assert, graph, [], []); }); QUnit.test("simple bar chart rendering (one groupBy)", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["false", "true"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label"], { backgroundColor: "#1f77b4", borderColor: undefined, data: [5, 3], label: "Count", }); checkLegend(assert, graph, "Count"); checkTooltip(assert, graph, { lines: [{ label: "false", value: "5" }] }, 0); checkTooltip(assert, graph, { lines: [{ label: "true", value: "3" }] }, 1); }); QUnit.test("simple bar chart rendering (two groupBy)", async function (assert) { assert.expect(20); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["false", "true"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "#1f77b4", borderColor: undefined, data: [1, 3], label: "xphone", }, { backgroundColor: "#ff7f0e", borderColor: undefined, data: [4, 0], label: "xpad", }, { backgroundColor: "rgba(0,0,0,0.4)", borderColor: "rgba(0,0,0,0.4)", data: [5, 3], label: "Sum", }, ] ); checkLegend(assert, graph, ["xphone", "xpad", "Sum"]); checkTooltip(assert, graph, { lines: [{ label: "false / xphone", value: "1" }] }, 0, 0); checkTooltip(assert, graph, { lines: [{ label: "true / xphone", value: "3" }] }, 1, 0); checkTooltip(assert, graph, { lines: [{ label: "false / xpad", value: "4" }] }, 0, 1); checkTooltip(assert, graph, { lines: [{ label: "true / xpad", value: "0" }] }, 1, 1); }); QUnit.test("bar chart rendering (no groupBy, several domains)", async function (assert) { assert.expect(11); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, groupBy: [], comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["Total"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "#1f77b4", borderColor: undefined, data: [6], label: "True group", }, { backgroundColor: "#ff7f0e", borderColor: undefined, data: [17], label: "False group", }, ] ); checkLegend(assert, graph, ["True group", "False group"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "Total / True group", value: "6" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "Total / False group", value: "17" }], }, 0, 1 ); }); QUnit.test("bar chart rendering (one groupBy, several domains)", async function (assert) { assert.expect(19); serverData.models.foo.records = [ { bar: true, foo: 1, revenue: 14 }, { bar: true, foo: 2, revenue: false }, { bar: false, foo: 1, revenue: 12 }, { bar: false, foo: 2, revenue: -4 }, { bar: false, foo: 3, revenue: 2 }, { bar: false, foo: 4, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["1", "2", "3", "4"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "#1f77b4", borderColor: undefined, data: [14, 0, 0, 0], label: "True group", }, { backgroundColor: "#ff7f0e", borderColor: undefined, data: [12, -4, 2, 0], label: "False group", }, ] ); checkLegend(assert, graph, ["True group", "False group"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "1 / True group", value: "14" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "1 / False group", value: "12" }], }, 0, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "2 / False group", value: "-4" }], }, 1, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "3 / False group", value: "2" }], }, 2, 1 ); }); QUnit.test("bar chart many2many groupBy", async function (assert) { assert.expect(16); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["None", "black", "red"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label"], { backgroundColor: "#1f77b4", borderColor: undefined, data: [8, 10, 13], label: "Revenue", }); checkLegend(assert, graph, "Revenue"); checkTooltip( assert, graph, { lines: [{ label: "None", value: "8" }], title: "Revenue" }, 0 ); checkTooltip( assert, graph, { lines: [{ label: "black", value: "10" }], title: "Revenue" }, 1 ); checkTooltip( assert, graph, { lines: [{ label: "red", value: "13" }], title: "Revenue" }, 2 ); }); QUnit.test("differentiate many2many values with same label", async function (assert) { assert.expect(19); serverData.models.color.records.push({ id: 21, display_name: "red" }); serverData.models.foo.records.push({ id: 30, color_ids: [21], revenue: 14 }); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["None", "black", "red", "red (2)"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label"], { backgroundColor: "#1f77b4", borderColor: undefined, data: [8, 10, 14, 13], label: "Revenue", }); checkTooltip( assert, graph, { lines: [{ label: "None", value: "8" }], title: "Revenue" }, 0 ); checkTooltip( assert, graph, { lines: [{ label: "black", value: "10" }], title: "Revenue" }, 1 ); checkTooltip( assert, graph, { lines: [{ label: "red", value: "14" }], title: "Revenue" }, 2 ); checkTooltip( assert, graph, { lines: [{ label: "red (2)", value: "13" }], title: "Revenue" }, 3 ); }); QUnit.test( "bar chart rendering (one groupBy, several domains with date identification)", async function (assert) { assert.expect(23); serverData.models.foo.records = [ { date: "2021-01-04", revenue: 12 }, { date: "2021-01-12", revenue: 5 }, { date: "2021-01-19", revenue: 15 }, { date: "2021-01-26", revenue: 2 }, { date: "2021-02-04", revenue: 14 }, { date: "2021-02-17", revenue: false }, { date: false, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); checkLabels(assert, graph, ["W05 2021", "W07 2021", "", ""]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "#1f77b4", borderColor: undefined, data: [14, 0], label: "February 2021", }, { backgroundColor: "#ff7f0e", borderColor: undefined, data: [12, 5, 15, 2], label: "January 2021", }, ] ); checkLegend(assert, graph, ["February 2021", "January 2021"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W05 2021 / February 2021", value: "14" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W01 2021 / January 2021", value: "12" }], }, 0, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W02 2021 / January 2021", value: "5" }], }, 1, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W03 2021 / January 2021", value: "15" }], }, 2, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W04 2021 / January 2021", value: "2" }], }, 3, 1 ); } ); QUnit.test( "bar chart rendering (two groupBy, several domains with no date identification)", async function (assert) { assert.expect(15); serverData.models.foo.records = [ { date: "2021-01-04", bar: false, revenue: 12 }, { date: "2021-01-12", bar: true, revenue: 5 }, { date: "2021-02-04", bar: false, revenue: 14 }, { date: "2021-02-17", bar: true, revenue: false }, { date: false, bar: false, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); checkLabels(assert, graph, ["false", "true"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "#1f77b4", borderColor: undefined, data: [14, 0], label: "February 2021 / W05 2021", }, { backgroundColor: "#ff7f0e", borderColor: undefined, data: [0, 0], label: "February 2021 / W07 2021", }, { backgroundColor: "#aec7e8", borderColor: undefined, data: [12, 0], label: "January 2021 / W01 2021", }, { backgroundColor: "#ffbb78", borderColor: undefined, data: [0, 5], label: "January 2021 / W02 2021", }, ] ); checkLegend(assert, graph, [ "February 2021 / W05 2021", "February 2021 / W07 2021", "January 2021 / W01 2021", "January 2021 / W02 2021", ]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "false / February 2021 / W05 2021", value: "14" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "false / January 2021 / W01 2021", value: "12" }], }, 0, 2 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "true / January 2021 / W02 2021", value: "5" }], }, 1, 3 ); } ); QUnit.test("line chart rendering (no groupBy)", async function (assert) { assert.expect(9); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); const { mode } = getGraphModelMetaData(graph); assert.strictEqual(mode, "line"); checkLabels(assert, graph, ["", "Total", ""]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label", "stack"], { backgroundColor: "rgba(31,119,180,0.4)", borderColor: "#1f77b4", data: [undefined, 8], label: "Count", stack: undefined, }); checkLegend(assert, graph, "Count"); checkTooltip(assert, graph, { lines: [{ label: "Total", value: "8" }] }, 1); }); QUnit.test("line chart rendering (one groupBy)", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["false", "true"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label"], { backgroundColor: "rgba(31,119,180,0.4)", borderColor: "#1f77b4", data: [5, 3], label: "Count", }); checkLegend(assert, graph, "Count"); checkTooltip(assert, graph, { lines: [{ label: "false", value: "5" }] }, 0); checkTooltip(assert, graph, { lines: [{ label: "true", value: "3" }] }, 1); }); QUnit.test("line chart rendering (two groupBy)", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["false", "true"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: undefined, borderColor: "#1f77b4", data: [1, 3], label: "xphone", }, { backgroundColor: undefined, borderColor: "#ff7f0e", data: [4, 0], label: "xpad", }, ] ); checkLegend(assert, graph, ["xphone", "xpad"]); checkTooltip( assert, graph, { lines: [ { label: "false / xpad", value: "4" }, { label: "false / xphone", value: "1" }, ], }, 0 ); checkTooltip( assert, graph, { lines: [ { label: "true / xphone", value: "3" }, { label: "true / xpad", value: "0" }, ], }, 1 ); }); QUnit.test("line chart many2many groupBy", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["black", "red"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label"], { backgroundColor: "rgba(31,119,180,0.4)", borderColor: "#1f77b4", data: [10, 13], label: "Revenue", }); checkLegend(assert, graph, "Revenue"); checkTooltip( assert, graph, { lines: [{ label: "black", value: "10" }], title: "Revenue" }, 0 ); checkTooltip( assert, graph, { lines: [{ label: "red", value: "13" }], title: "Revenue" }, 1 ); }); QUnit.test( "Check if values in tooltip are correctly sorted when groupBy filter are applied", async function (assert) { serverData.models.foo.records = [ { product_id: 37, foo: 1, revenue: 12 }, { product_id: 37, foo: 2, revenue: 5 }, { product_id: 37, foo: 3, revenue: 1.45e2 }, { product_id: 37, foo: 4, revenue: -9 }, { product_id: 41, foo: 5, revenue: 0 }, { product_id: 41, foo: 6, revenue: -1 }, { product_id: 41, foo: 7, revenue: Math.PI }, { product_id: 41, foo: 8, revenue: 80.67 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkTooltip( assert, graph, { lines: [ { label: "xphone / 3", value: "145.00" }, { label: "xphone / 1", value: "12.00" }, { label: "xphone / 2", value: "5.00" }, { label: "xphone / 5", value: "0.00" }, { label: "xphone / 6", value: "0.00" }, { label: "xphone / 7", value: "0.00" }, { label: "xphone / 8", value: "0.00" }, { label: "xphone / 4", value: "-9.00" }, ], title: "Revenue", }, 0 ); checkTooltip( assert, graph, { lines: [ { label: "xpad / 8", value: "80.67" }, { label: "xpad / 7", value: "3.14" }, { label: "xpad / 1", value: "0.00" }, { label: "xpad / 2", value: "0.00" }, { label: "xpad / 3", value: "0.00" }, { label: "xpad / 4", value: "0.00" }, { label: "xpad / 5", value: "0.00" }, { label: "xpad / 6", value: "-1.00" }, ], title: "Revenue", }, 1 ); } ); QUnit.test("format total in hh:mm when measure is unit_amount", async function (assert) { assert.expect(11); serverData.models["account.analytic.line"] = { fields: { unit_amount: { string: "Unit Amount", type: "float", group_operator: "sum", store: true, }, project_id: { string: "Project", type: "many2one", relation: "project.project", store: true, sortable: true, }, }, records: [{ id: 1, unit_amount: 8, project_id: false }], }; const graph = await makeView({ serverData, resModel: "account.analytic.line", type: "graph", arch: ` `, }); const { measure, fieldAttrs } = getGraphModelMetaData(graph); assert.hasClass(target.querySelector(".o_graph_view"), "o_view_controller"); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); assert.strictEqual(measure, "unit_amount", `the measure should be "unit_amount"`); checkLegend(assert, graph, "Unit Amount"); checkLabels(assert, graph, ["Total"]); assert.strictEqual( fieldAttrs[measure].widget, "float_time", "should be a float_time widget" ); checkYTicks(assert, graph, ["00:00", "02:00", "04:00", "06:00", "08:00"]); checkTooltip( assert, graph, { title: "Unit Amount", lines: [{ label: "Total", value: "08:00" }] }, 0 ); }); QUnit.test("Stacked button visible in the line chart", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); await selectMode(target, "line"); checkModeIs(assert, graph, "line"); assert.strictEqual(graph.model.metaData.stacked, true, "graph should be stacked."); assert.strictEqual( getScaleY(graph).stacked, true, "The y axes should have stacked property set to true" ); assert.containsOnce(target, `button.o_graph_button[data-tooltip="Stacked"]`); const stackButton = target.querySelector(`button.o_graph_button[data-tooltip="Stacked"]`); await click(stackButton); assert.strictEqual( graph.model.metaData.stacked, false, "graph should be a classic line chart." ); assert.strictEqual( getScaleY(graph).stacked == undefined, true, "The y axes should have stacked property set to undefined" ); }); QUnit.test("Stacked line prop click false", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); const stackButton = target.querySelector(`button.o_graph_button[data-tooltip="Stacked"]`); await click(stackButton); assert.strictEqual( graph.model.metaData.stacked, false, "graph should be a classic line chart." ); assert.strictEqual( !!getScaleY(graph).stacked, false, "the y axes should have a stacked property set to false since the stacked property in line chart is false." ); assert.strictEqual( getGraphRenderer(graph).getElementOptions().line.fill, false, "The fill property should be false since the stacked property is false." ); const expectedDatasets = [ { backgroundColor: undefined, borderColor: "#1f77b4", originIndex: 0, pointBackgroundColor: "#1f77b4", }, { backgroundColor: undefined, borderColor: "#ff7f0e", originIndex: 0, pointBackgroundColor: "#ff7f0e", }, ]; const keysToEvaluate = [ "backgroundColor", "borderColor", "originIndex", "pointBackgroundColor", ]; checkDatasets(assert, graph, keysToEvaluate, expectedDatasets); }); QUnit.test("Stacked prop and default line chart", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.strictEqual(graph.model.metaData.mode, "line", "should be in line chart mode."); assert.strictEqual(graph.model.metaData.stacked, true, "should be stacked by default."); assert.strictEqual( getScaleY(graph).stacked, true, "the stacked property in y axes should be true when the stacked is enabled in line chart" ); assert.strictEqual( getGraphRenderer(graph).getElementOptions().line.fill, true, "The fill property should be true to add backgroundColor in line chart." ); const expectedDatasets = []; const keysToEvaluate = [ "backgroundColor", "borderColor", "originIndex", "pointBackgroundColor", ]; const datasets = getChart(graph).data.datasets; const colors = getColors(); for (let i = 0; i < datasets.length; i++) { const expectedColor = colors[i]; expectedDatasets.push({ backgroundColor: hexToRGBA(expectedColor, 0.4), borderColor: expectedColor, originIndex: 0, pointBackgroundColor: expectedColor, }); } checkDatasets(assert, graph, keysToEvaluate, expectedDatasets); }); QUnit.test("Cumulative prop and default line chart", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.strictEqual(graph.model.metaData.mode, "line", "should be in line chart mode."); assert.strictEqual( graph.model.metaData.cumulated, false, "should not be cumulative by default." ); await click(target, '[data-tooltip="Cumulative"]'); assert.strictEqual(graph.model.metaData.cumulated, true, "should be in cumulative"); const expectedDatasets = [ { data: [1, 4], }, { data: [4, 4], }, ]; checkDatasets(assert, graph, ["data"], expectedDatasets); }); QUnit.test("Default cumulative prop", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.strictEqual(graph.model.metaData.mode, "line", "should be in line chart mode."); assert.strictEqual(graph.model.metaData.cumulated, true, "should be in cumulative"); assert.strictEqual( graph.model.metaData.cumulatedStart, false, "should have cumulated start opted-out" ); }); QUnit.test("Cumulative prop and cumulated start", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, context: { search_default_filter_after_march: 1, }, }); assert.strictEqual(graph.model.metaData.mode, "line", "should be in line chart mode."); assert.strictEqual(graph.model.metaData.cumulated, true, "should be in cumulative"); assert.strictEqual( graph.model.metaData.cumulatedStart, true, "should have cumulated start opted-in" ); const expectedDatasets = [ { data: [4, 4, 4], }, { data: [0, 1, 2], }, ]; checkDatasets(assert, graph, ["data"], expectedDatasets); }); QUnit.test("line chart rendering (no groupBy, several domains)", async function (assert) { assert.expect(7); const graph = await makeView({ serverData, resModel: "foo", type: "graph", arch: ` `, comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["", "Total", ""]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "rgba(31,119,180,0.4)", borderColor: "#1f77b4", data: [undefined, 6], label: "True group", }, { backgroundColor: undefined, borderColor: "#ff7f0e", data: [undefined, 17], label: "False group", }, ] ); checkLegend(assert, graph, ["True group", "False group"]); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "Total / False group", value: "17" }, { label: "Total / True group", value: "6" }, ], }, 1 ); }); QUnit.test("line chart rendering (one groupBy, several domains)", async function (assert) { assert.expect(19); serverData.models.foo.records = [ { bar: true, foo: 1, revenue: 14 }, { bar: true, foo: 2, revenue: false }, { bar: false, foo: 1, revenue: 12 }, { bar: false, foo: 2, revenue: -4 }, { bar: false, foo: 3, revenue: 2 }, { bar: false, foo: 4, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["1", "2", "3", "4"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "rgba(31,119,180,0.4)", borderColor: "#1f77b4", data: [14, 0, 0, 0], label: "True group", }, { backgroundColor: undefined, borderColor: "#ff7f0e", data: [12, -4, 2, 0], label: "False group", }, ] ); checkLegend(assert, graph, ["True group", "False group"]); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "1 / True group", value: "14" }, { label: "1 / False group", value: "12" }, ], }, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "2 / True group", value: "0" }, { label: "2 / False group", value: "-4" }, ], }, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "3 / False group", value: "2" }, { label: "3 / True group", value: "0" }, ], }, 2 ); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "4 / True group", value: "0" }, { label: "4 / False group", value: "0" }, ], }, 3 ); }); QUnit.test( "line chart rendering (one groupBy, several domains with date identification) without stacked attribute", async function (assert) { serverData.models.foo.records = [ { date: "2021-01-04", revenue: 12 }, { date: "2021-01-12", revenue: 5 }, { date: "2021-01-19", revenue: 15 }, { date: "2021-01-26", revenue: 2 }, { date: "2021-02-04", revenue: 14 }, { date: "2021-02-17", revenue: false }, { date: false, revenue: 0 }, ]; await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); assert.doesNotHaveClass( target.querySelector(".o_graph_button[data-tooltip=Stacked]"), "active", "The stacked mode should be disabled" ); } ); QUnit.test( "line chart rendering (one groupBy, several domains with date identification)", async function (assert) { assert.expect(19); serverData.models.foo.records = [ { date: "2021-01-04", revenue: 12 }, { date: "2021-01-12", revenue: 5 }, { date: "2021-01-19", revenue: 15 }, { date: "2021-01-26", revenue: 2 }, { date: "2021-02-04", revenue: 14 }, { date: "2021-02-17", revenue: false }, { date: false, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); checkLabels(assert, graph, ["W05 2021", "W07 2021", "", ""]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: "rgba(31,119,180,0.4)", borderColor: "#1f77b4", data: [14, 0], label: "February 2021", }, { backgroundColor: undefined, borderColor: "#ff7f0e", data: [12, 5, 15, 2], label: "January 2021", }, ] ); checkLegend(assert, graph, ["February 2021", "January 2021"]); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "W05 2021 / February 2021", value: "14" }, { label: "W01 2021 / January 2021", value: "12" }, ], }, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "W02 2021 / January 2021", value: "5" }, { label: "W07 2021 / February 2021", value: "0" }, ], }, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W03 2021 / January 2021", value: "15" }], }, 2 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "W04 2021 / January 2021", value: "2" }], }, 3 ); } ); QUnit.test( "line chart rendering (two groupBy, several domains with no date identification)", async function (assert) { assert.expect(11); serverData.models.foo.records = [ { date: "2021-01-04", bar: false, revenue: 12 }, { date: "2021-01-12", bar: true, revenue: 5 }, { date: "2021-02-04", bar: false, revenue: 14 }, { date: "2021-02-17", bar: true, revenue: false }, { date: false, bar: false, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); checkLabels(assert, graph, ["false", "true"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: undefined, borderColor: "#1f77b4", data: [14, 0], label: "February 2021 / W05 2021", }, { backgroundColor: undefined, borderColor: "#ff7f0e", data: [0, 0], label: "February 2021 / W07 2021", }, { backgroundColor: undefined, borderColor: "#aec7e8", data: [12, 0], label: "January 2021 / W01 2021", }, { backgroundColor: undefined, borderColor: "#ffbb78", data: [0, 5], label: "January 2021 / W02 2021", }, ] ); checkLegend(assert, graph, [ "February 2021 / W05 2021", "February 2021 / W07 2021", "January 2021 / W01 2021", "January 2021 / W02 2021", ]); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "false / February 2021 / W05 2021", value: "14" }, { label: "false / January 2021 / W01 2021", value: "12" }, { label: "false / February 2021 / W07 2021", value: "0" }, { label: "false / January 2021 / W02 2021", value: "0" }, ], }, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [ { label: "true / January 2021 / W02 2021", value: "5" }, { label: "true / February 2021 / W05 2021", value: "0" }, { label: "true / February 2021 / W07 2021", value: "0" }, { label: "true / January 2021 / W01 2021", value: "0" }, ], }, 1 ); } ); 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. serverData.models.foo.records = serverData.models.foo.records.slice(0, 1); await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); assert.containsOnce(target, "canvas", "should have a canvas"); }); QUnit.test("pie chart rendering (no groupBy)", async function (assert) { assert.expect(9); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); const { mode } = getGraphModelMetaData(graph); assert.strictEqual(mode, "pie"); checkLabels(assert, graph, ["Total"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label", "stack"], { backgroundColor: ["#1f77b4"], borderColor: getBorderWhite(), data: [8], label: "", stack: undefined, }); checkLegend(assert, graph, "Total"); checkTooltip(assert, graph, { lines: [{ label: "Total", value: "8 (100.00%)" }] }, 0); }); QUnit.test("pie chart rendering (one groupBy)", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["false", "true"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data"], { backgroundColor: ["#1f77b4", "#ff7f0e"], borderColor: getBorderWhite(), data: [5, 3], }); checkLegend(assert, graph, ["false", "true"]); checkTooltip(assert, graph, { lines: [{ label: "false", value: "5 (62.50%)" }] }, 0); checkTooltip(assert, graph, { lines: [{ label: "true", value: "3 (37.50%)" }] }, 1); }); QUnit.test("pie chart many2many groupby", async function (assert) { assert.expect(16); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["None", "black", "red"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data"], { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8"], borderColor: getBorderWhite(), data: [8, 10, 13], }); checkLegend(assert, graph, ["None", "black", "red"]); checkTooltip( assert, graph, { lines: [{ label: "None", value: "8 (25.81%)" }], title: "Revenue" }, 0 ); checkTooltip( assert, graph, { lines: [{ label: "black", value: "10 (32.26%)" }], title: "Revenue" }, 1 ); checkTooltip( assert, graph, { lines: [{ label: "red", value: "13 (41.94%)" }], title: "Revenue" }, 2 ); }); QUnit.test("pie chart rendering (two groupBy)", async function (assert) { assert.expect(16); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); checkLabels(assert, graph, ["false / xphone", "false / xpad", "true / xphone"]); checkDatasets(assert, graph, ["backgroundColor", "borderColor", "data", "label"], { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8"], borderColor: getBorderWhite(), data: [1, 4, 3], label: "", }); checkLegend(assert, graph, ["false / xphone", "false / xpad", "true / xphone"]); checkTooltip( assert, graph, { lines: [{ label: "false / xphone", value: "1 (12.50%)" }] }, 0 ); checkTooltip(assert, graph, { lines: [{ label: "false / xpad", value: "4 (50.00%)" }] }, 1); checkTooltip( assert, graph, { lines: [{ label: "true / xphone", value: "3 (37.50%)" }] }, 2 ); }); QUnit.test("pie chart rendering (no groupBy, several domains)", async function (assert) { assert.expect(11); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["Total"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: ["#1f77b4"], borderColor: getBorderWhite(), data: [6], label: "True group", }, { backgroundColor: ["#1f77b4"], borderColor: getBorderWhite(), data: [17], label: "False group", }, ] ); checkLegend(assert, graph, ["Total"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "True group / Total", value: "6 (100.00%)" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "False group / Total", value: "17 (100.00%)" }], }, 0, 1 ); }); QUnit.test("pie chart rendering (one groupBy, several domains)", async function (assert) { assert.expect(19); serverData.models.foo.records = [ { bar: true, foo: 1, revenue: 14 }, { bar: true, foo: 2, revenue: false }, { bar: false, foo: 1, revenue: 12 }, { bar: false, foo: 2, revenue: 5 }, { bar: false, foo: 3, revenue: 0 }, { bar: false, foo: 4, revenue: 2 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["1", "2", "4"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8"], borderColor: getBorderWhite(), data: [14, 0, 0], label: "True group", }, { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8"], borderColor: getBorderWhite(), data: [12, 5, 2], label: "False group", }, ] ); checkLegend(assert, graph, ["1", "2", "4"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "True group / 1", value: "14 (100.00%)" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "False group / 1", value: "12 (63.16%)" }], }, 0, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "False group / 2", value: "5 (26.32%)" }], }, 1, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "False group / 4", value: "2 (10.53%)" }], }, 2, 1 ); }); QUnit.test( "pie chart rendering (one groupBy, several domains with date identification)", async function (assert) { assert.expect(27); serverData.models.foo.records = [ { date: "2021-01-04" }, { date: "2021-01-12" }, { date: "2021-01-19" }, { date: "2021-01-26" }, { date: "2021-02-04" }, { date: "2021-02-17" }, { date: false }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); checkLabels(assert, graph, [ "W05 2021, W01 2021", "W07 2021, W02 2021", "W03 2021", "W04 2021", ]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8", "#ffbb78"], borderColor: getBorderWhite(), data: [1, 1, 0, 0], label: "February 2021", }, { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8", "#ffbb78"], borderColor: getBorderWhite(), data: [1, 1, 1, 1], label: "January 2021", }, ] ); checkLegend(assert, graph, [ "W05 2021, W01 2021", "W07 2021, W02 2021", "W03 2021", "W04 2021", ]); checkTooltip( assert, graph, { lines: [{ label: "February 2021 / W05 2021", value: "1 (50.00%)" }], }, 0, 0 ); checkTooltip( assert, graph, { lines: [{ label: "January 2021 / W01 2021", value: "1 (25.00%)" }], }, 0, 1 ); checkTooltip( assert, graph, { lines: [{ label: "February 2021 / W07 2021", value: "1 (50.00%)" }], }, 1, 0 ); checkTooltip( assert, graph, { lines: [{ label: "January 2021 / W02 2021", value: "1 (25.00%)" }], }, 1, 1 ); checkTooltip( assert, graph, { lines: [{ label: "January 2021 / W03 2021", value: "1 (25.00%)" }], }, 2, 1 ); checkTooltip( assert, graph, { lines: [{ label: "January 2021 / W04 2021", value: "1 (25.00%)" }], }, 3, 1 ); } ); QUnit.test( "pie chart rendering (two groupBy, several domains with no date identification)", async function (assert) { assert.expect(15); serverData.models.foo.records = [ { date: "2021-01-04", bar: false, revenue: 12 }, { date: "2021-01-12", bar: true, revenue: 5 }, { date: "2021-02-04", bar: false, revenue: 14 }, { date: "2021-02-17", bar: true, revenue: false }, { date: false, bar: false, revenue: 0 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [ ["date", ">=", "2021-02-01"], ["date", "<=", "2021-02-28"], ], description: "February 2021", }, { arrayRepr: [ ["date", ">=", "2021-01-01"], ["date", "<=", "2021-01-31"], ], description: "January 2021", }, ], fieldName: "date", }, }); checkLabels(assert, graph, ["false / W05 2021", "false / W01 2021", "true / W02 2021"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8"], borderColor: getBorderWhite(), data: [14, 0, 0], label: "February 2021", }, { backgroundColor: ["#1f77b4", "#ff7f0e", "#aec7e8"], borderColor: getBorderWhite(), data: [0, 12, 5], label: "January 2021", }, ] ); checkLegend(assert, graph, ["false / W05 2021", "false / W01 2021", "true / W02 2021"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "February 2021 / false / W05 2021", value: "14 (100.00%)" }], }, 0, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "January 2021 / false / W01 2021", value: "12 (70.59%)" }], }, 1, 1 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "January 2021 / true / W02 2021", value: "5 (29.41%)" }], }, 2, 1 ); } ); QUnit.test("pie chart rendering (no data)", async function (assert) { assert.expect(7); serverData.models.foo.records = []; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); checkLabels(assert, graph, ["No data"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: [DEFAULT_BG], borderColor: getBorderWhite(), data: [1], label: null, }, ] ); checkLegend(assert, graph, ["No data"]); checkTooltip(assert, graph, { lines: [{ label: "No data", value: "0 (100.00%)" }] }, 0); }); QUnit.test("pie chart rendering (no data, several domains)", async function (assert) { assert.expect(11); serverData.models.foo.records = [{ product_id: 37, bar: true }]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, comparison: { domains: [ { arrayRepr: [["bar", "=", true]], description: "True group" }, { arrayRepr: [["bar", "=", false]], description: "False group" }, ], }, }); checkLabels(assert, graph, ["xphone", "No data"]); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label"], [ { backgroundColor: ["#1f77b4"], borderColor: getBorderWhite(), data: [1], label: "True group", }, { backgroundColor: ["#1f77b4", DEFAULT_BG], borderColor: getBorderWhite(), data: [undefined, 1], label: "False group", }, ] ); checkLegend(assert, graph, ["xphone", "No data"]); checkTooltip( assert, graph, { lines: [{ label: "True group / xphone", value: "1 (100.00%)" }] }, 0, 0 ); checkTooltip( assert, graph, { lines: [{ label: "False group / No data", value: "0 (100.00%)" }] }, 1, 1 ); }); QUnit.test( "pie chart rendering (mix of positive and negative values)", async function (assert) { assert.expect(3); serverData.models.foo.records = [ { bar: true, revenue: 2 }, { bar: false, revenue: -3 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsNone(target, ".o_view_nocontent"); assert.containsOnce(target, ".o_graph_canvas_container"); checkDatasets( assert, graph, ["backgroundColor", "borderColor", "data", "label", "stack"], { backgroundColor: ["#1f77b4"], borderColor: getBorderWhite(), data: [2], label: "", stack: undefined, } ); } ); QUnit.test("pie chart toggling dataset hides label", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); checkLabels(assert, graph, ["Total"]); await clickOnLegend(graph, "Total"); assert.ok(getChart(graph).legend.legendItems[0].hidden); }); QUnit.test("mode props", async function (assert) { assert.expect(2); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); assert.strictEqual(getGraphModelMetaData(graph).mode, "pie", "should be in pie chart mode"); assert.strictEqual(getChart(graph).config.type, "pie"); }); QUnit.test("field id not in groupBy", async function (assert) { assert.expect(3); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["Total"]); checkDatasets(assert, graph, ["backgroundColor", "data", "label", "originIndex", "stack"], { backgroundColor: "#1f77b4", data: [8], label: "Count", originIndex: 0, stack: "", }); checkLegend(assert, graph, "Count"); }); QUnit.test("props modifications", async function (assert) { assert.expect(16); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, }); checkModeIs(assert, graph, "bar"); assert.strictEqual(getXAxeLabel(graph), "bar"); assert.strictEqual(getYAxeLabel(graph), "Count"); await selectMode(target, "line"); checkModeIs(assert, graph, "line"); assert.strictEqual(getXAxeLabel(graph), "bar"); await toggleMenu(target, "Measures"); await toggleMenuItem(target, "Revenue"); assert.strictEqual(getYAxeLabel(graph), "Revenue"); assert.ok(true, "Message"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Color"); checkModeIs(assert, graph, "line"); assert.strictEqual(getXAxeLabel(graph), "Color"); assert.strictEqual(getYAxeLabel(graph), "Revenue"); }); QUnit.test("switching mode", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, type: "graph", resModel: "foo" }); checkModeIs(assert, graph, "bar"); await selectMode(target, "bar"); // click on the active mode does not change anything checkModeIs(assert, graph, "bar"); await selectMode(target, "line"); checkModeIs(assert, graph, "line"); await selectMode(target, "pie"); checkModeIs(assert, graph, "pie"); }); QUnit.test("switching measure", async function (assert) { assert.expect(6); const graph = await makeView({ serverData, type: "graph", resModel: "foo" }); function checkMeasure(measure) { const yAxe = getChart(graph).config.options.scales.y; assert.strictEqual(yAxe.title.text, measure); const item = [...target.querySelectorAll(".o_menu_item")].find( (el) => el.innerText === measure ); assert.hasClass(item, "selected"); } await toggleMenu(target, "Measures"); checkMeasure("Count"); checkLegend(assert, graph, "Count"); await toggleMenuItem(target, "Foo"); checkMeasure("Foo"); checkLegend(assert, graph, "Foo"); }); QUnit.test("process default view description", async function (assert) { assert.expect(1); const propsFromArch = new GraphArchParser().parse(); assert.deepEqual(propsFromArch, { fields: {}, fieldAttrs: {}, groupBy: [], measures: [] }); }); QUnit.test("process simple arch (no field tag)", async function (assert) { assert.expect(2); const fields = serverData.models.foo.fields; const arch1 = ``; let propsFromArch = new GraphArchParser().parse(arch1, fields); assert.deepEqual(propsFromArch, { disableLinking: true, fields, fieldAttrs: {}, groupBy: [], measures: [], mode: "line", order: "ASC", }); const arch2 = ``; propsFromArch = new GraphArchParser().parse(arch2, fields); assert.deepEqual(propsFromArch, { disableLinking: false, fields, fieldAttrs: {}, groupBy: [], measures: [], stacked: false, title: "Title", }); }); QUnit.test("process arch with field tags", async function (assert) { assert.expect(1); const fields = serverData.models.foo.fields; fields.fighters = { type: "text", string: "Fighters" }; const arch = ` `; const propsFromArch = new GraphArchParser().parse(arch, fields); assert.deepEqual(propsFromArch, { fields, fieldAttrs: { bar: { isInvisible: true, string: "My invisible field" }, fighters: { string: "FooFighters" }, }, measure: "revenue", measures: ["revenue"], groupBy: ["date:day", "foo"], mode: "pie", }); }); QUnit.test("process arch with non stored field tags of type measure", async function (assert) { assert.expect(1); const fields = serverData.models.foo.fields; fields.revenue.store = false; const arch = ` `; const propsFromArch = new GraphArchParser().parse(arch, fields); assert.deepEqual(propsFromArch, { fields, fieldAttrs: {}, measure: "foo", measures: ["revenue", "foo"], groupBy: ["product_id"], }); }); QUnit.test("displaying chart data with three 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); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["xphone", "xpad"]); checkLegend(assert, graph, ["false / None", "true / None", "true / red", "Sum"]); await selectMode(target, "line"); checkLabels(assert, graph, ["xphone", "xpad"]); checkLegend(assert, graph, ["false / None", "true / None", "true / red"]); await selectMode(target, "pie"); checkLabels(assert, graph, [ "xphone / false / None", "xphone / true / None", "xphone / true / red", "xpad / false / None", ]); checkLegend(assert, graph, [ "xphone / false / None", "xphone / true / None", "xphone / true / red", "xpad / false / None", ]); }); QUnit.test("no content helper", async function (assert) { assert.expect(3); serverData.models.foo.records = []; await makeView({ serverData, type: "graph", resModel: "foo", noContentHelp: '

This helper should not be displayed in graph views

', }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); assert.containsNone(target, "div.o_view_nocontent"); assert.containsNone(target, ".abc"); }); QUnit.test("no content helper after update", async function (assert) { assert.expect(6); await makeView({ serverData, type: "graph", resModel: "foo", noContentHelp: '

This helper should not be displayed in graph views

', config: { views: [[false, "search"]], }, }); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); assert.containsNone(target, "div.o_view_nocontent"); assert.containsNone(target, ".abc"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "False Domain"); assert.containsOnce(target, "div.o_graph_canvas_container canvas"); assert.containsNone(target, "div.o_view_nocontent"); assert.containsNone(target, ".abc"); }); QUnit.test("can reload with other group by", async function (assert) { assert.expect(2); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, }); checkLabels(assert, graph, ["xphone", "xpad"]); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Color"); checkLabels(assert, graph, ["None", "red"]); }); QUnit.test("save params succeeds", async function (assert) { assert.expect(4); const expectedContexts = [ { graph_mode: "bar", graph_measure: "__count", graph_groupbys: ["product_id"], graph_order: null, graph_stacked: true, group_by: [], }, { graph_mode: "bar", graph_measure: "foo", graph_groupbys: ["product_id"], graph_order: null, graph_stacked: true, group_by: [], }, { graph_mode: "line", graph_measure: "foo", graph_cumulated: false, graph_groupbys: ["product_id"], graph_order: null, graph_stacked: true, group_by: [], }, { graph_mode: "line", graph_measure: "foo", graph_cumulated: false, graph_groupbys: ["product_id", "color_id"], graph_order: null, graph_stacked: true, group_by: ["product_id", "color_id"], }, ]; let serverId = 1; await makeView({ mockRPC: function (_, args) { if (args.method === "create_or_replace") { const favorite = args.args[0]; assert.deepEqual(favorite.context, expectedContexts.shift()); return serverId++; } }, serverData, resModel: "foo", type: "graph", arch: ` `, searchViewId: false, searchViewArch: ` `, }); await toggleSearchBarMenu(target); await toggleSaveFavorite(target); await editFavoriteName(target, "First Favorite"); await saveFavorite(target); await toggleMenu(target, "Measures"); await toggleMenuItem(target, "Foo"); await toggleSearchBarMenu(target); await toggleSaveFavorite(target); await editFavoriteName(target, "Second Favorite"); await saveFavorite(target); await selectMode(target, "line"); await toggleSearchBarMenu(target); await toggleSaveFavorite(target); await editFavoriteName(target, "Third Favorite"); await saveFavorite(target); await toggleMenuItem(target, "Product"); await toggleMenuItem(target, "Color"); await editFavoriteName(target, "Fourth Favorite"); await saveFavorite(target); }); QUnit.test("correctly uses graph_ keys from the context", async function (assert) { assert.expect(8); const recs = serverData.models.foo.records; const lastOne = recs[recs.length - 1]; lastOne.color_id = 14; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: '', context: { graph_measure: "foo", graph_mode: "line", graph_groupbys: ["color_id"], }, }); checkLabels(assert, graph, ["red", "black"]); checkLegend(assert, graph, "Foo"); checkModeIs(assert, graph, "line"); assert.strictEqual(getXAxeLabel(graph), "Color"); assert.strictEqual(getYAxeLabel(graph), "Foo"); const { mode } = getGraphModelMetaData(graph); assert.strictEqual(mode, "line"); }); QUnit.test("correctly use group_by key from the context", async function (assert) { assert.expect(8); const recs = serverData.models.foo.records; const lastOne = recs[recs.length - 1]; lastOne.color_id = 14; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, context: { search_default_filter_with_context: 1, }, }); checkLabels(assert, graph, ["red", "black"]); checkLegend(assert, graph, "Foo"); checkModeIs(assert, graph, "line"); assert.strictEqual(getXAxeLabel(graph), "Color"); assert.strictEqual(getYAxeLabel(graph), "Foo"); const mode = getGraphModelMetaData(graph).mode; assert.strictEqual(mode, "line"); }); QUnit.test("an invisible field should not be used as groupBy", async function (assert) { assert.expect(1); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["Total"]); }); QUnit.test( "format values as float in case at least one value is not an integer", async function (assert) { assert.expect(10); serverData.models.foo.records = [ { id: 1, bar: false, revenue: 1.5 }, { id: 2, bar: true, revenue: 2 }, ]; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkDatasets(assert, graph, "data", { data: [1.5, 2] }); checkLabels(assert, graph, ["false", "true"]); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "false", value: "1.50" }] }, 0 ); checkTooltip( assert, graph, { title: "Revenue", lines: [{ label: "true", value: "2.00" }] }, 1 ); } ); QUnit.test( "the active measure description is the arch string attribute in priority", async function (assert) { assert.expect(8); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkTooltip( assert, graph, { title: "FooFighters", lines: [{ label: "Total", value: "239" }] }, 0 ); await toggleMenu(target, "Measures"); await toggleMenuItem(target, "Nirvana"); checkTooltip( assert, graph, { title: "Nirvana", lines: [{ label: "Total", value: "23" }] }, 0 ); } ); QUnit.test("correctly uses graph_ keys from the context (at reload)", async function (assert) { assert.expect(10); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: '', searchViewArch: ` `, }); checkLegend(assert, graph, "Count"); assert.strictEqual(getYAxeLabel(graph), "Count"); checkModeIs(assert, graph, "bar"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Context"); checkLegend(assert, graph, "Foo"); assert.strictEqual(getYAxeLabel(graph), "Foo"); checkModeIs(assert, graph, "line"); }); QUnit.test("reload graph with correct fields", async function (assert) { assert.expect(2); await makeView({ serverData, mockRPC: function (_, args) { if (args.method === "web_read_group") { assert.deepEqual(args.kwargs.fields, ["__count", "foo:sum"]); } }, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, }); await toggleSearchBarMenu(target); await toggleMenuItem(target, "False Domain"); }); QUnit.test("initial groupby is kept when reloading", async function (assert) { assert.expect(12); const graph = await makeView({ serverData, mockRPC: function (_, args) { if (args.method === "web_read_group") { assert.deepEqual(args.kwargs.groupby, ["product_id"]); } }, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, }); checkLabels(assert, graph, ["xphone", "xpad"]); checkLegend(assert, graph, "Foo"); checkDatasets(assert, graph, "data", { data: [82, 157] }); assert.strictEqual(getXAxeLabel(graph), "Product"); assert.strictEqual(getYAxeLabel(graph), "Foo"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "False Domain"); checkLabels(assert, graph, []); checkLegend(assert, graph, []); checkDatasets(assert, graph, "data", []); assert.strictEqual(getXAxeLabel(graph), "Product"); assert.strictEqual(getYAxeLabel(graph), "Foo"); }); QUnit.test( "use a many2one as a measure should work (without groupBy)", async function (assert) { assert.expect(5); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["Total"]); checkLegend(assert, graph, "Product"); checkDatasets(assert, graph, "data", { data: [2] }); assert.strictEqual(getXAxeLabel(graph), ""); assert.strictEqual(getYAxeLabel(graph), "Product"); } ); QUnit.test("use a many2one as a measure should work (with groupBy)", async function (assert) { assert.expect(3); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["false", "true"]); checkLegend(assert, graph, "Product"); checkDatasets(assert, graph, "data", { data: [2, 1] }); }); QUnit.test("use a many2one as a measure and as a groupby should work", async function (assert) { assert.expect(5); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["xphone", "xpad"]); checkLegend(assert, graph, "Product"); checkDatasets(assert, graph, "data", { data: [1, 1] }); assert.strictEqual(getXAxeLabel(graph), "Product"); assert.strictEqual(getYAxeLabel(graph), "Product"); }); QUnit.test("differentiate many2one values with same label", async function (assert) { assert.expect(1); serverData.models.product.records.push({ id: 39, display_name: "xphone" }); serverData.models.foo.records.push({ id: 18, product_id: 39 }); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["xphone", "xphone (2)", "xpad"]); }); QUnit.test("not use a many2one as a measure by default", async function (assert) { assert.expect(1); await makeView({ serverData, type: "graph", resModel: "foo", arch: "", }); await toggleMenu(target, "Measures"); assert.deepEqual( [...target.querySelectorAll(".o-dropdown .o_menu_item")].map((el) => el.innerText), ["Foo", "Revenue", "Count"] ); }); QUnit.test( "graph view crash when moving from search view using Down key", async function (assert) { assert.expect(1); await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); await triggerEvent(target, ".o_searchview input", "keydown", { key: "ArrowDown" }); assert.ok(true, "should not generate any error"); } ); QUnit.test( "graph measures should be alphabetically sorted (exception: 'Count' is last)", async function (assert) { assert.expect(1); serverData.models.foo.fields.bouh = { string: "Bouh", type: "integer", store: true, group_operator: "sum", }; await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); await toggleMenu(target, "Measures"); assert.deepEqual( [...target.querySelectorAll(".o-dropdown .o_menu_item")].map((el) => el.innerText), ["Bouh", "Foo", "Revenue", "Count"] ); } ); QUnit.test("a many2one field can be added as measure in arch", async function (assert) { assert.expect(2); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLegend(assert, graph, "Product"); assert.strictEqual(getYAxeLabel(graph), "Product"); }); QUnit.test( "non store fields defined on the arch are present in the measures", async function (assert) { serverData.models.foo.fields.revenue.store = false; await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); await toggleMenu(target, "Measures"); assert.deepEqual( Array.from(target.querySelectorAll(".o_menu_item")).map((e) => e.innerText.trim()), ["Foo", "Revenue", "Count"] ); } ); QUnit.test('graph view "graph_measure" field in context', async function (assert) { assert.expect(6); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: "", context: { graph_measure: "product_id", }, }); checkLegend(assert, graph, "Product"); assert.strictEqual(getYAxeLabel(graph), "Product"); checkTooltip( assert, graph, { title: "Product", lines: [{ label: "Total", value: "2" }] }, 0 ); }); QUnit.test( '"graph_measure" in context is prefered to measure in arch', async function (assert) { assert.expect(6); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: '', context: { graph_measure: "product_id", }, }); checkLegend(assert, graph, "Product"); assert.strictEqual(getYAxeLabel(graph), "Product"); checkTooltip( assert, graph, { title: "Product", lines: [{ label: "Total", value: "2" }] }, 0 ); } ); QUnit.test( "None should appear in bar, pie graph but not in line graph with multiple groupbys", async function (assert) { assert.expect(4); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); function someNone() { return getChart(graph).data.labels.some((l) => /None/.test(l)); } assert.notOk(someNone()); await selectMode(target, "bar"); assert.ok(someNone()); await selectMode(target, "pie"); assert.ok(someNone()); // None should not appear after switching back to line chart await selectMode(target, "line"); assert.notOk(someNone()); } ); QUnit.test( "an invisible field can not be found in the 'Measures' menu", async function (assert) { assert.expect(5); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkTooltip(assert, graph, { lines: [{ label: "Total", value: "8" }] }, 0); await toggleMenu(target, "Measures"); assert.notOk( [...target.querySelectorAll(".o_menu_item")].find( (el) => el.innerText.trim() === "Revenue" ), `"Revenue" can not be found in the "Measures" menu` ); } ); QUnit.test( "graph view only keeps finer groupby filter option for a given groupby", async function (assert) { assert.expect(3); const graph = await makeView({ serverData, type: "graph", resModel: "foo", groupBy: ["date:year", "product_id", "date", "date:quarter"], arch: ``, config: { views: [[false, "search"]], }, }); checkLabels(assert, graph, ["January 2016", "March 2016", "May 2016", "April 2016"]); // mockReadGroup does not always sort groups -> May 2016 is before April 2016 for that reason. checkLegend(assert, graph, ["xphone", "xpad"]); checkDatasets( assert, graph, ["label", "data"], [ { label: "xphone", data: [2, 2, 0, 0], }, { label: "xpad", data: [0, 0, 1, 1], }, ] ); } ); QUnit.test("action name is displayed in breadcrumbs", async function (assert) { const target = getFixture(); const webClient = await createWebClient({ serverData }); await doAction(webClient, { name: "Glou glou", res_model: "foo", type: "ir.actions.act_window", views: [[false, "graph"]], }); assert.strictEqual( target.querySelector(".o_control_panel .o_breadcrumb .active:first-child").innerText, "Glou glou" ); }); QUnit.test("clicking on bar charts triggers a do_action", async function (assert) { assert.expect(6); serviceRegistry.add( "action", { start() { return { doAction(actionRequest, options) { assert.deepEqual(actionRequest, { context: { lang: "en", tz: "taht", uid: 7, }, domain: [["bar", "=", false]], name: "Foo Analysis", res_model: "foo", target: "current", type: "ir.actions.act_window", views: [ [false, "list"], [false, "form"], ], }); assert.deepEqual(options, { viewType: "list" }); }, }; }, }, { force: true } ); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkModeIs(assert, graph, "bar"); checkDatasets(assert, graph, ["domains"], { domains: [[["bar", "=", false]], [["bar", "=", true]]], }); await clickOnDataset(graph); }); QUnit.test( "Clicking on bar charts removes group_by and search_default_* context keys", async function (assert) { assert.expect(2); serviceRegistry.add( "action", { start() { return { doAction(actionRequest, options) { assert.deepEqual(actionRequest, { context: { lang: "en", tz: "taht", uid: 7, }, domain: [["bar", "=", false]], name: "Foo Analysis", res_model: "foo", target: "current", type: "ir.actions.act_window", views: [ [false, "list"], [false, "form"], ], }); assert.deepEqual(options, { viewType: "list" }); }, }; }, }, { force: true } ); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, context: { search_default_user: 1, group_by: "bar", }, }); await clickOnDataset(graph); } ); QUnit.test( "clicking on a pie chart trigger a do_action with correct views", async function (assert) { assert.expect(6); serverData.views["foo,364,list"] = ``; serverData.views["foo,29,form"] = `
`; serviceRegistry.add( "action", { start() { return { doAction(actionRequest, options) { assert.deepEqual(actionRequest, { context: { lang: "en", tz: "taht", uid: 7, }, domain: [["bar", "=", false]], name: "Foo Analysis", res_model: "foo", target: "current", type: "ir.actions.act_window", views: [ [364, "list"], [29, "form"], ], }); assert.deepEqual(options, { viewType: "list" }); }, }; }, }, { force: true } ); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, config: { views: [ [364, "list"], [29, "form"], ], }, }); checkModeIs(assert, graph, "pie"); checkDatasets(assert, graph, ["domains"], { domains: [[["bar", "=", false]], [["bar", "=", true]]], }); await clickOnDataset(graph); } ); QUnit.test('graph view with attribute disable_linking="1"', async function (assert) { assert.expect(4); serviceRegistry.add( "action", { start() { return { doAction() { throw new Error("Should not perform a do_action"); }, }; }, }, { force: true } ); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkModeIs(assert, graph, "bar"); checkDatasets(assert, graph, ["domains"], { domains: [[["bar", "=", false]], [["bar", "=", true]]], }); await clickOnDataset(graph); }); QUnit.test("graph view without invisible attribute on field", async function (assert) { assert.expect(4); await makeView({ serverData, type: "graph", resModel: "foo", arch: ``, }); await toggleMenu(target, "Measures"); assert.containsN( target, ".o_menu_item", 3, "there should be three menu item in the measures dropdown (count, revenue and foo)" ); assert.containsOnce(target, '.o_menu_item:contains("Revenue")'); assert.containsOnce(target, '.o_menu_item:contains("Foo")'); assert.containsOnce(target, '.o_menu_item:contains("Count")'); }); QUnit.test("graph view with invisible attribute on field", async function (assert) { assert.expect(2); await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); await toggleMenu(target, "Measures"); assert.containsN( target, ".o_menu_item", 2, "there should be only two menu item in the measures dropdown (count and foo)" ); assert.containsNone(target, '.o_menu_item:contains("Revenue")'); }); QUnit.test("graph view sort by measure", async function (assert) { assert.expect(20); // change first record from foo as there are 4 records count for each product serverData.models.product.records.push({ id: 38, display_name: "zphone" }); serverData.models.foo.records[7].product_id = 38; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, "button.fa-sort-amount-asc"); assert.containsOnce(target, "button.fa-sort-amount-desc"); checkLegend(assert, graph, "Count", "measure should be by count"); assert.hasClass( target.querySelector("button.fa-sort-amount-desc"), "active", 'sorting should be applie on descending order by default when sorting="desc"' ); checkDatasets(assert, graph, "data", { data: [4, 3, 1] }); await click(target, "button.fa-sort-amount-asc"); assert.hasClass( target.querySelector("button.fa-sort-amount-asc"), "active", "ascending order should be applied" ); checkDatasets(assert, graph, "data", { data: [1, 3, 4] }); await click(target, "button.fa-sort-amount-desc"); assert.hasClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order button should be active" ); checkDatasets(assert, graph, "data", { data: [4, 3, 1] }); // again click on descending button to deactivate order button await click(target, "button.fa-sort-amount-desc"); assert.doesNotHaveClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order button should not be active" ); checkDatasets(assert, graph, "data", { data: [4, 1, 3] }); // set line mode await selectMode(target, "line"); assert.containsOnce(target, "button.fa-sort-amount-asc"); assert.containsOnce(target, "button.fa-sort-amount-desc"); checkLegend(assert, graph, "Count", "measure should be by count"); assert.doesNotHaveClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order should be applied" ); checkDatasets(assert, graph, "data", { data: [4, 1, 3] }); await click(target, "button.fa-sort-amount-asc"); assert.hasClass( target.querySelector("button.fa-sort-amount-asc"), "active", "ascending order button should be active" ); checkDatasets(assert, graph, "data", { data: [1, 3, 4] }); await click(target, "button.fa-sort-amount-desc"); assert.hasClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order button should be active" ); checkDatasets(assert, graph, "data", { data: [4, 3, 1] }); }); QUnit.test("graph view sort by measure for grouped data", async function (assert) { assert.expect(8); // change first record from foo as there are 4 records count for each product serverData.models.product.records.push({ id: 38, display_name: "zphone" }); serverData.models.foo.records[7].product_id = 38; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLegend(assert, graph, ["false", "true", "Sum"], "measure should be by count"); checkDatasets(assert, graph, "data", [ { data: [1, 1, 3] }, { data: [3, 0, 0] }, { data: [4, 1, 3] }, ]); await click(target, "button.fa-sort-amount-asc"); assert.hasClass( target.querySelector("button.fa-sort-amount-asc"), "active", "ascending order should be applied by default" ); checkDatasets(assert, graph, "data", [ { data: [1, 3, 1] }, { data: [0, 0, 3] }, { data: [1, 3, 4] }, ]); await click(target, "button.fa-sort-amount-desc"); assert.hasClass( target.querySelector("button.fa-sort-amount-desc"), "active", "ascending order button should be active" ); checkDatasets(assert, graph, "data", [ { data: [1, 3, 1] }, { data: [3, 0, 0] }, { data: [4, 3, 1] }, ]); // again click on descending button to deactivate order button await click(target, "button.fa-sort-amount-desc"); assert.doesNotHaveClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order button should not be active" ); checkDatasets(assert, graph, "data", [ { data: [1, 1, 3] }, { data: [3, 0, 0] }, { data: [4, 1, 3] }, ]); }); QUnit.test("graph view sort by measure for multiple grouped data", async function (assert) { assert.expect(8); // change first record from foo as there are 4 records count for each product serverData.models.product.records.push({ id: 38, display_name: "zphone" }); serverData.models.foo.records[7].product_id = 38; serverData.models.foo.records.splice( 0, 4, { 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" } ); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLegend( assert, graph, ["xphone", "xpad", "zphone", "Sum"], "measure should be by count" ); checkDatasets(assert, graph, "data", [ { data: [1, 0, 0, 0] }, { data: [1, 2, 1, 2] }, { data: [0, 1, 0, 0] }, { data: [2, 3, 1, 2] }, ]); await click(target, "button.fa-sort-amount-asc"); assert.hasClass( target.querySelector("button.fa-sort-amount-asc"), "active", "ascending order should be applied by default" ); checkDatasets(assert, graph, "data", [ { data: [1, 1, 2, 2] }, { data: [0, 1, 0, 0] }, { data: [0, 0, 0, 1] }, { data: [1, 2, 2, 3] }, ]); await click(target, "button.fa-sort-amount-desc"); assert.hasClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order button should be active" ); checkDatasets(assert, graph, "data", [ { data: [1, 0, 0, 0] }, { data: [2, 1, 2, 1] }, { data: [0, 1, 0, 0] }, { data: [3, 2, 2, 1] }, ]); // again click on descending button to deactivate order button await click(target, "button.fa-sort-amount-desc"); assert.doesNotHaveClass( target.querySelector("button.fa-sort-amount-desc"), "active", "descending order button should not be active" ); checkDatasets(assert, graph, "data", [ { data: [1, 0, 0, 0] }, { data: [1, 2, 1, 2] }, { data: [0, 1, 0, 0] }, { data: [2, 3, 1, 2] }, ]); }); QUnit.test("empty graph view with sample data", async function (assert) { await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, context: { search_default_false_domain: 1 }, searchViewArch: ` `, noContentHelp: '

click to add a foo

', }); assert.hasClass(target.querySelector(".o_graph_view .o_content"), "o_view_sample_data"); assert.containsOnce(target, ".o_view_nocontent"); assert.containsOnce(target, ".o_graph_canvas_container canvas"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "False Domain"); assert.doesNotHaveClass( target.querySelector(".o_graph_view .o_content"), "o_view_sample_data" ); assert.containsNone(target, ".o_view_nocontent"); assert.containsOnce(target, ".o_graph_canvas_container canvas"); }); QUnit.test("non empty graph view with sample data", async function (assert) { await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, noContentHelp: '

click to add a foo

', }); assert.doesNotHaveClass(target, "o_view_sample_data"); assert.containsNone(target, ".o_view_nocontent"); assert.containsOnce(target, ".o_graph_canvas_container canvas"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "False Domain"); assert.doesNotHaveClass(target, "o_view_sample_data"); assert.containsOnce(target, ".o_graph_canvas_container canvas"); assert.containsOnce(target, ".o_view_nocontent"); }); QUnit.test("empty graph view without sample data after filter", async function (assert) { await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, domain: Domain.FALSE.toList(), noContentHelp: '

click to add a foo

', }); assert.containsOnce(target, ".o_graph_canvas_container canvas"); assert.containsOnce(target, ".o_view_nocontent"); }); QUnit.test("reload chart with switchView button keep internal state", async function (assert) { assert.expect(3); serverData.views["foo,false,list"] = ``; const target = getFixture(); const webClient = await createWebClient({ serverData }); await doAction(webClient, { name: "Foo Action 1", res_model: "foo", type: "ir.actions.act_window", views: [ [false, "graph"], [false, "list"], ], }); assert.hasClass(getModeButton(target, "bar"), "active"); await selectMode(target, "line"); assert.hasClass(getModeButton(target, "line"), "active"); await switchView(target, "graph"); assert.hasClass(getModeButton(target, "line"), "active"); }); QUnit.test( "fallback on initial groupby when the groupby from control panel has 0 length", async function (assert) { assert.expect(2); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, context: { search_default_group_by_foo: 1, }, }); checkLabels(assert, graph, ["2", "3", "4", "24", "42", "48", "53", "63"]); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Foo"); checkLabels(assert, graph, ["xphone", "xpad"]); } ); QUnit.test( "change mode, stacked, or order via the graph buttons does not reload datapoints, change measure does", async function (assert) { assert.expect(13); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, mockRPC: function (_, args) { if (args.method === "web_read_group") { assert.step(JSON.stringify(args.kwargs.fields)); } }, }); checkModeIs(assert, graph, "line"); await selectMode(target, "bar"); checkModeIs(assert, graph, "bar"); assert.hasClass(target.querySelector(`[data-tooltip="Stacked"]`), "active"); await click(target.querySelector(`[data-tooltip="Stacked"]`)); assert.doesNotHaveClass(target.querySelector(`[data-tooltip="Stacked"]`), "active"); assert.doesNotHaveClass(target.querySelector(`[data-tooltip="Ascending"]`), "active"); await click(target.querySelector(`[data-tooltip="Ascending"]`)); assert.hasClass(target.querySelector(`[data-tooltip="Ascending"]`), "active"); await toggleMenu(target, "Measures"); await toggleMenuItem(target, "Foo"); assert.verifySteps([ `["__count"]`, // first load `["__count","foo:sum"]`, // reload due to change in measure ]); } ); QUnit.test( "concurrent reloads: add a filter, and directly toggle a measure", async function (assert) { let def; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, mockRPC: function (route, args) { if (args.method === "web_read_group") { return Promise.resolve(def); } }, }); checkDatasets(assert, graph, ["data", "label"], { data: [4, 4], label: "Count", }); // Set a domain (this reload is delayed) def = makeDeferred(); await toggleSearchBarMenu(target); await toggleMenuItem(target, "My Filter"); checkDatasets(assert, graph, ["data", "label"], { data: [4, 4], label: "Count", }); // Toggle a measure await toggleMenu(target, "Measures"); await toggleMenuItem(target, "Foo"); checkDatasets(assert, graph, ["data", "label"], { data: [4, 4], label: "Count", }); def.resolve(); await nextTick(); checkDatasets(assert, graph, ["data", "label"], { data: [82, 4], label: "Foo", }); } ); QUnit.test("change graph mode while loading a filter", async function (assert) { let def; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, mockRPC: function (route, args) { if (args.method === "web_read_group") { return Promise.resolve(def); } }, }); checkDatasets(assert, graph, ["data", "label"], { data: [4, 4], label: "Count", }); checkModeIs(assert, graph, "line"); // Set a domain (this reload is delayed) def = makeDeferred(); await toggleSearchBarMenu(target); await toggleMenuItem(target, "My Filter"); checkDatasets(assert, graph, ["data", "label"], { data: [4, 4], label: "Count", }); checkModeIs(assert, graph, "line"); // Change graph mode await selectMode(target, "bar"); checkDatasets(assert, graph, ["data", "label"], { data: [4, 4], label: "Count", }); checkModeIs(assert, graph, "line"); def.resolve(); await nextTick(); checkDatasets(assert, graph, ["data", "label"], { data: [1], label: "Count", }); checkModeIs(assert, graph, "bar"); }); QUnit.test("only process most recent data for concurrent groupby", async function (assert) { assert.expect(6); let def; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, searchViewArch: ` `, mockRPC() { return Promise.resolve(def); }, }); checkLabels(assert, graph, ["xphone", "xpad"]); checkDatasets(assert, graph, "data", { data: [82, 157] }); def = makeDeferred(); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Color"); await toggleMenuItem(target, "Color"); await toggleMenuItem(target, "Date"); await toggleMenuItemOption(target, "Date", "Month"); checkLabels(assert, graph, ["xphone", "xpad"]); checkDatasets(assert, graph, "data", { data: [82, 157] }); def.resolve(); await nextTick(); checkLabels(assert, graph, [ "January 2016", "March 2016", "May 2016", "None", "April 2016", ]); checkDatasets(assert, graph, "data", { data: [56, 26, 4, 105, 48] }); }); QUnit.test("fill_temporal is true by default", async function (assert) { assert.expect(1); await makeView({ serverData, type: "graph", resModel: "foo", mockRPC: function (route, args) { if (args.method === "web_read_group") { assert.strictEqual( args.kwargs.context.fill_temporal, true, "The observal state of fill_temporal should be true" ); } }, }); }); QUnit.test("fill_temporal can be changed throught the context", async function (assert) { assert.expect(1); await makeView({ serverData, type: "graph", resModel: "foo", context: { fill_temporal: false }, mockRPC: function (route, args) { if (args.method === "web_read_group") { assert.strictEqual( args.kwargs.context.fill_temporal, false, "The observal state of fill_temporal should be false" ); } }, }); }); QUnit.test("fake data in line chart", async function (assert) { assert.expect(1); patchDate(2020, 4, 19, 1, 0, 0); serverData.models.foo.records = []; const graph = await makeView({ type: "graph", resModel: "foo", serverData, context: { search_default_date_filter: 1 }, arch: ` `, searchViewArch: ` `, }); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Date: Previous period"); checkLabels(assert, graph, ["", ""]); }); QUnit.test("no filling color for period of comparison", async function (assert) { assert.expect(1); patchDate(2020, 4, 19, 1, 0, 0); serverData.models.foo.records.forEach((r) => { if (r.date) { r.date = r.date.replace(/\d\d\d\d/, "2019"); } }); const graph = await makeView({ type: "graph", resModel: "foo", serverData, context: { search_default_date_filter: 1 }, arch: ` `, searchViewArch: ` `, }); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Date: Previous period"); checkDatasets(assert, graph, "backgroundColor", { backgroundColor: undefined, }); }); QUnit.test("group by a non stored, sortable field", async function (assert) { assert.expect(1); // When a field is non-stored but sortable it's inherited // from a stored field, so it can be sortable serverData.models.foo.fields.date.store = false; const graph = await makeView({ serverData, type: "graph", resModel: "foo", groupBy: ["date:month"], arch: ``, config: { views: [[false, "search"]], }, }); checkLabels(assert, graph, ["January 2016", "March 2016", "May 2016", "April 2016"]); }); QUnit.test("graph_groupbys should be also used after first load", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", groupBy: ["date:quarter"], arch: ``, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "Favorite", id: 1, context: `{ "group_by": [], "graph_measure": "revenue", "graph_mode": "bar", "graph_groupbys": ["color_id"], }`, sort: "[]", domain: "", is_default: false, model_id: "foo", action_id: false, }, ], }); checkModeIs(assert, graph, "bar"); checkLabels(assert, graph, ["Q1 2016", "Q2 2016", "None"]); checkLegend(assert, graph, "Count"); await toggleSearchBarMenu(target); await toggleMenuItem(target, "Favorite"); checkModeIs(assert, graph, "bar"); checkLabels(assert, graph, ["None", "red"]); checkLegend(assert, graph, "Revenue"); }); QUnit.test("order='desc' on arch", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkDatasets(assert, graph, ["data", "label"], { data: [2, 2, 2, 1, 1], label: "Count", }); }); QUnit.test("order='asc' on arch", async function (assert) { const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkDatasets(assert, graph, ["data", "label"], { data: [1, 1, 2, 2, 2], label: "Count", }); }); QUnit.test("renders banner_route", async (assert) => { await makeView({ type: "graph", resModel: "foo", serverData, arch: ` `, async mockRPC(route) { if (route === "/mybody/isacage") { assert.step(route); return { html: `
myBanner
` }; } }, }); assert.verifySteps(["/mybody/isacage"]); assert.containsOnce(target, ".setmybodyfree"); }); QUnit.test( "In the middle of a year, a graph view grouped by a date field with granularity 'year' should have a single group of SampleServer.MAIN_RECORDSET_SIZE records", async function (assert) { patchDate(2023, 5, 15, 8, 0, 0); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, domain: Domain.FALSE.toList(), }); checkDatasets(assert, graph, ["data"], { data: [SampleServer.MAIN_RECORDSET_SIZE] }); } ); QUnit.test( "no class 'o_view_sample_data' when real data are presented", async function (assert) { serverData.models.foo.records = []; const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); assert.containsOnce(target, ".o_graph_view .o_view_sample_data"); assert.ok(getChart(graph).data.datasets.length); await selectMode(target, "line"); assert.containsOnce(target, ".o_graph_view .o_view_sample_data"); assert.ok(getChart(graph).data.datasets.length); await toggleMenu(target, "Measures"); await toggleMenuItem(target, "Revenue"); assert.containsNone(target, ".o_graph_view .o_view_sample_data"); assert.notOk(getChart(graph).data.datasets.length); } ); QUnit.test("single chart rendering on search", async function (assert) { patchWithCleanup(GraphRenderer.prototype, { setup() { super.setup(...arguments); onRendered(() => { assert.step("rendering"); }); }, }); await makeView({ serverData, type: "graph", resModel: "foo", }); assert.verifySteps(["rendering"]); await validateSearch(target); assert.verifySteps(["rendering"]); }); QUnit.test("apply default filter label", async function (assert) { const graphView = registry.category("views").get("graph"); class CustomGraphModel extends graphView.Model { _getDefaultFilterLabel(fields) { return "None"; } } registry.category("views").add("custom_graph", { ...graphView, Model: CustomGraphModel, }); const graph = await makeView({ serverData, type: "graph", resModel: "foo", arch: ` `, }); checkLabels(assert, graph, ["xphone", "xpad"]); checkLegend(assert, graph, ["None", "red", "Sum"]); await selectMode(target, "line"); checkLabels(assert, graph, ["xphone", "xpad"]); checkLegend(assert, graph, ["None", "red"]); await selectMode(target, "pie"); checkLabels(assert, graph, ["xphone / None", "xphone / red", "xpad / None"]); checkLegend(assert, graph, ["xphone / None", "xphone / red", "xpad / None"]); }); QUnit.test("missing property field definition is fetched", async function (assert) { Object.assign(serverData.models.foo.fields, { properties: { string: "Properties", type: "properties", definition_record: "parent_id", definition_record_field: "properties_definition", name: "properties", }, parent_id: { string: "Parent", type: "many2one", relation: "foo", name: "parent_id", }, properties_definition: { string: "Properties", type: "properties_definition", }, }); const graph = await makeView({ type: "graph", resModel: "foo", serverData, arch: ``, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "My Filter", id: 5, context: `{"group_by": ['properties.my_char']}`, sort: "[]", domain: "[]", is_default: true, model_id: "foo", action_id: false, }, ], mockRPC(_, { method, kwargs }) { if (method === "web_read_group" && kwargs.groupby?.includes("properties.my_char")) { assert.step(JSON.stringify(kwargs.groupby)); return { groups: [ { "properties.my_char": false, __domain: [["properties.my_char", "=", false]], __count: 2, }, { "properties.my_char": "aaa", __domain: [["properties.my_char", "=", "aaa"]], __count: 1, }, ], length: 2, }; } else if (method === "get_property_definition") { return { name: "my_char", type: "char", }; } }, }); assert.verifySteps([`["properties.my_char"]`]); checkLabels(assert, graph, ["None", "aaa"]); checkDatasets( assert, graph, ["data", "label"], [ { data: [2, 1], label: "Count", }, ] ); }); QUnit.test("missing deleted property field definition is created", async function (assert) { Object.assign(serverData.models.foo.fields, { properties: { string: "Properties", type: "properties", definition_record: "parent_id", definition_record_field: "properties_definition", name: "properties", }, parent_id: { string: "Parent", type: "many2one", relation: "foo", name: "parent_id", }, properties_definition: { string: "Properties", type: "properties_definition", }, }); const graph = await makeView({ type: "graph", resModel: "foo", serverData, arch: ``, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "My Filter", id: 5, context: `{"group_by": ['properties.my_char']}`, sort: "[]", domain: "[]", is_default: true, model_id: "foo", action_id: false, }, ], mockRPC(_, { method, kwargs }) { if (method === "web_read_group" && kwargs.groupby?.includes("properties.my_char")) { assert.step(JSON.stringify(kwargs.groupby)); return { groups: [ { "properties.my_char": false, __domain: [["properties.my_char", "=", false]], __count: 2, }, { "properties.my_char": "aaa", __domain: [["properties.my_char", "=", "aaa"]], __count: 1, }, ], length: 2, }; } else if (method === "get_property_definition") { return {}; } }, }); assert.verifySteps([`["properties.my_char"]`]); checkLabels(assert, graph, ["None", "aaa"]); checkDatasets( assert, graph, ["data", "label"], [ { data: [2, 1], label: "Count", }, ] ); }); QUnit.test("limit dataset amount", async function (assert) { serverData.models.project = { fields: { id: { type: "integer" }, name: { type: "char" }, }, records: [], }; serverData.models.stage = { fields: { id: { type: "integer" }, name: { type: "char" }, }, records: [], }; serverData.models.task = { fields: { id: { type: "integer" }, name: { type: "char" }, project_id: { type: "many2one", relation: "project", sortable: true, string: "Project", }, stage_id: { type: "many2one", relation: "stage", sortable: true, string: "Stage" }, }, records: [], }; for (let i = 1; i <= 600; i++) { serverData.models.project.records.push({ id: i, name: `Project ${i}`, }); serverData.models.stage.records.push({ id: i, name: `Stage ${i}`, }); serverData.models.task.records.push({ id: i, project_id: i, stage_id: i, name: `Task ${i}`, }); } const graph = await makeView({ serverData, type: "graph", resModel: "task", arch: ` `, }); assert.strictEqual(graph.model.data.exceeds, true); assert.strictEqual(graph.model.data.datasets.length, 80); assert.strictEqual(graph.model.data.labels.length, 80); assert.containsN(target, `.o_graph_alert`, 1); patchWithCleanup(GraphModel.prototype, { notify() { assert.step("rerender"); }, }); await click(target, `.o_graph_load_all_btn`); assert.verifySteps(["rerender"]); assert.strictEqual(graph.model.data.exceeds, false); assert.strictEqual(graph.model.data.datasets.length, 600); assert.strictEqual(graph.model.data.labels.length, 600); }); });