Skip to content

Commit

Permalink
Merge pull request #29713 from storybookjs/jeppe/vitest-coverage-backend
Browse files Browse the repository at this point in the history
Test: Add coverage feature
  • Loading branch information
ndelangen authored Nov 29, 2024
2 parents c4b6bdf + d4d566a commit 8671ca1
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 65 deletions.
11 changes: 9 additions & 2 deletions code/addons/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
"import": "./dist/vitest-plugin/test-utils.mjs",
"require": "./dist/vitest-plugin/test-utils.js"
},
"./internal/coverage-reporter": {
"types": "./dist/node/coverage-reporter.d.ts",
"import": "./dist/node/coverage-reporter.mjs",
"require": "./dist/node/coverage-reporter.js"
},
"./preview": {
"types": "./dist/preview.d.ts",
"import": "./dist/preview.mjs",
Expand Down Expand Up @@ -87,7 +92,7 @@
},
"devDependencies": {
"@devtools-ds/object-inspector": "^1.1.2",
"@storybook/icons": "^1.2.12",
"@types/istanbul-lib-report": "^3.0.3",
"@types/node": "^22.0.0",
"@types/semver": "^7",
"@vitest/browser": "^2.1.3",
Expand All @@ -98,6 +103,7 @@
"execa": "^8.0.1",
"find-up": "^7.0.0",
"formik": "^2.2.9",
"istanbul-lib-report": "^3.0.1",
"pathe": "^1.1.2",
"picocolors": "^1.1.0",
"react": "^18.2.0",
Expand Down Expand Up @@ -146,7 +152,8 @@
"./src/vitest-plugin/index.ts",
"./src/vitest-plugin/global-setup.ts",
"./src/postinstall.ts",
"./src/node/vitest.ts"
"./src/node/vitest.ts",
"./src/node/coverage-reporter.ts"
]
},
"storybook": {
Expand Down
82 changes: 77 additions & 5 deletions code/addons/test/src/components/TestProviderRender.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,29 +124,83 @@ export const Running: Story = {
},
};

