Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
a45e827
feat: introduce @storybook/angular-vite package
brandonroberts Mar 6, 2026
4897dad
feat: add @storybook/angular-vite CLI integration
brandonroberts Apr 7, 2026
ff72530
fix(angular-vite): normalize generated import paths for Windows
brandonroberts Apr 9, 2026
96e1fab
feat(angular-vite): default to Vite in non-interactive init mode
brandonroberts Apr 9, 2026
e1df708
chore: regenerate yarn.lock and flip angular-vite sandbox template to…
valentinpalkovic May 11, 2026
57c01d7
fix(angular-vite): bump package version to 10.4.0-alpha.17
valentinpalkovic May 11, 2026
e27cf7d
ci: retrigger after storybookjs/sandboxes#19 merge
valentinpalkovic May 11, 2026
352b825
style(angular-vite): apply prettier formatting
valentinpalkovic May 11, 2026
2b450bc
ci(angular-vite): add angular-cli/vite-default-ts to normal cadence
valentinpalkovic May 11, 2026
ede6881
chore: yarn dedupe
valentinpalkovic May 11, 2026
693af2a
style(angular-vite): apply eslint --fix (import extensions, type-only…
valentinpalkovic May 11, 2026
3091e27
chore: pin rxjs ^7.8.2 resolution to unify hoisted version
valentinpalkovic May 11, 2026
61e8e87
fix(angular): pin webpack builder for legacy sandboxes, add @angular/…
valentinpalkovic May 11, 2026
b408ee4
fix(core-webpack): widen WebpackConfiguration.devtool to webpack 5.10…
valentinpalkovic May 11, 2026
4170dc7
fix(core-webpack): inline devtool union to avoid dts-bundle webpack t…
valentinpalkovic May 11, 2026
0cdcb04
fix(angular-vite): retarget template stories to @storybook/angular-vite
valentinpalkovic May 11, 2026
01d9023
fix(angular): include component sources in .storybook tsconfig
valentinpalkovic May 11, 2026
cb679ad
ci: retrigger after storybookjs/sandboxes#20 merge
valentinpalkovic May 11, 2026
33a026a
Merge branch 'next' into feat-storybookjs-angular-vite
valentinpalkovic May 11, 2026
f40ea6a
fix(sandbox): pin rolldown to 1.0.0 in sandbox resolutions
valentinpalkovic May 11, 2026
4075f74
ci: retrigger after storybookjs/sandboxes#21 merge
valentinpalkovic May 11, 2026
2fdb04e
Merge branch 'next' into feat-storybookjs-angular-vite
valentinpalkovic May 11, 2026
bcb0d5e
fix(angular-vite): configure JSX transform for esbuild and oxc
valentinpalkovic May 11, 2026
74b63bf
fix(angular-vite): merge @angular platform chunks to avoid rolldown l…
valentinpalkovic May 11, 2026
283e0a9
fix(angular-vite): include template-stories components in sandbox tsc…
valentinpalkovic May 12, 2026
f05c6e9
fix(angular-vite): narrow sandbox tsconfig glob to template components
valentinpalkovic May 12, 2026
4d0d215
fix(angular-vite): run analogjs plugin in vite pre group so automock …
valentinpalkovic May 12, 2026
905b22d
style(angular-vite): apply prettier formatting
valentinpalkovic May 12, 2026
68e0022
fix(sandbox): run prepareAngularSandbox for angular-vite template
valentinpalkovic May 13, 2026
a27d6c4
fix(angular-vite): bail cleanArgsDecorator when argTypes are absent
valentinpalkovic May 13, 2026
aa6a417
fix(angular-vite): restore keepNames flag for esbuild
brandonroberts May 13, 2026
51278bb
fix(angular-vite): keep class names through Rolldown's oxc minifier
brandonroberts May 13, 2026
bd9a5f0
fix(angular-vite): demote csf-plugin and mock-loader out of 'pre' bucket
brandonroberts May 13, 2026
0551936
fix(angular-vite): reapply __mocks__ redirects after analogjs transform
brandonroberts May 13, 2026
4a7c5e6
Merge remote-tracking branch 'upstream/next' into feat-storybookjs-an…
brandonroberts May 13, 2026
5ddb345
Merge remote-tracking branch 'upstream/next' into feat-storybookjs-an…
brandonroberts May 22, 2026
2083d9a
build: dedupe yarn.lock
brandonroberts May 22, 2026
39ba0f0
build: align @storybook/angular-vite version with monorepo
brandonroberts May 22, 2026
96c2a1f
build: drop stale type-fest resolution from bad next merge
brandonroberts May 22, 2026
f8601c2
fix: use OmitIndexSignature in angular-vite preview types
brandonroberts May 22, 2026
12e9df9
Merge remote-tracking branch 'upstream/next' into feat-storybookjs-an…
brandonroberts May 22, 2026
8cd03de
build: drop type-fest from EXISTING_RESOLUTIONS
brandonroberts May 22, 2026
a49797c
Angular: Drop pre-21 compatibility shims in @storybook/angular-vite
valentinpalkovic May 27, 2026
df666eb
Angular: Rename client/angular-beta/ to client/renderer/ in @storyboo…
valentinpalkovic May 27, 2026
53471ad
Angular: Drop "experimental" prefix from zoneless option in @storyboo…
valentinpalkovic May 27, 2026
d387d8a
Angular: Address post-review cleanup in @storybook/angular-vite
valentinpalkovic May 27, 2026
27bb5b5
Angular: Port model() signal detection to @storybook/angular-vite
valentinpalkovic May 27, 2026
2da6f5a
Angular: Bump @storybook/angular-vite to Vite 8
valentinpalkovic May 27, 2026
81503ea
Angular: Enable @storybook/addon-vitest for @storybook/angular-vite
valentinpalkovic May 27, 2026
e87646c
Angular: Derive Angular target selector from storyId instead of DOM n…
valentinpalkovic May 27, 2026
429de55
Angular: Fix @storybook/angular-vite sandbox failures under addon-vitest
valentinpalkovic May 27, 2026
0aed127
Angular: Harden storyUid sanitizer for non-ASCII docs autodocs
valentinpalkovic May 27, 2026
cdb7e76
Merge branch 'next' into feat-storybookjs-angular-vite
valentinpalkovic May 27, 2026
2f1d261
Dedupe
valentinpalkovic May 27, 2026
e0eee04
Add documentation
valentinpalkovic May 27, 2026
3f5c55e
docs(angular-vite): correct CLI builder support + framework preview s…
valentinpalkovic May 27, 2026
f6c138d
Add automigration from `@storybook/angular` to `@storybook/angular-vite`
valentinpalkovic May 27, 2026
0169c32
docs(angular-vite): apply prettier (trailing commas in jsonc block)
valentinpalkovic May 27, 2026
174a10a
Apply suggestions from code review
kylegach May 27, 2026
85c95f5
Angular: Skip automock reapply on already-wrapped JS modules
valentinpalkovic May 27, 2026
f79b87c
test(e2e): await sbPage.waitUntilLoaded in addon-actions tests
valentinpalkovic May 27, 2026
ae968d0
Angular sandbox: stub documentation.json so non-Storybook entry point…
valentinpalkovic May 27, 2026
bc5dfdd
Angular sandbox: stub documentation.json with valid compodoc shape
valentinpalkovic May 27, 2026
92d452c
Angular: Align RendererFactory tests with current renderer contract
valentinpalkovic May 28, 2026
e9ff8e0
Angular sandbox: pre-run docs:json before vitest
valentinpalkovic May 28, 2026
4c42c04
Angular: cleanArgsDecorator keeps args bound to component inputs/outputs
valentinpalkovic May 28, 2026
1f0cdc8
Angular sandbox: drop documentation.json stub; rely on framework cold…
valentinpalkovic May 28, 2026
994a980
docs(angular-vite): document compodoc framework.options + clarify inv…
valentinpalkovic May 28, 2026
76255ab
Angular-Vite: move compodoc to framework.options + bridge builder opt…
valentinpalkovic May 28, 2026
80c322f
Angular-Vite: drop framework.options.zoneless in favor of builder option
valentinpalkovic May 28, 2026
cf0451e
Docs tweaks
kylegach May 28, 2026
626f5c0
docs(angular-vite): document storybookAngularVitest standalone-vitest…
valentinpalkovic May 29, 2026
3a45c32
Angular-Vite: add storybookAngularVitest standalone-vitest options br…
valentinpalkovic May 29, 2026
8ae1344
Addon-vitest: auto-wire storybookAngularVitest for Angular projects o…
valentinpalkovic May 29, 2026
27288e4
Trigger CI
valentinpalkovic May 29, 2026
456c252
Trigger CI
valentinpalkovic May 29, 2026
aa85180
Angular-Vite migration: rewrite builder refs in Nx project.json files
valentinpalkovic May 29, 2026
2f247b2
Angular-Vite: stop running Compodoc from the builders
valentinpalkovic May 29, 2026
45fa66e
Angular-Vite migration: carry builder compodoc:false into framework.o…
valentinpalkovic May 29, 2026
edbd40b
Trigger CI
valentinpalkovic May 29, 2026
9bc9128
Merge branch 'next' into feat-storybookjs-angular-vite
valentinpalkovic Jun 1, 2026
0bc5d7a
Refactor Vitest configuration handling to support complex function no…
valentinpalkovic Jun 1, 2026
521e9cf
Angular-Vite migration: preserve framework.name when carrying compodo…
valentinpalkovic Jun 1, 2026
6175780
Angular-Vite init: put compodoc in main.ts framework.options, not the…
valentinpalkovic Jun 1, 2026
e204161
Addon-vitest: make storybookTest configDir relative to the generated …
valentinpalkovic Jun 1, 2026
9ccbc16
Merge remote-tracking branch 'origin/next' into feat-storybookjs-angu…
valentinpalkovic Jun 3, 2026
f7a9cb1
Angular: Defer addon postinstall in angular-to-angular-vite migration…
valentinpalkovic Jun 3, 2026
fc9cea7
Angular: Mark @storybook/angular-vite as "in preview" in the init bui…
valentinpalkovic Jun 3, 2026
b2f4d3f
Angular: Preserve getAbsolutePath()-wrapped framework name in angular…
valentinpalkovic Jun 3, 2026
7c93a2e
Angular: Run deferred addon postinstall on the storybook upgrade path
valentinpalkovic Jun 3, 2026
c119079
Addon-vitest: Read fresh main config in postinstall to avoid stale fr…
valentinpalkovic Jun 3, 2026
c71a780
Merge origin/next into valentin/angular-vite-testbed
valentinpalkovic Jun 9, 2026
8f350f7
Angular-vite: Bump package version to 10.5.0-alpha.5 to match monorepo
valentinpalkovic Jun 9, 2026
5ba869b
Angular-vite: Add TestBed-based canvas rendering behind previewTestBe…
valentinpalkovic Jun 9, 2026
7e831a1
Angular-vite: Trigger router initial navigation under TestBed mountin…
valentinpalkovic Jun 9, 2026
4bbec0d
Angular-vite: Trim over-limit chunk file names from JIT inline-style …
valentinpalkovic Jun 9, 2026
bfcfc93
Angular-vite: Scope the TestBed strategy to the Storybook preview canvas
valentinpalkovic Jun 9, 2026
e51d046
Angular-vite: Use the TestBed strategy under addon-vitest by resettin…
valentinpalkovic Jun 9, 2026
5a458c0
Build: Dedupe @angular-devkit entries in lockfile
valentinpalkovic Jun 11, 2026
c3091f2
Merge origin/next into valentin/angular-vite-testbed
valentinpalkovic Jun 11, 2026
875d999
Angular-vite: Pin sandbox @angular/forms & animations to the core maj…
valentinpalkovic Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
235 changes: 235 additions & 0 deletions code/addons/vitest/src/angular-vitest-postinstall.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { describe, expect, it } from 'vitest';

import type { types as t } from 'storybook/internal/babel';
import { babelParse, generate, traverse } from 'storybook/internal/babel';

import {
ANGULAR_VITEST_IMPORT_SOURCE,
ANGULAR_VITEST_PLUGIN_CALL,
collectStorybookTestLocalNames,
injectAngularVitestIntoAst,
injectAngularVitestIntoConfig,
isAngularVitestAlreadyWired,
} from './angular-vitest-postinstall.ts';

/** Returns the names of the elements (call callees / spread) inside the plugins array, in order. */
function pluginCalleesInSameArray(code: string, locatorName = 'storybookTest'): string[] | null {
const ast = babelParse(code);
let elements: string[] | null = null;
traverse(ast, {
CallExpression(path) {
if (elements) {
path.stop();
return;
}
const { callee } = path.node;
if (
callee.type === 'Identifier' &&
callee.name === locatorName &&
path.parentPath.isArrayExpression()
) {
const array = path.parentPath.node as t.ArrayExpression;
elements = array.elements.map((el) => {
if (el?.type === 'CallExpression' && el.callee.type === 'Identifier') {
return el.callee.name;
}
if (el?.type === 'SpreadElement') {
return 'spread';
}
return el?.type ?? 'null';
});
path.stop();
}
},
});
return elements;
}

function countOccurrences(haystack: string, needle: string): number {
return haystack.split(needle).length - 1;
}

const FRESH_V4 = `
import path from 'node:path';
import { defineConfig } from 'vitest/config';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';

export default defineConfig({
test: {
projects: [
{
extends: true,
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
storybookTest({ configDir: path.join(__dirname, '.storybook') }),
],
test: { name: 'storybook' },
},
],
},
});
`;

describe('isAngularVitestAlreadyWired', () => {
it('is false on a plain storybookTest config', () => {
expect(isAngularVitestAlreadyWired(FRESH_V4)).toBe(false);
});

it('is true when the import source is present', () => {
expect(
isAngularVitestAlreadyWired(
`import { storybookAngularVitest } from '${ANGULAR_VITEST_IMPORT_SOURCE}';`
)
).toBe(true);
});

it('is true when only the call is present (partial wiring)', () => {
expect(
isAngularVitestAlreadyWired('plugins: [storybookAngularVitest({}), storybookTest({})]')
).toBe(true);
});
});

describe('collectStorybookTestLocalNames', () => {
it('always seeds the bare storybookTest name', () => {
const names = collectStorybookTestLocalNames(babelParse('export default {};'));
expect(names.has('storybookTest')).toBe(true);
});

it('collects aliased import names', () => {
const names = collectStorybookTestLocalNames(
babelParse(`import { storybookTest as sbTest } from '@storybook/addon-vitest/vitest-plugin';`)
);
expect(names.has('sbTest')).toBe(true);
expect(names.has('storybookTest')).toBe(true);
});

it('ignores imports from other sources', () => {
const names = collectStorybookTestLocalNames(
babelParse(`import { storybookTest as other } from 'somewhere-else';`)
);
expect(names.has('other')).toBe(false);
});
});

describe('injectAngularVitestIntoConfig', () => {
it('co-locates the bridge in the same plugins array as storybookTest (case 1)', () => {
const out = injectAngularVitestIntoConfig(FRESH_V4);
expect(out).not.toBeNull();
const callees = pluginCalleesInSameArray(out!);
expect(callees).toEqual([ANGULAR_VITEST_PLUGIN_CALL, 'storybookTest']);
// Quote style is normalized later by prettier in the real flow; assert on the structural import.
expect(out).toContain(`{ ${ANGULAR_VITEST_PLUGIN_CALL} }`);
expect(out).toContain(ANGULAR_VITEST_IMPORT_SOURCE);
});

it('adds the scaffold comment (case 20: comments preserved through generate)', () => {
const out = injectAngularVitestIntoConfig(FRESH_V4)!;
expect(out).toContain('Forwards Angular build options');
// The template's own comment must also survive.
expect(out).toContain('The plugin will run tests for the stories');
});

it('is alias-aware: co-locates next to an aliased storybookTest (case 2)', () => {
const aliased = FRESH_V4.replace(
`import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';`,
`import { storybookTest as sbTest } from '@storybook/addon-vitest/vitest-plugin';`
).replace('storybookTest({ configDir', 'sbTest({ configDir');

const out = injectAngularVitestIntoConfig(aliased);
expect(out).not.toBeNull();
const callees = pluginCalleesInSameArray(out!, 'sbTest');
expect(callees).toEqual([ANGULAR_VITEST_PLUGIN_CALL, 'sbTest']);
});

it('returns null on spread storybookTest with no locatable array (case 3)', () => {
const spread = `
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
export default { test: { projects: [{ plugins: [...storybookTest()] }] } };
`;
expect(injectAngularVitestIntoConfig(spread)).toBeNull();
});

it('returns null when storybookTest is not in any array (exotic)', () => {
const exotic = `
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
const p = storybookTest();
export default { test: { projects: [{ plugins: p }] } };
`;
expect(injectAngularVitestIntoConfig(exotic)).toBeNull();
});

it('returns null on unparsable content', () => {
expect(injectAngularVitestIntoConfig('this is ::: not valid <<< ts')).toBeNull();
});

it('is idempotent: a second pass adds no duplicate import or call (case 8)', () => {
const once = injectAngularVitestIntoConfig(FRESH_V4)!;
const twice = injectAngularVitestIntoConfig(once);
// Already wired by the import source — the string path short-circuits via callers, but the
// injector itself must also not duplicate when re-run on already-injected content.
expect(twice).not.toBeNull();
expect(countOccurrences(twice!, ANGULAR_VITEST_IMPORT_SOURCE)).toBe(1);
expect(countOccurrences(twice!, `${ANGULAR_VITEST_PLUGIN_CALL}(`)).toBe(1);
});

it('partial wiring: call already present, adds import without duplicating the call (case 10a)', () => {
const callOnly = FRESH_V4.replace(
'plugins: [',
'plugins: [\n storybookAngularVitest({}),'
);
const out = injectAngularVitestIntoConfig(callOnly)!;
expect(countOccurrences(out, `${ANGULAR_VITEST_PLUGIN_CALL}(`)).toBe(1);
expect(countOccurrences(out, ANGULAR_VITEST_IMPORT_SOURCE)).toBe(1);
});

it('partial wiring: import already present, adds call without duplicating the import (case 10b)', () => {
const importOnly = `import { storybookAngularVitest } from '${ANGULAR_VITEST_IMPORT_SOURCE}';\n${FRESH_V4}`;
const out = injectAngularVitestIntoConfig(importOnly)!;
expect(countOccurrences(out, ANGULAR_VITEST_IMPORT_SOURCE)).toBe(1);
expect(countOccurrences(out, `${ANGULAR_VITEST_PLUGIN_CALL}(`)).toBe(1);
const callees = pluginCalleesInSameArray(out);
expect(callees).toEqual([ANGULAR_VITEST_PLUGIN_CALL, 'storybookTest']);
});
});

describe('injectAngularVitestIntoAst', () => {
it('mutates the AST in place and returns true', () => {
const ast = babelParse(FRESH_V4);
expect(injectAngularVitestIntoAst(ast)).toBe(true);
const callees = pluginCalleesInSameArray(generate(ast).code);
expect(callees).toEqual([ANGULAR_VITEST_PLUGIN_CALL, 'storybookTest']);
});

it('returns false for non-locatable arrays', () => {
const ast = babelParse('export default { test: {} };');
expect(injectAngularVitestIntoAst(ast)).toBe(false);
});

it('co-locates in a workspace (defineWorkspace) element plugins array (case 6)', () => {
const workspace = `
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
export default defineWorkspace([
'./vite.config.ts',
{
extends: './vite.config.ts',
plugins: [storybookTest({ configDir: '.storybook' })],
test: { name: 'storybook' },
},
]);
`;
const out = injectAngularVitestIntoConfig(workspace);
expect(out).not.toBeNull();
const callees = pluginCalleesInSameArray(out!);
expect(callees).toEqual([ANGULAR_VITEST_PLUGIN_CALL, 'storybookTest']);
});

it('returns null for a workspace that only references external configs (case 7)', () => {
const referenced = `
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace(['./vite.config.ts', './vitest.storybook.config.ts']);
`;
expect(injectAngularVitestIntoConfig(referenced)).toBeNull();
});
});
Loading