Skip to content

Commit b52b74a

Browse files
authored
Merge branch 'feature/test-syntax-in-csf' into tags-filter-exclude
2 parents c652477 + 41c83af commit b52b74a

File tree

9 files changed

+311
-82
lines changed

9 files changed

+311
-82
lines changed

code/addons/vitest/src/node/test-manager.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import path from 'pathe';
1515
import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants';
1616
import type { StoreEvent, StoreState } from '../types';
1717
import { TestManager, type TestManagerOptions } from './test-manager';
18+
import { DOUBLE_SPACES } from './vitest-manager';
1819

1920
const setTestNamePattern = vi.hoisted(() => vi.fn());
2021
const vitest = vi.hoisted(() => ({
@@ -204,7 +205,7 @@ describe('TestManager', () => {
204205
triggeredBy: 'global',
205206
},
206207
});
207-
expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/);
208+
expect(setTestNamePattern).toHaveBeenCalledWith(new RegExp(`^One$`));
208209
expect(vitest.runTestSpecifications).toHaveBeenCalledWith(tests.slice(0, 1), true);
209210
});
210211

@@ -220,7 +221,7 @@ describe('TestManager', () => {
220221
},
221222
});
222223
// regex should be exact match of the story name
223-
expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/);
224+
expect(setTestNamePattern).toHaveBeenCalledWith(new RegExp(`^One$`));
224225
});
225226

226227
it('should trigger a single story test', async () => {
@@ -235,7 +236,9 @@ describe('TestManager', () => {
235236
},
236237
});
237238
// regex should be Parent Story Name + Test Name
238-
expect(setTestNamePattern).toHaveBeenCalledWith(/^Parentstory Test name$/);
239+
expect(setTestNamePattern).toHaveBeenCalledWith(
240+
new RegExp(`^Parent story${DOUBLE_SPACES} Test name$`)
241+
);
239242
});
240243

241244
it('should trigger all tests of a story', async () => {
@@ -249,8 +252,7 @@ describe('TestManager', () => {
249252
triggeredBy: 'global',
250253
},
251254
});
252-
// regex should be parent story name with no spaces in between plus a space at the end
253-
expect(setTestNamePattern).toHaveBeenCalledWith(/^Parentstory /);
255+
expect(setTestNamePattern).toHaveBeenCalledWith(new RegExp(`^Parent story${DOUBLE_SPACES}`));
254256
});
255257

256258
it('should restart Vitest before a test run if coverage is enabled', async () => {

code/addons/vitest/src/node/vitest-manager.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ const packageDir = dirname(require.resolve('@storybook/addon-vitest/package.json
3131
// We have to tell Vitest that it runs as part of Storybook
3232
process.env.VITEST_STORYBOOK = 'true';
3333

34+
/**
35+
* The Storybook vitest plugin adds double space characters so that it's possible to do a regex for
36+
* all test run use cases. Otherwise, if there were two unrelated stories like "Primary Button" and
37+
* "Primary Button Mobile", once you run tests for "Primary Button" and its children it would also
38+
* match "Primary Button Mobile". As it turns out, this limitation is also present in the Vitest
39+
* VSCode extension and the issue would occur with normal vitest tests as well, but because we use
40+
* double spaces, we circumvent the issue.
41+
*/
42+
export const DOUBLE_SPACES = ' ';
43+
const getTestName = (name: string) => `${name}${DOUBLE_SPACES}`;
44+
3445
export class VitestManager {
3546
vitest: Vitest | null = null;
3647

@@ -271,10 +282,6 @@ export class VitestManager {
271282
? allStories.filter((story) => runPayload.storyIds?.includes(story.id))
272283
: allStories;
273284

274-
// When we run tests that involved a describe block (parent story + tests)
275-
// we need to remove spaces from the parent story name, as the describe block does not have spaces
276-
const getExportName = (name: string) => name.replace(/\s+/g, '');
277-
278285
const isSingleStoryRun = runPayload.storyIds?.length === 1;
279286
if (isSingleStoryRun) {
280287
const selectedStory = filteredStories.find((story) => story.id === runPayload.storyIds?.[0]);
@@ -291,31 +298,20 @@ export class VitestManager {
291298
if (isParentStory) {
292299
// Use case 1: "Single" story run on a story with tests
293300
// -> run all tests of that story, as storyName is a describe block
294-
/**
295-
* The vitest transformation keeps the export name as is, e.g. "PrimaryButton", while the
296-
* storybook sidebar changes the name to "Primary Button". That's why we need to remove
297-
* spaces from the story name, to match the test name. If we were to also beautify the test
298-
* name, doing a regex wouldn't be precise because there could be two describes, for
299-
* instance: "Primary Button" and "Primary Button Mobile" and both would match. The fact
300-
* that there are no spaces in the test name is what makes "PrimaryButton" and
301-
* "PrimaryButtonMobile" work well in the regex. As it turns out, this limitation is also
302-
* present in the Vitest VSCode extension and the issue would occur with normal vitest tests
303-
* as well.
304-
*/
305-
const parentName = getExportName(selectedStory.name);
306-
regex = new RegExp(`^${parentName} `); // the extra space is intentional!
301+
const parentName = getTestName(selectedStory.name);
302+
regex = new RegExp(`^${parentName}`);
307303
} else if (hasParentStory) {
308304
// Use case 2: Single story run on a specific story test
309-
// in this case the regex pattern should be the story parentName + story.name
305+
// in this case the regex pattern should be the story parentName + space + story.name
310306
const parentStory = allStories.find((story) => story.id === selectedStory.parent);
311307
if (!parentStory) {
312308
throw new Error(`Parent story not found for story ${selectedStory.id}`);
313309
}
314310

315-
const parentName = getExportName(parentStory.name);
311+
const parentName = getTestName(parentStory.name);
316312
regex = new RegExp(`^${parentName} ${storyName}$`);
317313
} else {
318-
// Use case 3: Single story run on a story without tests
314+
// Use case 3: Single story run on a story without tests, should be exact match of story name
319315
regex = new RegExp(`^${storyName}$`);
320316
}
321317
this.vitest!.setGlobalTestNamePattern(regex);

code/addons/vitest/src/vitest-plugin/test-utils.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const testStory = (
3636
story: ComposedStoryFn | Story<Renderer>,
3737
meta: ComponentAnnotations | Meta<Renderer>,
3838
skipTags: string[],
39+
storyId: string,
3940
testName?: string
4041
) => {
4142
return async (context: TestContext & { story: ComposedStoryFn }) => {
@@ -66,12 +67,9 @@ export const testStory = (
6667
meta: TaskMeta & { storyId: string; reports: Report[] };
6768
};
6869

69-
if (testName) {
70-
// TODO: [test-syntax] isn't this done by the csf plugin somehow?
71-
_task.meta.storyId = toTestId(composedStory.id, testName);
72-
} else {
73-
_task.meta.storyId = composedStory.id;
74-
}
70+
// The id will always be present, calculated by CsfFile
71+
// and is needed so that we can add the test to the story in Storybook's UI for the status
72+
_task.meta.storyId = storyId;
7573

7674
await setViewport(composedStory.parameters, composedStory.globals);
7775

0 commit comments

Comments
 (0)