Skip to content

Commit

Permalink
Merge branch 'next' into kasper/phases
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperpeulen authored Jul 3, 2024
2 parents de38fa6 + f9474c0 commit cbade14
Show file tree
Hide file tree
Showing 47 changed files with 477 additions and 155 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<img src="https://opencollective.com/storybook/backers/badge.svg" alt="Backers on Open Collective" />
</a>
<a href="#sponsors">
<img src="https://opencollective.com/storybook/sponsors/badge.svg" alt="Sponsors on Open Collective" />
<img src="https://opencollective.com/storybook/tiers/sponsors/badge.svg" alt="Sponsors on Open Collective" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=storybookjs">
<img src="https://img.shields.io/twitter/follow/storybookjs?color=blue&logo=twitter" alt="Official Twitter Handle" />
Expand Down Expand Up @@ -215,7 +215,32 @@ Storybook is organized as a monorepo. Useful scripts include:

Become a sponsor to have your logo and website URL on our README on Github. \[[Become a sponsor](https://opencollective.com/storybook#sponsor)]

<a href="https://opencollective.com/storybook"><img src="https://opencollective.com/storybook/tiers/sponsors.svg?limit=80&button=false&avatarHeight=46&width=750"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/0/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/0/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/1/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/1/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/2/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/2/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/3/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/3/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/4/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/4/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/5/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/5/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/6/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/6/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/7/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/7/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/8/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/8/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/9/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/9/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/10/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/10/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/11/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/11/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/12/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/12/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/13/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/13/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/14/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/14/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/15/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/15/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/16/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/16/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/17/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/17/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/18/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/18/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/19/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/19/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/20/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/20/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/21/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/21/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/22/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/22/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/23/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/23/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/24/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/24/avatar.svg?requireActive=true"></a>
<a href="https://opencollective.com/storybook/tiers/sponsors/25/website?requireActive=true" target="_blank"><img src="https://opencollective.com/storybook/tiers/sponsors/25/avatar.svg?requireActive=true"></a>

### Backers

Expand Down
4 changes: 3 additions & 1 deletion code/addons/interactions/src/components/MethodCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ export const ArrayNode = ({
}
const nodes = value
.slice(0, 3)
.map((v) => <Node key={JSON.stringify(v)} value={v} nested callsById={callsById} />);
.map((v, index) => (
<Node key={`${index}--${JSON.stringify(v)}`} value={v} nested callsById={callsById} />
));
const nodelist = interleave(nodes, <span>, </span>);
if (value.length <= 3) {
return <span style={{ color: colors.base }}>[{nodelist}]</span>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import * as webpackReal from 'webpack';
import { logger } from 'storybook/internal/node-logger';
import type { Options } from 'storybook/internal/types';
import type { Options, PresetProperty } from 'storybook/internal/types';
import type { Configuration } from 'webpack';
import { loadCustomWebpackConfig } from '@storybook/core-webpack';
import { createDefaultWebpackConfig } from '../preview/base-webpack.config';

export const swc: PresetProperty<'swc'> = (config: Record<string, any>): Record<string, any> => {
return {
...config,
env: {
...(config?.env ?? {}),
targets: config?.env?.targets ?? {
chrome: 100,
safari: 15,
firefox: 91,
},
// Transpiles the broken syntax to the closest non-broken modern syntax.
// E.g. it won't transpile parameter destructuring in Safari
// which would break how we detect if the mount context property is used in the play function.
bugfixes: config?.env?.bugfixes ?? true,
},
};
};

export async function webpack(config: Configuration, options: Options) {
const { configDir, configType, presets } = options;

Expand Down
10 changes: 6 additions & 4 deletions code/core/src/core-server/utils/StoryIndexGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,16 @@ export class StoryIndexGenerator {
this.specifiers.map(async (specifier) => {
const pathToSubIndex = {} as SpecifierStoriesCache;

const fullGlob = slash(
path.join(this.options.workingDir, specifier.directory, specifier.files)
);
const fullGlob = slash(path.join(specifier.directory, specifier.files));

// Dynamically import globby because it is a pure ESM module
const { globby } = await import('globby');

const files = await globby(fullGlob, commonGlobOptions(fullGlob));
const files = await globby(fullGlob, {
absolute: true,
cwd: this.options.workingDir,
...commonGlobOptions(fullGlob),
});

if (files.length === 0) {
once.warn(
Expand Down
Empty file.
2 changes: 1 addition & 1 deletion code/core/src/core-server/withTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type TelemetryOptions = {
};

const promptCrashReports = async () => {
if (process.env.CI && process.env.NODE_ENV !== 'test') {
if (process.env.CI) {
return undefined;
}

Expand Down
2 changes: 1 addition & 1 deletion code/core/src/csf-tools/ConfigFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export class ConfigFile {
return _getPathProperties(rest, exported);
}

getFieldValue(path: string[]) {
getFieldValue<T = any>(path: string[]): T | undefined {
const node = this.getFieldNode(path);
if (node) {
const { code } = generate(node, {});
Expand Down
40 changes: 30 additions & 10 deletions code/core/src/preview-api/modules/preview-web/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,22 @@ import {
UPDATE_GLOBALS,
UPDATE_STORY_ARGS,
} from '@storybook/core/core-events';
import type { CleanupCallback } from '@storybook/csf';
import type { Channel } from '@storybook/core/channels';
import type {
Renderer,
Args,
Globals,
ModuleImportFn,
RenderContextCallbacks,
RenderToCanvas,
PreparedStory,
StoryIndex,
ProjectAnnotations,
StoryId,
StoryRenderOptions,
SetGlobalsPayload,
} from '@storybook/core/types';
import {
CalledPreviewMethodBeforeInitializationError,
MissingRenderToCanvasError,
Expand All @@ -34,16 +49,6 @@ import { StoryRender } from './render/StoryRender';
import type { CsfDocsRender } from './render/CsfDocsRender';
import type { MdxDocsRender } from './render/MdxDocsRender';
import { mountDestructured } from './render/mount-utils';
import type { Args, Globals, Renderer, StoryId } from '@storybook/core/types';
import type {
ModuleImportFn,
PreparedStory,
ProjectAnnotations,
RenderToCanvas,
} from '@storybook/core/types';
import type { RenderContextCallbacks, StoryRenderOptions } from '@storybook/core/types';
import type { StoryIndex } from '@storybook/core/types';
import type { SetGlobalsPayload } from '@storybook/core/types';

const { fetch } = global;

Expand All @@ -69,6 +74,8 @@ export class Preview<TRenderer extends Renderer> {
// project annotations. Once the index loads, it is stored on the store and this will get unset.
private projectAnnotationsBeforeInitialization?: ProjectAnnotations<TRenderer>;

private beforeAllCleanup?: CleanupCallback | void;

protected storeInitializationPromise: Promise<void>;

protected resolveStoreInitializationPromise!: () => void;
Expand Down Expand Up @@ -120,6 +127,7 @@ export class Preview<TRenderer extends Renderer> {

try {
const projectAnnotations = await this.getProjectAnnotationsOrRenderError();
await this.runBeforeAllHook(projectAnnotations);
await this.initializeWithProjectAnnotations(projectAnnotations);
} catch (err) {
this.rejectStoreInitializationPromise(err as Error);
Expand Down Expand Up @@ -168,6 +176,16 @@ export class Preview<TRenderer extends Renderer> {
}
}

async runBeforeAllHook(projectAnnotations: ProjectAnnotations<TRenderer>) {
try {
await this.beforeAllCleanup?.();
this.beforeAllCleanup = await projectAnnotations.beforeAll?.();
} catch (err) {
this.renderPreviewEntryError('Error in beforeAll hook:', err as Error);
throw err;
}
}

async getStoryIndexFromServer() {
const result = await fetch(STORY_INDEX_PATH);
if (result.status === 200) {
Expand Down Expand Up @@ -223,6 +241,8 @@ export class Preview<TRenderer extends Renderer> {
this.getProjectAnnotations = getProjectAnnotations;

const projectAnnotations = await this.getProjectAnnotationsOrRenderError();
await this.runBeforeAllHook(projectAnnotations);

if (!this.storyStoreValue) {
await this.initializeWithProjectAnnotations(projectAnnotations);
return;
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/preview-api/modules/store/StoryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
normalizeProjectAnnotations,
prepareContext,
} from './csf';
import type { CleanupCallback } from '@storybook/csf';
import type { Canvas, CleanupCallback } from '@storybook/csf';
import type {
BoundStory,
CSFFile,
Expand Down Expand Up @@ -375,7 +375,7 @@ export class StoryStore<TRenderer extends Renderer> {
step: (label, play) => story.runStep(label, play, context),
context: null!,
mount: null!,
canvas: {},
canvas: {} as Canvas,
viewMode: 'story',
} as StoryContext<TRenderer>;

Expand Down
71 changes: 71 additions & 0 deletions code/core/src/preview-api/modules/store/csf/beforeAll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { composeBeforeAllHooks } from './beforeAll';

const calls: string[] = [];

beforeEach(() => {
calls.length = 0;
});

const basicHook = (label: string) => () => {
calls.push(label);
};
const asyncHook = (label: string, delay: number) => async () => {
await new Promise((resolve) => setTimeout(resolve, delay));
calls.push(label);
};
const cleanupHook = (label: string) => () => {
calls.push(label);
return () => {
calls.push(label + ' cleanup');
};
};
const asyncCleanupHook = (label: string, delay: number) => () => {
calls.push(label);
return async () => {
await new Promise((resolve) => setTimeout(resolve, delay));
calls.push(label + ' cleanup');
};
};

describe('composeBeforeAllHooks', () => {
it('should return a composed hook function', async () => {
await composeBeforeAllHooks([basicHook('one'), basicHook('two'), basicHook('three')])();
expect(calls).toEqual(['one', 'two', 'three']);
});

it('should execute cleanups in reverse order', async () => {
const cleanup = await composeBeforeAllHooks([
cleanupHook('one'),
cleanupHook('two'),
cleanupHook('three'),
])();
expect(calls).toEqual(['one', 'two', 'three']);

await cleanup?.();
expect(calls).toEqual(['one', 'two', 'three', 'three cleanup', 'two cleanup', 'one cleanup']);
});

it('should execute async hooks in sequence', async () => {
await composeBeforeAllHooks([
asyncHook('one', 10),
asyncHook('two', 100),
asyncHook('three', 10),
])();
expect(calls).toEqual(['one', 'two', 'three']);
});

it('should execute async cleanups in reverse order', async () => {
const hooks = [
asyncCleanupHook('one', 10),
asyncCleanupHook('two', 100),
asyncCleanupHook('three', 10),
];

const cleanup = await composeBeforeAllHooks(hooks)();
expect(calls).toEqual(['one', 'two', 'three']);

await cleanup?.();
expect(calls).toEqual(['one', 'two', 'three', 'three cleanup', 'two cleanup', 'one cleanup']);
});
});
17 changes: 17 additions & 0 deletions code/core/src/preview-api/modules/store/csf/beforeAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type BeforeAll, type CleanupCallback } from '@storybook/csf';

// Execute all the hooks in sequence, and return a function that will execute cleanups in reverse order
export const composeBeforeAllHooks = (hooks: BeforeAll[]): BeforeAll => {
return async () => {
const cleanups: CleanupCallback[] = [];
for (const hook of hooks) {
const cleanup = await hook();
if (cleanup) cleanups.unshift(cleanup);
}
return async () => {
for (const cleanup of cleanups) {
await cleanup();
}
};
};
};
Loading

0 comments on commit cbade14

Please sign in to comment.