spreadsheet/static/tests/charts/model/odoo_chart_plugin_test.js

583 lines
24 KiB
JavaScript

/** @odoo-module */
import { OdooBarChart } from "@spreadsheet/chart/odoo_chart/odoo_bar_chart";
import { OdooChart } from "@spreadsheet/chart/odoo_chart/odoo_chart";
import { OdooLineChart } from "@spreadsheet/chart/odoo_chart/odoo_line_chart";
import { nextTick } from "@web/../tests/helpers/utils";
import { createSpreadsheetWithChart, insertChartInSpreadsheet } from "../../utils/chart";
import { insertListInSpreadsheet } from "../../utils/list";
import { createModelWithDataSource, waitForDataSourcesLoaded } from "../../utils/model";
import { addGlobalFilter } from "../../utils/commands";
import { THIS_YEAR_GLOBAL_FILTER } from "../../utils/global_filter";
import * as spreadsheet from "@odoo/o-spreadsheet";
import { makeServerError } from "@web/../tests/helpers/mock_server";
import { session } from "@web/session";
import { getBasicServerData } from "../../utils/data";
const { toZone } = spreadsheet.helpers;
QUnit.module("spreadsheet > odoo chart plugin", {}, () => {
QUnit.test("Can add an Odoo Bar chart", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_bar" });
const sheetId = model.getters.getActiveSheetId();
assert.strictEqual(model.getters.getChartIds(sheetId).length, 1);
const chartId = model.getters.getChartIds(sheetId)[0];
const chart = model.getters.getChart(chartId);
assert.ok(chart instanceof OdooBarChart);
assert.strictEqual(chart.getDefinitionForExcel(), undefined);
assert.strictEqual(model.getters.getChartRuntime(chartId).chartJsConfig.type, "bar");
});
QUnit.test("Can add an Odoo Line chart", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_line" });
const sheetId = model.getters.getActiveSheetId();
assert.strictEqual(model.getters.getChartIds(sheetId).length, 1);
const chartId = model.getters.getChartIds(sheetId)[0];
const chart = model.getters.getChart(chartId);
assert.ok(chart instanceof OdooLineChart);
assert.strictEqual(chart.getDefinitionForExcel(), undefined);
assert.strictEqual(model.getters.getChartRuntime(chartId).chartJsConfig.type, "line");
});
QUnit.test("Can add an Odoo Pie chart", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" });
const sheetId = model.getters.getActiveSheetId();
assert.strictEqual(model.getters.getChartIds(sheetId).length, 1);
const chartId = model.getters.getChartIds(sheetId)[0];
const chart = model.getters.getChart(chartId);
assert.ok(chart instanceof OdooChart);
assert.strictEqual(chart.getDefinitionForExcel(), undefined);
assert.strictEqual(model.getters.getChartRuntime(chartId).chartJsConfig.type, "pie");
});
QUnit.test("A data source is added after a chart creation", async (assert) => {
const { model } = await createSpreadsheetWithChart();
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
assert.ok(model.getters.getChartDataSource(chartId));
});
QUnit.test("Odoo bar chart runtime loads the data", async (assert) => {
const { model } = await createSpreadsheetWithChart({
type: "odoo_bar",
mockRPC: async function (route, args) {
if (args.method === "web_read_group") {
assert.step("web_read_group");
}
},
});
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
assert.verifySteps([], "it should not be loaded eagerly");
assert.deepEqual(model.getters.getChartRuntime(chartId).chartJsConfig.data, {
datasets: [],
labels: [],
});
await nextTick();
assert.deepEqual(model.getters.getChartRuntime(chartId).chartJsConfig.data, {
datasets: [
{
backgroundColor: "rgb(31,119,180)",
borderColor: "rgb(31,119,180)",
data: [1, 3],
label: "Count",
},
],
labels: ["false", "true"],
});
assert.verifySteps(["web_read_group"], "it should have loaded the data");
});
QUnit.test("Odoo pie chart runtime loads the data", async (assert) => {
const { model } = await createSpreadsheetWithChart({
type: "odoo_pie",
mockRPC: async function (route, args) {
if (args.method === "web_read_group") {
assert.step("web_read_group");
}
},
});
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
assert.verifySteps([], "it should not be loaded eagerly");
assert.deepEqual(model.getters.getChartRuntime(chartId).chartJsConfig.data, {
datasets: [],
labels: [],
});
await nextTick();
assert.deepEqual(model.getters.getChartRuntime(chartId).chartJsConfig.data, {
datasets: [
{
backgroundColor: ["rgb(31,119,180)", "rgb(255,127,14)", "rgb(174,199,232)"],
borderColor: "#FFFFFF",
data: [1, 3],
label: "",
},
],
labels: ["false", "true"],
});
assert.verifySteps(["web_read_group"], "it should have loaded the data");
});
QUnit.test("Odoo line chart runtime loads the data", async (assert) => {
const { model } = await createSpreadsheetWithChart({
type: "odoo_line",
mockRPC: async function (route, args) {
if (args.method === "web_read_group") {
assert.step("web_read_group");
}
},
});
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
assert.verifySteps([], "it should not be loaded eagerly");
assert.deepEqual(model.getters.getChartRuntime(chartId).chartJsConfig.data, {
datasets: [],
labels: [],
});
await nextTick();
assert.deepEqual(model.getters.getChartRuntime(chartId).chartJsConfig.data, {
datasets: [
{
backgroundColor: "#1F77B466",
borderColor: "rgb(31,119,180)",
data: [1, 3],
label: "Count",
lineTension: 0,
fill: "origin",
pointBackgroundColor: "rgb(31,119,180)",
},
],
labels: ["false", "true"],
});
assert.verifySteps(["web_read_group"], "it should have loaded the data");
});
QUnit.test("Data reloaded strictly upon domain update", async (assert) => {
const { model } = await createSpreadsheetWithChart({
type: "odoo_line",
mockRPC: async function (route, args) {
if (args.method === "web_read_group") {
assert.step("web_read_group");
}
},
});
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
const definition = model.getters.getChartDefinition(chartId);
// force runtime computation
model.getters.getChartRuntime(chartId);
await nextTick();
assert.verifySteps(["web_read_group"], "it should have loaded the data");
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
searchParams: { ...definition.searchParams, domain: [["1", "=", "1"]] },
},
id: chartId,
sheetId,
});
// force runtime computation
model.getters.getChartRuntime(chartId);
await nextTick();
assert.verifySteps(["web_read_group"], "it should have loaded the data with a new domain");
const newDefinition = model.getters.getChartDefinition(chartId);
model.dispatch("UPDATE_CHART", {
definition: {
...newDefinition,
type: "odoo_bar",
},
id: chartId,
sheetId,
});
// force runtime computation
model.getters.getChartRuntime(chartId);
await nextTick();
assert.verifySteps(
[],
"it should have not have loaded the data since the domain was unchanged"
);
});
QUnit.test("Can import/export an Odoo chart", async (assert) => {
const model = await createModelWithDataSource();
insertChartInSpreadsheet(model, "odoo_line");
const data = model.exportData();
const figures = data.sheets[0].figures;
assert.strictEqual(figures.length, 1);
const figure = figures[0];
assert.strictEqual(figure.tag, "chart");
assert.strictEqual(figure.data.type, "odoo_line");
const m1 = await createModelWithDataSource({ spreadsheetData: data });
const sheetId = m1.getters.getActiveSheetId();
assert.strictEqual(m1.getters.getChartIds(sheetId).length, 1);
const chartId = m1.getters.getChartIds(sheetId)[0];
assert.ok(m1.getters.getChartDataSource(chartId));
assert.strictEqual(m1.getters.getChartRuntime(chartId).chartJsConfig.type, "line");
});
QUnit.test("can import (export) contextual domain", async function (assert) {
const chartId = "1";
const uid = session.user_context.uid;
const spreadsheetData = {
sheets: [
{
figures: [
{
id: chartId,
x: 10,
y: 10,
width: 536,
height: 335,
tag: "chart",
data: {
type: "odoo_line",
title: "Partners",
legendPosition: "top",
searchParams: {
domain: '[("foo", "=", uid)]',
groupBy: [],
orderBy: [],
},
metaData: {
groupBy: ["foo"],
measure: "__count",
resModel: "partner",
},
},
},
],
},
],
};
const model = await createModelWithDataSource({
spreadsheetData,
mockRPC: function (route, args) {
if (args.method === "web_read_group") {
assert.deepEqual(args.kwargs.domain, [["foo", "=", uid]]);
assert.step("web_read_group");
}
},
});
model.getters.getChartRuntime(chartId).chartJsConfig.data; // force loading the chart data
await nextTick();
assert.strictEqual(
model.exportData().sheets[0].figures[0].data.searchParams.domain,
'[("foo", "=", uid)]',
"the domain is exported with the dynamic parts"
);
assert.verifySteps(["web_read_group"]);
});
QUnit.test("Can undo/redo an Odoo chart creation", async (assert) => {
const model = await createModelWithDataSource();
insertChartInSpreadsheet(model, "odoo_line");
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
assert.ok(model.getters.getChartDataSource(chartId));
model.dispatch("REQUEST_UNDO");
assert.strictEqual(model.getters.getChartIds(sheetId).length, 0);
model.dispatch("REQUEST_REDO");
assert.ok(model.getters.getChartDataSource(chartId));
assert.strictEqual(model.getters.getChartIds(sheetId).length, 1);
});
QUnit.test("charts with no legend", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" });
insertChartInSpreadsheet(model, "odoo_bar");
insertChartInSpreadsheet(model, "odoo_line");
const sheetId = model.getters.getActiveSheetId();
const [pieChartId, barChartId, lineChartId] = model.getters.getChartIds(sheetId);
const pie = model.getters.getChartDefinition(pieChartId);
const bar = model.getters.getChartDefinition(barChartId);
const line = model.getters.getChartDefinition(lineChartId);
assert.strictEqual(
model.getters.getChartRuntime(pieChartId).chartJsConfig.options.plugins.legend.display,
true
);
assert.strictEqual(
model.getters.getChartRuntime(barChartId).chartJsConfig.options.plugins.legend.display,
true
);
assert.strictEqual(
model.getters.getChartRuntime(lineChartId).chartJsConfig.options.plugins.legend.display,
true
);
model.dispatch("UPDATE_CHART", {
definition: {
...pie,
legendPosition: "none",
},
id: pieChartId,
sheetId,
});
model.dispatch("UPDATE_CHART", {
definition: {
...bar,
legendPosition: "none",
},
id: barChartId,
sheetId,
});
model.dispatch("UPDATE_CHART", {
definition: {
...line,
legendPosition: "none",
},
id: lineChartId,
sheetId,
});
assert.strictEqual(
model.getters.getChartRuntime(pieChartId).chartJsConfig.options.plugins.legend.display,
false
);
assert.strictEqual(
model.getters.getChartRuntime(barChartId).chartJsConfig.options.plugins.legend.display,
false
);
assert.strictEqual(
model.getters.getChartRuntime(lineChartId).chartJsConfig.options.plugins.legend.display,
false
);
});
QUnit.test("Bar chart with stacked attribute is supported", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_bar" });
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
const definition = model.getters.getChartDefinition(chartId);
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
stacked: true,
},
id: chartId,
sheetId,
});
assert.ok(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.x.stacked);
assert.ok(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.y.stacked);
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
stacked: false,
},
id: chartId,
sheetId,
});
assert.notOk(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.x.stacked);
assert.notOk(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.y.stacked);
});
QUnit.test("Can copy/paste Odoo chart", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" });
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
model.dispatch("SELECT_FIGURE", { id: chartId });
model.dispatch("COPY");
model.dispatch("PASTE", { target: [toZone("A1")] });
const chartIds = model.getters.getChartIds(sheetId);
assert.strictEqual(chartIds.length, 2);
assert.ok(model.getters.getChart(chartIds[1]) instanceof OdooChart);
assert.strictEqual(
JSON.stringify(model.getters.getChartRuntime(chartIds[1])),
JSON.stringify(model.getters.getChartRuntime(chartId))
);
assert.notEqual(
model.getters.getChart(chartId).dataSource,
model.getters.getChart(chartIds[1]).dataSource,
"The datasource is also duplicated"
);
});
QUnit.test("Can cut/paste Odoo chart", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" });
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
const chartRuntime = model.getters.getChartRuntime(chartId);
model.dispatch("SELECT_FIGURE", { id: chartId });
model.dispatch("CUT");
model.dispatch("PASTE", { target: [toZone("A1")] });
const chartIds = model.getters.getChartIds(sheetId);
assert.strictEqual(chartIds.length, 1);
assert.notEqual(chartIds[0], chartId);
assert.ok(model.getters.getChart(chartIds[0]) instanceof OdooChart);
assert.strictEqual(
JSON.stringify(model.getters.getChartRuntime(chartIds[0])),
JSON.stringify(chartRuntime)
);
});
QUnit.test("Duplicating a sheet correctly duplicates Odoo chart", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_bar" });
const sheetId = model.getters.getActiveSheetId();
const secondSheetId = "secondSheetId";
const chartId = model.getters.getChartIds(sheetId)[0];
model.dispatch("DUPLICATE_SHEET", { sheetId, sheetIdTo: secondSheetId });
const chartIds = model.getters.getChartIds(secondSheetId);
assert.strictEqual(chartIds.length, 1);
assert.ok(model.getters.getChart(chartIds[0]) instanceof OdooChart);
assert.strictEqual(
JSON.stringify(model.getters.getChartRuntime(chartIds[0])),
JSON.stringify(model.getters.getChartRuntime(chartId))
);
assert.notEqual(
model.getters.getChart(chartId).dataSource,
model.getters.getChart(chartIds[0]).dataSource,
"The datasource is also duplicated"
);
});
QUnit.test("Line chart with stacked attribute is supported", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_line" });
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
const definition = model.getters.getChartDefinition(chartId);
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
stacked: true,
},
id: chartId,
sheetId,
});
assert.notOk(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.x.stacked);
assert.ok(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.y.stacked);
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
stacked: false,
},
id: chartId,
sheetId,
});
assert.notOk(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.x.stacked);
assert.notOk(model.getters.getChartRuntime(chartId).chartJsConfig.options.scales.y.stacked);
});
QUnit.test(
"Load odoo chart spreadsheet with models that cannot be accessed",
async function (assert) {
let hasAccessRights = true;
const { model } = await createSpreadsheetWithChart({
mockRPC: async function (route, args) {
if (
args.model === "partner" &&
args.method === "web_read_group" &&
!hasAccessRights
) {
throw makeServerError({ description: "ya done!" });
}
},
});
const chartId = model.getters.getFigures(model.getters.getActiveSheetId())[0].id;
const chartDataSource = model.getters.getChartDataSource(chartId);
await waitForDataSourcesLoaded(model);
const data = chartDataSource.getData();
assert.equal(data.datasets.length, 1);
assert.equal(data.labels.length, 2);
hasAccessRights = false;
chartDataSource.load({ reload: true });
await waitForDataSourcesLoaded(model);
assert.deepEqual(chartDataSource.getData(), { datasets: [], labels: [] });
}
);
QUnit.test("Line chart to support cumulative data", async (assert) => {
const { model } = await createSpreadsheetWithChart({ type: "odoo_line" });
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
const definition = model.getters.getChartDefinition(chartId);
await waitForDataSourcesLoaded(model);
assert.deepEqual(
model.getters.getChartRuntime(chartId).chartJsConfig.data.datasets[0].data,
[1, 3]
);
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
cumulative: true,
},
id: chartId,
sheetId,
});
assert.deepEqual(
model.getters.getChartRuntime(chartId).chartJsConfig.data.datasets[0].data,
[1, 4]
);
model.dispatch("UPDATE_CHART", {
definition: {
...definition,
cumulative: false,
},
id: chartId,
sheetId,
});
assert.deepEqual(
model.getters.getChartRuntime(chartId).chartJsConfig.data.datasets[0].data,
[1, 3]
);
});
QUnit.test("cumulative line chart with past data before domain period", async (assert) => {
const serverData = getBasicServerData();
serverData.models.partner.records = [
{ date: "2020-01-01", probability: 10 },
{ date: "2021-01-01", probability: 2 },
{ date: "2022-01-01", probability: 3 },
{ date: "2022-03-01", probability: 4 },
{ date: "2022-06-01", probability: 5 },
];
const { model } = await createSpreadsheetWithChart({
type: "odoo_line",
serverData,
definition: {
type: "odoo_line",
metaData: {
groupBy: ["date"],
measure: "probability",
order: null,
resModel: "partner",
},
searchParams: {
comparison: null,
context: {},
domain: [
["date", ">=", "2022-01-01"],
["date", "<=", "2022-12-31"],
],
groupBy: [],
orderBy: [],
},
cumulative: true,
title: "Partners",
dataSourceId: "42",
id: "42",
},
});
const sheetId = model.getters.getActiveSheetId();
const chartId = model.getters.getChartIds(sheetId)[0];
await waitForDataSourcesLoaded(model);
assert.deepEqual(
model.getters.getChartRuntime(chartId).chartJsConfig.data.datasets[0].data,
[15, 19, 24]
);
});
QUnit.test("Can insert odoo chart from a different model", async (assert) => {
const model = await createModelWithDataSource();
insertListInSpreadsheet(model, { model: "product", columns: ["name"] });
await addGlobalFilter(model, THIS_YEAR_GLOBAL_FILTER);
const sheetId = model.getters.getActiveSheetId();
assert.strictEqual(model.getters.getChartIds(sheetId).length, 0);
insertChartInSpreadsheet(model);
assert.strictEqual(model.getters.getChartIds(sheetId).length, 1);
});
});