export const EnableA11y: Story = {
export const Watching: Story = {
args: {
state: {
...config,
...baseState,
watching: true,
},
},
};

export const WithCoverageNegative: Story = {
args: {
state: {
...config,
...baseState,
details: {
testResults: [],
coverageSummary: {
percentage: 20,
status: 'negative',
},
},
config: {
a11y: true,
coverage: false,
a11y: false,
coverage: true,
},
},
},
};

export const EnableEditing: Story = {
export const WithCoverageWarning: Story = {
args: {
state: {
...config,
...baseState,
details: {
testResults: [],
coverageSummary: {
percentage: 50,
status: 'warning',
},
},
config: {
a11y: true,
a11y: false,
coverage: true,
},
},
},
};

export const WithCoveragePositive: Story = {
args: {
state: {
...config,
...baseState,
details: {
testResults: [],
coverageSummary: {
percentage: 80,
status: 'positive',
},
},
config: {
a11y: false,
coverage: true,
},
},
},
};

export const Editing: Story = {
args: {
state: {
...config,
...baseState,
config: {
a11y: false,
coverage: false,
},
details: {
Expand All @@ -161,3 +215,21 @@ export const EnableEditing: Story = {
screen.getByLabelText(/Open settings/).click();
},
};

export const EditingAndWatching: Story = {
args: {
state: {
...config,
...baseState,
watching: true,
config: {
a11y: true,
coverage: true, // should be automatically disabled in the UI
},
details: {
testResults: [],
},
},
},
play: Editing.play,
};
31 changes: 24 additions & 7 deletions code/addons/test/src/components/TestProviderRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const TestProviderRender: FC<{

const title = state.crashed || state.failed ? 'Local tests failed' : 'Run local tests';
const errorMessage = state.error?.message;
const coverageSummary = state.details?.coverageSummary;

const [config, updateConfig] = useConfig(
api,
Expand Down Expand Up @@ -159,8 +160,8 @@ export const TestProviderRender: FC<{
right={
<Checkbox
type="checkbox"
disabled // TODO: Implement coverage
checked={config.coverage}
checked={state.watching ? false : config.coverage}
disabled={state.watching}
onChange={() => updateConfig({ coverage: !config.coverage })}
/>
}
Expand All @@ -185,11 +186,27 @@ export const TestProviderRender: FC<{
title="Component tests"
icon={<TestStatusIcon status="positive" aria-label="status: passed" />}
/>
<ListItem
title="Coverage"
icon={<TestStatusIcon percentage={60} status="warning" aria-label="status: warning" />}
right={`60%`}
/>
{coverageSummary ? (
<ListItem
title="Coverage"
href={'/coverage/index.html'}
// @ts-expect-error ListItem doesn't include all anchor attributes in types, but it is an achor element
target="_blank"
icon={
<TestStatusIcon
percentage={coverageSummary.percentage}
status={coverageSummary.status}
aria-label={`status: ${coverageSummary.status}`}
/>
}
right={`${coverageSummary.percentage}%`}
/>
) : (
<ListItem
title="Coverage"
icon={<TestStatusIcon status="unknown" aria-label={`status: unknown`} />}
/>
)}
<ListItem
title="Accessibility"
icon={<TestStatusIcon status="negative" aria-label="status: failed" />}
Expand Down
6 changes: 6 additions & 0 deletions code/addons/test/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ export const DOCUMENTATION_LINK = 'writing-tests/test-addon';
export const DOCUMENTATION_DISCREPANCY_LINK = `${DOCUMENTATION_LINK}#what-happens-when-there-are-different-test-results-in-multiple-environments`;
export const DOCUMENTATION_FATAL_ERROR_LINK = `${DOCUMENTATION_LINK}#what-happens-if-vitest-itself-has-an-error`;

export const COVERAGE_DIRECTORY = 'coverage';

export interface Config {
coverage: boolean;
a11y: boolean;
}

export type Details = {
testResults: TestResult[];
coverageSummary?: {
status: 'positive' | 'warning' | 'negative' | 'unknown';
percentage: number;
};
};
5 changes: 5 additions & 0 deletions code/addons/test/src/node/boot-test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type ChildProcess } from 'node:child_process';
import type { Channel } from 'storybook/internal/channels';
import {
TESTING_MODULE_CANCEL_TEST_RUN_REQUEST,
TESTING_MODULE_CONFIG_CHANGE,
TESTING_MODULE_CRASH_REPORT,
TESTING_MODULE_RUN_REQUEST,
TESTING_MODULE_WATCH_MODE_REQUEST,
Expand Down Expand Up @@ -43,11 +44,14 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a
child?.send({ args, from: 'server', type: TESTING_MODULE_WATCH_MODE_REQUEST });
const forwardCancel = (...args: any[]) =>
child?.send({ args, from: 'server', type: TESTING_MODULE_CANCEL_TEST_RUN_REQUEST });
const forwardConfigChange = (...args: any[]) =>
child?.send({ args, from: 'server', type: TESTING_MODULE_CONFIG_CHANGE });

const killChild = () => {
channel.off(TESTING_MODULE_RUN_REQUEST, forwardRun);
channel.off(TESTING_MODULE_WATCH_MODE_REQUEST, forwardWatchMode);
channel.off(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, forwardCancel);
channel.off(TESTING_MODULE_CONFIG_CHANGE, forwardConfigChange);
child?.kill();
child = null;
};
Expand Down Expand Up @@ -86,6 +90,7 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a
channel.on(TESTING_MODULE_RUN_REQUEST, forwardRun);
channel.on(TESTING_MODULE_WATCH_MODE_REQUEST, forwardWatchMode);
channel.on(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, forwardCancel);
channel.on(TESTING_MODULE_CONFIG_CHANGE, forwardConfigChange);

resolve();
} else if (result.type === 'error') {
Expand Down
53 changes: 53 additions & 0 deletions code/addons/test/src/node/coverage-reporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ResolvedCoverageOptions } from 'vitest/node';

import type { ReportNode, Visitor } from 'istanbul-lib-report';
import { ReportBase } from 'istanbul-lib-report';

import { type Details, TEST_PROVIDER_ID } from '../constants';
import type { TestManager } from './test-manager';

export type StorybookCoverageReporterOptions = {
testManager: TestManager;
coverageOptions: ResolvedCoverageOptions<'v8'>;
};

export default class StorybookCoverageReporter extends ReportBase implements Partial<Visitor> {
#testManager: StorybookCoverageReporterOptions['testManager'];

#coverageOptions: StorybookCoverageReporterOptions['coverageOptions'];

constructor(opts: StorybookCoverageReporterOptions) {
super();
this.#testManager = opts.testManager;
this.#coverageOptions = opts.coverageOptions;
}

onSummary(node: ReportNode) {
if (!node.isRoot()) {
return;
}
const coverageSummary = node.getCoverageSummary(false);

const percentage = Math.round(coverageSummary.data.statements.pct);

// Fallback to Vitest's default watermarks https://vitest.dev/config/#coverage-watermarks
const [lowWatermark = 50, highWatermark = 80] =
this.#coverageOptions.watermarks?.statements ?? [];

const coverageDetails: Details['coverageSummary'] = {
percentage,
status:
percentage < lowWatermark
? 'negative'
: percentage < highWatermark
? 'warning'
: 'positive',
};
this.#testManager.sendProgressReport({
providerId: TEST_PROVIDER_ID,
details: {
coverageSummary: coverageDetails,
},
});
}
}
Loading

0 comments on commit 8671ca1

Please sign in to comment.