From d35052812771f20722219fb5edfc464892c526de Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Wed, 5 Aug 2026 13:52:12 +0200 Subject: [PATCH 1/4] test: migrate Playwright component tests to the story-gallery model --- package.json | 15 +- ...estComponents.tsx => BarChart.gallery.tsx} | 0 .../BarChart/test/BarChart.spec.tsx | 44 +-- ...Components.tsx => BulletChart.gallery.tsx} | 0 .../BulletChart/test/BulletChart.spec.tsx | 55 +--- ...Components.tsx => ColumnChart.gallery.tsx} | 0 .../ColumnChart/test/ColumnChart.spec.tsx | 63 +--- ...s.tsx => ColumnChartWithTrend.gallery.tsx} | 0 .../test/ColumnChartWithTrend.spec.tsx | 45 +-- ...mponents.tsx => ComposedChart.gallery.tsx} | 0 .../ComposedChart/test/ComposedChart.spec.tsx | 63 +--- ...tComponents.tsx => DonutChart.gallery.tsx} | 0 .../DonutChart/test/DonutChart.spec.tsx | 46 +-- ...stComponents.tsx => LineChart.gallery.tsx} | 0 .../LineChart/test/LineChart.spec.tsx | 52 +--- ...estComponents.tsx => PieChart.gallery.tsx} | 0 .../PieChart/test/PieChart.spec.tsx | 49 +--- ...tComponents.tsx => RadarChart.gallery.tsx} | 0 .../RadarChart/test/RadarChart.spec.tsx | 43 +-- ...Components.tsx => RadialChart.gallery.tsx} | 0 .../RadialChart/test/RadialChart.spec.tsx | 15 +- ...omponents.tsx => ScatterChart.gallery.tsx} | 0 .../ScatterChart/test/ScatterChart.spec.tsx | 41 +-- ...mponents.tsx => TimelineChart.gallery.tsx} | 10 + .../TimelineChart/test/TimelineChart.spec.tsx | 66 ++--- ...nts.tsx => HookTestComponents.gallery.tsx} | 0 .../src/hooks/test/useLabelFormatter.spec.tsx | 9 +- .../usePrepareDimensionsAndMeasures.spec.tsx | 13 +- .../hooks/test/useTooltipFormatter.spec.tsx | 9 +- .../charts/src/resources/chartHarnessData.ts | 123 ++++++++ .../src/test-utils/ChartHarness.gallery.tsx | 69 +++++ .../src/test-utils/chartGalleryTests.tsx | 111 +++++++ packages/charts/src/test-utils/shared.tsx | 2 +- .../charts/src/test-utils/sharedTests.tsx | 125 -------- ...omponents.tsx => SelectDialog.gallery.tsx} | 0 .../SelectDialog/test/SelectDialog.spec.tsx | 29 +- ...ponents.tsx => SplitterLayout.gallery.tsx} | 38 ++- .../test/SplitterLayout.spec.tsx | 29 +- playwright-ct.config.ts | 85 ------ playwright.config.ts | 38 +++ playwright/fixtures/gallery-fixtures.ts | 46 +++ playwright/fixtures/main-fixtures.ts | 34 --- playwright/fixtures/ui5-fixtures.ts | 2 +- playwright/{ => gallery}/index.html | 4 +- playwright/gallery/main.tsx | 84 ++++++ playwright/gallery/story-wrapper.tsx | 6 + playwright/gallery/vite-env.d.ts | 1 + playwright/gallery/vite.config.ts | 36 +++ playwright/index.tsx | 12 - ...Components.tsx => UI5Fixtures.gallery.tsx} | 0 playwright/test/internal-fixtures.spec.tsx | 5 +- playwright/test/ui5-fixtures.spec.tsx | 47 ++- tsconfig.playwright.json | 2 +- yarn.lock | 277 ++---------------- 54 files changed, 773 insertions(+), 1070 deletions(-) rename packages/charts/src/components/BarChart/test/{BarChartTestComponents.tsx => BarChart.gallery.tsx} (100%) rename packages/charts/src/components/BulletChart/test/{BulletChartTestComponents.tsx => BulletChart.gallery.tsx} (100%) rename packages/charts/src/components/ColumnChart/test/{ColumnChartTestComponents.tsx => ColumnChart.gallery.tsx} (100%) rename packages/charts/src/components/ColumnChartWithTrend/test/{ColumnChartWithTrendTestComponents.tsx => ColumnChartWithTrend.gallery.tsx} (100%) rename packages/charts/src/components/ComposedChart/test/{ComposedChartTestComponents.tsx => ComposedChart.gallery.tsx} (100%) rename packages/charts/src/components/DonutChart/test/{DonutChartTestComponents.tsx => DonutChart.gallery.tsx} (100%) rename packages/charts/src/components/LineChart/test/{LineChartTestComponents.tsx => LineChart.gallery.tsx} (100%) rename packages/charts/src/components/PieChart/test/{PieChartTestComponents.tsx => PieChart.gallery.tsx} (100%) rename packages/charts/src/components/RadarChart/test/{RadarChartTestComponents.tsx => RadarChart.gallery.tsx} (100%) rename packages/charts/src/components/RadialChart/test/{RadialChartTestComponents.tsx => RadialChart.gallery.tsx} (100%) rename packages/charts/src/components/ScatterChart/test/{ScatterChartTestComponents.tsx => ScatterChart.gallery.tsx} (100%) rename packages/charts/src/components/TimelineChart/test/{TimelineChartTestComponents.tsx => TimelineChart.gallery.tsx} (96%) rename packages/charts/src/hooks/test/{HookTestComponents.tsx => HookTestComponents.gallery.tsx} (100%) create mode 100644 packages/charts/src/resources/chartHarnessData.ts create mode 100644 packages/charts/src/test-utils/ChartHarness.gallery.tsx create mode 100644 packages/charts/src/test-utils/chartGalleryTests.tsx delete mode 100644 packages/charts/src/test-utils/sharedTests.tsx rename packages/main/src/components/SelectDialog/test/{SelectDialogTestComponents.tsx => SelectDialog.gallery.tsx} (100%) rename packages/main/src/components/SplitterLayout/test/{SplitterLayoutTestComponents.tsx => SplitterLayout.gallery.tsx} (84%) delete mode 100644 playwright-ct.config.ts create mode 100644 playwright.config.ts create mode 100644 playwright/fixtures/gallery-fixtures.ts delete mode 100644 playwright/fixtures/main-fixtures.ts rename playwright/{ => gallery}/index.html (76%) create mode 100644 playwright/gallery/main.tsx create mode 100644 playwright/gallery/story-wrapper.tsx create mode 100644 playwright/gallery/vite-env.d.ts create mode 100644 playwright/gallery/vite.config.ts delete mode 100644 playwright/index.tsx rename playwright/test/{UI5FixturesTestComponents.tsx => UI5Fixtures.gallery.tsx} (100%) diff --git a/package.json b/package.json index a00b0a9052e..e1b10dbb97f 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,11 @@ "test:prepare": "rimraf temp && lerna run build", "test:open": "CYPRESS_COVERAGE=false cypress open --component --browser chrome", "test": "yarn test:prepare && cypress run --component --browser chrome --spec packages", - "test:pw": "playwright test -c playwright-ct.config.ts", - "test:pw:open": "playwright test -c playwright-ct.config.ts --ui", - "test:pw:ci": "playwright test -c playwright-ct.config.ts --project chromium", - "clean": "tsc --build --clean && tsc --build tsconfig.build.json --clean && rimraf temp .out test-results playwright-report playwright-ct && lerna run clean", + "test:pw": "playwright test", + "test:pw:open": "playwright test --ui", + "test:pw:ci": "cross-env PW_COVERAGE=true playwright test --project chromium && yarn test:pw:coverage:report", + "test:pw:coverage:report": "nyc report --temp-dir temp/.nyc_output_playwright --report-dir temp/playwright-coverage --reporter lcovonly", + "clean": "tsc --build --clean && tsc --build tsconfig.build.json --clean && rimraf temp .out test-results playwright-report && lerna run clean", "clean:remove-modules": "yarn clean && rimraf node_modules", "prettier:all": "prettier --write --config ./prettier.config.js \"**/*\"", "lint": "eslint .", @@ -68,7 +69,6 @@ "@cypress/code-coverage": "4.0.3", "@eslint/compat": "2.1.0", "@eslint/js": "9.39.5", - "@playwright/experimental-ct-react": "1.62.1", "@playwright/test": "1.62.1", "@semantic-release/github": "12.0.9", "@testing-library/cypress": "10.1.3", @@ -98,7 +98,6 @@ "husky": "9.1.7", "lerna": "10.0.0", "lint-staged": "17.2.0", - "monocart-reporter": "2.12.4", "npm-run-all2": "9.0.3", "pagefind": "1.5.2", "postcss": "8.5.25", @@ -140,6 +139,10 @@ "**/src/enums/*", "**/*.stories.tsx", "**/*.test.{ts,tsx}", + "**/*.spec.{ts,tsx}", + "**/*.gallery.tsx", + "**/test/**", + "packages/charts/src/test-utils/**", "**/*.module.css.ts", "**/node_modules/**", "**/dist/**", diff --git a/packages/charts/src/components/BarChart/test/BarChartTestComponents.tsx b/packages/charts/src/components/BarChart/test/BarChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/BarChart/test/BarChartTestComponents.tsx rename to packages/charts/src/components/BarChart/test/BarChart.gallery.tsx diff --git a/packages/charts/src/components/BarChart/test/BarChart.spec.tsx b/packages/charts/src/components/BarChart/test/BarChart.spec.tsx index f93932c16ea..154d4e85a43 100644 --- a/packages/charts/src/components/BarChart/test/BarChart.spec.tsx +++ b/packages/charts/src/components/BarChart/test/BarChart.spec.tsx @@ -1,38 +1,23 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { complexDataSet } from '../../../resources/DemoProps.js'; import { testLoadingStates, testPassThroughProps, testStackAggregateTotals, testZoomingTool, -} from '../../../test-utils/sharedTests.js'; -import { BarChart } from '../index.js'; -import { - BarChartClickTest, - BarChartDataPointClickTest, - BarChartHighlightColorTest, - BarChartLegendConfigTest, - BarChartSecondYAxisTest, -} from './BarChartTestComponents.js'; - -const dimensions = [{ accessor: 'name', interval: 0 }]; -const measures = [ - { accessor: 'users', label: 'Users' }, - { accessor: 'sessions', label: 'Active Sessions' }, - { accessor: 'volume', label: 'Vol.' }, -]; -const baseProps = { dataset: complexDataSet, dimensions, measures }; +} from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('BarChart', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('ChartHarness/Chart', { chart: 'BarChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-bar')).toHaveCount(3); await expect(page.locator('.recharts-bar-rectangles')).toHaveCount(3); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('BarChart/BarChartClickTest'); await page.getByText('January').click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -49,24 +34,21 @@ test.describe('BarChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('volume'); }); - testLoadingStates(BarChart, baseProps, { dimensions: [], measures: [] }, '.recharts-bar'); + testLoadingStates('BarChart', '.recharts-bar'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('BarChart/BarChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testZoomingTool(BarChart, baseProps); + testZoomingTool('BarChart'); - testPassThroughProps(BarChart, { dimensions: [], measures: [] }); + testPassThroughProps('BarChart'); - testStackAggregateTotals(BarChart, { dataset: complexDataSet.slice(0, 3), dimensions }, [ - { accessor: 'users', stackId: 'A', label: 'Users' }, - { accessor: 'sessions', stackId: 'A', label: 'Active Sessions' }, - ]); + testStackAggregateTotals('BarChart'); test('onDataPointClick', async ({ mount, page }) => { - await mount(); + await mount('BarChart/BarChartDataPointClickTest'); await page.locator('[name="January"]').first().click(); await expect(page.getByTestId('dp-click-count')).toHaveText('1'); @@ -77,7 +59,7 @@ test.describe('BarChart', () => { }); test('highlightColor', async ({ mount, page }) => { - await mount(); + await mount('BarChart/BarChartHighlightColorTest'); // January has users=100 (<=200 → green), February has users=230 (>200 → red) await expect(page.locator('.recharts-bar-rectangle [fill="green"]').first()).toBeAttached(); @@ -85,7 +67,7 @@ test.describe('BarChart', () => { }); test('secondYAxis', async ({ mount, page }) => { - await mount(); + await mount('BarChart/BarChartSecondYAxisTest'); // BarChart is horizontal so the secondary "Y" axis renders as an additional XAxis await expect(page.locator('.recharts-xAxis')).toHaveCount(2); }); diff --git a/packages/charts/src/components/BulletChart/test/BulletChartTestComponents.tsx b/packages/charts/src/components/BulletChart/test/BulletChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/BulletChart/test/BulletChartTestComponents.tsx rename to packages/charts/src/components/BulletChart/test/BulletChart.gallery.tsx diff --git a/packages/charts/src/components/BulletChart/test/BulletChart.spec.tsx b/packages/charts/src/components/BulletChart/test/BulletChart.spec.tsx index 61bf1350da0..96c27fbeff3 100644 --- a/packages/charts/src/components/BulletChart/test/BulletChart.spec.tsx +++ b/packages/charts/src/components/BulletChart/test/BulletChart.spec.tsx @@ -1,42 +1,18 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { complexDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps, testZoomingTool } from '../../../test-utils/sharedTests.js'; -import { BulletChart } from '../index.js'; -import { - BulletChartClickTest, - BulletChartDataPointClickTest, - BulletChartLegendConfigTest, - BulletChartVerticalLayoutTest, -} from './BulletChartTestComponents.js'; - -const dimensions = [{ accessor: 'name', interval: 0 }]; -const measures = [ - { accessor: 'users', label: 'Users', type: 'primary' as const }, - { accessor: 'sessions', label: 'Active Sessions', type: 'comparison' as const }, - { accessor: 'volume', label: 'Vol.', type: 'additional' as const }, -]; -const baseProps = { dataset: complexDataSet, dimensions, measures }; +import { testLoadingStates, testPassThroughProps, testZoomingTool } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('BulletChart', () => { test('Basic', async ({ mount, page }) => { - await mount( - , - ); + await mount('ChartHarness/Chart', { chart: 'BulletChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-bar')).toHaveCount(3); await expect(page.locator('.recharts-bar-rectangles')).toHaveCount(3); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('BulletChart/BulletChartClickTest'); await page.getByText('January').click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -53,28 +29,19 @@ test.describe('BulletChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('volume'); }); - testLoadingStates( - BulletChart, - { - dataset: complexDataSet, - dimensions: [{ accessor: 'name', interval: 0 }], - measures: [{ accessor: 'users', label: 'Users', type: 'primary' as const }], - }, - { dimensions: [], measures: [] }, - '.recharts-bar', - ); + testLoadingStates('BulletChart', '.recharts-bar'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('BulletChart/BulletChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testZoomingTool(BulletChart, baseProps); + testZoomingTool('BulletChart'); - testPassThroughProps(BulletChart, { dimensions: [], measures: [] }); + testPassThroughProps('BulletChart'); test('onDataPointClick', async ({ mount, page }) => { - await mount(); + await mount('BulletChart/BulletChartDataPointClickTest'); // make sure not to click the label, as currently the event is only fired when the actual bar is clicked. const firstBar = page.locator('.recharts-bar-rectangle path').first(); @@ -87,7 +54,7 @@ test.describe('BulletChart', () => { }); test('layout="vertical"', async ({ mount, page }) => { - await mount(); + await mount('BulletChart/BulletChartVerticalLayoutTest'); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); // Vertical layout renders bars along Y axis and uses XAxis for values await expect(page.locator('.recharts-bar')).toHaveCount(3); diff --git a/packages/charts/src/components/ColumnChart/test/ColumnChartTestComponents.tsx b/packages/charts/src/components/ColumnChart/test/ColumnChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/ColumnChart/test/ColumnChartTestComponents.tsx rename to packages/charts/src/components/ColumnChart/test/ColumnChart.gallery.tsx diff --git a/packages/charts/src/components/ColumnChart/test/ColumnChart.spec.tsx b/packages/charts/src/components/ColumnChart/test/ColumnChart.spec.tsx index 8dccf2ff1e0..48eb48333d1 100644 --- a/packages/charts/src/components/ColumnChart/test/ColumnChart.spec.tsx +++ b/packages/charts/src/components/ColumnChart/test/ColumnChart.spec.tsx @@ -1,48 +1,23 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { complexDataSet } from '../../../resources/DemoProps.js'; import { testLoadingStates, testPassThroughProps, testStackAggregateTotals, testZoomingTool, -} from '../../../test-utils/sharedTests.js'; -import { ColumnChart } from '../index.js'; -import { - ColumnChartClickTest, - ColumnChartDataPointClickTest, - ColumnChartHighlightColorTest, - ColumnChartLegendConfigTest, - ColumnChartSecondYAxisTest, -} from './ColumnChartTestComponents.js'; - -const dimensions = [{ accessor: 'name', interval: 0 }]; -const measures = [ - { accessor: 'users', label: 'Users' }, - { accessor: 'sessions', label: 'Active Sessions' }, - { accessor: 'volume', label: 'Vol.' }, -]; -const baseProps = { dataset: complexDataSet, dimensions, measures }; +} from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('ColumnChart', () => { test('Basic', async ({ mount, page }) => { - await mount( - , - ); + await mount('ChartHarness/Chart', { chart: 'ColumnChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-bar')).toHaveCount(3); await expect(page.locator('.recharts-bar-rectangles')).toHaveCount(3); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('ColumnChart/ColumnChartClickTest'); await page.getByText('January').click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -59,33 +34,21 @@ test.describe('ColumnChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('volume'); }); - testLoadingStates( - ColumnChart, - { - dataset: complexDataSet, - dimensions: [{ accessor: 'name', interval: 0 }], - measures: [{ accessor: 'users', label: 'Users' }], - }, - { dimensions: [], measures: [] }, - '.recharts-bar', - ); + testLoadingStates('ColumnChart', '.recharts-bar'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('ColumnChart/ColumnChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testZoomingTool(ColumnChart, baseProps); + testZoomingTool('ColumnChart'); - testPassThroughProps(ColumnChart, { dimensions: [], measures: [] }); + testPassThroughProps('ColumnChart'); - testStackAggregateTotals(ColumnChart, { dataset: complexDataSet.slice(0, 3), dimensions }, [ - { accessor: 'users', stackId: 'A', label: 'Users' }, - { accessor: 'sessions', stackId: 'A', label: 'Active Sessions' }, - ]); + testStackAggregateTotals('ColumnChart'); test('onDataPointClick', async ({ mount, page }) => { - await mount(); + await mount('ColumnChart/ColumnChartDataPointClickTest'); await page.locator('[name="January"]').first().click(); await expect(page.getByTestId('dp-click-count')).toHaveText('1'); @@ -96,7 +59,7 @@ test.describe('ColumnChart', () => { }); test('highlightColor', async ({ mount, page }) => { - await mount(); + await mount('ColumnChart/ColumnChartHighlightColorTest'); // January has users=100 (<=200 → green), February has users=230 (>200 → red) const greenCells = page.locator('.recharts-bar-rectangle [fill="green"]'); @@ -106,7 +69,7 @@ test.describe('ColumnChart', () => { }); test('secondYAxis', async ({ mount, page }) => { - await mount(); + await mount('ColumnChart/ColumnChartSecondYAxisTest'); // ColumnChart is vertical so secondYAxis renders as an additional YAxis await expect(page.locator('.recharts-yAxis')).toHaveCount(2); diff --git a/packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrendTestComponents.tsx b/packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrend.gallery.tsx similarity index 100% rename from packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrendTestComponents.tsx rename to packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrend.gallery.tsx diff --git a/packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrend.spec.tsx b/packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrend.spec.tsx index 390ae0656e2..bacde971776 100644 --- a/packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrend.spec.tsx +++ b/packages/charts/src/components/ColumnChartWithTrend/test/ColumnChartWithTrend.spec.tsx @@ -1,23 +1,10 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; -import { complexDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps, testZoomingTool } from '../../../test-utils/sharedTests.js'; -import { ColumnChartWithTrend } from '../index.js'; -import { - ColumnChartWithTrendClickTest, - ColumnChartWithTrendGridTest, - ColumnChartWithTrendLegendConfigTest, -} from './ColumnChartWithTrendTestComponents.js'; - -const dimensions = [{ accessor: 'name', interval: 0 }]; -const measures = [ - { accessor: 'users', label: 'Users', type: 'line' as const }, - { accessor: 'sessions', label: 'Active Sessions', type: 'bar' as const }, -]; -const baseProps = { dataset: complexDataSet, dimensions, measures }; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; +import { testLoadingStates, testPassThroughProps, testZoomingTool } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('ColumnChartWithTrend', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('ChartHarness/Chart', { chart: 'ColumnChartWithTrend' }); await expect(page.locator('.recharts-responsive-container').first()).toBeVisible(); await expect(page.locator('.recharts-bar')).toHaveCount(1); await expect(page.locator('.recharts-line')).toHaveCount(1); @@ -26,7 +13,7 @@ test.describe('ColumnChartWithTrend', () => { }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('ColumnChartWithTrend/ColumnChartWithTrendClickTest'); await page.getByText('January').click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -42,22 +29,10 @@ test.describe('ColumnChartWithTrend', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('users'); }); - testLoadingStates( - ColumnChartWithTrend, - { - dataset: complexDataSet, - dimensions: [{ accessor: 'name', interval: 0 }], - measures: [ - { accessor: 'users', label: 'Users', type: 'line' as const }, - { accessor: 'sessions', label: 'Active Sessions', type: 'bar' as const }, - ], - }, - { dimensions: [], measures: [] }, - '.recharts-bar', - ); + testLoadingStates('ColumnChartWithTrend', '.recharts-bar'); test('in Grid', async ({ mount, page }) => { - await mount(); + await mount('ColumnChartWithTrend/ColumnChartWithTrendGridTest'); const chart = page.getByTestId('ccwt'); await expect(chart).toBeVisible(); const box = await chart.boundingBox(); @@ -66,11 +41,11 @@ test.describe('ColumnChartWithTrend', () => { }); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('ColumnChartWithTrend/ColumnChartWithTrendLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testZoomingTool(ColumnChartWithTrend, baseProps); + testZoomingTool('ColumnChartWithTrend'); - testPassThroughProps(ColumnChartWithTrend, { dimensions: [], measures: [] }); + testPassThroughProps('ColumnChartWithTrend'); }); diff --git a/packages/charts/src/components/ComposedChart/test/ComposedChartTestComponents.tsx b/packages/charts/src/components/ComposedChart/test/ComposedChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/ComposedChart/test/ComposedChartTestComponents.tsx rename to packages/charts/src/components/ComposedChart/test/ComposedChart.gallery.tsx diff --git a/packages/charts/src/components/ComposedChart/test/ComposedChart.spec.tsx b/packages/charts/src/components/ComposedChart/test/ComposedChart.spec.tsx index a8c73b7697a..396c586fa60 100644 --- a/packages/charts/src/components/ComposedChart/test/ComposedChart.spec.tsx +++ b/packages/charts/src/components/ComposedChart/test/ComposedChart.spec.tsx @@ -1,41 +1,16 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { complexDataSet } from '../../../resources/DemoProps.js'; import { testLoadingStates, testPassThroughProps, testStackAggregateTotals, testZoomingTool, -} from '../../../test-utils/sharedTests.js'; -import { ComposedChart } from '../index.js'; -import { - ComposedChartClickTest, - ComposedChartDataPointClickTest, - ComposedChartLegendConfigTest, - ComposedChartSecondYAxisTest, - ComposedChartVerticalLayoutTest, -} from './ComposedChartTestComponents.js'; - -const dimensions = [{ accessor: 'name', interval: 0 }]; -const measures = [ - { accessor: 'users', label: 'Users', type: 'line' as const }, - { accessor: 'sessions', label: 'Active Sessions', type: 'bar' as const }, - { accessor: 'volume', label: 'Vol.', type: 'area' as const }, -]; -const baseProps = { dataset: complexDataSet, dimensions, measures }; +} from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('ComposedChart', () => { test('Basic', async ({ mount, page }) => { - await mount( - , - ); + await mount('ChartHarness/Chart', { chart: 'ComposedChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-line')).toHaveCount(1); await expect(page.locator('.recharts-bar')).toHaveCount(1); @@ -46,7 +21,7 @@ test.describe('ComposedChart', () => { }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('ComposedChart/ComposedChartClickTest'); await page.getByText('January').click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -60,33 +35,21 @@ test.describe('ComposedChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('users'); }); - testLoadingStates( - ComposedChart, - { - dataset: complexDataSet, - dimensions: [{ accessor: 'name', interval: 0 }], - measures: [{ accessor: 'users', label: 'Users', type: 'bar' }], - }, - { dimensions: [], measures: [] }, - '.recharts-bar', - ); + testLoadingStates('ComposedChart', '.recharts-bar'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('ComposedChart/ComposedChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testZoomingTool(ComposedChart, baseProps); + testZoomingTool('ComposedChart'); - testPassThroughProps(ComposedChart, { dimensions: [], measures: [] }); + testPassThroughProps('ComposedChart'); - testStackAggregateTotals(ComposedChart, { dataset: complexDataSet.slice(0, 3), dimensions }, [ - { accessor: 'users', stackId: 'A', label: 'Users', type: 'bar' as const }, - { accessor: 'sessions', stackId: 'A', label: 'Active Sessions', type: 'bar' as const }, - ]); + testStackAggregateTotals('ComposedChart'); test('layout="vertical"', async ({ mount, page }) => { - await mount(); + await mount('ComposedChart/ComposedChartVerticalLayoutTest'); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); // Vertical layout swaps axes: measure axis becomes XAxis (type=number) await expect(page.locator('.recharts-xAxis')).toBeAttached(); @@ -97,7 +60,7 @@ test.describe('ComposedChart', () => { }); test('onDataPointClick', async ({ mount, page }) => { - await mount(); + await mount('ComposedChart/ComposedChartDataPointClickTest'); await page.locator('[name="January"]').first().click(); await expect(page.getByTestId('dp-click-count')).toHaveText('1'); @@ -108,7 +71,7 @@ test.describe('ComposedChart', () => { }); test('secondYAxis', async ({ mount, page }) => { - await mount(); + await mount('ComposedChart/ComposedChartSecondYAxisTest'); // ComposedChart renders secondYAxis as an additional YAxis await expect(page.locator('.recharts-yAxis')).toHaveCount(2); diff --git a/packages/charts/src/components/DonutChart/test/DonutChartTestComponents.tsx b/packages/charts/src/components/DonutChart/test/DonutChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/DonutChart/test/DonutChartTestComponents.tsx rename to packages/charts/src/components/DonutChart/test/DonutChart.gallery.tsx diff --git a/packages/charts/src/components/DonutChart/test/DonutChart.spec.tsx b/packages/charts/src/components/DonutChart/test/DonutChart.spec.tsx index 368e42b74da..053159a1462 100644 --- a/packages/charts/src/components/DonutChart/test/DonutChart.spec.tsx +++ b/packages/charts/src/components/DonutChart/test/DonutChart.spec.tsx @@ -1,31 +1,18 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { simpleDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps } from '../../../test-utils/sharedTests.js'; -import { DonutChart } from '../index.js'; -import { - DonutChartClickTest, - DonutChartLegendConfigTest, - DonutChartSectorFocusActiveTest, - DonutChartSectorFocusDatasetShrinkTest, - DonutChartSectorFocusEmptyTest, - DonutChartSectorFocusHandlersTest, - DonutChartSectorFocusOutOfBoundsTest, - DonutChartSectorFocusTest, -} from './DonutChartTestComponents.js'; - -const dimension = { accessor: 'name' }; -const measure = { accessor: 'users' }; +import { testLoadingStates, testPassThroughProps } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('DonutChart', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('ChartHarness/Chart', { chart: 'DonutChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-pie')).toHaveCount(1); await expect(page.locator('.recharts-pie-sector')).toHaveCount(12); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartClickTest'); await page.locator('[name="January"]').first().click({ force: true }); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -36,23 +23,18 @@ test.describe('DonutChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('users'); }); - testLoadingStates( - DonutChart, - { dataset: simpleDataSet, dimension, measure }, - { dimension: {}, measure: {} }, - '.recharts-pie', - ); + testLoadingStates('DonutChart', '.recharts-pie'); - testPassThroughProps(DonutChart, { dimension: {}, measure: {} }); + testPassThroughProps('DonutChart'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); test.describe('Sector Focus - keyboard navigation', () => { test('Tab, arrows, Enter, wrap-around', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartSectorFocusTest'); // Focus "before" button then Tab into chart container await page.getByText('before').focus(); @@ -95,7 +77,7 @@ test.describe('DonutChart', () => { }); test('activeSegment with Enter and Space', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartSectorFocusActiveTest'); // Initial activeSegment is 2 await expect(page.getByTestId('active-segment')).toHaveText('2'); @@ -137,7 +119,7 @@ test.describe('DonutChart', () => { }); test('empty dataset is non-interactive', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartSectorFocusEmptyTest'); // The chart container should have tabindex 0 but no role="application" const chartContainer = page.locator('[aria-roledescription="chart"]'); @@ -146,7 +128,7 @@ test.describe('DonutChart', () => { }); test('consumer event handlers are composed', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartSectorFocusHandlersTest'); // Focus the chart container directly (triggers onFocus) const chartContainer = page.locator('[aria-roledescription="chart"]'); @@ -167,7 +149,7 @@ test.describe('DonutChart', () => { }); test('activeSegment out of bounds is clamped', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartSectorFocusOutOfBoundsTest'); await page.getByText('before').focus(); await page.keyboard.press('Tab'); @@ -176,7 +158,7 @@ test.describe('DonutChart', () => { }); test('dataset shrink resets keyboard state', async ({ mount, page }) => { - await mount(); + await mount('DonutChart/DonutChartSectorFocusDatasetShrinkTest'); // Tab past "shrink" button into chart, then into sector mode await page.getByText('before').focus(); diff --git a/packages/charts/src/components/LineChart/test/LineChartTestComponents.tsx b/packages/charts/src/components/LineChart/test/LineChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/LineChart/test/LineChartTestComponents.tsx rename to packages/charts/src/components/LineChart/test/LineChart.gallery.tsx diff --git a/packages/charts/src/components/LineChart/test/LineChart.spec.tsx b/packages/charts/src/components/LineChart/test/LineChart.spec.tsx index 83e7c89f75b..1a6fbf90d79 100644 --- a/packages/charts/src/components/LineChart/test/LineChart.spec.tsx +++ b/packages/charts/src/components/LineChart/test/LineChart.spec.tsx @@ -1,34 +1,11 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { complexDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps, testZoomingTool } from '../../../test-utils/sharedTests.js'; -import { LineChart } from '../index.js'; -import { - LineChartClickTest, - LineChartDataPointClickTest, - LineChartLegendConfigTest, -} from './LineChartTestComponents.js'; - -const dimensions = [{ accessor: 'name', interval: 0 }]; -const measures = [ - { accessor: 'users', label: 'Users' }, - { accessor: 'sessions', label: 'Active Sessions' }, - { accessor: 'volume', label: 'Vol.' }, -]; -const baseProps = { dataset: complexDataSet, dimensions, measures }; +import { testLoadingStates, testPassThroughProps, testZoomingTool } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('LineChart', () => { test('Basic', async ({ mount, page }) => { - await mount( - , - ); + await mount('ChartHarness/Chart', { chart: 'LineChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-line')).toHaveCount(3); await expect(page.locator('.recharts-line-curve')).toHaveCount(3); @@ -36,7 +13,7 @@ test.describe('LineChart', () => { }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('LineChart/LineChartClickTest'); await page.locator('.recharts-line-dot[name="Users"]').first().click({ force: true }); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -47,28 +24,19 @@ test.describe('LineChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('users'); }); - testLoadingStates( - LineChart, - { - dataset: complexDataSet, - dimensions: [{ accessor: 'name', interval: 0 }], - measures: [{ accessor: 'users', label: 'Users' }], - }, - { dimensions: [], measures: [] }, - '.recharts-line', - ); + testLoadingStates('LineChart', '.recharts-line'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('LineChart/LineChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testZoomingTool(LineChart, baseProps); + testZoomingTool('LineChart'); - testPassThroughProps(LineChart, { dimensions: [], measures: [] }); + testPassThroughProps('LineChart'); test('onDataPointClick', async ({ mount, page }) => { - await mount(); + await mount('LineChart/LineChartDataPointClickTest'); // LineChart fires onDataPointClick via activeDot — hover to trigger the active dot, then click it const firstDot = page.locator('.recharts-line-dot[name="Users"]').first(); diff --git a/packages/charts/src/components/PieChart/test/PieChartTestComponents.tsx b/packages/charts/src/components/PieChart/test/PieChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/PieChart/test/PieChartTestComponents.tsx rename to packages/charts/src/components/PieChart/test/PieChart.gallery.tsx diff --git a/packages/charts/src/components/PieChart/test/PieChart.spec.tsx b/packages/charts/src/components/PieChart/test/PieChart.spec.tsx index 6e9ed5e1ab2..8849eeddfe1 100644 --- a/packages/charts/src/components/PieChart/test/PieChart.spec.tsx +++ b/packages/charts/src/components/PieChart/test/PieChart.spec.tsx @@ -1,32 +1,18 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { simpleDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps } from '../../../test-utils/sharedTests.js'; -import { PieChart } from '../index.js'; -import { - PieChartClickTest, - PieChartCustomLabelTest, - PieChartLegendConfigTest, - PieChartSectorFocusActiveTest, - PieChartSectorFocusDatasetShrinkTest, - PieChartSectorFocusEmptyTest, - PieChartSectorFocusHandlersTest, - PieChartSectorFocusOutOfBoundsTest, - PieChartSectorFocusTest, -} from './PieChartTestComponents.js'; - -const dimension = { accessor: 'name' }; -const measure = { accessor: 'users' }; +import { testLoadingStates, testPassThroughProps } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('PieChart', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('ChartHarness/Chart', { chart: 'PieChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-pie')).toHaveCount(1); await expect(page.locator('.recharts-pie-sector')).toHaveCount(12); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartClickTest'); await page.locator('[name="January"]').first().click({ force: true }); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -37,28 +23,23 @@ test.describe('PieChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('users'); }); - testLoadingStates( - PieChart, - { dataset: simpleDataSet, dimension, measure }, - { dimension: {}, measure: {} }, - '.recharts-pie', - ); + testLoadingStates('PieChart', '.recharts-pie'); - testPassThroughProps(PieChart, { dimension: {}, measure: {} }); + testPassThroughProps('PieChart'); test('custom label', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartCustomLabelTest'); await expect(page.getByText('CustomLabel')).toHaveCount(12); }); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); test.describe('Sector Focus - keyboard navigation', () => { test('Tab, arrows, Enter, wrap-around', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartSectorFocusTest'); // Focus "before" button then Tab into chart container await page.getByText('before').focus(); @@ -101,7 +82,7 @@ test.describe('PieChart', () => { }); test('activeSegment with Enter and Space', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartSectorFocusActiveTest'); // Initial activeSegment is 2 await expect(page.getByTestId('active-segment')).toHaveText('2'); @@ -143,7 +124,7 @@ test.describe('PieChart', () => { }); test('empty dataset is non-interactive', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartSectorFocusEmptyTest'); // The chart container should have tabindex 0 but no role="application" const chartContainer = page.locator('[aria-roledescription="chart"]'); @@ -152,7 +133,7 @@ test.describe('PieChart', () => { }); test('consumer event handlers are composed', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartSectorFocusHandlersTest'); // Focus the chart container directly (triggers onFocus) const chartContainer = page.locator('[aria-roledescription="chart"]'); @@ -173,7 +154,7 @@ test.describe('PieChart', () => { }); test('activeSegment out of bounds is clamped', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartSectorFocusOutOfBoundsTest'); await page.getByText('before').focus(); await page.keyboard.press('Tab'); @@ -182,7 +163,7 @@ test.describe('PieChart', () => { }); test('dataset shrink resets keyboard state', async ({ mount, page }) => { - await mount(); + await mount('PieChart/PieChartSectorFocusDatasetShrinkTest'); // Tab past "shrink" button into chart, then into sector mode await page.getByText('before').focus(); diff --git a/packages/charts/src/components/RadarChart/test/RadarChartTestComponents.tsx b/packages/charts/src/components/RadarChart/test/RadarChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/RadarChart/test/RadarChartTestComponents.tsx rename to packages/charts/src/components/RadarChart/test/RadarChart.gallery.tsx diff --git a/packages/charts/src/components/RadarChart/test/RadarChart.spec.tsx b/packages/charts/src/components/RadarChart/test/RadarChart.spec.tsx index 3f4a73765e8..8abcce41c61 100644 --- a/packages/charts/src/components/RadarChart/test/RadarChart.spec.tsx +++ b/packages/charts/src/components/RadarChart/test/RadarChart.spec.tsx @@ -1,33 +1,17 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; -import { complexDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps } from '../../../test-utils/sharedTests.js'; -import { RadarChart } from '../index.js'; -import { - RadarChartClickTest, - RadarChartDataPointClickTest, - RadarChartLegendConfigTest, -} from './RadarChartTestComponents.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; +import { testLoadingStates, testPassThroughProps } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('RadarChart', () => { test('Basic', async ({ mount, page }) => { - await mount( - , - ); + await mount('ChartHarness/Chart', { chart: 'RadarChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-radar')).toHaveCount(3); await expect(page.locator('.recharts-radar-polygon')).toHaveCount(3); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('RadarChart/RadarChartClickTest'); await page.getByText('January').click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -40,26 +24,17 @@ test.describe('RadarChart', () => { await expect(page.getByTestId('last-legend-datakey')).toHaveText('users'); }); - testLoadingStates( - RadarChart, - { - dataset: complexDataSet, - dimensions: [{ accessor: 'name', interval: 0 }], - measures: [{ accessor: 'users', label: 'Users' }], - }, - { dimensions: [], measures: [] }, - '.recharts-radar', - ); + testLoadingStates('RadarChart', '.recharts-radar'); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('RadarChart/RadarChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testPassThroughProps(RadarChart, { dimensions: [], measures: [] }); + testPassThroughProps('RadarChart'); test('onDataPointClick', async ({ mount, page }) => { - await mount(); + await mount('RadarChart/RadarChartDataPointClickTest'); // RadarChart fires onDataPointClick via activeDot on . // Hover the chart to activate a data index, making the active dot appear. diff --git a/packages/charts/src/components/RadialChart/test/RadialChartTestComponents.tsx b/packages/charts/src/components/RadialChart/test/RadialChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/RadialChart/test/RadialChartTestComponents.tsx rename to packages/charts/src/components/RadialChart/test/RadialChart.gallery.tsx diff --git a/packages/charts/src/components/RadialChart/test/RadialChart.spec.tsx b/packages/charts/src/components/RadialChart/test/RadialChart.spec.tsx index d6a6f96ce03..158be4c801c 100644 --- a/packages/charts/src/components/RadialChart/test/RadialChart.spec.tsx +++ b/packages/charts/src/components/RadialChart/test/RadialChart.spec.tsx @@ -1,11 +1,10 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; -import { testLoadingStates, testPassThroughProps } from '../../../test-utils/sharedTests.js'; -import { RadialChart } from '../index.js'; -import { RadialChartClickTest } from './RadialChartTestComponents.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; +import { testLoadingStates, testPassThroughProps } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; test.describe('RadialChart', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('ChartHarness/Chart', { chart: 'RadialChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-area')).toHaveCount(1); await expect(page.locator('.recharts-radial-bar-sectors')).toHaveCount(1); @@ -13,7 +12,7 @@ test.describe('RadialChart', () => { }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('RadialChart/RadialChartClickTest'); const sector = page.locator('.recharts-radial-bar-sector'); await expect(sector).toBeVisible(); await sector.dispatchEvent('click'); @@ -21,7 +20,7 @@ test.describe('RadialChart', () => { await expect(page.getByTestId('last-payload-value')).toHaveText('67'); }); - testPassThroughProps(RadialChart, {}); + testPassThroughProps('RadialChart'); - testLoadingStates(RadialChart, { value: 67, displayValue: '67%' }, {}, '.recharts-radial-bar-sectors'); + testLoadingStates('RadialChart', '.recharts-radial-bar-sectors'); }); diff --git a/packages/charts/src/components/ScatterChart/test/ScatterChartTestComponents.tsx b/packages/charts/src/components/ScatterChart/test/ScatterChart.gallery.tsx similarity index 100% rename from packages/charts/src/components/ScatterChart/test/ScatterChartTestComponents.tsx rename to packages/charts/src/components/ScatterChart/test/ScatterChart.gallery.tsx diff --git a/packages/charts/src/components/ScatterChart/test/ScatterChart.spec.tsx b/packages/charts/src/components/ScatterChart/test/ScatterChart.spec.tsx index 5377c91efab..4388a29267e 100644 --- a/packages/charts/src/components/ScatterChart/test/ScatterChart.spec.tsx +++ b/packages/charts/src/components/ScatterChart/test/ScatterChart.spec.tsx @@ -1,23 +1,8 @@ import type { Page } from '@playwright/test'; -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { scatterComplexDataSet } from '../../../resources/DemoProps.js'; -import { testLoadingStates, testPassThroughProps } from '../../../test-utils/sharedTests.js'; -import { ScatterChart } from '../index.js'; -import { - ScatterChartAccessibilityTest, - ScatterChartClickTest, - ScatterChartEmptyAccessibilityTest, - ScatterChartEmptyTest, - ScatterChartLegendConfigTest, - ScatterChartMultiDatasetAccessibilityTest, - ScatterChartMultipleChartsTest, -} from './ScatterChartTestComponents.js'; - -const measures = [ - { accessor: 'users', label: 'Number', axis: 'x' as const }, - { accessor: 'sessions', label: 'Sessions', axis: 'y' as const }, - { accessor: 'volume', axis: 'z' as const }, -]; +import { testLoadingStates, testPassThroughProps } from '../../../test-utils/chartGalleryTests.js'; +import type { Chart } from '../../../test-utils/ChartHarness.gallery.js'; async function expectActivePointLabel(page: Page, containerSelector: string, ...matchers: string[]) { const container = page.locator(containerSelector).first(); @@ -32,14 +17,14 @@ async function expectActivePointLabel(page: Page, containerSelector: string, ... test.describe('ScatterChart', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('ChartHarness/Chart', { chart: 'ScatterChart' }); await expect(page.locator('.recharts-responsive-container')).toBeVisible(); await expect(page.locator('.recharts-scatter')).toHaveCount(2); await expect(page.locator('.recharts-symbols[name="APJ"]')).toHaveCount(12); }); test('click handlers', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartClickTest'); await page.locator('[name="Users"]').first().click(); await expect(page.getByTestId('click-count')).toHaveText('1'); @@ -50,10 +35,10 @@ test.describe('ScatterChart', () => { await expect(page.getByTestId('last-legend-value')).toHaveText('Users'); }); - testLoadingStates(ScatterChart, { dataset: scatterComplexDataSet, measures }, { measures: [] }, '.recharts-scatter'); + testLoadingStates('ScatterChart', '.recharts-scatter'); test('accessibilityLayer: keyboard navigation, Enter, blur/re-focus, consumer handlers', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartAccessibilityTest'); const containerSelector = '[aria-roledescription="chart"]'; await expect(page.locator('[role="img"][aria-label]')).toHaveCount(3); @@ -114,7 +99,7 @@ test.describe('ScatterChart', () => { }); test('accessibilityLayer: multi-dataset points sorted by X then datasetIndex', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartMultiDatasetAccessibilityTest'); const containerSelector = '[aria-roledescription="chart"]'; await expect(page.locator('[role="img"][aria-label]')).toHaveCount(3); @@ -130,7 +115,7 @@ test.describe('ScatterChart', () => { }); test('accessibilityLayer: multiple charts', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartMultipleChartsTest'); // Verify unique IDs across all points const ids = await page.locator('[role="img"][id]').evaluateAll((els) => els.map((el) => el.id)); @@ -149,13 +134,13 @@ test.describe('ScatterChart', () => { }); test('empty dataset (accessibilityLayer: false)', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartEmptyTest'); await expect(page.locator('.recharts-scatter')).not.toBeAttached(); await expect(page.getByText('Loading...')).toBeAttached(); }); test('empty dataset (accessibilityLayer: true)', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartEmptyAccessibilityTest'); await expect(page.locator('.recharts-scatter')).not.toBeAttached(); await expect(page.getByText('Loading...')).toBeAttached(); const chart = page.locator('[aria-roledescription="chart"]'); @@ -164,9 +149,9 @@ test.describe('ScatterChart', () => { }); test('legendConfig', async ({ mount, page }) => { - await mount(); + await mount('ScatterChart/ScatterChartLegendConfigTest'); await expect(page.getByTestId('catval').first()).toBeVisible(); }); - testPassThroughProps(ScatterChart, { measures: [] }); + testPassThroughProps('ScatterChart'); }); diff --git a/packages/charts/src/components/TimelineChart/test/TimelineChartTestComponents.tsx b/packages/charts/src/components/TimelineChart/test/TimelineChart.gallery.tsx similarity index 96% rename from packages/charts/src/components/TimelineChart/test/TimelineChartTestComponents.tsx rename to packages/charts/src/components/TimelineChart/test/TimelineChart.gallery.tsx index edd454768c3..0a1295b55a3 100644 --- a/packages/charts/src/components/TimelineChart/test/TimelineChartTestComponents.tsx +++ b/packages/charts/src/components/TimelineChart/test/TimelineChart.gallery.tsx @@ -6,6 +6,16 @@ import { TimelineChartAnnotation } from '../TimelineChartAnnotation.js'; // --- Basic rendering tests (no state needed, used directly in spec) --- +export function BasicDatasetTest() { + return ( + + ); +} + // --- Connection layer toggle --- export function ConnectionLayerToggle() { const [showConn, toggleShowConn] = useReducer((prev) => !prev, undefined); diff --git a/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx b/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx index 230c6780a22..d4e0ae92e4d 100644 --- a/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx +++ b/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx @@ -1,5 +1,4 @@ -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; -import { TimelineChart } from '../index.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; import { HOVER_OPACITY, MOUSE_CURSOR_AUTO, @@ -7,25 +6,6 @@ import { MOUSE_CURSOR_GRABBING, NORMAL_OPACITY, } from '../util/constants.js'; -import { - AnnotationLayerToggle, - AnnotationPositionTest, - ColumnLabelsContinuousTest, - ColumnLabelsDiscreteTest, - ColumnLabelsDiscreteWithLabelsTest, - ConnectionLayerToggle, - IllegalConnectionTest1, - IllegalConnectionTest2, - InvalidDiscreteLabelTest, - LayerStructureTest, - MouseCursorTest, - RowHeightTest, - StartPropTest, - TooltipHiddenTest, - TooltipOpacityTest, - UnitAndTitlesTest, - ValueFormatTest, -} from './TimelineChartTestComponents.js'; // Tests dropped during the Cypress → Playwright migration: // - "TimelineChartBody: scales when the mouse wheel event happens": the wheel @@ -36,13 +16,7 @@ import { test.describe('TimelineChart', () => { test('renders TimelineChart with dataset', async ({ mount, page }) => { - await mount( - , - ); + await mount('TimelineChart/BasicDatasetTest'); const tlc = page.getByTestId('tlc'); await expect(tlc).toBeVisible(); const tagName = await tlc.evaluate((el) => el.tagName); @@ -50,21 +24,21 @@ test.describe('TimelineChart', () => { }); test('calls the valueFormat callback & renders labels', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/ValueFormatTest'); for (let i = 0; i <= 150; i += 30) { await expect(page.getByText(`${i}-formatted`, { exact: true })).toBeVisible(); } }); test('render connection layer', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/ConnectionLayerToggle'); await expect(page.locator('[data-component-name="TimelineChartConnectionLayer"]')).not.toBeAttached(); await page.getByText('Toggle Connection').click(); await expect(page.locator('[data-component-name="TimelineChartConnectionLayer"]')).toBeVisible(); }); test('render annotation layer', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/AnnotationLayerToggle'); await expect(page.locator('[data-component-name="TimelineChartAnnotationLayer"]')).not.toBeAttached(); await page.getByText('Toggle Annotations', { exact: true }).click(); await expect(page.locator('[data-component-name="TimelineChartAnnotationLayer"]')).not.toBeAttached(); @@ -79,7 +53,7 @@ test.describe('TimelineChart', () => { test('throws InvalidDiscreteLabelError', async ({ mount, page }) => { const errors: Error[] = []; page.on('pageerror', (err) => errors.push(err)); - await mount(); + await mount('TimelineChart/InvalidDiscreteLabelTest'); await expect .poll(() => errors.some((e) => e.name === 'InvalidDiscreteLabelError' || e.message.includes('discreteLabels'))) .toBe(true); @@ -88,7 +62,7 @@ test.describe('TimelineChart', () => { test('throws IllegalConnectionError (1)', async ({ mount, page }) => { const errors: Error[] = []; page.on('pageerror', (err) => errors.push(err)); - await mount(); + await mount('TimelineChart/IllegalConnectionTest1'); await expect .poll(() => errors.some((e) => e.name === 'IllegalConnectionError' || e.message.includes('connection'))) .toBe(true); @@ -97,14 +71,14 @@ test.describe('TimelineChart', () => { test('throws IllegalConnectionError (2)', async ({ mount, page }) => { const errors: Error[] = []; page.on('pageerror', (err) => errors.push(err)); - await mount(); + await mount('TimelineChart/IllegalConnectionTest2'); await expect .poll(() => errors.some((e) => e.name === 'IllegalConnectionError' || e.message.includes('connection'))) .toBe(true); }); test('shows the right mouse cursor', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/MouseCursorTest'); const bodyContainer = page.locator('[data-component-name="TimelineChartBodyContainer"]'); await expect(bodyContainer).toHaveCSS('cursor', MOUSE_CURSOR_AUTO); @@ -124,7 +98,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartAnnotation: positions itself correctly in the parent', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/AnnotationPositionTest'); await expect(page.locator('[data-component-name="TimelineChartAnnotation"]')).toHaveCSS( 'inset-block-start', '40px', @@ -132,7 +106,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartRow: tooltip & opacity for tasks', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/TooltipOpacityTest'); const task = page.locator('[data-component-name="TimelineChartTask"]').first(); await expect(task).toHaveCSS('opacity', `${NORMAL_OPACITY}`); @@ -146,7 +120,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartRow: tooltip & opacity for milestones', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/TooltipOpacityTest'); const milestoneRect = page.locator('[data-component-name="TimelineChartMilestone"] > rect'); await expect(milestoneRect).toHaveCSS('opacity', `${NORMAL_OPACITY}`); @@ -159,7 +133,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartRow: hideTooltip still changes opacity', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/TooltipHiddenTest'); const task = page.locator('[data-component-name="TimelineChartTask"]').first(); await expect(task).toHaveCSS('opacity', `${NORMAL_OPACITY}`); @@ -179,7 +153,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartLayer', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/LayerStructureTest'); const gridLayer = page.locator('[data-component-name="TimelineChartGridLayer"]'); await expect(gridLayer).toHaveCSS('pointer-events', 'none'); @@ -203,7 +177,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartHeaders: ColumnLabels continuous', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/ColumnLabelsContinuousTest'); for (let i = 0; i <= 10; i += 2) { await expect(page.getByText(`${i}`, { exact: true })).toBeVisible(); } @@ -211,7 +185,7 @@ test.describe('TimelineChart', () => { }); test('TimelineChartHeaders: ColumnLabels discrete', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/ColumnLabelsDiscreteTest'); for (let i = 0; i <= 9; i++) { await expect(page.getByText(`${i}`, { exact: true })).toBeVisible(); } @@ -219,19 +193,19 @@ test.describe('TimelineChart', () => { }); test('TimelineChartHeaders: ColumnLabels discrete with labels', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/ColumnLabelsDiscreteWithLabelsTest'); await expect(page.getByText('label')).toHaveCount(8); await expect(page.getByText('one')).toBeVisible(); await expect(page.getByText('two')).toBeVisible(); }); test('rowHeight', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/RowHeightTest'); await expect(page.locator('[data-component-name="TimelineChartRow"]').first()).toHaveAttribute('height', '200'); }); test('unit and titles', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/UnitAndTitlesTest'); await expect(page.getByText('Activities')).not.toBeAttached(); await expect(page.getByText('Duration')).not.toBeAttached(); await expect(page.getByText('columnTitle (unit)')).toBeVisible(); @@ -239,7 +213,7 @@ test.describe('TimelineChart', () => { }); test('start', async ({ mount, page }) => { - await mount(); + await mount('TimelineChart/StartPropTest'); for (let i = 5; i <= 15; i += 2) { await expect(page.getByText(`${i}`, { exact: true })).toBeVisible(); } diff --git a/packages/charts/src/hooks/test/HookTestComponents.tsx b/packages/charts/src/hooks/test/HookTestComponents.gallery.tsx similarity index 100% rename from packages/charts/src/hooks/test/HookTestComponents.tsx rename to packages/charts/src/hooks/test/HookTestComponents.gallery.tsx diff --git a/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx b/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx index c128a6e0317..de383e5c466 100644 --- a/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx +++ b/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx @@ -1,19 +1,18 @@ -import { expect, test } from '../../../../../playwright/fixtures/main-fixtures.js'; -import { LabelFormatterInvalid, LabelFormatterNull, LabelFormatterValid } from './HookTestComponents.js'; +import { expect, test } from '../../../../../playwright/fixtures/gallery-fixtures.js'; test.describe('useLabelFormatter', () => { test('should return value when no formatter is present', async ({ mount }) => { - const component = await mount(); + const component = await mount('HookTestComponents/LabelFormatterNull'); await expect(component.getByText('100')).toBeVisible(); }); test('should not crash on invalid formatter', async ({ mount }) => { - const component = await mount(); + const component = await mount('HookTestComponents/LabelFormatterInvalid'); await expect(component.getByText('100')).toBeVisible(); }); test('should format the value with a valid formatter', async ({ mount }) => { - const component = await mount(); + const component = await mount('HookTestComponents/LabelFormatterValid'); await expect(component.getByText('10')).toBeVisible(); }); }); diff --git a/packages/charts/src/hooks/test/usePrepareDimensionsAndMeasures.spec.tsx b/packages/charts/src/hooks/test/usePrepareDimensionsAndMeasures.spec.tsx index 95536903127..755efc551d4 100644 --- a/packages/charts/src/hooks/test/usePrepareDimensionsAndMeasures.spec.tsx +++ b/packages/charts/src/hooks/test/usePrepareDimensionsAndMeasures.spec.tsx @@ -1,13 +1,8 @@ -import { expect, test } from '../../../../../playwright/fixtures/main-fixtures.js'; -import { - PrepareDimensionsDefault, - PrepareDimensionsNoOverwrite, - PrepareDimensionsWithDefaults, -} from './HookTestComponents.js'; +import { expect, test } from '../../../../../playwright/fixtures/gallery-fixtures.js'; test.describe('usePrepareDimensionsAndMeasures', () => { test('should not throw an error when no defaults are passed', async ({ mount, page }) => { - await mount(); + await mount('HookTestComponents/PrepareDimensionsDefault'); const resultText = await page.getByTestId('result').textContent(); const result = JSON.parse(resultText); expect(result).toEqual({ @@ -19,7 +14,7 @@ test.describe('usePrepareDimensionsAndMeasures', () => { }); test('should merge defaults', async ({ mount, page }) => { - await mount(); + await mount('HookTestComponents/PrepareDimensionsWithDefaults'); const resultText = await page.getByTestId('result').textContent(); const result = JSON.parse(resultText); expect(result).toEqual({ @@ -31,7 +26,7 @@ test.describe('usePrepareDimensionsAndMeasures', () => { }); test('should merge defaults but not overwrite existing properties', async ({ mount, page }) => { - await mount(); + await mount('HookTestComponents/PrepareDimensionsNoOverwrite'); const resultText = await page.getByTestId('result').textContent(); const result = JSON.parse(resultText); expect(result).toEqual({ diff --git a/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx b/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx index 9ce93831e69..2905c60926c 100644 --- a/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx +++ b/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx @@ -1,19 +1,18 @@ -import { expect, test } from '../../../../../playwright/fixtures/main-fixtures.js'; -import { TooltipFormatterInvalid, TooltipFormatterNoFormatter, TooltipFormatterValid } from './HookTestComponents.js'; +import { expect, test } from '../../../../../playwright/fixtures/gallery-fixtures.js'; test.describe('useTooltipFormatter', () => { test('should return value when no formatter is present', async ({ mount }) => { - const component = await mount(); + const component = await mount('HookTestComponents/TooltipFormatterNoFormatter'); await expect(component.getByText('100')).toBeVisible(); }); test('should not crash on invalid formatter', async ({ mount }) => { - const component = await mount(); + const component = await mount('HookTestComponents/TooltipFormatterInvalid'); await expect(component.getByText('100')).toBeVisible(); }); test('should format the value with a valid formatter', async ({ mount }) => { - const component = await mount(); + const component = await mount('HookTestComponents/TooltipFormatterValid'); await expect(component.getByText('10')).toBeVisible(); }); }); diff --git a/packages/charts/src/resources/chartHarnessData.ts b/packages/charts/src/resources/chartHarnessData.ts new file mode 100644 index 00000000000..576bd49af69 --- /dev/null +++ b/packages/charts/src/resources/chartHarnessData.ts @@ -0,0 +1,123 @@ +/** + * @fileoverview Per-chart prop sets consumed by the shared chart gallery harness. + * + * Add an entry here for every chart whose spec uses the shared tests in `chartGalleryTests.tsx`: + * `baseProps` (populated chart), `emptyProps` (placeholder path), and `stack` (only for charts + * whose spec calls `testStackAggregateTotals`). Keep values plain and serializable — no React, + * no functions — so both the browser harness and the Node-side test helpers can import this. + */ + +import { complexDataSet, scatterComplexDataSet, simpleDataSet } from '../resources/DemoProps.js'; + +// Serializable chart prop sets for the gallery harness + the Node-side shared tests. +// Plain data only, so both the browser harness and `chartGalleryTests.tsx` can import it. +export interface ChartHarnessEntry { + baseProps: Record; + emptyProps: Record; + stack?: { + baseProps: Record; + measures: Array<{ accessor: string; stackId?: string; label?: string; type?: string }>; + }; +} + +const catDimensions = [{ accessor: 'name', interval: 0 }]; +const catMeasures = [ + { accessor: 'users', label: 'Users' }, + { accessor: 'sessions', label: 'Active Sessions' }, + { accessor: 'volume', label: 'Vol.' }, +]; +const stackMeasures = [ + { accessor: 'users', stackId: 'A', label: 'Users' }, + { accessor: 'sessions', stackId: 'A', label: 'Active Sessions' }, +]; +const stackBaseProps = { dataset: complexDataSet.slice(0, 3), dimensions: catDimensions }; + +// dimension/measure (singular) charts +const singularDimension = { accessor: 'name' }; +const singularMeasure = { accessor: 'users' }; + +// scatter measures +const scatterMeasures = [ + { accessor: 'users', label: 'Number', axis: 'x' as const }, + { accessor: 'sessions', label: 'Sessions', axis: 'y' as const }, + { accessor: 'volume', axis: 'z' as const }, +]; + +export const chartHarnessData: Record = { + BarChart: { + baseProps: { dataset: complexDataSet, dimensions: catDimensions, measures: catMeasures }, + emptyProps: { dimensions: [], measures: [] }, + stack: { baseProps: stackBaseProps, measures: stackMeasures }, + }, + ColumnChart: { + baseProps: { dataset: complexDataSet, dimensions: catDimensions, measures: catMeasures }, + emptyProps: { dimensions: [], measures: [] }, + stack: { baseProps: stackBaseProps, measures: stackMeasures }, + }, + ComposedChart: { + baseProps: { + dataset: complexDataSet, + dimensions: catDimensions, + measures: [ + { accessor: 'users', label: 'Users', type: 'line' as const }, + { accessor: 'sessions', label: 'Active Sessions', type: 'bar' as const }, + { accessor: 'volume', label: 'Vol.', type: 'area' as const }, + ], + }, + emptyProps: { dimensions: [], measures: [] }, + stack: { + baseProps: stackBaseProps, + measures: [ + { accessor: 'users', stackId: 'A', label: 'Users', type: 'bar' as const }, + { accessor: 'sessions', stackId: 'A', label: 'Active Sessions', type: 'bar' as const }, + ], + }, + }, + BulletChart: { + baseProps: { + dataset: complexDataSet, + dimensions: catDimensions, + measures: [ + { accessor: 'users', label: 'Users', type: 'primary' as const }, + { accessor: 'sessions', label: 'Active Sessions', type: 'comparison' as const }, + { accessor: 'volume', label: 'Vol.', type: 'additional' as const }, + ], + }, + emptyProps: { dimensions: [], measures: [] }, + }, + ColumnChartWithTrend: { + baseProps: { + dataset: complexDataSet, + dimensions: catDimensions, + measures: [ + { accessor: 'users', label: 'Users', type: 'line' as const }, + { accessor: 'sessions', label: 'Active Sessions', type: 'bar' as const }, + ], + }, + emptyProps: { dimensions: [], measures: [] }, + }, + LineChart: { + baseProps: { dataset: complexDataSet, dimensions: catDimensions, measures: catMeasures }, + emptyProps: { dimensions: [], measures: [] }, + }, + RadarChart: { + baseProps: { dataset: complexDataSet, dimensions: catDimensions, measures: catMeasures }, + emptyProps: { dimensions: [], measures: [] }, + }, + DonutChart: { + baseProps: { dataset: simpleDataSet, dimension: singularDimension, measure: singularMeasure }, + emptyProps: { dimension: {}, measure: {} }, + }, + PieChart: { + baseProps: { dataset: simpleDataSet, dimension: singularDimension, measure: singularMeasure }, + emptyProps: { dimension: {}, measure: {} }, + }, + RadialChart: { + baseProps: { value: 67, displayValue: '67%' }, + emptyProps: {}, + }, + ScatterChart: { + baseProps: { dataset: scatterComplexDataSet, measures: scatterMeasures }, + emptyProps: { measures: [] }, + }, +}; diff --git a/packages/charts/src/test-utils/ChartHarness.gallery.tsx b/packages/charts/src/test-utils/ChartHarness.gallery.tsx new file mode 100644 index 00000000000..c2d2df9d083 --- /dev/null +++ b/packages/charts/src/test-utils/ChartHarness.gallery.tsx @@ -0,0 +1,69 @@ +/** + * @fileoverview Browser-side gallery story shared by every chart's parametric tests. + * + * Exposes one story (`Chart`) that renders a chart resolved by string key — the mount boundary + * only carries serializable data, so specs pass the chart name, not the component. When adding a + * chart to the shared tests, register its component in `chartRegistry` here and its prop sets in + * `chartHarnessData.ts`. Keep this file limited to resolution + rendering; per-chart data and the + * test logic live in `chartHarnessData.ts` and `chartGalleryTests.tsx` respectively. + */ + +import type { ComponentType } from 'react'; +import { BarChart } from '../components/BarChart/index.js'; +import { BulletChart } from '../components/BulletChart/index.js'; +import { ColumnChart } from '../components/ColumnChart/index.js'; +import { ColumnChartWithTrend } from '../components/ColumnChartWithTrend/index.js'; +import { ComposedChart } from '../components/ComposedChart/index.js'; +import { DonutChart } from '../components/DonutChart/index.js'; +import { LineChart } from '../components/LineChart/index.js'; +import { PieChart } from '../components/PieChart/index.js'; +import { RadarChart } from '../components/RadarChart/index.js'; +import { RadialChart } from '../components/RadialChart/index.js'; +import { ScatterChart } from '../components/ScatterChart/index.js'; +import { chartHarnessData } from '../resources/chartHarnessData.js'; + +// Shared gallery story for the parametric chart tests. Specs pass a chart's string key +// (components can't cross the mount boundary); it resolves the component + prop set here. +const chartRegistry: Record> = { + BarChart, + BulletChart, + ColumnChart, + ColumnChartWithTrend, + ComposedChart, + DonutChart, + LineChart, + PieChart, + RadarChart, + RadialChart, + ScatterChart, +}; + +type HarnessProps = { + chart: string; + variant?: 'base' | 'empty' | 'stack'; + overrides?: Record; +}; + +function resolve(chart: string) { + const Component = chartRegistry[chart]; + const data = chartHarnessData[chart]; + if (!Component || !data) { + throw new Error( + `ChartHarness: unknown chart "${chart}". Register it in ChartHarness.gallery.tsx + chartHarnessData.ts.`, + ); + } + return { Component, data }; +} + +export const Chart = ({ chart, variant = 'base', overrides = {} }: HarnessProps) => { + const { Component, data } = resolve(chart); + let props: Record; + if (variant === 'empty') { + props = data.emptyProps; + } else if (variant === 'stack') { + props = { ...data.stack.baseProps, measures: data.stack.measures }; + } else { + props = data.baseProps; + } + return ; +}; diff --git a/packages/charts/src/test-utils/chartGalleryTests.tsx b/packages/charts/src/test-utils/chartGalleryTests.tsx new file mode 100644 index 00000000000..995793e9acc --- /dev/null +++ b/packages/charts/src/test-utils/chartGalleryTests.tsx @@ -0,0 +1,111 @@ +/** + * @fileoverview Shared parametric chart tests, reused across chart specs. + * + * Each helper registers Playwright tests for one concern (loading states, zooming tool, + * pass-through props, stack totals) and is called from a chart's spec with that chart's string + * key. Add a new helper here when a behavior is shared by multiple charts; keep chart-specific + * tests in the individual spec files. Helpers mount the shared `ChartHarness/Chart` story, so the + * chart must be registered in `ChartHarness.gallery.tsx` + `chartHarnessData.ts` first. + */ + +import { expect, test } from '../../../../playwright/fixtures/gallery-fixtures.js'; +import { chartHarnessData } from '../resources/chartHarnessData.js'; +import { assertPassThroughProps, passThroughProps } from './shared.js'; + +// Gallery-model shared chart tests. Each helper takes a chart's string key and mounts the +// shared `ChartHarness/Chart` story; the component + base props are resolved in the harness. +const HARNESS = 'ChartHarness/Chart'; + +/** Verifies the chart forwards standard HTML props onto its rendered root element. */ +export function testPassThroughProps(chart: string) { + test('Pass Through HTML Standard Props', async ({ mount, page }) => { + await mount(HARNESS, { chart, variant: 'empty', overrides: passThroughProps() }); + await assertPassThroughProps(page); + }); +} + +/** Verifies the three ChartContainer paths: empty, empty+loading (no-op), data+loading (BusyIndicator). */ +export function testLoadingStates(chart: string, chartElementSelector: string) { + test('loading states', async ({ mount, page }) => { + const busyIndicator = page.locator('[data-component-name="ChartContainerBusyIndicator"]').first(); + const chartElement = page.locator(chartElementSelector); + const loadingText = page.getByText('Loading...').first(); + + let result = await mount(HARNESS, { chart, variant: 'empty' }); + await expect(loadingText).toBeAttached(); + await expect(chartElement).not.toBeAttached(); + await expect(busyIndicator).not.toBeAttached(); + await result.unmount(); + + result = await mount(HARNESS, { chart, variant: 'empty', overrides: { loading: true } }); + await expect(loadingText).toBeAttached(); + await expect(chartElement).not.toBeAttached(); + await expect(busyIndicator).not.toBeAttached(); + await result.unmount(); + + await mount(HARNESS, { chart, variant: 'base', overrides: { loading: true } }); + await expect(busyIndicator).toBeAttached(); + }); +} + +/** Verifies `chartConfig.zoomingTool`: true → brush, false → none, custom → styled brush. */ +export function testZoomingTool(chart: string) { + test.describe('zoomingTool', () => { + test('enabled', async ({ mount, page }) => { + await mount(HARNESS, { chart, overrides: { chartConfig: { zoomingTool: true } } }); + await expect(page.locator('.recharts-brush')).toBeVisible(); + }); + + test('disabled', async ({ mount, page }) => { + await mount(HARNESS, { chart, overrides: { chartConfig: { zoomingTool: false } } }); + await expect(page.locator('.recharts-brush')).not.toBeAttached(); + }); + + test('custom config', async ({ mount, page }) => { + await mount(HARNESS, { chart, overrides: { chartConfig: { zoomingTool: { stroke: 'red' } } } }); + await expect(page.locator('.recharts-brush')).toBeVisible(); + await expect(page.locator('.recharts-brush [stroke="red"]')).toBeVisible(); + }); + }); +} + +/** Verifies `chartConfig.showStackAggregateTotals`: enabled → bold totals + tooltip, disabled → none. */ +export function testStackAggregateTotals(chart: string) { + const stack = chartHarnessData[chart]?.stack; + if (!stack) { + throw new Error(`testStackAggregateTotals: chart "${chart}" has no stack data in chartHarnessData.ts`); + } + const stackedAccessors = stack.measures.filter((m) => m.stackId).map((m) => m.accessor); + const expectedTotals = (stack.baseProps.dataset as Record[]).map((entry) => + stackedAccessors.reduce((sum, acc) => sum + (Number(entry[acc]) || 0), 0), + ); + + test.describe('showStackAggregateTotals', () => { + test('enabled', async ({ mount, page }) => { + await mount(HARNESS, { chart, variant: 'stack', overrides: { chartConfig: { showStackAggregateTotals: true } } }); + + for (const total of expectedTotals) { + await expect(page.locator(`text[font-weight="bold"]`).filter({ hasText: String(total) })).toBeAttached(); + } + + const wrapper = page.locator('.recharts-wrapper'); + await wrapper.hover({ position: { x: 200, y: 100 }, force: true }); + const tooltipTotal = page.locator('.recharts-tooltip-item').last(); + await expect(tooltipTotal).toContainText('Total'); + await expect(tooltipTotal).toHaveCSS('font-weight', '700'); + const tooltipText = await tooltipTotal.textContent(); + const totalValue = Number(tooltipText.replace(/\D/g, '')); + expect(expectedTotals).toContain(totalValue); + }); + + test('disabled', async ({ mount, page }) => { + await mount(HARNESS, { + chart, + variant: 'stack', + overrides: { chartConfig: { showStackAggregateTotals: false } }, + }); + await expect(page.locator('.recharts-bar-rectangles').first()).toBeAttached(); + await expect(page.locator('text[font-weight="bold"]')).not.toBeAttached(); + }); + }); +} diff --git a/packages/charts/src/test-utils/shared.tsx b/packages/charts/src/test-utils/shared.tsx index 99d782a1067..a412ceb568d 100644 --- a/packages/charts/src/test-utils/shared.tsx +++ b/packages/charts/src/test-utils/shared.tsx @@ -1,4 +1,4 @@ -import { expect } from '@playwright/experimental-ct-react'; +import { expect } from '@playwright/test'; import type { Page } from '@playwright/test'; export async function assertPassThroughProps(page: Page) { diff --git a/packages/charts/src/test-utils/sharedTests.tsx b/packages/charts/src/test-utils/sharedTests.tsx deleted file mode 100644 index b836b614c1f..00000000000 --- a/packages/charts/src/test-utils/sharedTests.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import type { ComponentType } from 'react'; -import { expect, test } from '../../../../playwright/fixtures/main-fixtures.js'; -import { assertPassThroughProps, passThroughProps } from './shared.js'; - -/** - * Registers a `Pass Through HTML Standard Props` test that verifies that the chart forwards - * the standard HTML props (data-testid, data-*, aria-*, id, className, style.pointerEvents, - * title, custom attribute) onto its rendered root element. - */ -export function testPassThroughProps>(Chart: ComponentType, emptyProps: T) { - test('Pass Through HTML Standard Props', async ({ mount, page }) => { - await mount(); - await assertPassThroughProps(page); - }); -} - -/** - * Registers a `loading states` test that verifies the three distinct rendering paths in - * ChartContainer: - * - empty dataset → Placeholder, no BusyIndicator, no chart elements (loading prop has no effect) - * - empty dataset + loading=true → identical to empty (loading is a no-op without data) - * - has data + loading=true → BusyIndicator overlay on top of the rendered chart - * - * @param chartElementSelector A selector unique to the chart's rendered shape, e.g. `.recharts-bar` - * for BarChart or `.recharts-pie` for PieChart. Used to assert the chart isn't rendered in the - * placeholder path. - */ -export function testLoadingStates>( - Chart: ComponentType, - baseProps: T, - emptyProps: T, - chartElementSelector: string, -) { - test('loading states', async ({ mount, page }) => { - const busyIndicator = page.locator('[data-component-name="ChartContainerBusyIndicator"]').first(); - const chartElement = page.locator(chartElementSelector); - const loadingText = page.getByText('Loading...').first(); - - let result = await mount(); - await expect(loadingText).toBeAttached(); - await expect(chartElement).not.toBeAttached(); - await expect(busyIndicator).not.toBeAttached(); - await result.unmount(); - - result = await mount(); - await expect(loadingText).toBeAttached(); - await expect(chartElement).not.toBeAttached(); - await expect(busyIndicator).not.toBeAttached(); - await result.unmount(); - - await mount(); - await expect(busyIndicator).toBeAttached(); - }); -} - -/** - * Registers `zoomingTool` describe block with three sub-tests verifying the chart's - * `chartConfig.zoomingTool` prop: - * - `true` → recharts brush is rendered - * - `false` → no brush - * - `{ stroke: 'red' }` → brush rendered with the custom stroke color - */ -export function testZoomingTool>(Chart: ComponentType, baseProps: T) { - test.describe('zoomingTool', () => { - test('enabled', async ({ mount, page }) => { - await mount(); - await expect(page.locator('.recharts-brush')).toBeVisible(); - }); - - test('disabled', async ({ mount, page }) => { - await mount(); - await expect(page.locator('.recharts-brush')).not.toBeAttached(); - }); - - test('custom config', async ({ mount, page }) => { - await mount(); - await expect(page.locator('.recharts-brush')).toBeVisible(); - await expect(page.locator('.recharts-brush [stroke="red"]')).toBeVisible(); - }); - }); -} - -/** - * Registers a `showStackAggregateTotals` describe block with two sub-tests verifying the - * `chartConfig.showStackAggregateTotals` prop: - * - enabled → stack totals rendered as bold labels, tooltip shows "Total : " - * - disabled → no bold totals; bars still render - * - * Expected stack totals are computed from the dataset and the stacked measure accessors. - */ -export function testStackAggregateTotals>( - Chart: ComponentType, - baseProps: T, - stackMeasures: Array<{ accessor: string; stackId?: string; label?: string; type?: string }>, -) { - const stackedAccessors = stackMeasures.filter((m) => m.stackId).map((m) => m.accessor); - const expectedTotals = (baseProps.dataset as Record[]).map((entry) => - stackedAccessors.reduce((sum, acc) => sum + (Number(entry[acc]) || 0), 0), - ); - - test.describe('showStackAggregateTotals', () => { - test('enabled', async ({ mount, page }) => { - await mount(); - - for (const total of expectedTotals) { - await expect(page.locator(`text[font-weight="bold"]`).filter({ hasText: String(total) })).toBeAttached(); - } - - const wrapper = page.locator('.recharts-wrapper'); - await wrapper.hover({ position: { x: 200, y: 100 }, force: true }); - const tooltipTotal = page.locator('.recharts-tooltip-item').last(); - await expect(tooltipTotal).toContainText('Total'); - await expect(tooltipTotal).toHaveCSS('font-weight', '700'); - const tooltipText = await tooltipTotal.textContent(); - const totalValue = Number(tooltipText.replace(/\D/g, '')); - expect(expectedTotals).toContain(totalValue); - }); - - test('disabled', async ({ mount, page }) => { - await mount(); - await expect(page.locator('.recharts-bar-rectangles').first()).toBeAttached(); - await expect(page.locator('text[font-weight="bold"]')).not.toBeAttached(); - }); - }); -} diff --git a/packages/main/src/components/SelectDialog/test/SelectDialogTestComponents.tsx b/packages/main/src/components/SelectDialog/test/SelectDialog.gallery.tsx similarity index 100% rename from packages/main/src/components/SelectDialog/test/SelectDialogTestComponents.tsx rename to packages/main/src/components/SelectDialog/test/SelectDialog.gallery.tsx diff --git a/packages/main/src/components/SelectDialog/test/SelectDialog.spec.tsx b/packages/main/src/components/SelectDialog/test/SelectDialog.spec.tsx index 0ba1ae03474..a1bd46b0873 100644 --- a/packages/main/src/components/SelectDialog/test/SelectDialog.spec.tsx +++ b/packages/main/src/components/SelectDialog/test/SelectDialog.spec.tsx @@ -1,19 +1,8 @@ -import { expect } from '@playwright/experimental-ct-react'; -import { test } from '../../../../../../playwright/fixtures/main-fixtures.js'; -import { - SelectDialogBasicTestComp, - SelectDialogHeaderTestComp, - SelectDialogSelectionWithToggleTestComp, - SelectDialogSearchTestComp, - SelectDialogConfirmButtonTextTestComp, - SelectDialogNumberOfSelectedItemsTestComp, - SelectDialogCancelWithToggleTestComp, - SelectDialogConfirmButtonPropsTestComp, -} from './SelectDialogTestComponents.js'; +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; test.describe('SelectDialog', () => { test('Basic', async ({ mount, page }) => { - await mount(); + await mount('SelectDialog/SelectDialogBasicTestComp'); await expect(page.locator('[ui5-dialog]')).toBeVisible(); await expect(page.locator('[ui5-input][placeholder="Search"]')).toBeVisible(); await page.getByText('Cancel').click(); @@ -21,7 +10,7 @@ test.describe('SelectDialog', () => { }); test('with headerText', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('SelectDialog/SelectDialogHeaderTestComp'); const header = page.getByText('Select Dialog'); await expect(header).toHaveCSS('grid-column-start', 'titleStart'); await expect(header).toHaveCSS('grid-column-end', 'titleCenter'); @@ -40,7 +29,7 @@ test.describe('SelectDialog', () => { }); test('selection', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('SelectDialog/SelectDialogSelectionWithToggleTestComp'); const list = page.locator('[ui5-list]'); // Single mode - no rememberSelections @@ -125,7 +114,7 @@ test.describe('SelectDialog', () => { }); test('Search', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('SelectDialog/SelectDialogSearchTestComp'); await expect(page.locator('[accessible-name="Reset"][ui5-icon]')).not.toBeVisible(); const input = page.locator('[ui5-input]'); @@ -163,7 +152,7 @@ test.describe('SelectDialog', () => { }); test('confirmButtonText', async ({ mount, page }) => { - await mount(); + await mount('SelectDialog/SelectDialogConfirmButtonTextTestComp'); await expect(page.locator('[ui5-dialog]')).toBeVisible(); await page.getByText('Exterminate').click(); await expect(page.getByTestId('confirm-count')).toHaveText('1'); @@ -171,12 +160,12 @@ test.describe('SelectDialog', () => { }); test('numberOfSelectedItems', async ({ mount, page }) => { - await mount(); + await mount('SelectDialog/SelectDialogNumberOfSelectedItemsTestComp'); await expect(page.getByText('Selected: 1337')).toBeVisible(); }); test('onCancel', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('SelectDialog/SelectDialogCancelWithToggleTestComp'); // Single mode await page.getByTestId('open-btn').click(); @@ -201,7 +190,7 @@ test.describe('SelectDialog', () => { }); test('confirmButtonProps', async ({ mount, page }) => { - await mount(); + await mount('SelectDialog/SelectDialogConfirmButtonPropsTestComp'); const btn = page.getByTestId('confirmBtn'); await expect(btn).toBeVisible(); await expect(btn).toHaveAttribute('disabled'); diff --git a/packages/main/src/components/SplitterLayout/test/SplitterLayoutTestComponents.tsx b/packages/main/src/components/SplitterLayout/test/SplitterLayout.gallery.tsx similarity index 84% rename from packages/main/src/components/SplitterLayout/test/SplitterLayoutTestComponents.tsx rename to packages/main/src/components/SplitterLayout/test/SplitterLayout.gallery.tsx index d9a80f9a899..1a323264255 100644 --- a/packages/main/src/components/SplitterLayout/test/SplitterLayoutTestComponents.tsx +++ b/packages/main/src/components/SplitterLayout/test/SplitterLayout.gallery.tsx @@ -40,27 +40,25 @@ export const SplitterMoveResetTestComp = ({ ); }; -export const SplitterMultipleElementsTestComp = ({ - vertical, - onBtnClick, -}: { - vertical: boolean; - onBtnClick?: () => void; -}) => { +export const SplitterMultipleElementsTestComp = ({ vertical }: { vertical: boolean }) => { + const [clickCount, setClickCount] = useState(0); return ( - - - - - - - - - - - + <> + + + + + + + + + + + + {clickCount} + ); }; diff --git a/packages/main/src/components/SplitterLayout/test/SplitterLayout.spec.tsx b/packages/main/src/components/SplitterLayout/test/SplitterLayout.spec.tsx index 832eea03164..0b268c9a830 100644 --- a/packages/main/src/components/SplitterLayout/test/SplitterLayout.spec.tsx +++ b/packages/main/src/components/SplitterLayout/test/SplitterLayout.spec.tsx @@ -1,11 +1,10 @@ import type { Page } from '@playwright/test'; -import { expect, test } from '../../../../../../playwright/fixtures/main-fixtures.js'; -import { +import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js'; +import type { + SplitterControlledTestComp, SplitterMoveResetTestComp, SplitterMultipleElementsTestComp, - SplitterEmptyTestComp, - SplitterControlledTestComp, -} from './SplitterLayoutTestComponents.js'; +} from './SplitterLayout.gallery.js'; /** * Move first separator 10 times via ArrowUp/ArrowLeft (RTL aware) @@ -31,7 +30,7 @@ test.describe('SplitterLayout', () => { for (const vertical of [false, true]) { test(`Splitter Move & Reset - ${dir} - vertical: ${vertical}`, async ({ mount, page }) => { await page.setViewportSize({ width: 2000, height: 2000 }); - await mount(); + await mount('SplitterLayout/SplitterMoveResetTestComp', { vertical, dir }); const se1 = page.getByTestId('se1'); const se2 = page.getByTestId('se2'); @@ -69,18 +68,12 @@ test.describe('SplitterLayout', () => { for (const vertical of [false, true]) { test(`SplitterLayout w/ multiple SplitterElements - vertical: ${vertical}`, async ({ mount, page }) => { - let clickCount = 0; - await mount( - { - clickCount++; - }} - />, - ); + await mount('SplitterLayout/SplitterMultipleElementsTestComp', { + vertical, + }); await page.getByTestId('btn').click(); - expect(clickCount).toBe(1); + await expect(page.getByTestId('btn-click-count')).toHaveText('1'); // only one separator (resizable=false for #2) const separators = page.locator('[role="separator"]'); @@ -103,7 +96,7 @@ test.describe('SplitterLayout', () => { } test('empty content', async ({ mount, page }) => { - await mount(); + await mount('SplitterLayout/SplitterEmptyTestComp'); const sl = page.getByTestId('sl'); await expect(sl).toBeAttached(); await expect(sl).not.toBeVisible(); @@ -111,7 +104,7 @@ test.describe('SplitterLayout', () => { for (const vertical of [true, false]) { test(`controlled width (${vertical ? 'vertical' : 'horizontal'})`, async ({ mount, page }) => { - await mount(); + await mount('SplitterLayout/SplitterControlledTestComp', { vertical }); await expect(page.getByTestId('resize-count')).toHaveText('0'); await expect(page.getByTestId('0')).toHaveText('200px'); diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts deleted file mode 100644 index 45b9ae31ee6..00000000000 --- a/playwright-ct.config.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { fileURLToPath } from 'node:url'; -import { defineConfig, devices } from '@playwright/experimental-ct-react'; -import react from '@vitejs/plugin-react'; -import tsconfigPaths from 'vite-tsconfig-paths'; - -export default defineConfig({ - testDir: '.', - testMatch: [ - '**/packages/main/src/components/**/test/*.spec.tsx', - '**/packages/charts/src/**/test/*.spec.tsx', - '**/playwright/test/**/*.spec.tsx', - ], - testIgnore: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx', '**/*.mdx'], - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 0, - workers: process.env.CI ? '100%' : undefined, - // https://github.com/microsoft/playwright/issues/14511#issuecomment-1552589959 - reporter: process.env.CI - ? [ - ['list'], - ['github'], - [ - 'monocart-reporter', - { - name: 'Playwright Coverage Report', - outputFile: 'temp/playwright-coverage/report.html', - coverage: { - sourceFilter: (sourcePath: string) => { - const included = - sourcePath.includes('packages/main/src/components/SelectDialog') || - sourcePath.includes('packages/main/src/components/Splitter') || - (sourcePath.includes('packages/charts/src/') && - !sourcePath.includes('packages/charts/src/resources/') && - !sourcePath.includes('packages/charts/src/test-utils/') && - !sourcePath.includes('packages/charts/src/interfaces/') && - !sourcePath.includes('packages/charts/src/enums/')); - return ( - included && - !sourcePath.includes('node_modules') && - !sourcePath.includes('/dist/') && - !sourcePath.includes('/test/') && - !sourcePath.endsWith('.stories.tsx') && - !sourcePath.endsWith('.module.css.ts') && - !/packages\/[^/]+\/src\/index\.ts$/.test(sourcePath) - ); - }, - reports: ['lcovonly'], - outputDir: 'temp/playwright-coverage', - }, - }, - ], - ] - : 'html', - timeout: 10_000, - expect: { timeout: 4000 }, - use: { - trace: 'on-first-retry', - ctViteConfig: { - plugins: [ - react(), - tsconfigPaths({ - projects: [fileURLToPath(new URL('tsconfig.base.json', import.meta.url))], - }), - ], - optimizeDeps: { - esbuildOptions: { - target: 'esnext', - }, - exclude: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx'], - }, - build: { - target: 'esnext', - rollupOptions: { - external: [/\.cy\.tsx$/, /\.cy\.ts$/, /\.stories\.tsx$/], - }, - }, - }, - }, - projects: [ - { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, - { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, - { name: 'webkit', use: { ...devices['Desktop Safari'] } }, - ], -}); diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000000..13653851d13 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,38 @@ +import { defineConfig, devices } from '@playwright/test'; + +const GALLERY_PORT = 9876; +const GALLERY_URL = `http://localhost:${GALLERY_PORT}/`; + +export default defineConfig({ + testDir: '.', + testMatch: [ + 'packages/main/src/components/**/test/*.spec.tsx', + 'packages/charts/src/**/test/*.spec.tsx', + 'playwright/test/**/*.spec.tsx', + ], + testIgnore: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx', '**/*.mdx'], + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? '100%' : undefined, + reporter: process.env.CI ? [['list'], ['github']] : 'html', + timeout: 10_000, + expect: { timeout: 4000 }, + webServer: { + command: `yarn vite --config playwright/gallery/vite.config.ts --port ${GALLERY_PORT} --strictPort`, + url: GALLERY_URL, + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, + use: { + baseURL: GALLERY_URL, + serviceWorkers: 'block', + reuseContext: true, + trace: 'on-first-retry', + }, + projects: [ + { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, + { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, + { name: 'webkit', use: { ...devices['Desktop Safari'] } }, + ], +}); diff --git a/playwright/fixtures/gallery-fixtures.ts b/playwright/fixtures/gallery-fixtures.ts new file mode 100644 index 00000000000..6a7640d0d4a --- /dev/null +++ b/playwright/fixtures/gallery-fixtures.ts @@ -0,0 +1,46 @@ +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { test as base, expect } from '@playwright/test'; +import { UI5WCInternalHelpers } from './ui5-fixtures-internal.js'; + +// Gallery-model fixtures: `ui5wc` helpers + an auto `coverage` fixture that dumps Istanbul's `window.__coverage__` (present when served with `PW_COVERAGE=true`) for nyc. +const COVERAGE = process.env.PW_COVERAGE === 'true'; +const NYC_OUTPUT_DIR = path.resolve(process.cwd(), 'temp/.nyc_output_playwright'); + +type IstanbulCoverage = Record; + +export interface UI5WCGalleryFixtures { + coverage: void; + ui5wc: UI5WCInternalHelpers; +} + +export const test = base.extend({ + coverage: [ + async ({ page }, use, testInfo) => { + await use(); + + if (!COVERAGE) { + return; + } + + const data = await page + .evaluate( + () => (globalThis as unknown as { __coverage__?: IstanbulCoverage }).__coverage__, + ) + .catch(() => undefined); + + if (data && Object.keys(data).length > 0) { + await mkdir(NYC_OUTPUT_DIR, { recursive: true }); + const safeId = testInfo.testId.replace(/[^a-z0-9_-]/gi, '_'); + await writeFile(path.join(NYC_OUTPUT_DIR, `${safeId}.json`), JSON.stringify(data), 'utf8'); + } + }, + { scope: 'test', auto: true }, + ], + ui5wc: async ({ page }, use) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(new UI5WCInternalHelpers(page)); + }, +}); + +export { expect }; diff --git a/playwright/fixtures/main-fixtures.ts b/playwright/fixtures/main-fixtures.ts deleted file mode 100644 index 10ac56e2f9e..00000000000 --- a/playwright/fixtures/main-fixtures.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { TestInfo } from '@playwright/test'; -import { addCoverageReport } from 'monocart-reporter'; -import { test as base, expect, UI5WCInternalHelpers } from './ui5-fixtures-internal'; - -export interface UI5WCComponentTestFixtures { - autoTestFixture: string; - ui5wc: UI5WCInternalHelpers; -} - -export const test = base.extend({ - autoTestFixture: [ - async ({ page }, use: (arg: string) => Promise, testInfo: TestInfo) => { - const isChromium = testInfo.project.name === 'chromium'; - - if (isChromium) { - await page.coverage.startJSCoverage({ resetOnNavigation: false }); - } - - await use('autoTestFixture'); - - if (isChromium) { - const jsCoverage = await page.coverage.stopJSCoverage(); - await addCoverageReport(jsCoverage, testInfo); - } - }, - { scope: 'test', auto: true }, - ], - ui5wc: async ({ page }, use) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - await use(new UI5WCInternalHelpers(page)); - }, -}); - -export { expect }; diff --git a/playwright/fixtures/ui5-fixtures.ts b/playwright/fixtures/ui5-fixtures.ts index 11b462501c9..fbe21fa734d 100644 --- a/playwright/fixtures/ui5-fixtures.ts +++ b/playwright/fixtures/ui5-fixtures.ts @@ -1,4 +1,4 @@ -import { test as base, expect } from '@playwright/experimental-ct-react'; +import { test as base, expect } from '@playwright/test'; import type { Page, Locator } from '@playwright/test'; export interface UI5WCFixtures { diff --git a/playwright/index.html b/playwright/gallery/index.html similarity index 76% rename from playwright/index.html rename to playwright/gallery/index.html index d11d6018986..583938633ac 100644 --- a/playwright/index.html +++ b/playwright/gallery/index.html @@ -3,7 +3,7 @@ - Playwright Component Tests + Playwright Component Gallery - +
diff --git a/playwright/gallery/main.tsx b/playwright/gallery/main.tsx new file mode 100644 index 00000000000..52e625ff920 --- /dev/null +++ b/playwright/gallery/main.tsx @@ -0,0 +1,84 @@ +/** + * @fileoverview Playwright component-testing gallery entry. + * + * Serves the contract the built-in `mount` fixture (@playwright/test) expects: + * the page exposes `window.mount({ story, props })` / `window.unmount()`, rendering + * a story into `#root`. The mount fixture navigates here, calls `window.mount`, and + * returns a locator for `#root`. + * + * Stories are the named exports of `*.gallery.tsx` files anywhere in the repo. A + * story id is `/`, e.g. + * `playwright/test/UI5Fixtures/InputTestComp`. Any unique trailing suffix of an id + * also resolves. + */ + +import '@ui5/webcomponents-react/dist/Assets.js'; +import { createElement, StrictMode } from 'react'; +import { flushSync } from 'react-dom'; +import { createRoot, type Root } from 'react-dom/client'; +import { StoryWrapper } from './story-wrapper.js'; + +type StoryComponent = (props?: Record) => React.ReactNode; +type MountParams = { story: string; props?: Record }; + +declare global { + interface Window { + mount: (params: MountParams) => Promise; + unmount: () => Promise; + } +} + +// Eagerly glob all gallery files (relative to this file) so resolution is synchronous. +const modules = import.meta.glob(['../**/*.gallery.tsx', '../../packages/**/*.gallery.tsx'], { + eager: true, +}); + +// Flatten to `/` -> component. +const stories = new Map(); +for (const [path, mod] of Object.entries(modules)) { + const base = path.replace(/^(?:\.\.\/)+/, '').replace(/\.gallery\.tsx$/, ''); + for (const [exportName, value] of Object.entries(mod)) { + if (typeof value === 'function') { + stories.set(`${base}/${exportName}`, value as StoryComponent); + } + } +} + +function resolveStory(story: string): StoryComponent { + const exact = stories.get(story); + if (exact) { + return exact; + } + // Fall back to a shorthand suffix of the full id, as long as it's unique — e.g. a spec may mount `UI5Fixtures/InputTestComp` instead of the full `test/UI5Fixtures/InputTestComp`. + const matches = [...stories.keys()].filter((id) => id === story || id.endsWith(`/${story}`)); + if (matches.length === 1) { + return stories.get(matches[0])!; + } + if (matches.length > 1) { + throw new Error(`Ambiguous story id "${story}" matches:\n${matches.join('\n')}`); + } + throw new Error( + `Unknown story "${story}". Available stories:\n${[...stories.keys()].sort().join('\n') || '(none found)'}`, + ); +} + +const container = document.getElementById('root')!; +// Reuse a single root across mounts so update() reconciles and state is preserved. +let root: Root | null = null; + +// async to match the contract (the fixture awaits it); renders synchronously here. +// eslint-disable-next-line @typescript-eslint/require-await +window.mount = async ({ story, props }) => { + const Component = resolveStory(story); + root ??= createRoot(container); + // flushSync so a render error rejects mount() instead of being swallowed & enable StrictMode + flushSync(() => { + root!.render(createElement(StrictMode, null, createElement(StoryWrapper, null, createElement(Component, props)))); + }); +}; + +// eslint-disable-next-line @typescript-eslint/require-await +window.unmount = async () => { + root?.unmount(); + root = null; +}; diff --git a/playwright/gallery/story-wrapper.tsx b/playwright/gallery/story-wrapper.tsx new file mode 100644 index 00000000000..b9e90e8bb56 --- /dev/null +++ b/playwright/gallery/story-wrapper.tsx @@ -0,0 +1,6 @@ +import { ThemeProvider } from '@ui5/webcomponents-react/ThemeProvider'; +import type { ReactNode } from 'react'; + +export function StoryWrapper({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/playwright/gallery/vite-env.d.ts b/playwright/gallery/vite-env.d.ts new file mode 100644 index 00000000000..11f02fe2a00 --- /dev/null +++ b/playwright/gallery/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/playwright/gallery/vite.config.ts b/playwright/gallery/vite.config.ts new file mode 100644 index 00000000000..fabf72dc80e --- /dev/null +++ b/playwright/gallery/vite.config.ts @@ -0,0 +1,36 @@ +import { fileURLToPath } from 'node:url'; +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; +import IstanbulPlugin from 'vite-plugin-istanbul'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +const withCoverage = process.env.PW_COVERAGE === 'true'; + +export default defineConfig({ + root: fileURLToPath(new URL('.', import.meta.url)), + optimizeDeps: { + esbuildOptions: { + target: 'esnext', + }, + }, + build: { + target: 'esnext', + }, + plugins: [ + react(), + withCoverage && + IstanbulPlugin({ + include: [ + 'packages/main/src/components/SelectDialog/**/*', + 'packages/main/src/components/Splitter*/**/*', + 'packages/charts/src/**/*', + ], + extension: ['.ts', '.tsx'], + requireEnv: false, + forceBuildInstrument: true, + }), + tsconfigPaths({ + projects: [fileURLToPath(new URL('../../tsconfig.base.json', import.meta.url))], + }), + ].filter(Boolean), +}); diff --git a/playwright/index.tsx b/playwright/index.tsx deleted file mode 100644 index 67d7ab1e056..00000000000 --- a/playwright/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import '@ui5/webcomponents-react/dist/Assets.js'; -import { beforeMount } from '@playwright/experimental-ct-react/hooks'; -import { ThemeProvider } from '@ui5/webcomponents-react/ThemeProvider'; - -// eslint-disable-next-line @typescript-eslint/require-await -beforeMount(async ({ App }) => { - return ( - - - - ); -}); diff --git a/playwright/test/UI5FixturesTestComponents.tsx b/playwright/test/UI5Fixtures.gallery.tsx similarity index 100% rename from playwright/test/UI5FixturesTestComponents.tsx rename to playwright/test/UI5Fixtures.gallery.tsx diff --git a/playwright/test/internal-fixtures.spec.tsx b/playwright/test/internal-fixtures.spec.tsx index 4a2602aed52..2dd1918b197 100644 --- a/playwright/test/internal-fixtures.spec.tsx +++ b/playwright/test/internal-fixtures.spec.tsx @@ -1,10 +1,9 @@ -import { test } from '../fixtures/ui5-fixtures-internal'; -import { AttributeTestComp } from './UI5FixturesTestComponents'; +import { test } from '../fixtures/gallery-fixtures.js'; test.describe('Internal UI5 Web Components Fixtures', () => { // todo: create a useful test test('shouldNeverHaveAttribute - passes when attribute never appears', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/AttributeTestComp'); const button = page.getByTestId('test-button'); await ui5wc.shouldNeverHaveAttribute(button, 'disabled', { observerTime: 300 }); diff --git a/playwright/test/ui5-fixtures.spec.tsx b/playwright/test/ui5-fixtures.spec.tsx index 9fe07e56739..9ef54debed7 100644 --- a/playwright/test/ui5-fixtures.spec.tsx +++ b/playwright/test/ui5-fixtures.spec.tsx @@ -1,21 +1,8 @@ -import { expect, test } from '../fixtures/ui5-fixtures-internal'; -import { - ClearInputTestComp, - ComboBoxTestComp, - DialogTestComp, - InputTestComp, - InputWithSuggestionsTestComp, - MultiComboBoxTestComp, - MultiInputWithSuggestionsTestComp, - SelectTestComp, - TabContainerTestComp, - TabContainerWithNestedTabsTestComp, - TextAreaTestComp, -} from './UI5FixturesTestComponents.js'; +import { expect, test } from '../fixtures/gallery-fixtures.js'; test.describe('UI5 Web Components Fixtures', () => { test('typeIntoInput - types text into UI5 input', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/InputTestComp'); const input = page.getByTestId('test-input'); await ui5wc.typeIntoInput(input, 'Hello World'); @@ -25,7 +12,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('clearInput - clears UI5 input', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/ClearInputTestComp'); const input = page.getByTestId('test-input'); await ui5wc.typeIntoInput(input, 'initial value'); @@ -37,7 +24,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('typeIntoTextArea - types text into UI5 textarea', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/TextAreaTestComp'); const textarea = page.getByTestId('test-textarea'); await ui5wc.typeIntoTextArea(textarea, 'Multi-line\ntext'); @@ -47,7 +34,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('closePopupWithEsc - closes dialog with Escape key', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/DialogTestComp'); const dialog = page.getByTestId('test-dialog'); await expect(dialog).toHaveAttribute('open'); await expect(page.getByTestId('dialog-state')).toHaveText('open'); @@ -58,7 +45,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('openDropdownByClick - opens Select dropdown', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/SelectTestComp'); const select = page.getByTestId('test-select'); await ui5wc.openDropdownByClick(select); @@ -67,7 +54,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('openDropdownByClick - opens ComboBox dropdown', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/ComboBoxTestComp'); const combobox = page.getByTestId('test-combobox'); await ui5wc.openDropdownByClick(combobox); @@ -76,7 +63,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('openDropdownByClick - opens MultiComboBox dropdown', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/MultiComboBoxTestComp'); const multiComboBox = page.getByTestId('test-multicombobox'); await ui5wc.openDropdownByClick(multiComboBox); @@ -85,7 +72,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('clickDropdownItemByText - selects ComboBox item', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/ComboBoxTestComp'); const combobox = page.getByTestId('test-combobox'); await ui5wc.openDropdownByClick(combobox); @@ -95,7 +82,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('clickDropdownItemByText - selects Select option', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/SelectTestComp'); const select = page.getByTestId('test-select'); await ui5wc.openDropdownByClick(select); @@ -105,7 +92,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('clickDropdownItemByText - selects MultiComboBox item', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/MultiComboBoxTestComp'); const multiComboBox = page.getByTestId('test-multicombobox'); await ui5wc.openDropdownByClick(multiComboBox); @@ -115,7 +102,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('findTabByText - finds and clicks tab', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/TabContainerTestComp'); const tabContainer = page.getByTestId('test-tabcontainer'); const tab2 = ui5wc.findTabByText(tabContainer, 'Tab 2'); @@ -126,7 +113,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('findTabPopoverButtonByText - opens nested tabs popover', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/TabContainerWithNestedTabsTestComp'); const tabContainer = page.getByTestId('test-tabcontainer-nested'); const popoverButton = ui5wc.findTabPopoverButtonByText(tabContainer, 'Tab 2'); @@ -140,7 +127,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('typeIntoInput - shows ComboBox suggestions', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/ComboBoxTestComp'); const combobox = page.getByTestId('test-combobox'); await ui5wc.typeIntoInput(combobox, 'A'); @@ -149,7 +136,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('typeIntoInput - shows Input suggestions', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/InputWithSuggestionsTestComp'); const input = page.getByTestId('test-input-suggestions'); await ui5wc.typeIntoInput(input, 'S'); @@ -158,7 +145,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('typeIntoInput - shows MultiComboBox suggestions', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/MultiComboBoxTestComp'); const mcb = page.getByTestId('test-multicombobox'); await ui5wc.typeIntoInput(mcb, 'R'); @@ -167,7 +154,7 @@ test.describe('UI5 Web Components Fixtures', () => { }); test('typeIntoInput - shows MultiInput suggestions', async ({ mount, page, ui5wc }) => { - await mount(); + await mount('test/UI5Fixtures/MultiInputWithSuggestionsTestComp'); const multiInput = page.getByTestId('test-multiinput-suggestions'); await ui5wc.typeIntoInput(multiInput, 'X'); diff --git a/tsconfig.playwright.json b/tsconfig.playwright.json index 995060bafad..a898a45571a 100644 --- a/tsconfig.playwright.json +++ b/tsconfig.playwright.json @@ -8,5 +8,5 @@ "composite": true, "noEmit": true }, - "include": ["playwright", "**/*.spec.ts", "**/*.spec.tsx", "playwright-ct.config.ts"] + "include": ["playwright", "**/*.spec.ts", "**/*.spec.tsx", "playwright.config.ts"] } diff --git a/yarn.lock b/yarn.lock index ad118cc670c..14c25364771 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3628,29 +3628,6 @@ __metadata: languageName: node linkType: hard -"@playwright/experimental-ct-core@npm:1.62.1": - version: 1.62.1 - resolution: "@playwright/experimental-ct-core@npm:1.62.1" - dependencies: - playwright: "npm:1.62.1" - playwright-core: "npm:1.62.1" - vite: "npm:^8.1.0" - checksum: 10c0/f7daf149266fe770c46257b298c7712543d85d994bbc73764d7fcafd4070b73b1b76381a24c700b25fd3b6a19dabff5539bcce451f87601926e04070b4e02502 - languageName: node - linkType: hard - -"@playwright/experimental-ct-react@npm:1.62.1": - version: 1.62.1 - resolution: "@playwright/experimental-ct-react@npm:1.62.1" - dependencies: - "@playwright/experimental-ct-core": "npm:1.62.1" - "@vitejs/plugin-react": "npm:^6.0.3" - bin: - playwright: cli.js - checksum: 10c0/37ce8e3da770e7ce4f916b1629137f150f43c59569cec4304a48e78cd9f83526f0f5ff7aed9733eb0b62b4229dbe75063af1e56bd6f881c961d3eae1c44213e2 - languageName: node - linkType: hard - "@playwright/test@npm:1.62.1": version: 1.62.1 resolution: "@playwright/test@npm:1.62.1" @@ -5987,7 +5964,7 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-react@npm:6.0.5, @vitejs/plugin-react@npm:^6.0.3": +"@vitejs/plugin-react@npm:6.0.5": version: 6.0.5 resolution: "@vitejs/plugin-react@npm:6.0.5" dependencies: @@ -6459,16 +6436,6 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.8, accepts@npm:~1.3.8": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 - languageName: node - linkType: hard - "accepts@npm:^2.0.0": version: 2.0.0 resolution: "accepts@npm:2.0.0" @@ -6479,6 +6446,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + "acorn-import-attributes@npm:^1.9.5": version: 1.9.5 resolution: "acorn-import-attributes@npm:1.9.5" @@ -6497,15 +6474,6 @@ __metadata: languageName: node linkType: hard -"acorn-loose@npm:^8.5.2": - version: 8.5.2 - resolution: "acorn-loose@npm:8.5.2" - dependencies: - acorn: "npm:^8.15.0" - checksum: 10c0/169a85ad2df888586fe6eda2af9ff8fbcca7174ce83d00632cea5fffa73476218012af91be0cd6b68e4df6183baffa463506fd2ab54800903378140e679302fd - languageName: node - linkType: hard - "acorn-walk@npm:^8.3.5": version: 8.3.5 resolution: "acorn-walk@npm:8.3.5" @@ -8256,13 +8224,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^14.0.3": - version: 14.0.3 - resolution: "commander@npm:14.0.3" - checksum: 10c0/755652564bbf56ff2ff083313912b326450d3f8d8c85f4b71416539c9a05c3c67dbd206821ca72635bf6b160e2afdefcb458e86b317827d5cb333b69ce7f1a24 - languageName: node - linkType: hard - "commander@npm:^6.2.0, commander@npm:^6.2.1": version: 6.2.1 resolution: "commander@npm:6.2.1" @@ -8361,13 +8322,6 @@ __metadata: languageName: node linkType: hard -"console-grid@npm:^2.2.4": - version: 2.2.4 - resolution: "console-grid@npm:2.2.4" - checksum: 10c0/4e8b1d56a6537a33882c1b7f4ea49b1aaec3b01b81d18476f1e3a33a359ff823afb13f04e160e712cc24b0af4fd8ecc3037006b650653f39638885c68d2d8cb4 - languageName: node - linkType: hard - "content-disposition@npm:^1.0.0": version: 1.1.0 resolution: "content-disposition@npm:1.1.0" @@ -8384,13 +8338,6 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:~1.0.1": - version: 1.0.1 - resolution: "content-disposition@npm:1.0.1" - checksum: 10c0/bd7ff1fe8d2542d3a2b9a29428cc3591f6ac27bb5595bba2c69664408a68f9538b14cbd92479796ea835b317a09a527c8c7209c4200381dedb0c34d3b658849e - languageName: node - linkType: hard - "content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" @@ -8542,16 +8489,6 @@ __metadata: languageName: node linkType: hard -"cookies@npm:~0.9.1": - version: 0.9.1 - resolution: "cookies@npm:0.9.1" - dependencies: - depd: "npm:~2.0.0" - keygrip: "npm:~1.1.0" - checksum: 10c0/3ffa1c0e992b62ee119adae4dd2ddd4a89166fa5434cd9bd9ff84ec4d2f14dfe2318a601280abfe32a4f64f884ec9345fb1912e488b002d188d2efa0d3919ba3 - languageName: node - linkType: hard - "core-js-compat@npm:^3.48.0": version: 3.49.0 resolution: "core-js-compat@npm:3.49.0" @@ -9209,13 +9146,6 @@ __metadata: languageName: node linkType: hard -"deep-equal@npm:~1.0.1": - version: 1.0.1 - resolution: "deep-equal@npm:1.0.1" - checksum: 10c0/bef838ef9824e124d10335deb9c7540bfc9f2f0eab17ad1bb870d0eee83ee4e7e6f6f892e5eebc2bd82759a76676926ad5246180097e28e57752176ff7dae888 - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -9315,13 +9245,6 @@ __metadata: languageName: node linkType: hard -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 - languageName: node - linkType: hard - "depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" @@ -9329,13 +9252,6 @@ __metadata: languageName: node linkType: hard -"depd@npm:~1.1.2": - version: 1.1.2 - resolution: "depd@npm:1.1.2" - checksum: 10c0/acb24aaf936ef9a227b6be6d495f0d2eb20108a9a6ad40585c5bda1a897031512fef6484e4fdbb80bd249fdaa82841fa1039f416ece03188e677ba11bcfda249 - languageName: node - linkType: hard - "dependency-graph@npm:^0.11.0": version: 0.11.0 resolution: "dependency-graph@npm:0.11.0" @@ -9364,7 +9280,7 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0, destroy@npm:^1.2.0, destroy@npm:~1.2.0": +"destroy@npm:1.2.0, destroy@npm:~1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 @@ -9676,13 +9592,6 @@ __metadata: languageName: node linkType: hard -"eight-colors@npm:^1.3.3": - version: 1.3.3 - resolution: "eight-colors@npm:1.3.3" - checksum: 10c0/b72daf79397fd95f14f1b4db374ea6c69d964d39e4affea61a1d17614b6b2b60b90f57725b6db8b9d93fc2db941c2c6555ab09afe9fc34c66cc0cae35f194057 - languageName: node - linkType: hard - "ejs@npm:5.0.1": version: 5.0.1 resolution: "ejs@npm:5.0.1" @@ -11295,15 +11204,6 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^4.0.3": - version: 4.0.3 - resolution: "foreground-child@npm:4.0.3" - dependencies: - signal-exit: "npm:^4.0.1" - checksum: 10c0/bc4964f92478ff17bac24d875e894e02131a369604a8339b8137c7dfdc38f0e4423eddb35583f417b89b01845507bfb318b149d2428aba65b43880f2214dc416 - languageName: node - linkType: hard - "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -12264,16 +12164,6 @@ __metadata: languageName: node linkType: hard -"http-assert@npm:^1.5.0": - version: 1.5.0 - resolution: "http-assert@npm:1.5.0" - dependencies: - deep-equal: "npm:~1.0.1" - http-errors: "npm:~1.8.0" - checksum: 10c0/7b4e631114a1a77654f9ba3feb96da305ddbdeb42112fe384b7b3249c7141e460d7177970155bea6e54e655a04850415b744b452c1fe5052eba6f4186d16b095 - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" @@ -12294,19 +12184,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:~1.8.0": - version: 1.8.1 - resolution: "http-errors@npm:1.8.1" - dependencies: - depd: "npm:~1.1.2" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:>= 1.5.0 < 2" - toidentifier: "npm:1.0.1" - checksum: 10c0/f01aeecd76260a6fe7f08e192fcbe9b2f39ed20fc717b852669a69930167053b01790998275c6297d44f435cf0e30edd50c05223d1bec9bc484e6cf35b2d6f43 - languageName: node - linkType: hard - "http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -13254,7 +13131,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b @@ -13296,7 +13173,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": +"istanbul-lib-report@npm:^3.0.0": version: 3.0.1 resolution: "istanbul-lib-report@npm:3.0.1" dependencies: @@ -13318,7 +13195,7 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.0.2, istanbul-reports@npm:^3.2.0": +"istanbul-reports@npm:^3.0.2": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" dependencies: @@ -13671,15 +13548,6 @@ __metadata: languageName: node linkType: hard -"keygrip@npm:~1.1.0": - version: 1.1.0 - resolution: "keygrip@npm:1.1.0" - dependencies: - tsscmp: "npm:1.0.6" - checksum: 10c0/2aceec1a1e642a0caf938044056ed67b1909cfe67a93a59b32aae2863e0f35a1a53782ecc8f9cd0e3bdb60863fa0f401ccbd257cd7dfae61915f78445139edea - languageName: node - linkType: hard - "keyv@npm:^4.0.0, keyv@npm:^4.5.3, keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -13703,46 +13571,6 @@ __metadata: languageName: node linkType: hard -"koa-compose@npm:^4.1.0": - version: 4.1.0 - resolution: "koa-compose@npm:4.1.0" - checksum: 10c0/f1f786f994a691931148e7f38f443865bf2702af4a61610d1eea04dab79c04b1232285b59d82a0cf61c830516dd92f10ab0d009b024fcecd4098e7d296ab771a - languageName: node - linkType: hard - -"koa-static-resolver@npm:^1.0.6": - version: 1.0.6 - resolution: "koa-static-resolver@npm:1.0.6" - checksum: 10c0/c23d61b322737bb6252acd5aee1642cc1957d12957a9b8e4a0fc1cbbca6c49bac24b5b5c0d80ec6a9137bf4dee87930150928c72b16620a94b7114a422f89e83 - languageName: node - linkType: hard - -"koa@npm:^3.2.1": - version: 3.2.1 - resolution: "koa@npm:3.2.1" - dependencies: - accepts: "npm:^1.3.8" - content-disposition: "npm:~1.0.1" - content-type: "npm:^1.0.5" - cookies: "npm:~0.9.1" - delegates: "npm:^1.0.0" - destroy: "npm:^1.2.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - fresh: "npm:~0.5.2" - http-assert: "npm:^1.5.0" - http-errors: "npm:^2.0.0" - koa-compose: "npm:^4.1.0" - mime-types: "npm:^3.0.1" - on-finished: "npm:^2.4.1" - parseurl: "npm:^1.3.3" - statuses: "npm:^2.0.1" - type-is: "npm:^2.0.1" - vary: "npm:^1.1.2" - checksum: 10c0/84352ef0ec0f54898f119ac589c7d96442ac21f494f3e1abca55d19c0cd51177f43b75440092d74730261d4d91270b5ba0396156e04b21132ab2251f8b4d2cde - languageName: node - linkType: hard - "konan@npm:^2.1.1": version: 2.1.1 resolution: "konan@npm:2.1.1" @@ -14404,13 +14232,6 @@ __metadata: languageName: node linkType: hard -"lz-utils@npm:^2.1.1": - version: 2.1.1 - resolution: "lz-utils@npm:2.1.1" - checksum: 10c0/f9d160a487641762ca09cab92468a0f3d7e6f6b849cd2607faf7ac3d8dcbd64336103f01ade056387daabaf0f970c309af978fd5298bb17093f8cfe6733e7914 - languageName: node - linkType: hard - "magic-string@npm:^0.30.0, magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" @@ -15657,7 +15478,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1, mime-types@npm:^3.0.2": +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.2": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -15945,52 +15766,6 @@ __metadata: languageName: node linkType: hard -"monocart-coverage-reports@npm:^2.12.12": - version: 2.12.12 - resolution: "monocart-coverage-reports@npm:2.12.12" - dependencies: - acorn: "npm:^8.16.0" - acorn-loose: "npm:^8.5.2" - acorn-walk: "npm:^8.3.5" - commander: "npm:^14.0.3" - console-grid: "npm:^2.2.4" - eight-colors: "npm:^1.3.3" - foreground-child: "npm:^4.0.3" - istanbul-lib-coverage: "npm:^3.2.2" - istanbul-lib-report: "npm:^3.0.1" - istanbul-reports: "npm:^3.2.0" - lz-utils: "npm:^2.1.1" - monocart-locator: "npm:^1.0.3" - bin: - mcr: lib/cli.js - checksum: 10c0/71c9a2f6bb330fd1f0ec4628bb1a943828a8647a68c4286dcdc05b901a38da4834d37f6acf3883d9db2cfb2558a75250245cd4e9ca81d24c5565f71851b520cd - languageName: node - linkType: hard - -"monocart-locator@npm:^1.0.3": - version: 1.0.3 - resolution: "monocart-locator@npm:1.0.3" - checksum: 10c0/4682a0a32f255aa9f204fe7264280c9479c3035183b473ba60feafea773e2d973d2b6a4eddb1057bf5cc3e8ad48a1a41e2a2ac4919e7984e45e1e9e270e443ba - languageName: node - linkType: hard - -"monocart-reporter@npm:2.12.4": - version: 2.12.4 - resolution: "monocart-reporter@npm:2.12.4" - dependencies: - console-grid: "npm:^2.2.4" - eight-colors: "npm:^1.3.3" - koa: "npm:^3.2.1" - koa-static-resolver: "npm:^1.0.6" - lz-utils: "npm:^2.1.1" - monocart-coverage-reports: "npm:^2.12.12" - monocart-locator: "npm:^1.0.3" - bin: - monocart: lib/cli.js - checksum: 10c0/1b5bea14515a7b73c350797bc299c511119b2562f85f2a491c0ce0f9655a00ea7a66328ab1b50d8e4cefc8513f4fe3bd3a2758e2dd50c0681ca101153010afd5 - languageName: node - linkType: hard - "morgan@npm:^1.7.0": version: 1.11.0 resolution: "morgan@npm:1.11.0" @@ -20162,13 +19937,6 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.5.0 < 2": - version: 1.5.0 - resolution: "statuses@npm:1.5.0" - checksum: 10c0/e433900956357b3efd79b1c547da4d291799ac836960c016d10a98f6a810b1b5c0dcc13b5a7aa609a58239b5190e1ea176ad9221c2157d2fd1c747393e6b2940 - languageName: node - linkType: hard - "statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.1, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" @@ -20811,7 +20579,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": +"toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 @@ -20954,13 +20722,6 @@ __metadata: languageName: node linkType: hard -"tsscmp@npm:1.0.6": - version: 1.0.6 - resolution: "tsscmp@npm:1.0.6" - checksum: 10c0/2f79a9455e7e3e8071995f98cdf3487ccfc91b760bec21a9abb4d90519557eafaa37246e87c92fa8bf3fef8fd30cfd0cc3c4212bb929baa9fb62494bfa4d24b2 - languageName: node - linkType: hard - "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -21247,7 +21008,6 @@ __metadata: "@cypress/code-coverage": "npm:4.0.3" "@eslint/compat": "npm:2.1.0" "@eslint/js": "npm:9.39.5" - "@playwright/experimental-ct-react": "npm:1.62.1" "@playwright/test": "npm:1.62.1" "@semantic-release/github": "npm:12.0.9" "@stackblitz/sdk": "npm:1.11.1" @@ -21286,7 +21046,6 @@ __metadata: husky: "npm:9.1.7" lerna: "npm:10.0.0" lint-staged: "npm:17.2.0" - monocart-reporter: "npm:2.12.4" npm-run-all2: "npm:9.0.3" pagefind: "npm:1.5.2" postcss: "npm:8.5.25" @@ -21949,7 +21708,7 @@ __metadata: languageName: node linkType: hard -"vite@npm:8.2.0, vite@npm:^8.1.0": +"vite@npm:8.2.0": version: 8.2.0 resolution: "vite@npm:8.2.0" dependencies: From f405267654888fa395d579c03b83ff65fe0df0e4 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Wed, 5 Aug 2026 14:05:21 +0200 Subject: [PATCH 2/4] Update package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index e1b10dbb97f..910323fc69b 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,7 @@ "test": "yarn test:prepare && cypress run --component --browser chrome --spec packages", "test:pw": "playwright test", "test:pw:open": "playwright test --ui", - "test:pw:ci": "cross-env PW_COVERAGE=true playwright test --project chromium && yarn test:pw:coverage:report", - "test:pw:coverage:report": "nyc report --temp-dir temp/.nyc_output_playwright --report-dir temp/playwright-coverage --reporter lcovonly", + "test:pw:ci": "cross-env PW_COVERAGE=true playwright test --project chromium && nyc report --temp-dir temp/.nyc_output_playwright --report-dir temp/playwright-coverage --reporter lcovonly", "clean": "tsc --build --clean && tsc --build tsconfig.build.json --clean && rimraf temp .out test-results playwright-report && lerna run clean", "clean:remove-modules": "yarn clean && rimraf node_modules", "prettier:all": "prettier --write --config ./prettier.config.js \"**/*\"", From ba86df2b25e2401dda6d67d9ed489cb4e838f3c1 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Wed, 5 Aug 2026 14:20:15 +0200 Subject: [PATCH 3/4] add `nyc` --- package.json | 3 ++- yarn.lock | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 910323fc69b..9e82a5ffd94 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "yarn test:prepare && cypress run --component --browser chrome --spec packages", "test:pw": "playwright test", "test:pw:open": "playwright test --ui", - "test:pw:ci": "cross-env PW_COVERAGE=true playwright test --project chromium && nyc report --temp-dir temp/.nyc_output_playwright --report-dir temp/playwright-coverage --reporter lcovonly", + "test:pw:ci": "PW_COVERAGE=true playwright test --project chromium && nyc report --temp-dir temp/.nyc_output_playwright --report-dir temp/playwright-coverage --reporter lcovonly", "clean": "tsc --build --clean && tsc --build tsconfig.build.json --clean && rimraf temp .out test-results playwright-report && lerna run clean", "clean:remove-modules": "yarn clean && rimraf node_modules", "prettier:all": "prettier --write --config ./prettier.config.js \"**/*\"", @@ -98,6 +98,7 @@ "lerna": "10.0.0", "lint-staged": "17.2.0", "npm-run-all2": "9.0.3", + "nyc": "18.0.0", "pagefind": "1.5.2", "postcss": "8.5.25", "postcss-cli": "11.0.1", diff --git a/yarn.lock b/yarn.lock index 51bf0e1ba3c..8f80ca3313a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16418,7 +16418,7 @@ __metadata: languageName: node linkType: hard -"nyc@npm:^18.0.0": +"nyc@npm:18.0.0, nyc@npm:^18.0.0": version: 18.0.0 resolution: "nyc@npm:18.0.0" dependencies: @@ -21047,6 +21047,7 @@ __metadata: lerna: "npm:10.0.0" lint-staged: "npm:17.2.0" npm-run-all2: "npm:9.0.3" + nyc: "npm:18.0.0" pagefind: "npm:1.5.2" postcss: "npm:8.5.25" postcss-cli: "npm:11.0.1" From e8b5685d59c0281297374b72cbac1b8cf6daaf1f Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Wed, 5 Aug 2026 14:38:01 +0200 Subject: [PATCH 4/4] Update TimelineChart.spec.tsx --- .../components/TimelineChart/test/TimelineChart.spec.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx b/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx index d4e0ae92e4d..0513858265a 100644 --- a/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx +++ b/packages/charts/src/components/TimelineChart/test/TimelineChart.spec.tsx @@ -53,7 +53,9 @@ test.describe('TimelineChart', () => { test('throws InvalidDiscreteLabelError', async ({ mount, page }) => { const errors: Error[] = []; page.on('pageerror', (err) => errors.push(err)); - await mount('TimelineChart/InvalidDiscreteLabelTest'); + // The component throws in an effect; with flushSync that can reject mount() too — the error + // still reaches `pageerror`, which is what we assert on, so ignore the mount rejection. + await mount('TimelineChart/InvalidDiscreteLabelTest').catch(() => undefined); await expect .poll(() => errors.some((e) => e.name === 'InvalidDiscreteLabelError' || e.message.includes('discreteLabels'))) .toBe(true); @@ -62,7 +64,7 @@ test.describe('TimelineChart', () => { test('throws IllegalConnectionError (1)', async ({ mount, page }) => { const errors: Error[] = []; page.on('pageerror', (err) => errors.push(err)); - await mount('TimelineChart/IllegalConnectionTest1'); + await mount('TimelineChart/IllegalConnectionTest1').catch(() => undefined); await expect .poll(() => errors.some((e) => e.name === 'IllegalConnectionError' || e.message.includes('connection'))) .toBe(true); @@ -71,7 +73,7 @@ test.describe('TimelineChart', () => { test('throws IllegalConnectionError (2)', async ({ mount, page }) => { const errors: Error[] = []; page.on('pageerror', (err) => errors.push(err)); - await mount('TimelineChart/IllegalConnectionTest2'); + await mount('TimelineChart/IllegalConnectionTest2').catch(() => undefined); await expect .poll(() => errors.some((e) => e.name === 'IllegalConnectionError' || e.message.includes('connection'))) .toBe(true);