@neo4j-ndl/react-charts
Version:
React implementation of charts from Neo4j Design System
374 lines • 17.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { jsx as _jsx } from "react/jsx-runtime";
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { tokens } from '@neo4j-ndl/base';
import { Chart } from '@neo4j-ndl/react-charts';
import { expect } from '@playwright/experimental-ct-react';
import { ChartWrapperWithTooltipFormatter } from './ChartWrapperWithTooltipFormatter';
/**
* Convert hex color to rgb format
*
* @param hex color in hex code format
* @returns color in rgb format rgb(_, _, _)
*/
const convertHexToRGB = (hex) => {
const normalizedHex = hex.replace(/^#/, '');
const red = parseInt(normalizedHex.substring(0, 2), 16);
const green = parseInt(normalizedHex.substring(2, 4), 16);
const blue = parseInt(normalizedHex.substring(4, 6), 16);
return `rgb(${red}, ${green}, ${blue})`;
};
export const chartColorsAfterHighlighting = {
1: 'rgb(93,207,216)',
10: 'rgb(210,126,49)',
11: 'rgb(78,151,121)',
12: 'rgb(190,255,117)',
2: 'rgb(84,80,223)',
3: 'rgb(242,152,62)',
4: 'rgb(221,75,155)',
5: 'rgb(156,154,255)',
6: 'rgb(132,244,136)',
7: 'rgb(69,140,249)',
8: 'rgb(113,69,188)',
9: 'rgb(240,210,70)',
};
export const pieChartProps = (seriesName, value, rgbSeriesColor) => ({
dataset: [
{
source: [
['product', '2012'],
[seriesName, value],
],
},
],
legend: {
show: true,
},
series: [
{
center: ['50%', '50%'],
encode: {
itemName: 'product',
value: '2012',
},
itemStyle: {
color: rgbSeriesColor,
},
radius: '90%',
type: 'pie',
},
],
});
export const barChartProps = (seriesName, value, rgbSeriesColor) => ({
dataset: {
dimensions: ['Country', 'Sales'],
source: [['Finland', value]],
},
legend: {
show: true,
},
series: [
{
encode: {
x: 'Country',
y: 'Sales',
},
itemStyle: {
color: rgbSeriesColor,
},
name: seriesName,
type: 'bar',
},
],
xAxis: {
type: 'category',
},
yAxis: {
type: 'value',
},
});
export const multiSeriesChartProps = {
dataset: {
dimensions: ['day', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth'],
source: [
['Monday', 20, 40, 60, 80, 100, 120],
['Tuesday', 30, 50, 70, 90, 110, 130],
['Wednesday', 25, 45, 65, 85, 105, 125],
['Thursday', 35, 55, 75, 95, 115, 135],
['Friday', 40, 60, 80, 100, 120, 140],
],
},
legend: {
show: true,
},
option: {},
series: [
{
encode: { x: 'day', y: 'first' },
name: 'First Series',
type: 'line',
},
{
encode: { x: 'day', y: 'second' },
name: 'Second Series',
type: 'line',
},
{
encode: { x: 'day', y: 'third' },
name: 'Third Series',
type: 'line',
},
{
encode: { x: 'day', y: 'fourth' },
name: 'Fourth Series',
type: 'line',
},
{
encode: { x: 'day', y: 'fifth' },
name: 'Fifth Series',
type: 'line',
},
{
encode: { x: 'day', y: 'sixth' },
name: 'Sixth Series',
type: 'line',
},
],
xAxis: {
type: 'category',
},
yAxis: {
type: 'value',
},
};
export const multiSeriesChart = ({ chartProps, width = '100%', height = '400px', }) => (_jsx("div", { style: { height, width }, className: "ndl-chart", children: _jsx(Chart, Object.assign({}, multiSeriesChartProps, chartProps)) }));
export const pieChartWithTooltipFormatter = ({ seriesName, value, rgbSeriesColor, chartProps, width = '100%', height = '400px', }) => (_jsx("div", { style: { height, width }, children: _jsx(ChartWrapperWithTooltipFormatter, Object.assign({}, pieChartProps(seriesName, value, rgbSeriesColor), chartProps)) }));
export const barChartWithTooltipFormatter = ({ seriesName, value, rgbSeriesColor, chartProps, width = '100%', height = '400px', }) => (_jsx("div", { style: { height, width }, children: _jsx(ChartWrapperWithTooltipFormatter, Object.assign({}, barChartProps(seriesName, value, rgbSeriesColor), chartProps)) }));
export const barChart = ({ seriesName, value, rgbSeriesColor, chartProps, width = '100%', height = '400px', }) => (_jsx("div", { style: { height, width }, children: _jsx(Chart, Object.assign({}, barChartProps(seriesName, value, rgbSeriesColor), chartProps)) }));
export const expectChartElementVisible = (page) => __awaiter(void 0, void 0, void 0, function* () {
yield expect(page.locator('svg').first()).toBeVisible();
yield expect(page.locator('.ndl-chart-legend')).toBeVisible();
});
export const startLegendMeasuringObserver = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, options = {}) {
yield page.evaluate(({ chartLayoutSelector }) => {
var _a;
const windowWithLegendObserver = window;
const legendElement = document.querySelector('.ndl-chart-legend');
const chartLayout = chartLayoutSelector
? document.querySelector(chartLayoutSelector)
: undefined;
windowWithLegendObserver.__chartHeightDuringLegendMeasurement = undefined;
windowWithLegendObserver.__legendMeasuringClassWasAdded = false;
(_a = windowWithLegendObserver.__legendMeasuringObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
if (!legendElement) {
return;
}
const observer = new MutationObserver(() => {
if (!legendElement.classList.contains('ndl-chart-legend-calculating')) {
return;
}
windowWithLegendObserver.__legendMeasuringClassWasAdded = true;
if (chartLayout &&
windowWithLegendObserver.__chartHeightDuringLegendMeasurement ===
undefined) {
windowWithLegendObserver.__chartHeightDuringLegendMeasurement =
chartLayout.getBoundingClientRect().height;
}
});
observer.observe(legendElement, {
attributeFilter: ['class'],
attributes: true,
});
windowWithLegendObserver.__legendMeasuringObserver = observer;
}, options);
});
export const getLegendMeasuringObserverResult = (page) => __awaiter(void 0, void 0, void 0, function* () {
return page.evaluate(() => {
var _a, _b;
const windowWithLegendObserver = window;
(_a = windowWithLegendObserver.__legendMeasuringObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
return {
chartHeightDuringMeasurement: windowWithLegendObserver.__chartHeightDuringLegendMeasurement,
didAddMeasuringClass: (_b = windowWithLegendObserver.__legendMeasuringClassWasAdded) !== null && _b !== void 0 ? _b : false,
};
});
});
const SELECTORS = {
CHECKMARK: '.ndl-chart-legend > .ndl-chart-legend-item .ndl-chart-legend-item-square-checkmark',
DESELECTED: '.ndl-chart-legend > .ndl-chart-legend-item-deselected',
seriesCheckmark: (name) => `.ndl-chart-legend > [data-labelname="${name}"] .ndl-chart-legend-item-square-checkmark`,
seriesDeselected: (name) => `.ndl-chart-legend > [data-labelname="${name}"].ndl-chart-legend-item-deselected`,
};
const getGraphPaletteColorFromSeriesIndex = (index) => {
const hexColor = tokens.categorical[index];
if (!hexColor) {
throw new Error(`Categorical palette index ${index} not found`);
}
return hexColor;
};
/**
* Checks so a single series is not blurred.
*/
export const expectSingleSeriesNotToBeBlurred = (page, seriesName) => __awaiter(void 0, void 0, void 0, function* () {
const allChartSeries = multiSeriesChartProps.series;
const seriesIndex = allChartSeries.findIndex((seriesItem) => seriesItem.name === seriesName) +
1;
const rgbSeriesColor = chartColorsAfterHighlighting[seriesIndex];
const hexSeriesColor = getGraphPaletteColorFromSeriesIndex(seriesIndex.toString());
// More flexible color matching - try both with and without spaces
const rgbWithSpaces = rgbSeriesColor.replace(/,/g, ', ');
const hexRgb = convertHexToRGB(hexSeriesColor);
const rgbWithoutSpaces = hexRgb.replace(/, /g, ',');
const pathLocator = page
.locator(`path[stroke="${rgbSeriesColor}"]`)
.or(page.locator(`path[stroke="${rgbWithSpaces}"]`))
.or(page.locator(`path[stroke="${hexSeriesColor}"]`))
.or(page.locator(`path[stroke="${convertHexToRGB(hexSeriesColor)}"]`))
.or(page.locator(`path[stroke="${rgbWithoutSpaces}"]`));
// Wait for the element to be attached to the DOM before checking its attributes
yield expect(pathLocator).toBeAttached({ timeout: 10000 });
yield expect(pathLocator).not.toHaveAttribute('stroke-opacity');
});
/**
* Checks so that the seriesNamesToBeHighlighted are visible in the chart and that other series are blurred.
*/
export const expectLegendSeriesToBeHighlightedInChart = (page, seriesNamesToBeHighlighted) => __awaiter(void 0, void 0, void 0, function* () {
const allChartSeries = multiSeriesChartProps.series;
for (const name of seriesNamesToBeHighlighted) {
const highlightedSeriesIndex = allChartSeries.findIndex((seriesItem) => seriesItem.name === name) + 1;
const rgbSeriesColor = chartColorsAfterHighlighting[highlightedSeriesIndex];
const hexSeriesColor = getGraphPaletteColorFromSeriesIndex(highlightedSeriesIndex.toString());
const rgbWithSpaces = rgbSeriesColor.replace(/,/g, ', ');
const hexRgb = convertHexToRGB(hexSeriesColor);
const rgbWithoutSpaces = hexRgb.replace(/, /g, ',');
const pathLocator = page
.locator(`path[stroke="${rgbSeriesColor}"]`)
.or(page.locator(`path[stroke="${rgbWithSpaces}"]`))
.or(page.locator(`path[stroke="${hexSeriesColor}"]`))
.or(page.locator(`path[stroke="${convertHexToRGB(hexSeriesColor)}"]`))
.or(page.locator(`path[stroke="${rgbWithoutSpaces}"]`));
yield expect(pathLocator).not.toHaveAttribute('stroke-opacity');
}
const seriesToBeBlurred = allChartSeries.filter((series) => !seriesNamesToBeHighlighted.includes(series.name));
for (const series of seriesToBeBlurred) {
const blurredSeriesIndex = allChartSeries.findIndex((seriesItem) => seriesItem.name === series.name) + 1;
const rgbSeriesColor = chartColorsAfterHighlighting[blurredSeriesIndex];
const hexSeriesColor = getGraphPaletteColorFromSeriesIndex(blurredSeriesIndex.toString());
const rgbWithSpaces = rgbSeriesColor.replace(/,/g, ', ');
const hexRgb = convertHexToRGB(hexSeriesColor);
const rgbWithoutSpaces = hexRgb.replace(/, /g, ',');
const pathLocator = page
.locator(`path[stroke="${rgbSeriesColor}"]`)
.or(page.locator(`path[stroke="${rgbWithSpaces}"]`))
.or(page.locator(`path[stroke="${hexSeriesColor}"]`))
.or(page.locator(`path[stroke="${convertHexToRGB(hexSeriesColor)}"]`))
.or(page.locator(`path[stroke="${rgbWithoutSpaces}"]`));
yield expect(pathLocator).toHaveAttribute('stroke-opacity');
}
});
/**
* Identifies if the series are visible / not visible in the chart by checking
* component-owned legend state. ECharts rewrites generated SVG paths during
* legend updates, which makes exact path color selectors flaky in Firefox.
*/
export const expectLegendSeriesToBeVisibleInChart = (page, seriesNamesToBeVisible) => __awaiter(void 0, void 0, void 0, function* () {
yield expect(page.locator('svg path').first()).toBeAttached();
for (const name of seriesNamesToBeVisible) {
const visibleLegendItem = page.locator(`.ndl-chart-legend > [data-labelname="${name}"]`);
if ((yield visibleLegendItem.count()) > 0) {
yield expect(visibleLegendItem).not.toHaveClass(/ndl-chart-legend-item-deselected/);
}
}
const hiddenSeries = multiSeriesChartProps.series.filter((series) => !seriesNamesToBeVisible.includes(series.name));
for (const hiddenSeriesItem of hiddenSeries) {
const hiddenLegendItem = page.locator(SELECTORS.seriesDeselected(hiddenSeriesItem.name));
if ((yield hiddenLegendItem.count()) > 0) {
yield expect(hiddenLegendItem).toBeVisible();
}
}
});
// Check so legends are selected -> has checkmark and no strike-through classes
export const expectLegendsSelected = (page, seriesNames) => __awaiter(void 0, void 0, void 0, function* () {
for (const name of seriesNames) {
yield expect(page.locator(SELECTORS.seriesCheckmark(name))).toBeVisible();
yield expect(page.locator(SELECTORS.seriesDeselected(name))).not.toBeVisible();
}
});
// Check so legends are deselected -> has strike-through and no checkmark classes
export const expectLegendsDeselected = (page, seriesNames) => __awaiter(void 0, void 0, void 0, function* () {
for (const name of seriesNames) {
yield expect(page.locator(SELECTORS.seriesDeselected(name))).toBeVisible();
yield expect(page.locator(SELECTORS.seriesCheckmark(name))).not.toBeVisible();
}
});
// Check so all legends are neutral -> no checkmark and no strike-through classes
export const expectAllLegendsNeutral = (page) => __awaiter(void 0, void 0, void 0, function* () {
yield expect(page.locator(SELECTORS.CHECKMARK)).not.toBeVisible();
yield expect(page.locator(SELECTORS.DESELECTED)).not.toBeVisible();
});
/**
* Clicks the series legend item and hover the chart to make sure no blur effect exists on other series in the chart.
* This is needed to make sure the series has the correct color when looking for them in the chart in later steps.
*/
export const clickSeries = (page, seriesName) => __awaiter(void 0, void 0, void 0, function* () {
yield page
.locator(`.ndl-chart-legend > [data-labelname="${seriesName}"]`)
.click();
yield page.hover('.ndl-chart');
});
export const expectFormattedTooltipToBeVisible = (page, seriesName, value, rgbSeriesColor) => __awaiter(void 0, void 0, void 0, function* () {
const rgbWithSpaces = rgbSeriesColor.replace(/,/g, ', ');
yield expect(page
.locator(`path[fill="${rgbSeriesColor}"]`)
.or(page.locator(`fill[stroke="${rgbWithSpaces}"]`))).toBeVisible();
yield page
.locator(`path[fill="${rgbSeriesColor}"]`)
.or(page.locator(`fill[stroke="${rgbWithSpaces}"]`))
.hover();
const tooltip = page.getByText(`${seriesName}formatted ${value}`);
yield expect(tooltip).toBeVisible();
});
export const expectDefaultFormattedTooltipToBeVisible = (page, seriesName, value, rgbSeriesColor) => __awaiter(void 0, void 0, void 0, function* () {
const rgbWithSpaces = rgbSeriesColor.replace(/,/g, ', ');
const chartElement = page
.locator(`path[fill="${rgbSeriesColor}"]`)
.or(page.locator(`path[fill="${rgbWithSpaces}"]`));
yield expect(chartElement).toBeVisible();
yield chartElement.hover();
const tooltip = page.locator('.ndl-charts-chart-tooltip');
yield expect(tooltip).toContainText(seriesName);
yield expect(tooltip).toContainText(new Intl.NumberFormat('en-US').format(value));
});
export const waitForAnimationEnd = (page, selector) => {
return page
.locator(selector)
.evaluate((element) => Promise.all(element.getAnimations().map((animation) => animation.finished)));
};
//# sourceMappingURL=chart-test-utils.js.map