import { before, expect } from "@odoo/hoot"; import { queryAllTexts, queryOne } from "@odoo/hoot-dom"; import { contains, findComponent, preloadBundle } from "@web/../tests/web_test_helpers"; import { ensureArray } from "@web/core/utils/arrays"; import { patch } from "@web/core/utils/patch"; import { GraphController } from "@web/views/graph/graph_controller"; import { GraphRenderer } from "@web/views/graph/graph_renderer"; /** * @typedef {"bar" | "line" | "pie"} GraphMode * * @typedef {import("@web/views/view").View} GraphView */ /** * @param {GraphView} view * @param {string | Iterable<string>} keys * @param {Record<string, any> | Iterable<Record<string, any>>} expectedDatasets */ export const checkDatasets = (view, keys, expectedDatasets) => { keys = ensureArray(keys); const datasets = getChart(view).data.datasets; const actualValues = []; for (const dataset of datasets) { const partialDataset = {}; for (const key of keys) { partialDataset[key] = dataset[key]; } actualValues.push(partialDataset); } expect(actualValues).toEqual(ensureArray(expectedDatasets)); }; /** * @param {GraphView} view * @param {GraphMode} mode */ export const checkModeIs = (view, mode) => { expect(getGraphModelMetaData(view).mode).toBe(mode); expect(getChart(view).config.type).toBe(mode); expect(getModeButton(mode)).toHaveClass("active"); }; /** * @param {GraphView} view * @param {{ lines: { label: string, value: string }[], title?: string }} expectedTooltip * @param {number} index * @param {number} datasetIndex */ export const checkTooltip = (view, { title, lines }, index, datasetIndex = null) => { // 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 chart = getChart(view); const { datasets } = chart.data; const dataPoints = []; for (let i = 0; i < datasets.length; i++) { const dataset = datasets[i]; const raw = dataset.data[index]; if (raw !== undefined && (datasetIndex === null || datasetIndex === i)) { dataPoints.push({ datasetIndex: i, dataIndex: index, raw, }); } } chart.config.options.plugins.tooltip.external({ tooltip: { opacity: 1, x: 1, y: 1, dataPoints }, }); const lineLabels = []; const lineValues = []; for (const line of lines) { lineLabels.push(line.label); lineValues.push(String(line.value)); } expect(`.o_graph_custom_tooltip`).toHaveCount(1); expect(`table thead tr th.o_measure`).toHaveText(title || "Count"); expect(queryAllTexts(`table tbody tr td small.o_label`)).toEqual(lineLabels); expect(queryAllTexts(`table tbody tr td.o_value`)).toEqual(lineValues); }; /** * @param {"asc" | "desc"} direction */ export const clickSort = (direction) => contains(`.fa-sort-amount-${direction}`).click(); /** * @param {GraphView} view */ export const getChart = (view) => getGraphRenderer(view).chart; /** * @param {GraphView} view */ export const getGraphModelMetaData = (view) => getGraphModel(view).metaData; /** * @param {GraphMode} mode */ export const getModeButton = (mode) => queryOne`.o_graph_button[data-mode=${mode}]`; /** * @param {GraphView} view */ export const getScaleY = (view) => getChart(view).config.options.scales.y; /** * @param {GraphView} view */ export const getYAxisLabel = (view) => getChart(view).config.options.scales.y.title.text; /** * @param {GraphView} view * @param {string | Iterable<string>} expectedLabels */ export function checkLabels(view, expectedLabels) { expect(getChart(view).data.labels.map(String)).toEqual(ensureArray(expectedLabels)); } /** * @param {GraphView} view * @param {string | Iterable<string>} expectedLabels */ export function checkYTicks(view, expectedLabels) { const labels = getChart(view).scales.y.ticks.map((l) => l.label); expect(labels).toEqual(expectedLabels); } /** * @param {GraphView} view * @param {string | Iterable<string>} expectedLabels */ export function checkLegend(view, expectedLabels) { const chart = getChart(view); const labels = chart.config.options.plugins.legend.labels .generateLabels(chart) .map((o) => o.text); const expectedLabelsList = ensureArray(expectedLabels); expect(labels).toEqual(expectedLabelsList, { message: `Legend should be matching: ${expectedLabelsList .map((label) => `"${label}"`) .join(", ")}`, }); } /** * @param {GraphView} view */ export async function clickOnDataset(view) { const chart = getChart(view); const point = chart.getDatasetMeta(0).data[0].getCenterPoint(); return contains(chart.canvas).click({ position: point, relative: true }); } /** * @param {GraphView} view */ export function getGraphController(view) { return findComponent(view, (c) => c instanceof GraphController); } /** * @param {GraphView} view */ export function getGraphModel(view) { return getGraphController(view).model; } /** * @param {GraphView} view * @returns {GraphRenderer} */ export function getGraphRenderer(view) { return findComponent(view, (c) => c instanceof GraphRenderer); } /** * @param {GraphMode} mode */ export function selectMode(mode) { return contains(getModeButton(mode)).click(); } /** * @param {GraphView} view * @param {string} text */ export async function clickOnLegend(view, text) { const chart = getChart(view); const index = chart.legend.legendItems.findIndex((e) => e.text === text); const { left, top, width, height } = chart.legend.legendHitBoxes[index]; const point = { x: left + width / 2, y: top + height / 2, }; return contains(chart.canvas).click({ position: point, relative: true }); } /** * Helper to call at the start of a test suite using the Chart.js lib. * * It will: * - pre-load the Chart.js lib before tests are run; * - disable all animations in the lib. */ export function setupChartJsForTests() { preloadBundle("web.chartjs_lib"); before(() => patch(Chart.defaults, { animation: false })); }