Skip to content

Commit

Permalink
Merge pull request #29999 from storybookjs/version-non-patch-from-8.5…
Browse files Browse the repository at this point in the history
….0-alpha.19

Release: Prerelease 8.5.0-alpha.20
  • Loading branch information
shilman authored Dec 11, 2024
2 parents 71af1b8 + 28e19d0 commit 6260aac
Show file tree
Hide file tree
Showing 26 changed files with 763 additions and 267 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 8.5.0-alpha.20

- Addon Test: Add `@vitest/coverage-v8` during postinstall if no coverage reporter is installed - [#29993](https://github.com/storybookjs/storybook/pull/29993), thanks @ghengeveld!
- Addon Test: Add support for previewHead - [#29808](https://github.com/storybookjs/storybook/pull/29808), thanks @ndelangen!
- Addon Test: Always run Vitest in watch mode internally - [#29749](https://github.com/storybookjs/storybook/pull/29749), thanks @JReinhold!
- Addon Test: Filter out falsy test results in TestProviderRender - [#30001](https://github.com/storybookjs/storybook/pull/30001), thanks @valentinpalkovic!
- Addon Test: Handle undefined storyId - [#29998](https://github.com/storybookjs/storybook/pull/29998), thanks @ghengeveld!
- Addon Test: Make component tests status row link to the story's tests panel - [#29992](https://github.com/storybookjs/storybook/pull/29992), thanks @ghengeveld!
- Addon Test: Merge viteFinal config into vitest config - [#29806](https://github.com/storybookjs/storybook/pull/29806), thanks @ndelangen!
- Addon Test: Prompt switch to `experimental-nextjs-vite` - [#29814](https://github.com/storybookjs/storybook/pull/29814), thanks @ndelangen!
- Addon Test: Use ProgressSpinner for stop button in Testing Module - [#29997](https://github.com/storybookjs/storybook/pull/29997), thanks @ghengeveld!

## 8.5.0-alpha.19

- Addon A11y: Create a11y test provider and revamp a11y addon - [#29643](https://github.com/storybookjs/storybook/pull/29643), thanks @valentinpalkovic!
Expand Down
1 change: 0 additions & 1 deletion code/addons/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
"@storybook/icons": "^1.2.12",
"@storybook/instrumenter": "workspace:*",
"@storybook/test": "workspace:*",
"@storybook/theming": "workspace:*",
"polished": "^4.2.2",
"prompts": "^2.4.0",
"ts-dedent": "^2.2.0"
Expand Down
59 changes: 51 additions & 8 deletions code/addons/test/src/components/TestProviderRender.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { type ComponentProps, type FC, useCallback, useMemo, useRef, useState } from 'react';

import { Button, ListItem } from 'storybook/internal/components';
import { Button, ListItem, ProgressSpinner } from 'storybook/internal/components';
import {
TESTING_MODULE_CONFIG_CHANGE,
type TestProviderConfig,
Expand All @@ -17,15 +17,17 @@ import {
PlayHollowIcon,
PointerHandIcon,
ShieldIcon,
StopAltHollowIcon,
StopAltIcon,
} from '@storybook/icons';

import { isEqual } from 'es-toolkit';
import { debounce } from 'es-toolkit/compat';

// Relatively importing from a11y to get the ADDON_ID
import { ADDON_ID as A11Y_ADDON_ID } from '../../../a11y/src/constants';
import { type Config, type Details } from '../constants';
import {
ADDON_ID as A11Y_ADDON_ID,
PANEL_ID as A11y_ADDON_PANEL_ID,
} from '../../../a11y/src/constants';
import { type Config, type Details, PANEL_ID } from '../constants';
import { type TestStatus } from '../node/reporter';
import { Description } from './Description';
import { TestStatusIcon } from './TestStatusIcon';
Expand Down Expand Up @@ -66,6 +68,14 @@ const Checkbox = styled.input({
},
});

const Progress = styled(ProgressSpinner)({
margin: 2,
});

const StopIcon = styled(StopAltIcon)({
width: 10,
});

const statusOrder: TestStatus[] = ['failed', 'warning', 'pending', 'passed', 'skipped'];
const statusMap: Record<TestStatus, ComponentProps<typeof TestStatusIcon>['status']> = {
failed: 'negative',
Expand Down Expand Up @@ -101,7 +111,8 @@ export const TestProviderRender: FC<

return state.details?.testResults?.flatMap((result) =>
result.results
.filter((it) => !entryId || it.storyId === entryId || it.storyId.startsWith(`${entryId}-`))
.filter(Boolean)
.filter((r) => !entryId || r.storyId === entryId || r.storyId?.startsWith(`${entryId}-`))
.map((r) => r.reports.find((report) => report.type === 'a11y'))
);
}, [isA11yAddon, state.details?.testResults, entryId]);
Expand Down Expand Up @@ -145,6 +156,12 @@ export const TestProviderRender: FC<

const status = (state.failed ? 'failed' : results[0]?.status) || 'unknown';

const openPanel = (id: string, panelId: string) => {
api.selectStory(id);
api.setSelectedPanel(panelId);
api.togglePanel(true);
};

return (
<Container {...props}>
<Heading>
Expand Down Expand Up @@ -182,11 +199,13 @@ export const TestProviderRender: FC<
<Button
aria-label={`Stop ${state.name}`}
variant="ghost"
padding="small"
padding="none"
onClick={() => api.cancelTestProvider(state.id)}
disabled={state.cancelling}
>
<StopAltHollowIcon />
<Progress percentage={state.progress?.percentageCompleted}>
<StopIcon />
</Progress>
</Button>
) : (
<Button
Expand Down Expand Up @@ -244,6 +263,16 @@ export const TestProviderRender: FC<
<Extras>
<ListItem
title="Component tests"
onClick={
(status === 'failed' || status === 'warning') && results.length
? () => {
const firstNotPassed = results.find(
(r) => r.status === 'failed' || r.status === 'warning'
);
openPanel(firstNotPassed.storyId, PANEL_ID);
}
: null
}
icon={
state.crashed ? (
<TestStatusIcon status="critical" aria-label="status: crashed" />
Expand Down Expand Up @@ -278,6 +307,20 @@ export const TestProviderRender: FC<
{isA11yAddon && (
<ListItem
title="Accessibility"
onClick={
(a11yStatus === 'negative' || a11yStatus === 'warning') && a11yResults.length
? () => {
const firstNotPassed = results.find((r) =>
r.reports
.filter((report) => report.type === 'a11y')
.find(
(report) => report.status === 'failed' || report.status === 'warning'
)
);
openPanel(firstNotPassed.storyId, A11y_ADDON_PANEL_ID);
}
: null
}
icon={<TestStatusIcon status={a11yStatus} aria-label={`status: ${a11yStatus}`} />}
right={a11yNotPassedAmount || null}
/>
Expand Down
14 changes: 7 additions & 7 deletions code/addons/test/src/manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const statusMap: Record<TestStatus, API_StatusValue> = {
addons.register(ADDON_ID, (api) => {
const storybookBuilder = (globalThis as any).STORYBOOK_BUILDER || '';
if (storybookBuilder.includes('vite')) {
const openAddonPanel = () => {
const openTestsPanel = () => {
api.setSelectedPanel(PANEL_ID);
api.togglePanel(true);
};
Expand Down Expand Up @@ -94,9 +94,9 @@ addons.register(ADDON_ID, (api) => {
? rest.failureMessages.join('\n')
: '',
data: { testRunId },
onClick: openAddonPanel,
onClick: openTestsPanel,
sidebarContextMenu: false,
} as API_StatusObject,
} satisfies API_StatusObject,
])
)
)
Expand All @@ -108,12 +108,12 @@ addons.register(ADDON_ID, (api) => {
update.details.testResults.flatMap((testResult) =>
testResult.results
.filter(({ storyId }) => storyId)
.map(({ storyId, status, testRunId, reports, ...rest }) => {
.map(({ storyId, testRunId, reports }) => {
const a11yReport = reports.find((r: any) => r.type === 'a11y');
return [
storyId,
a11yReport
? {
? ({
title: 'Accessibility tests',
description: '',
status: statusMap[a11yReport.status],
Expand All @@ -123,9 +123,9 @@ addons.register(ADDON_ID, (api) => {
api.togglePanel(true);
},
sidebarContextMenu: false,
}
} satisfies API_StatusObject)
: null,
] as const;
];
})
)
)
Expand Down
9 changes: 7 additions & 2 deletions code/addons/test/src/node/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type TestResultResult =
reports: Report[];
}
| {
status: Extract<TestStatus, 'failed'>;
status: Extract<TestStatus, 'failed' | 'warning'>;
storyId: string;
duration: number;
testRunId: string;
Expand All @@ -39,7 +39,7 @@ export type TestResult = {
results: TestResultResult[];
startTime: number;
endTime: number;
status: Extract<TestStatus, 'passed' | 'failed'>;
status: Extract<TestStatus, 'passed' | 'failed' | 'warning'>;
message?: string;
};

Expand Down Expand Up @@ -165,6 +165,11 @@ export class StorybookReporter implements Reporter {
numTotalTests,
startedAt: this.start,
finishedAt,
percentageCompleted: finishedAt
? 100
: numTotalTests
? ((numPassedTests + numFailedTests) / numTotalTests) * 100
: 0,
} as TestingModuleProgressReportProgress,
details: {
testResults,
Expand Down
58 changes: 56 additions & 2 deletions code/addons/test/src/node/test-manager.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest';
import { createVitest } from 'vitest/node';
import { createVitest as actualCreateVitest } from 'vitest/node';

import { Channel, type ChannelTransport } from '@storybook/core/channels';
import type { StoryIndex } from '@storybook/types';
Expand Down Expand Up @@ -34,6 +34,7 @@ const vitest = vi.hoisted(() => ({
vi.mock('vitest/node', () => ({
createVitest: vi.fn(() => Promise.resolve(vitest)),
}));
const createVitest = vi.mocked(actualCreateVitest);

const transport = { setHandler: vi.fn(), send: vi.fn() } satisfies ChannelTransport;
const mockChannel = new Channel({ transport });
Expand Down Expand Up @@ -109,7 +110,7 @@ describe('TestManager', () => {

await testManager.handleWatchModeRequest({ providerId: TEST_PROVIDER_ID, watchMode: true });
expect(testManager.watchMode).toBe(true);
expect(createVitest).toHaveBeenCalledTimes(2);
expect(createVitest).toHaveBeenCalledTimes(1); // shouldn't restart vitest
});

it('should handle run request', async () => {
Expand Down Expand Up @@ -145,4 +146,57 @@ describe('TestManager', () => {
expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/);
expect(vitest.runFiles).toHaveBeenCalledWith(tests.slice(0, 1), true);
});

it('should handle coverage toggling', async () => {
const testManager = await TestManager.start(mockChannel, options);
expect(testManager.coverage).toBe(false);
expect(createVitest).toHaveBeenCalledTimes(1);
createVitest.mockClear();

await testManager.handleConfigChange({
providerId: TEST_PROVIDER_ID,
config: { coverage: true, a11y: false },
});
expect(testManager.coverage).toBe(true);
expect(createVitest).toHaveBeenCalledTimes(1);
createVitest.mockClear();

await testManager.handleConfigChange({
providerId: TEST_PROVIDER_ID,
config: { coverage: false, a11y: false },
});
expect(testManager.coverage).toBe(false);
expect(createVitest).toHaveBeenCalledTimes(1);
});

it('should temporarily disable coverage on focused tests', async () => {
vitest.globTestSpecs.mockImplementation(() => tests);
const testManager = await TestManager.start(mockChannel, options);
expect(testManager.coverage).toBe(false);
expect(createVitest).toHaveBeenCalledTimes(1);

await testManager.handleConfigChange({
providerId: TEST_PROVIDER_ID,
config: { coverage: true, a11y: false },
});
expect(testManager.coverage).toBe(true);
expect(createVitest).toHaveBeenCalledTimes(2);

await testManager.handleRunRequest({
providerId: TEST_PROVIDER_ID,
indexUrl: 'http://localhost:6006/index.json',
storyIds: ['button--primary', 'button--secondary'],
});
// expect vitest to be restarted twice, without and with coverage
expect(createVitest).toHaveBeenCalledTimes(4);
expect(vitest.runFiles).toHaveBeenCalledWith([], true);

await testManager.handleRunRequest({
providerId: TEST_PROVIDER_ID,
indexUrl: 'http://localhost:6006/index.json',
});
// don't expect vitest to be restarted, as we're running all tests
expect(createVitest).toHaveBeenCalledTimes(4);
expect(vitest.runFiles).toHaveBeenCalledWith(tests, true);
});
});
Loading

0 comments on commit 6260aac

Please sign in to comment.