Skip to content

Commit

Permalink
Further cleanup and adjustments to fix portable stories
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinpalkovic committed Mar 11, 2025
1 parent 7135a94 commit 8e30709
Show file tree
Hide file tree
Showing 19 changed files with 136 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export default {
layout: 'fullscreen',
},
beforeEach: async () => {
mockStore.setState.mockClear();
return () => {
mockStore.setState(storeOptions.initialState);
};
Expand Down
8 changes: 8 additions & 0 deletions code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@
"import": "./dist/test/preview.js",
"require": "./dist/test/preview.cjs"
},
"./test/spy": {
"types": "./dist/test/spy.d.ts",
"import": "./dist/test/spy.js",
"require": "./dist/test/spy.cjs"
},
"./test": {
"types": "./dist/test/index.d.ts",
"import": "./dist/test/index.js",
Expand Down Expand Up @@ -326,6 +331,9 @@
"test/preview": [
"./dist/test/preview.d.ts"
],
"test/spy": [
"./dist/test/spy.d.ts"
],
"test": [
"./dist/test/index.d.ts"
]
Expand Down
2 changes: 2 additions & 0 deletions code/core/scripts/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ async function run() {
'typescript',
'storybook',
'storybook/test',
'storybook/test/spy',
'storybook/test/preview',
'storybook/actions',
'storybook/actions/preview',
'storybook/actions/manager',
Expand Down
29 changes: 11 additions & 18 deletions code/core/scripts/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,16 @@ export const getEntries = (cwd: string) => {
true,
['util', 'react'],
[],
[
'@testing-library/dom',
'@testing-library/jest-dom',
'@testing-library/user-event',
'chai',
'@vitest/expect',
'@vitest/spy',
'@vitest/utils',
],
['chai', '@vitest/expect', '@vitest/spy', '@vitest/utils'],
true
),
define(
'src/test/spy.ts',
['browser', 'node'],
true,
['util', 'react'],
[],
['chai', '@vitest/expect', '@vitest/spy', '@vitest/utils'],
true
),
define(
Expand All @@ -78,15 +79,7 @@ export const getEntries = (cwd: string) => {
true,
['util', 'react'],
[],
[
'@testing-library/dom',
'@testing-library/jest-dom',
'@testing-library/user-event',
'chai',
'@vitest/expect',
'@vitest/spy',
'@vitest/utils',
],
['chai', '@vitest/expect', '@vitest/spy', '@vitest/utils'],
true
),
];
Expand Down
1 change: 1 addition & 0 deletions code/core/scripts/helpers/sourcefiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const localAlias = {
'storybook/internal': join(__dirname, '..', '..', 'src'),
storybook: join(__dirname, '..', '..', 'src', 'test'),
'storybook/test': join(__dirname, '..', '..', 'src', 'test'),
'storybook/test/spy': join(__dirname, '..', '..', 'src', 'test', 'spy'),
'storybook/test/preview': join(__dirname, '..', '..', 'src', 'test', 'preview'),
'storybook/actions': join(__dirname, '..', '..', 'src', 'actions'),
'storybook/actions/preview': join(__dirname, '..', '..', 'src', 'actions', 'preview'),
Expand Down
6 changes: 0 additions & 6 deletions code/core/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { definePreview } from 'storybook/internal/preview-api';

import * as addonAnnotations from 'storybook/actions/preview';

export * from './constants';
export * from './models';
export * from './runtime';

export default () => definePreview(addonAnnotations);

export type { ActionsParameters } from './types';
8 changes: 3 additions & 5 deletions code/core/src/actions/loaders.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import type { LoaderFunction } from 'storybook/internal/types';

import { onMockCall } from 'storybook/test';
import { onMockCall } from 'storybook/test/spy';

import { action } from './runtime';

let subscribed = false;

const logActionsWhenMockCalled: LoaderFunction = (context) => {
const {
parameters: { actions },
} = context;
const { parameters } = context;

if (actions?.disable) {
if (parameters?.actions?.disable) {
return;
}

Expand Down
12 changes: 11 additions & 1 deletion code/core/src/actions/preview.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
export * from './addArgs';
import { definePreview } from 'storybook/internal/preview-api';

import * as addArgs from './addArgs';
import * as loaders from './loaders';

export * from './loaders';

export default () =>
definePreview({
...addArgs,
...loaders,
});
4 changes: 2 additions & 2 deletions code/core/src/csf/csf-factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import type {
StoryAnnotations,
} from 'storybook/internal/types';

import actionAnnotations from 'storybook/actions';
import testAnnotations from 'storybook/test';
import actionAnnotations from 'storybook/actions/preview';
import testAnnotations from 'storybook/test/preview';

export interface Preview<TRenderer extends Renderer = Renderer> {
readonly _tag: 'Preview';
Expand Down
3 changes: 2 additions & 1 deletion code/core/src/instrumenter/instrumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ export class Instrumenter {
}
};

addons.ready().then(() => {
// Support portable stories where addons are not available
(addons ? addons.ready() : Promise.resolve()).then(() => {
this.channel = addons.getChannel();

// A forceRemount might be triggered for debugging (on `start`), or elsewhere in Storybook.
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/preview-api/core-annotations.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { composeConfigs } from 'storybook/internal/preview-api';
import type { Renderer } from 'storybook/internal/types';

import actionAnnotations from 'storybook/actions';
import testAnnotations from 'storybook/test';
import actionAnnotations from 'storybook/actions/preview';
import testAnnotations from 'storybook/test/preview';

import type { NormalizedProjectAnnotations } from '../types';

Expand Down
3 changes: 2 additions & 1 deletion code/core/src/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import { describe, it, test } from 'vitest';

import { action } from 'storybook/actions';
import { expect, fn, isMockFunction, traverseArgs } from 'storybook/test';
import { expect, fn, isMockFunction } from 'storybook/test';
import { traverseArgs } from 'storybook/test/preview';

it('storybook expect and fn can be used in vitest test', () => {
const spy = fn();
Expand Down
83 changes: 2 additions & 81 deletions code/core/src/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import type { BoundFunctions } from '@testing-library/dom';
import { userEvent } from '@testing-library/user-event';
import type { userEvent } from '@testing-library/user-event';

import type { LoaderFunction } from 'storybook/internal/csf';
import { instrument } from 'storybook/internal/instrumenter';
import { definePreview } from 'storybook/internal/preview-api';

import { Assertion } from 'chai';

import { expect as rawExpect } from './expect';
import { clearAllMocks, fn, isMockFunction, resetAllMocks, restoreAllMocks } from './spy';
import { type queries, within } from './testing-library';
import { type queries } from './testing-library';

export * from './spy';

Expand Down Expand Up @@ -42,79 +39,3 @@ export const { expect } = instrument(
);

export * from './testing-library';

export const resetAllMocksLoader: LoaderFunction = ({ parameters }) => {
if (parameters?.test?.mockReset === true) {
resetAllMocks();
} else if (parameters?.test?.clearMocks === true) {
clearAllMocks();
} else if (parameters?.test?.restoreMocks !== false) {
restoreAllMocks();
}
};

export const traverseArgs = (value: unknown, depth = 0, key?: string): unknown => {
// Make sure to not get in infinite loops with self referencing args
if (depth > 5) {
return value;
}

if (value == null) {
return value;
}
if (isMockFunction(value)) {
// Makes sure we get the arg name in the interactions panel
if (key) {
value.mockName(key);
}
return value;
}

// wrap explicit actions in a spy
if (
typeof value === 'function' &&
'isAction' in value &&
value.isAction &&
!('implicit' in value && value.implicit)
) {
const mock = fn(value as any);

if (key) {
mock.mockName(key);
}
return mock;
}

if (Array.isArray(value)) {
depth++;
return value.map((item) => traverseArgs(item, depth));
}

if (typeof value === 'object' && value.constructor === Object) {
depth++;
for (const [k, v] of Object.entries(value)) {
if (Object.getOwnPropertyDescriptor(value, k)?.writable) {
// We have to mutate the original object for this to survive HMR.
(value as Record<string, unknown>)[k] = traverseArgs(v, depth, k);
}
}
return value;
}
return value;
};

export const nameSpiesAndWrapActionsInSpies: LoaderFunction = ({ initialArgs }) => {
traverseArgs(initialArgs);
};

export const enhanceContext: LoaderFunction = (context) => {
if (globalThis.HTMLElement && context.canvasElement instanceof globalThis.HTMLElement) {
context.canvas = within(context.canvasElement);
}
context.userEvent = userEvent.setup();
};

export default () =>
definePreview({
loaders: [resetAllMocksLoader, nameSpiesAndWrapActionsInSpies, enhanceContext],
});
90 changes: 83 additions & 7 deletions code/core/src/test/preview.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,85 @@
import {
enhanceContext,
nameSpiesAndWrapActionsInSpies,
resetAllMocksLoader,
} from 'storybook/test';
import { within } from '@testing-library/dom';
import { userEvent } from '@testing-library/user-event';

export default {
loaders: [resetAllMocksLoader, nameSpiesAndWrapActionsInSpies, enhanceContext],
import type { LoaderFunction } from 'storybook/internal/csf';
import { definePreview } from 'storybook/internal/preview-api';

import { clearAllMocks, fn, isMockFunction, resetAllMocks, restoreAllMocks } from './spy';

const resetAllMocksLoader: LoaderFunction = ({ parameters }) => {
if (parameters?.test?.mockReset === true) {
resetAllMocks();
} else if (parameters?.test?.clearMocks === true) {
clearAllMocks();
} else if (parameters?.test?.restoreMocks !== false) {
restoreAllMocks();
}
};

export const traverseArgs = (value: unknown, depth = 0, key?: string): unknown => {
// Make sure to not get in infinite loops with self referencing args
if (depth > 5) {
return value;
}

if (value == null) {
return value;
}
if (isMockFunction(value)) {
// Makes sure we get the arg name in the interactions panel
if (key) {
value.mockName(key);
}
return value;
}

// wrap explicit actions in a spy
if (
typeof value === 'function' &&
'isAction' in value &&
value.isAction &&
!('implicit' in value && value.implicit)
) {
const mock = fn(value as any);

if (key) {
mock.mockName(key);
}
return mock;
}

if (Array.isArray(value)) {
depth++;
return value.map((item) => traverseArgs(item, depth));
}

if (typeof value === 'object' && value.constructor === Object) {
depth++;
for (const [k, v] of Object.entries(value)) {
if (Object.getOwnPropertyDescriptor(value, k)?.writable) {
// We have to mutate the original object for this to survive HMR.
(value as Record<string, unknown>)[k] = traverseArgs(v, depth, k);
}
}
return value;
}
return value;
};

const nameSpiesAndWrapActionsInSpies: LoaderFunction = ({ initialArgs }) => {
traverseArgs(initialArgs);
};

const enhanceContext: LoaderFunction = (context) => {
if (globalThis.HTMLElement && context.canvasElement instanceof globalThis.HTMLElement) {
context.canvas = within(context.canvasElement);
}
if (globalThis.window) {
context.userEvent = userEvent.setup();
}
};

export default () =>
definePreview({
loaders: [resetAllMocksLoader, nameSpiesAndWrapActionsInSpies, enhanceContext],
});
3 changes: 2 additions & 1 deletion code/frameworks/angular/src/client/decorateStory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { componentWrapperDecorator } from './decorators';
import decorateStory from './decorateStory';
import { AngularRenderer } from './types';

describe('decorateStory', () => {
// TODO: Fix. Test is infinitely running.
describe.skip('decorateStory', () => {
describe('angular behavior', () => {
it('should use componentWrapperDecorator with args', () => {
const decorators: DecoratorFunction<AngularRenderer>[] = [
Expand Down
1 change: 1 addition & 0 deletions code/frameworks/angular/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default mergeConfig(
defineConfig({
// Add custom config here
test: {
globals: true,
setupFiles: ['src/test-setup.ts'],
},
})
Expand Down
3 changes: 1 addition & 2 deletions code/renderers/react/src/__test__/portable-stories.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';

import React from 'react';

import type { ProjectAnnotations } from 'storybook/internal/csf';
import { addons } from 'storybook/internal/preview-api';

import type { Meta, ReactRenderer } from '@storybook/react';
import type { Meta } from '@storybook/react';

import { expectTypeOf } from 'expect-type';

Expand Down
Loading

0 comments on commit 8e30709

Please sign in to comment.