) {
+ return JSON.stringify({
+ ...result,
+ lastInStack: [...result.lastInStack],
+ });
+}
+
+export function PrepareDimensionsDefault() {
+ const result = usePrepareDimensionsAndMeasures(dimensions, measures);
+ return {serializeResult(result)};
+}
+
+export function PrepareDimensionsWithDefaults() {
+ const result = usePrepareDimensionsAndMeasures(
+ dimensions,
+ measures,
+ { dimensionDefault: true },
+ { measureDefault: true },
+ );
+ return {serializeResult(result)};
+}
+
+export function PrepareDimensionsNoOverwrite() {
+ const result = usePrepareDimensionsAndMeasures(
+ dimensions,
+ measures,
+ { dimensionDefault: true, accessor: 'I should not be in the result' },
+ { measureDefault: true, accessor: 'I should not be in the result' },
+ );
+ return {serializeResult(result)};
+}
+
+// --- useTooltipFormatter test components ---
+
+export function TooltipFormatterNoFormatter() {
+ const val = useTooltipFormatter([{ accessor: 'test' }]);
+ return {val(100, 'value', { dataKey: 'test' })};
+}
+
+export function TooltipFormatterInvalid() {
+ const val = useTooltipFormatter([{ accessor: 'test', formatter: 'abc' }]);
+ return {val(100, 'value', { dataKey: 'test' })};
+}
+
+export function TooltipFormatterValid() {
+ const val = useTooltipFormatter([{ accessor: 'test', formatter: (v: number) => v / 10 }]);
+ return {val(100, 'value', { dataKey: 'test' })};
+}
diff --git a/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx b/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx
new file mode 100644
index 00000000000..c128a6e0317
--- /dev/null
+++ b/packages/charts/src/hooks/test/useLabelFormatter.spec.tsx
@@ -0,0 +1,19 @@
+import { expect, test } from '../../../../../playwright/fixtures/main-fixtures.js';
+import { LabelFormatterInvalid, LabelFormatterNull, LabelFormatterValid } from './HookTestComponents.js';
+
+test.describe('useLabelFormatter', () => {
+ test('should return value when no formatter is present', async ({ mount }) => {
+ const component = await mount();
+ await expect(component.getByText('100')).toBeVisible();
+ });
+
+ test('should not crash on invalid formatter', async ({ mount }) => {
+ const component = await mount();
+ await expect(component.getByText('100')).toBeVisible();
+ });
+
+ test('should format the value with a valid formatter', async ({ mount }) => {
+ const component = await mount();
+ 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
new file mode 100644
index 00000000000..95536903127
--- /dev/null
+++ b/packages/charts/src/hooks/test/usePrepareDimensionsAndMeasures.spec.tsx
@@ -0,0 +1,44 @@
+import { expect, test } from '../../../../../playwright/fixtures/main-fixtures.js';
+import {
+ PrepareDimensionsDefault,
+ PrepareDimensionsNoOverwrite,
+ PrepareDimensionsWithDefaults,
+} from './HookTestComponents.js';
+
+test.describe('usePrepareDimensionsAndMeasures', () => {
+ test('should not throw an error when no defaults are passed', async ({ mount, page }) => {
+ await mount();
+ const resultText = await page.getByTestId('result').textContent();
+ const result = JSON.parse(resultText);
+ expect(result).toEqual({
+ dimensions: [{ accessor: 'a', reactKey: 'a' }],
+ measures: [{ accessor: 'b', reactKey: 'b' }],
+ stackGroups: {},
+ lastInStack: [],
+ });
+ });
+
+ test('should merge defaults', async ({ mount, page }) => {
+ await mount();
+ const resultText = await page.getByTestId('result').textContent();
+ const result = JSON.parse(resultText);
+ expect(result).toEqual({
+ dimensions: [{ accessor: 'a', dimensionDefault: true, reactKey: 'a' }],
+ measures: [{ accessor: 'b', measureDefault: true, reactKey: 'b' }],
+ stackGroups: {},
+ lastInStack: [],
+ });
+ });
+
+ test('should merge defaults but not overwrite existing properties', async ({ mount, page }) => {
+ await mount();
+ const resultText = await page.getByTestId('result').textContent();
+ const result = JSON.parse(resultText);
+ expect(result).toEqual({
+ dimensions: [{ accessor: 'a', dimensionDefault: true, reactKey: 'a' }],
+ measures: [{ accessor: 'b', measureDefault: true, reactKey: 'b' }],
+ stackGroups: {},
+ lastInStack: [],
+ });
+ });
+});
diff --git a/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx b/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx
new file mode 100644
index 00000000000..9ce93831e69
--- /dev/null
+++ b/packages/charts/src/hooks/test/useTooltipFormatter.spec.tsx
@@ -0,0 +1,19 @@
+import { expect, test } from '../../../../../playwright/fixtures/main-fixtures.js';
+import { TooltipFormatterInvalid, TooltipFormatterNoFormatter, TooltipFormatterValid } from './HookTestComponents.js';
+
+test.describe('useTooltipFormatter', () => {
+ test('should return value when no formatter is present', async ({ mount }) => {
+ const component = await mount();
+ await expect(component.getByText('100')).toBeVisible();
+ });
+
+ test('should not crash on invalid formatter', async ({ mount }) => {
+ const component = await mount();
+ await expect(component.getByText('100')).toBeVisible();
+ });
+
+ test('should format the value with a valid formatter', async ({ mount }) => {
+ const component = await mount();
+ await expect(component.getByText('10')).toBeVisible();
+ });
+});
diff --git a/packages/charts/src/hooks/useLabelFormatter.cy.tsx b/packages/charts/src/hooks/useLabelFormatter.cy.tsx
deleted file mode 100644
index 4a34040cf2d..00000000000
--- a/packages/charts/src/hooks/useLabelFormatter.cy.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { TooltipLabelFormatter } from '../interfaces/index.js';
-import { useLabelFormatter } from './useLabelFormatter.js';
-
-function LabelFormatterComponent({ formatter }: { formatter?: TooltipLabelFormatter | string }) {
- const val = useLabelFormatter(formatter as any);
- return {val(100, undefined)};
-}
-
-describe('useLabelFormatter', () => {
- it('should return value when no formatter is present', () => {
- cy.mount();
- cy.findByText('100').should('be.visible');
- });
-
- it('should not crash on invalid formatter', () => {
- cy.mount();
- cy.findByText('100').should('be.visible');
- });
-
- it('should format the value with a valid formatter', () => {
- cy.mount( val / 10} />);
- cy.findByText('10').should('be.visible');
- });
-});
diff --git a/packages/charts/src/hooks/usePrepareDimensionsAndMeasures.cy.tsx b/packages/charts/src/hooks/usePrepareDimensionsAndMeasures.cy.tsx
deleted file mode 100644
index 5dbe0a33822..00000000000
--- a/packages/charts/src/hooks/usePrepareDimensionsAndMeasures.cy.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { usePrepareDimensionsAndMeasures } from './usePrepareDimensionsAndMeasures.js';
-
-const dimensions = [
- {
- accessor: 'a',
- },
-];
-
-const measures = [
- {
- accessor: 'b',
- },
-];
-
-// eslint-disable-next-line react/prop-types
-function TestComponent({ onHookResult, dimensionOptions = undefined, measureOptions = undefined }) {
- const result = usePrepareDimensionsAndMeasures(dimensions, measures, dimensionOptions, measureOptions);
- onHookResult(result);
- return null;
-}
-
-describe('useLabelFormatter', () => {
- it('should not throw an error when no defaults are passed', () => {
- const result = cy.spy().as('result');
- cy.mount();
-
- cy.get('@result').should('have.been.calledWith', {
- dimensions: [
- {
- accessor: 'a',
- reactKey: 'a',
- },
- ],
- measures: [
- {
- accessor: 'b',
- reactKey: 'b',
- },
- ],
- stackGroups: {},
- lastInStack: new Set(),
- });
- });
-
- it('should merge defaults', () => {
- const result = cy.spy().as('result');
- cy.mount(
- ,
- );
-
- cy.get('@result').should('have.been.calledWith', {
- dimensions: [
- {
- accessor: 'a',
- dimensionDefault: true,
- reactKey: 'a',
- },
- ],
- measures: [
- {
- accessor: 'b',
- measureDefault: true,
- reactKey: 'b',
- },
- ],
- stackGroups: {},
- lastInStack: new Set(),
- });
- });
-
- it('should merge defaults but not overwrite existing properties', () => {
- const result = cy.spy().as('result');
- cy.mount(
- ,
- );
-
- cy.get('@result').should('have.been.calledWith', {
- dimensions: [
- {
- accessor: 'a',
- dimensionDefault: true,
- reactKey: 'a',
- },
- ],
- measures: [
- {
- accessor: 'b',
- measureDefault: true,
- reactKey: 'b',
- },
- ],
- stackGroups: {},
- lastInStack: new Set(),
- });
- });
-});
diff --git a/packages/charts/src/hooks/useTooltipFormatter.cy.tsx b/packages/charts/src/hooks/useTooltipFormatter.cy.tsx
deleted file mode 100644
index 962e6a0ae64..00000000000
--- a/packages/charts/src/hooks/useTooltipFormatter.cy.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { useTooltipFormatter } from './useTooltipFormatter.js';
-
-// eslint-disable-next-line react/prop-types
-function TooltipFormatterComponent({ measure, value, name, options }) {
- const val = useTooltipFormatter([measure]);
- return {val(value, name, options)};
-}
-
-describe('useTooltipFormatter', () => {
- it('should return value when no formatter is present', () => {
- cy.mount(
- ,
- );
- cy.findByText('100').should('be.visible');
- });
-
- it('should not crash on invalid formatter', () => {
- cy.mount(
- ,
- );
- cy.findByText('100').should('be.visible');
- });
-
- it('should format the value with a valid formatter', () => {
- cy.mount(
- val / 10 }}
- value={100}
- name={'value'}
- options={{ dataKey: 'test' }}
- />,
- );
- cy.findByText('10').should('be.visible');
- });
-});
diff --git a/packages/charts/src/test-utils/componentFactories.tsx b/packages/charts/src/test-utils/componentFactories.tsx
new file mode 100644
index 00000000000..7582d7789a4
--- /dev/null
+++ b/packages/charts/src/test-utils/componentFactories.tsx
@@ -0,0 +1,147 @@
+import type { ComponentType } from 'react';
+import { useState } from 'react';
+
+/**
+ * Factory that creates a click-tracking test component for charts that use onClick + onLegendClick.
+ *
+ * Options:
+ * - noAnimation: pass `noAnimation` prop to the chart
+ * - trackLegendValue: include a `last-legend-value` span tracking `e.detail?.value`
+ * - trackPayload: include a `last-payload` span tracking `e.detail?.payload` (default: true)
+ */
+export function createClickTestComponent(
+ Chart: ComponentType,
+ baseProps: Record,
+ options?: { noAnimation?: boolean; trackLegendValue?: boolean; trackPayload?: boolean },
+) {
+ const { noAnimation = false, trackLegendValue = false, trackPayload = true } = options || {};
+
+ return function ClickTestComponent() {
+ const [clickCount, setClickCount] = useState(0);
+ const [lastPayload, setLastPayload] = useState('');
+ const [legendClickCount, setLegendClickCount] = useState(0);
+ const [lastLegendValue, setLastLegendValue] = useState('');
+ const [lastLegendDataKey, setLastLegendDataKey] = useState('');
+
+ return (
+ <>
+ {clickCount}
+ {trackPayload && {lastPayload}}
+ {legendClickCount}
+ {trackLegendValue && {lastLegendValue}}
+ {lastLegendDataKey}
+ {
+ setClickCount((c) => c + 1);
+ if (trackPayload) {
+ setLastPayload(JSON.stringify(e.detail?.payload));
+ }
+ }}
+ onLegendClick={(e: any) => {
+ setLegendClickCount((c) => c + 1);
+ if (trackLegendValue) {
+ setLastLegendValue(e.detail?.value || '');
+ }
+ setLastLegendDataKey(e.detail?.dataKey || '');
+ }}
+ />
+ >
+ );
+ };
+}
+
+/**
+ * Factory for legend config test component.
+ */
+export function createLegendConfigTestComponent(Chart: ComponentType, baseProps: Record) {
+ return function LegendConfigTestComponent() {
+ return (
+ {value}🐱,
+ },
+ }}
+ />
+ );
+ };
+}
+
+/**
+ * Factory for onDataPointClick test component.
+ * Tracks: click count, dataKey, value, dataIndex, payload.
+ */
+export function createDataPointClickTestComponent(
+ Chart: ComponentType,
+ baseProps: Record,
+ options?: { noAnimation?: boolean },
+) {
+ const { noAnimation = true } = options || {};
+
+ return function DataPointClickTestComponent() {
+ const [clickCount, setClickCount] = useState(0);
+ const [lastDataKey, setLastDataKey] = useState('');
+ const [lastValue, setLastValue] = useState('');
+ const [lastDataIndex, setLastDataIndex] = useState(-1);
+ const [lastPayload, setLastPayload] = useState('');
+
+ return (
+ <>
+ {clickCount}
+ {lastDataKey}
+ {lastValue}
+ {lastDataIndex}
+ {lastPayload}
+ {
+ setClickCount((c) => c + 1);
+ setLastDataKey(e.detail?.dataKey || '');
+ setLastValue(String(e.detail?.value ?? ''));
+ setLastDataIndex(e.detail?.dataIndex ?? -1);
+ setLastPayload(JSON.stringify(e.detail?.payload));
+ }}
+ />
+ >
+ );
+ };
+}
+
+/**
+ * Factory for highlightColor test component.
+ */
+export function createHighlightColorTestComponent(
+ Chart: ComponentType,
+ baseProps: Record,
+ highlightMeasures: any[],
+) {
+ return function HighlightColorTestComponent() {
+ return ;
+ };
+}
+
+/**
+ * Factory for secondYAxis test component.
+ */
+export function createSecondYAxisTestComponent(
+ Chart: ComponentType,
+ baseProps: Record,
+ secondYAxisDataKey: string,
+) {
+ return function SecondYAxisTestComponent() {
+ return ;
+ };
+}
+
+/**
+ * Factory for vertical layout test component.
+ */
+export function createVerticalLayoutTestComponent(Chart: ComponentType, baseProps: Record) {
+ return function VerticalLayoutTestComponent() {
+ return ;
+ };
+}
diff --git a/packages/charts/src/test-utils/shared.tsx b/packages/charts/src/test-utils/shared.tsx
new file mode 100644
index 00000000000..99d782a1067
--- /dev/null
+++ b/packages/charts/src/test-utils/shared.tsx
@@ -0,0 +1,29 @@
+import { expect } from '@playwright/experimental-ct-react';
+import type { Page } from '@playwright/test';
+
+export async function assertPassThroughProps(page: Page) {
+ const testId = 'component-to-be-tested';
+ const el = page.getByTestId(testId);
+ await expect(el).toBeAttached();
+ await expect(el).toHaveClass(/thisClassIsUsedForTestingPurposesOnly/);
+ await expect(el).toHaveCSS('pointer-events', 'none');
+ await expect(el).toHaveAttribute('aria-labelledby', 'aria-prop');
+ await expect(el).toHaveAttribute('customattribute', 'true');
+ await expect(el).toHaveAttribute('data-special-test-prop', 'data-prop');
+ await expect(el).toHaveAttribute('id', 'element-id');
+ await expect(page.locator('[title="Tooltip"]')).toBeAttached();
+}
+
+export function passThroughProps(extraProps?: object) {
+ return {
+ 'data-testid': 'component-to-be-tested',
+ 'data-special-test-prop': 'data-prop',
+ 'aria-labelledby': 'aria-prop',
+ id: 'element-id',
+ className: 'thisClassIsUsedForTestingPurposesOnly',
+ style: { pointerEvents: 'none' as const },
+ title: 'Tooltip',
+ customattribute: 'true',
+ ...extraProps,
+ };
+}
diff --git a/packages/charts/src/test-utils/sharedTests.tsx b/packages/charts/src/test-utils/sharedTests.tsx
new file mode 100644
index 00000000000..b836b614c1f
--- /dev/null
+++ b/packages/charts/src/test-utils/sharedTests.tsx
@@ -0,0 +1,125 @@
+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/charts/tsconfig.build.json b/packages/charts/tsconfig.build.json
index 0604991aae0..73dc0046591 100644
--- a/packages/charts/tsconfig.build.json
+++ b/packages/charts/tsconfig.build.json
@@ -8,5 +8,13 @@
"path": "../main/tsconfig.build.json"
}
],
- "exclude": ["node_modules", "**/*.cy.ts", "**/*.cy.tsx", "**/*.stories.tsx"]
+ "exclude": [
+ "node_modules",
+ "**/*.cy.ts",
+ "**/*.cy.tsx",
+ "**/*.spec.tsx",
+ "**/*.stories.tsx",
+ "**/test",
+ "src/test-utils"
+ ]
}
diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts
index 35ddd9f819d..45b9ae31ee6 100644
--- a/playwright-ct.config.ts
+++ b/playwright-ct.config.ts
@@ -5,7 +5,11 @@ import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
testDir: '.',
- testMatch: ['**/packages/main/src/components/**/test/*.spec.tsx', '**/playwright/test/**/*.spec.tsx'],
+ 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,
@@ -22,11 +26,25 @@ export default defineConfig({
name: 'Playwright Coverage Report',
outputFile: 'temp/playwright-coverage/report.html',
coverage: {
- sourceFilter: (sourcePath: string) =>
- (sourcePath.includes('packages/main/src/components/SelectDialog') ||
- sourcePath.includes('packages/main/src/components/Splitter')) &&
- !sourcePath.includes('node_modules') &&
- !sourcePath.includes('/test/'),
+ 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',
},