Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-Test: Implement Addon Test TestProvider Backend #29171

Open
wants to merge 24 commits into
base: unified-ui-testing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
40ca30b
Add experimental_TEST_PROVIDER addon type
ghengeveld Sep 12, 2024
b423331
Test for experimental_TEST_PROVIDER addon type
ghengeveld Sep 13, 2024
cea8021
Register Test addon as TEST_PROVIDER addon type
ghengeveld Sep 13, 2024
cb619ae
Addon-Test: Implement Addon Test TestProvider Backend
valentinpalkovic Sep 20, 2024
123c6d2
Lazy boot for Vitest runner
ghengeveld Sep 23, 2024
72bd862
Replace Node's process.fork with Execa and refactor/cleanup a bunch o…
ghengeveld Sep 24, 2024
840fa04
make test run succeed
yannbf Sep 24, 2024
76c1c4e
Add unit tests and fix timeout logic
ghengeveld Sep 24, 2024
b71e2f2
Merge branch 'unified-ui-testing' into valentin/implement-addon-test-…
yannbf Sep 24, 2024
998b8e1
fix remaining tests
yannbf Sep 24, 2024
1d45366
Fix unit test on Windows and avoid default export
ghengeveld Sep 25, 2024
b307cd9
add test module crash report event
yannbf Sep 25, 2024
1966455
fix exports
yannbf Sep 25, 2024
04d892b
Fix timeout after ready and debouple test manager from process context
ghengeveld Sep 25, 2024
8573831
Fix progress reporting
ghengeveld Sep 25, 2024
0d00235
Add missing deps
ghengeveld Sep 25, 2024
54dc6e3
Linting
ghengeveld Sep 25, 2024
b332fac
bring back vitest patch
yannbf Sep 25, 2024
b0a68af
fix user event interactions on firefox
yannbf Sep 25, 2024
0123f8f
fix vitest patch again
yannbf Sep 25, 2024
23329d9
exclude story files from coverage
yannbf Sep 25, 2024
5b11aa2
Add tests for TestManager and VitestManager
ghengeveld Sep 25, 2024
b7c33b5
Comment out log messages for now
ghengeveld Sep 25, 2024
f8095ba
Increase wait time
ghengeveld Sep 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions code/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ const config: StorybookConfig = {
directory: '../addons/interactions/src',
titlePrefix: 'addons/interactions',
},
// {
// directory: '../addons/interactions/template/stories',
// titlePrefix: 'addons/interactions',
// },
{
directory: '../addons/interactions/template/stories',
titlePrefix: 'addons/interactions/tests',
},
],
addons: [
'@storybook/addon-links',
Expand Down
6 changes: 5 additions & 1 deletion code/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ export const loaders = [
* The DocsContext will then be added via the decorator below.
*/
async ({ parameters: { relativeCsfPaths, attached = true } }) => {
if (!relativeCsfPaths) {
// TODO bring a better way to skip tests when running as part of the vitest plugin instead of __STORYBOOK_URL__
// eslint-disable-next-line no-underscore-dangle
if (!relativeCsfPaths || (import.meta as any).env?.__STORYBOOK_URL__) {
return {};
}
const csfFiles = await Promise.all(
Expand Down Expand Up @@ -358,3 +360,5 @@ export const parameters = {
},
},
};

export const tags = ['test', 'vitest'];
16 changes: 13 additions & 3 deletions code/.storybook/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,39 @@ if (process.env.INSPECT === 'true') {

export default mergeConfig(
vitestCommonConfig,
// @ts-expect-error added this because of testNamePattern below
defineProject({
plugins: [
import('@storybook/experimental-addon-test/vitest-plugin').then(({ storybookTest }) =>
storybookTest({
configDir: process.cwd(),
tags: {
include: ['vitest'],
},
})
),
...extraPlugins,
],
test: {
name: 'storybook-ui',
include: [
// TODO: test all core and addon stories later
// './core/**/components/**/*.{story,stories}.?(c|m)[jt]s?(x)',
'../addons/**/src/**/*.{story,stories}.?(c|m)[jt]s?(x)',
'../addons/**/*.{story,stories}.?(c|m)[jt]s?(x)',
'../core/template/stories/**/*.{story,stories}.?(c|m)[jt]s?(x)',
'../core/src/manager/**/*.{story,stories}.?(c|m)[jt]s?(x)',
'../core/src/preview-api/**/*.{story,stories}.?(c|m)[jt]s?(x)',
'../core/src/components/{brand,components}/**/*.{story,stories}.?(c|m)[jt]s?(x)',
],
exclude: [
...defaultExclude,
'../node_modules/**',
'**/__mockdata__/**',
'../**/__mockdata__/**',
// expected to fail in Vitest because of fetching /iframe.html to cause ECONNREFUSED
'**/Zoom.stories.tsx',
],
// TODO: bring this back once portable stories support @storybook/core/preview-api hooks
// @ts-expect-error this type does not exist but the property does!
testNamePattern: /^(?!.*(UseState)).*$/,
browser: {
enabled: true,
name: 'chromium',
Expand Down
13 changes: 10 additions & 3 deletions code/addons/interactions/template/stories/basics.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,17 @@ const UserEventSetup = {
{ keys: '[TouchA>]', target: canvas.getByRole('textbox') },
{ keys: '[/TouchA]' },
]);
await user.tab();
await user.keyboard('{enter}');
const submitButton = await canvas.findByRole('button');
await expect(submitButton).toHaveFocus();

if (navigator.userAgent.toLowerCase().includes('firefox')) {
// user event has a few issues on firefox, therefore we do it differently
await fireEvent.click(submitButton);
} else {
await user.tab();
await user.keyboard('{enter}');
await expect(submitButton).toHaveFocus();
}

await expect(args.onSuccess).toHaveBeenCalled();
});
},
Expand Down
19 changes: 14 additions & 5 deletions code/addons/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,28 @@
"prep": "jiti ../../../scripts/prepare/addon-bundle.ts"
},
"dependencies": {
"@storybook/csf": "^0.1.11"
"@storybook/csf": "^0.1.11",
"@storybook/icons": "^1.2.10",
"chalk": "^5.3.0"
},
"devDependencies": {
"@types/semver": "^7",
"@vitest/browser": "^2.0.0",
"@vitest/browser": "^2.1.1",
"@vitest/runner": "^2.1.1",
"boxen": "^8.0.1",
"execa": "^8.0.1",
"find-up": "^7.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semver": "^7.6.3",
"tinyrainbow": "^1.2.0",
"ts-dedent": "^2.2.0",
"vitest": "^2.0.0"
"vitest": "^2.1.1"
},
"peerDependencies": {
"@vitest/browser": "^2.0.0",
"@vitest/browser": "^2.1.1",
"@vitest/runner": "^2.1.1",
"storybook": "workspace:^",
"vitest": "^2.0.0"
},
Expand All @@ -103,7 +111,8 @@
"./src/preset.ts",
"./src/vitest-plugin/index.ts",
"./src/vitest-plugin/global-setup.ts",
"./src/postinstall.ts"
"./src/postinstall.ts",
"./src/node/vitest.ts"
]
}
}
3 changes: 2 additions & 1 deletion code/addons/test/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const ADDON_ID = 'storybook/vitest';
export const ADDON_ID = 'storybook/test';
export const TEST_PROVIDER_ID = `${ADDON_ID}/test-provider`;
7 changes: 7 additions & 0 deletions code/addons/test/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import chalk from 'chalk';

import { ADDON_ID } from './constants';

export const log = (message: any) => {
valentinpalkovic marked this conversation as resolved.
Show resolved Hide resolved
console.log(`${chalk.magenta(ADDON_ID)}: ${message.toString().trim()}`);
};
18 changes: 15 additions & 3 deletions code/addons/test/src/manager.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { type API, addons } from 'storybook/internal/manager-api';
import React from 'react';

import { ADDON_ID } from './constants';
import { addons } from 'storybook/internal/manager-api';
import { Addon_TypesEnum } from 'storybook/internal/types';

addons.register(ADDON_ID, () => {});
import { PointerHandIcon } from '@storybook/icons';

import { ADDON_ID, TEST_PROVIDER_ID } from './constants';

addons.register(ADDON_ID, () => {
addons.add(TEST_PROVIDER_ID, {
type: Addon_TypesEnum.experimental_TEST_PROVIDER,
icon: <PointerHandIcon />,
title: 'Component tests',
description: () => 'Not yet run',
});
});
145 changes: 145 additions & 0 deletions code/addons/test/src/node/boot-test-runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { Channel, type ChannelTransport } from '@storybook/core/channels';

import {
TESTING_MODULE_CANCEL_TEST_RUN_REQUEST,
TESTING_MODULE_RUN_ALL_REQUEST,
TESTING_MODULE_RUN_PROGRESS_RESPONSE,
TESTING_MODULE_RUN_REQUEST,
TESTING_MODULE_WATCH_MODE_REQUEST,
} from '@storybook/core/core-events';

import { execaNode } from 'execa';

import { log } from '../logger';
import { bootTestRunner } from './boot-test-runner';

let stdout: (chunk: any) => void;
let stderr: (chunk: any) => void;
let message: (event: any) => void;

const child = vi.hoisted(() => ({
stdout: {
on: vi.fn((event, callback) => {
stdout = callback;
}),
},
stderr: {
on: vi.fn((event, callback) => {
stderr = callback;
}),
},
on: vi.fn((event, callback) => {
message = callback;
}),
send: vi.fn(),
kill: vi.fn(),
}));

vi.mock('execa', () => ({
execaNode: vi.fn().mockReturnValue(child),
}));

vi.mock('../logger', () => ({
log: vi.fn(),
}));

beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

const transport = { setHandler: vi.fn(), send: vi.fn() } satisfies ChannelTransport;
const mockChannel = new Channel({ transport });

describe('bootTestRunner', () => {
it('should execute vitest.js', async () => {
bootTestRunner(mockChannel);
expect(execaNode).toHaveBeenCalledWith(expect.stringMatching(/vitest\.js$/));
});

it('should log stdout and stderr', async () => {
bootTestRunner(mockChannel);
stdout('foo');
stderr('bar');
expect(log).toHaveBeenCalledWith('foo');
expect(log).toHaveBeenCalledWith('bar');
});

it('should wait for vitest to be ready', async () => {
let ready;
const promise = bootTestRunner(mockChannel).then(() => {
ready = true;
});
expect(ready).toBeUndefined();
message({ type: 'ready' });
await expect(promise).resolves.toBeUndefined();
expect(ready).toBe(true);
});

it('should abort if vitest doesn’t become ready in time', async () => {
const promise = bootTestRunner(mockChannel);
vi.advanceTimersByTime(10000);
await expect(promise).rejects.toThrow();
});

it('should abort if vitest fails to start repeatedly', async () => {
const promise = bootTestRunner(mockChannel);
message({ type: 'error' });
vi.advanceTimersByTime(1000);
message({ type: 'error' });
vi.advanceTimersByTime(1000);
message({ type: 'error' });
await expect(promise).rejects.toThrow();
});

it('should forward channel events', async () => {
bootTestRunner(mockChannel);
message({ type: 'ready' });

message({ type: TESTING_MODULE_RUN_PROGRESS_RESPONSE, args: ['foo'] });
expect(mockChannel.last(TESTING_MODULE_RUN_PROGRESS_RESPONSE)).toEqual(['foo']);

mockChannel.emit(TESTING_MODULE_RUN_REQUEST, 'foo');
expect(child.send).toHaveBeenCalledWith({
args: ['foo'],
from: 'server',
type: TESTING_MODULE_RUN_REQUEST,
});

mockChannel.emit(TESTING_MODULE_RUN_ALL_REQUEST, 'bar');
expect(child.send).toHaveBeenCalledWith({
args: ['bar'],
from: 'server',
type: TESTING_MODULE_RUN_ALL_REQUEST,
});

mockChannel.emit(TESTING_MODULE_WATCH_MODE_REQUEST, 'baz');
expect(child.send).toHaveBeenCalledWith({
args: ['baz'],
from: 'server',
type: TESTING_MODULE_WATCH_MODE_REQUEST,
});

mockChannel.emit(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, 'qux');
expect(child.send).toHaveBeenCalledWith({
args: ['qux'],
from: 'server',
type: TESTING_MODULE_CANCEL_TEST_RUN_REQUEST,
});
});

it('should resend init event', async () => {
bootTestRunner(mockChannel, 'init', ['foo']);
message({ type: 'ready' });
expect(child.send).toHaveBeenCalledWith({
args: ['foo'],
from: 'server',
type: 'init',
});
});
});
Loading