Merge remote-tracking branch 'refs/remotes/origin/portable-stories-play-to-run' into kasper/introduce-run

This commit is contained in:
Kasper Peulen 2024-07-31 13:37:04 +02:00
commit d85f877294
162 changed files with 975 additions and 503 deletions

View File

@ -1,3 +1,7 @@
## 8.2.6
- CPC: Fix missing exports for addon-kit - [#28691](https://github.com/storybookjs/storybook/pull/28691), thanks @ndelangen!
## 8.2.5
- CPC: Add the globals export for manager - [#28650](https://github.com/storybookjs/storybook/pull/28650), thanks @ndelangen!

View File

@ -1,3 +1,18 @@
## 8.3.0-alpha.3
- Angular: Fix Angular template error for props with a circular reference - [#28498](https://github.com/storybookjs/storybook/pull/28498), thanks @Marklb!
- Angular: Fix template props not able to use dot notation - [#28588](https://github.com/storybookjs/storybook/pull/28588), thanks @Marklb!
- CLI: Fix the initialization of Storybook in workspaces - [#28699](https://github.com/storybookjs/storybook/pull/28699), thanks @valentinpalkovic!
- CPC: Fix missing exports for addon-kit - [#28691](https://github.com/storybookjs/storybook/pull/28691), thanks @ndelangen!
- CPC: Fix type usage in renderers - [#28745](https://github.com/storybookjs/storybook/pull/28745), thanks @ndelangen!
- Controls: Add disableSave parameter - [#28734](https://github.com/storybookjs/storybook/pull/28734), thanks @valentinpalkovic!
- React: Avoid 'Dynamic require of react is not possible' issue - [#28730](https://github.com/storybookjs/storybook/pull/28730), thanks @valentinpalkovic!
- Telemetry: Add mount, beforeEach, moduleMock stats - [#28624](https://github.com/storybookjs/storybook/pull/28624), thanks @shilman!
- Telemetry: CSF feature usage - [#28622](https://github.com/storybookjs/storybook/pull/28622), thanks @shilman!
- Types: Adjust beforeAll to be non-nullable in NormalizedProjectAnnotations - [#28671](https://github.com/storybookjs/storybook/pull/28671), thanks @kasperpeulen!
- Vue: Fix out of memory error when using vue-component-meta for events and slots - [#28674](https://github.com/storybookjs/storybook/pull/28674), thanks @larsrickert!
- Vue: Improve generated code snippets - [#27194](https://github.com/storybookjs/storybook/pull/27194), thanks @larsrickert!
## 8.3.0-alpha.2
- Addon-Interactions: Fix status in panel tab - [#28580](https://github.com/storybookjs/storybook/pull/28580), thanks @yannbf!

View File

@ -1632,7 +1632,7 @@ export const Primary = {
## From version 6.5.x to 7.0.0
A number of these changes can be made automatically by the Storybook CLI. To take advantage of these "automigrations", run `npx storybook@latest upgrade --prerelease` or `pnpx dlx storybook@latest upgrade --prerelease`.
A number of these changes can be made automatically by the Storybook CLI. To take advantage of these "automigrations", run `npx storybook@7 upgrade` or `pnpx dlx storybook@7 upgrade`.
### 7.0 breaking changes

View File

@ -8,34 +8,78 @@ const managerApiPath = path.join(__dirname, '../core/src/manager-api');
const config: StorybookConfig = {
stories: [
{
directory: '../core/template/stories',
titlePrefix: 'core',
},
{
directory: '../core/src/manager',
titlePrefix: '@manager',
titlePrefix: 'manager',
},
{
directory: '../core/src/preview-api',
titlePrefix: '@preview',
titlePrefix: 'preview',
},
{
directory: '../core/src/components',
titlePrefix: '@components',
directory: '../core/src/components/brand',
titlePrefix: 'brand',
},
{
directory: '../core/src/components/components',
titlePrefix: 'components',
},
{
directory: '../lib/blocks/src',
titlePrefix: '@blocks',
titlePrefix: 'blocks',
},
{
directory: '../addons/a11y/template/stories',
titlePrefix: 'addons/a11y',
},
{
directory: '../addons/actions/template/stories',
titlePrefix: 'addons/actions',
},
{
directory: '../addons/backgrounds/template/stories',
titlePrefix: 'addons/backgrounds',
},
{
directory: '../addons/controls/src',
titlePrefix: '@addons/controls',
titlePrefix: 'addons/controls',
},
{
directory: '../addons/controls/template/stories',
titlePrefix: 'addons/controls',
},
{
directory: '../addons/docs/template/stories',
titlePrefix: 'addons/docs',
},
{
directory: '../addons/links/template/stories',
titlePrefix: 'addons/links',
},
{
directory: '../addons/viewport/template/stories',
titlePrefix: 'addons/viewport',
},
{
directory: '../addons/toolbars/template/stories',
titlePrefix: 'addons/toolbars',
},
{
directory: '../addons/onboarding/src',
titlePrefix: '@addons/onboarding',
titlePrefix: 'addons/onboarding',
},
{
directory: '../addons/interactions/src',
titlePrefix: '@addons/interactions',
titlePrefix: 'addons/interactions',
},
// {
// directory: '../addons/interactions/template/stories',
// titlePrefix: 'addons/interactions',
// },
],
addons: [
'@storybook/addon-links',
@ -46,6 +90,11 @@ const config: StorybookConfig = {
'@storybook/addon-a11y',
'@chromatic-com/storybook',
],
previewAnnotations: [
'./core/template/stories/preview.ts',
'./addons/toolbars/template/stories/preview.ts',
'./renderers/react/template/components/index.js',
],
build: {
test: {
// we have stories for the blocks here, we can't exclude them
@ -58,9 +107,21 @@ const config: StorybookConfig = {
name: '@storybook/react-vite',
options: {},
},
refs: {
icons: {
title: 'Icons',
url: 'https://main--64b56e737c0aeefed9d5e675.chromatic.com',
expanded: false,
},
},
core: {
disableTelemetry: true,
},
features: {
viewportStoryGlobals: true,
themesStoryGlobals: true,
backgroundsStoryGlobals: true,
},
viteFinal: (viteConfig, { configType }) =>
mergeConfig(viteConfig, {
resolve: {
@ -81,7 +142,7 @@ const config: StorybookConfig = {
sourcemap: process.env.CI !== 'true',
},
}),
logLevel: 'debug',
// logLevel: 'debug',
};
export default config;

View File

@ -296,20 +296,3 @@ export const parameters = {
],
},
};
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme for components',
toolbar: {
icon: 'circlehollow',
title: 'Theme',
items: [
{ value: 'light', icon: 'circlehollow', title: 'light' },
{ value: 'dark', icon: 'circle', title: 'dark' },
{ value: 'side-by-side', icon: 'sidebar', title: 'side by side' },
{ value: 'stacked', icon: 'bottombar', title: 'stacked' },
],
},
},
};

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Test component compliance with web accessibility standards",
"keywords": [
"a11y",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Get UI feedback when an action is performed on an interactive element",
"keywords": [
"storybook",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Switch backgrounds to view components in different settings",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-controls",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Interact with component inputs dynamically in the Storybook UI",
"keywords": [
"addon",

View File

@ -34,6 +34,7 @@ interface ControlsParameters {
sort?: SortType;
expanded?: boolean;
presetColors?: PresetColor[];
disableSaveFromUI?: boolean;
}
interface ControlsPanelProps {
@ -46,7 +47,12 @@ export const ControlsPanel = ({ saveStory, createStory }: ControlsPanelProps) =>
const [args, updateArgs, resetArgs, initialArgs] = useArgs();
const [globals] = useGlobals();
const rows = useArgTypes();
const { expanded, sort, presetColors } = useParameter<ControlsParameters>(PARAM_KEY, {});
const {
expanded,
sort,
presetColors,
disableSaveFromUI = false,
} = useParameter<ControlsParameters>(PARAM_KEY, {});
const { path, previewInitialized } = useStorybookState();
// If the story is prepared, then show the args table
@ -84,9 +90,10 @@ export const ControlsPanel = ({ saveStory, createStory }: ControlsPanelProps) =>
sort={sort}
isLoading={isLoading}
/>
{hasControls && hasUpdatedArgs && global.CONFIG_TYPE === 'DEVELOPMENT' && (
<SaveStory {...{ resetArgs, saveStory, createStory }} />
)}
{hasControls &&
hasUpdatedArgs &&
global.CONFIG_TYPE === 'DEVELOPMENT' &&
disableSaveFromUI !== true && <SaveStory {...{ resetArgs, saveStory, createStory }} />}
</AddonWrapper>
);
};

View File

@ -184,7 +184,7 @@ The Storybook UI is a workshop for developing components in isolation. Storybook
To address this, weve added a CLI flag to only export the docs. This flag is also available in dev mode:
```sh
yarn build-storybook --docs
yarn storybook build --docs
```
## Disabling docs stories

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Document component usage and properties in Markdown",
"keywords": [
"addon",

View File

@ -2,7 +2,7 @@ import { global as globalThis } from '@storybook/global';
export default {
component: globalThis.Components.Button,
tags: ['autodocs'],
tags: ['autodocs', '!test'],
args: { label: 'Click Me!' },
parameters: { chromatic: { disable: true } },
};
@ -12,10 +12,7 @@ export default {
*/
export const ErrorStory = {
decorators: [
(storyFn) => {
// Don't throw in the test runner; there's no easy way to skip (yet)
if (window?.navigator?.userAgent?.match(/StorybookTestRunner/)) return storyFn();
() => {
throw new Error('Story did something wrong');
},
],

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-essentials",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Curated addons to bring out the best of Storybook",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-mdx-gfm",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "GitHub Flavored Markdown in Storybook",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-highlight",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Highlight DOM nodes within your stories",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-interactions",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Automate, test and debug user interactions",
"keywords": [
"storybook-addons",

View File

@ -13,7 +13,7 @@ export default {
actions: { argTypesRegex: '^on[A-Z].*' },
chromatic: { disable: true },
},
tags: ['test-skip'],
tags: ['!test'],
};
export const Default = {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Link stories together to build demos and prototypes with your UI components",
"keywords": [
"addon",
@ -67,7 +67,7 @@
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/addon-bundle.ts"
},
"dependencies": {
"@storybook/csf": "0.1.11",
"@storybook/csf": "^0.1.11",
"@storybook/global": "^5.0.0",
"ts-dedent": "^2.0.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-measure",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Inspect layouts by visualizing the box model",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-onboarding",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook Addon Onboarding - Introduces a new onboarding experience",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-outline",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Outline all elements with CSS to help with layout placement and alignment",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storysource",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "View a storys source code to see how it works and paste into your app",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-themes",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Switch between multiple themes for you components in Storybook",
"keywords": [
"css",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-toolbars",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Create your own toolbar items that control story rendering",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-viewport",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Build responsive components by adjusting Storybooks viewport size and orientation",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "A plugin to run and build Storybooks with Vite",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme",
"bugs": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
@ -253,7 +253,7 @@
"prep": "bun ./scripts/prep.ts"
},
"dependencies": {
"@storybook/csf": "0.1.11",
"@storybook/csf": "^0.1.11",
"@types/express": "^4.17.21",
"@types/node": "^18.0.0",
"browser-assert": "^1.2.1",

View File

@ -64,22 +64,6 @@ export abstract class JsPackageManager {
return packageJSON ? packageJSON.version ?? null : null;
}
// NOTE: for some reason yarn prefers the npm registry in
// local development, so always use npm
async setRegistryURL(url: string) {
if (url) {
await this.executeCommand({ command: 'npm', args: ['config', 'set', 'registry', url] });
} else {
await this.executeCommand({ command: 'npm', args: ['config', 'delete', 'registry'] });
}
}
async getRegistryURL() {
const res = await this.executeCommand({ command: 'npm', args: ['config', 'get', 'registry'] });
const url = res.trim();
return url === 'undefined' ? undefined : url;
}
constructor(options?: JsPackageManagerOptions) {
this.cwd = options?.cwd || process.cwd();
}
@ -487,6 +471,8 @@ export abstract class JsPackageManager {
): // Use generic and conditional type to force `string[]` if fetchAllVersions is true and `string` if false
Promise<T extends true ? string[] : string>;
public abstract getRegistryURL(): Promise<string | undefined>;
public abstract runPackageCommand(
command: string,
args: string[],

View File

@ -34,21 +34,6 @@ describe('NPM Proxy', () => {
});
});
describe('setRegistryUrl', () => {
it('should run `npm config set registry https://foo.bar`', async () => {
const executeCommandSpy = vi.spyOn(npmProxy, 'executeCommand').mockResolvedValueOnce('');
await npmProxy.setRegistryURL('https://foo.bar');
expect(executeCommandSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npm',
args: ['config', 'set', 'registry', 'https://foo.bar'],
})
);
});
});
describe('installDependencies', () => {
describe('npm6', () => {
it('should run `npm install`', async () => {

View File

@ -181,6 +181,17 @@ export class NPMProxy extends JsPackageManager {
});
}
public async getRegistryURL() {
const res = await this.executeCommand({
command: 'npm',
// "npm config" commands are not allowed in workspaces per default
// https://github.com/npm/cli/issues/6099#issuecomment-1847584792
args: ['config', 'get', 'registry', '-ws=false', '-iwr'],
});
const url = res.trim();
return url === 'undefined' ? undefined : url;
}
protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) {
const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream();
let args = [...dependencies];

View File

@ -24,21 +24,6 @@ describe('PNPM Proxy', () => {
});
});
describe('setRegistryUrl', () => {
it('should run `npm config set registry https://foo.bar`', async () => {
const executeCommandSpy = vi.spyOn(pnpmProxy, 'executeCommand').mockResolvedValueOnce('');
await pnpmProxy.setRegistryURL('https://foo.bar');
expect(executeCommandSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npm',
args: ['config', 'set', 'registry', 'https://foo.bar'],
})
);
});
});
describe('installDependencies', () => {
it('should run `pnpm install`', async () => {
const executeCommandSpy = vi

View File

@ -90,6 +90,15 @@ export class PNPMProxy extends JsPackageManager {
});
}
public async getRegistryURL() {
const res = await this.executeCommand({
command: 'pnpm',
args: ['config', 'get', 'registry'],
});
const url = res.trim();
return url === 'undefined' ? undefined : url;
}
async runPackageCommand(command: string, args: string[], cwd?: string): Promise<string> {
return this.executeCommand({
command: 'pnpm',

View File

@ -25,21 +25,6 @@ describe('Yarn 1 Proxy', () => {
});
});
describe('setRegistryUrl', () => {
it('should run `yarn config set npmRegistryServer https://foo.bar`', async () => {
const executeCommandSpy = vi.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce('');
await yarn1Proxy.setRegistryURL('https://foo.bar');
expect(executeCommandSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npm',
args: ['config', 'set', 'registry', 'https://foo.bar'],
})
);
});
});
describe('installDependencies', () => {
it('should run `yarn`', async () => {
const executeCommandSpy = vi.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce('');

View File

@ -83,6 +83,15 @@ export class Yarn1Proxy extends JsPackageManager {
return JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as Record<string, any>;
}
public async getRegistryURL() {
const res = await this.executeCommand({
command: 'yarn',
args: ['config', 'get', 'registry'],
});
const url = res.trim();
return url === 'undefined' ? undefined : url;
}
public async findInstallations(pattern: string[], { depth = 99 }: { depth?: number } = {}) {
const yarnArgs = ['list', '--pattern', pattern.map((p) => `"${p}"`).join(' '), '--json'];

View File

@ -53,21 +53,6 @@ describe('Yarn 2 Proxy', () => {
});
});
describe('setRegistryUrl', () => {
it('should run `yarn config set npmRegistryServer https://foo.bar`', async () => {
const executeCommandSpy = vi.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce('');
await yarn2Proxy.setRegistryURL('https://foo.bar');
expect(executeCommandSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npm',
args: ['config', 'set', 'registry', 'https://foo.bar'],
})
);
});
});
describe('addDependencies', () => {
it('with devDep it should run `yarn install -D @storybook/core`', async () => {
const executeCommandSpy = vi.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce('');

View File

@ -239,6 +239,15 @@ export class Yarn2Proxy extends JsPackageManager {
await removeLogFile();
}
public async getRegistryURL() {
const res = await this.executeCommand({
command: 'yarn',
args: ['config', 'get', 'npmRegistryServer'],
});
const url = res.trim();
return url === 'undefined' ? undefined : url;
}
protected async runRemoveDeps(dependencies: string[]) {
const args = [...dependencies];

View File

@ -1,84 +1,84 @@
// auto generated file, do not edit
export default {
'@storybook/addon-a11y': '8.3.0-alpha.2',
'@storybook/addon-actions': '8.3.0-alpha.2',
'@storybook/addon-backgrounds': '8.3.0-alpha.2',
'@storybook/addon-controls': '8.3.0-alpha.2',
'@storybook/addon-docs': '8.3.0-alpha.2',
'@storybook/addon-essentials': '8.3.0-alpha.2',
'@storybook/addon-mdx-gfm': '8.3.0-alpha.2',
'@storybook/addon-highlight': '8.3.0-alpha.2',
'@storybook/addon-interactions': '8.3.0-alpha.2',
'@storybook/addon-jest': '8.3.0-alpha.2',
'@storybook/addon-links': '8.3.0-alpha.2',
'@storybook/addon-measure': '8.3.0-alpha.2',
'@storybook/addon-onboarding': '8.3.0-alpha.2',
'@storybook/addon-outline': '8.3.0-alpha.2',
'@storybook/addon-storysource': '8.3.0-alpha.2',
'@storybook/addon-themes': '8.3.0-alpha.2',
'@storybook/addon-toolbars': '8.3.0-alpha.2',
'@storybook/addon-viewport': '8.3.0-alpha.2',
'@storybook/builder-vite': '8.3.0-alpha.2',
'@storybook/builder-webpack5': '8.3.0-alpha.2',
'@storybook/core': '8.3.0-alpha.2',
'@storybook/builder-manager': '8.3.0-alpha.2',
'@storybook/channels': '8.3.0-alpha.2',
'@storybook/client-logger': '8.3.0-alpha.2',
'@storybook/components': '8.3.0-alpha.2',
'@storybook/core-common': '8.3.0-alpha.2',
'@storybook/core-events': '8.3.0-alpha.2',
'@storybook/core-server': '8.3.0-alpha.2',
'@storybook/csf-tools': '8.3.0-alpha.2',
'@storybook/docs-tools': '8.3.0-alpha.2',
'@storybook/manager': '8.3.0-alpha.2',
'@storybook/manager-api': '8.3.0-alpha.2',
'@storybook/node-logger': '8.3.0-alpha.2',
'@storybook/preview': '8.3.0-alpha.2',
'@storybook/preview-api': '8.3.0-alpha.2',
'@storybook/router': '8.3.0-alpha.2',
'@storybook/telemetry': '8.3.0-alpha.2',
'@storybook/theming': '8.3.0-alpha.2',
'@storybook/types': '8.3.0-alpha.2',
'@storybook/angular': '8.3.0-alpha.2',
'@storybook/ember': '8.3.0-alpha.2',
'@storybook/html-vite': '8.3.0-alpha.2',
'@storybook/html-webpack5': '8.3.0-alpha.2',
'@storybook/nextjs': '8.3.0-alpha.2',
'@storybook/preact-vite': '8.3.0-alpha.2',
'@storybook/preact-webpack5': '8.3.0-alpha.2',
'@storybook/react-vite': '8.3.0-alpha.2',
'@storybook/react-webpack5': '8.3.0-alpha.2',
'@storybook/server-webpack5': '8.3.0-alpha.2',
'@storybook/svelte-vite': '8.3.0-alpha.2',
'@storybook/svelte-webpack5': '8.3.0-alpha.2',
'@storybook/sveltekit': '8.3.0-alpha.2',
'@storybook/vue3-vite': '8.3.0-alpha.2',
'@storybook/vue3-webpack5': '8.3.0-alpha.2',
'@storybook/web-components-vite': '8.3.0-alpha.2',
'@storybook/web-components-webpack5': '8.3.0-alpha.2',
'@storybook/blocks': '8.3.0-alpha.2',
storybook: '8.3.0-alpha.2',
sb: '8.3.0-alpha.2',
'@storybook/cli': '8.3.0-alpha.2',
'@storybook/codemod': '8.3.0-alpha.2',
'@storybook/core-webpack': '8.3.0-alpha.2',
'@storybook/csf-plugin': '8.3.0-alpha.2',
'@storybook/instrumenter': '8.3.0-alpha.2',
'@storybook/react-dom-shim': '8.3.0-alpha.2',
'@storybook/source-loader': '8.3.0-alpha.2',
'@storybook/test': '8.3.0-alpha.2',
'@storybook/preset-create-react-app': '8.3.0-alpha.2',
'@storybook/preset-html-webpack': '8.3.0-alpha.2',
'@storybook/preset-preact-webpack': '8.3.0-alpha.2',
'@storybook/preset-react-webpack': '8.3.0-alpha.2',
'@storybook/preset-server-webpack': '8.3.0-alpha.2',
'@storybook/preset-svelte-webpack': '8.3.0-alpha.2',
'@storybook/preset-vue3-webpack': '8.3.0-alpha.2',
'@storybook/html': '8.3.0-alpha.2',
'@storybook/preact': '8.3.0-alpha.2',
'@storybook/react': '8.3.0-alpha.2',
'@storybook/server': '8.3.0-alpha.2',
'@storybook/svelte': '8.3.0-alpha.2',
'@storybook/vue3': '8.3.0-alpha.2',
'@storybook/web-components': '8.3.0-alpha.2',
'@storybook/addon-a11y': '8.3.0-alpha.3',
'@storybook/addon-actions': '8.3.0-alpha.3',
'@storybook/addon-backgrounds': '8.3.0-alpha.3',
'@storybook/addon-controls': '8.3.0-alpha.3',
'@storybook/addon-docs': '8.3.0-alpha.3',
'@storybook/addon-essentials': '8.3.0-alpha.3',
'@storybook/addon-mdx-gfm': '8.3.0-alpha.3',
'@storybook/addon-highlight': '8.3.0-alpha.3',
'@storybook/addon-interactions': '8.3.0-alpha.3',
'@storybook/addon-jest': '8.3.0-alpha.3',
'@storybook/addon-links': '8.3.0-alpha.3',
'@storybook/addon-measure': '8.3.0-alpha.3',
'@storybook/addon-onboarding': '8.3.0-alpha.3',
'@storybook/addon-outline': '8.3.0-alpha.3',
'@storybook/addon-storysource': '8.3.0-alpha.3',
'@storybook/addon-themes': '8.3.0-alpha.3',
'@storybook/addon-toolbars': '8.3.0-alpha.3',
'@storybook/addon-viewport': '8.3.0-alpha.3',
'@storybook/builder-vite': '8.3.0-alpha.3',
'@storybook/builder-webpack5': '8.3.0-alpha.3',
'@storybook/core': '8.3.0-alpha.3',
'@storybook/builder-manager': '8.3.0-alpha.3',
'@storybook/channels': '8.3.0-alpha.3',
'@storybook/client-logger': '8.3.0-alpha.3',
'@storybook/components': '8.3.0-alpha.3',
'@storybook/core-common': '8.3.0-alpha.3',
'@storybook/core-events': '8.3.0-alpha.3',
'@storybook/core-server': '8.3.0-alpha.3',
'@storybook/csf-tools': '8.3.0-alpha.3',
'@storybook/docs-tools': '8.3.0-alpha.3',
'@storybook/manager': '8.3.0-alpha.3',
'@storybook/manager-api': '8.3.0-alpha.3',
'@storybook/node-logger': '8.3.0-alpha.3',
'@storybook/preview': '8.3.0-alpha.3',
'@storybook/preview-api': '8.3.0-alpha.3',
'@storybook/router': '8.3.0-alpha.3',
'@storybook/telemetry': '8.3.0-alpha.3',
'@storybook/theming': '8.3.0-alpha.3',
'@storybook/types': '8.3.0-alpha.3',
'@storybook/angular': '8.3.0-alpha.3',
'@storybook/ember': '8.3.0-alpha.3',
'@storybook/html-vite': '8.3.0-alpha.3',
'@storybook/html-webpack5': '8.3.0-alpha.3',
'@storybook/nextjs': '8.3.0-alpha.3',
'@storybook/preact-vite': '8.3.0-alpha.3',
'@storybook/preact-webpack5': '8.3.0-alpha.3',
'@storybook/react-vite': '8.3.0-alpha.3',
'@storybook/react-webpack5': '8.3.0-alpha.3',
'@storybook/server-webpack5': '8.3.0-alpha.3',
'@storybook/svelte-vite': '8.3.0-alpha.3',
'@storybook/svelte-webpack5': '8.3.0-alpha.3',
'@storybook/sveltekit': '8.3.0-alpha.3',
'@storybook/vue3-vite': '8.3.0-alpha.3',
'@storybook/vue3-webpack5': '8.3.0-alpha.3',
'@storybook/web-components-vite': '8.3.0-alpha.3',
'@storybook/web-components-webpack5': '8.3.0-alpha.3',
'@storybook/blocks': '8.3.0-alpha.3',
storybook: '8.3.0-alpha.3',
sb: '8.3.0-alpha.3',
'@storybook/cli': '8.3.0-alpha.3',
'@storybook/codemod': '8.3.0-alpha.3',
'@storybook/core-webpack': '8.3.0-alpha.3',
'@storybook/csf-plugin': '8.3.0-alpha.3',
'@storybook/instrumenter': '8.3.0-alpha.3',
'@storybook/react-dom-shim': '8.3.0-alpha.3',
'@storybook/source-loader': '8.3.0-alpha.3',
'@storybook/test': '8.3.0-alpha.3',
'@storybook/preset-create-react-app': '8.3.0-alpha.3',
'@storybook/preset-html-webpack': '8.3.0-alpha.3',
'@storybook/preset-preact-webpack': '8.3.0-alpha.3',
'@storybook/preset-react-webpack': '8.3.0-alpha.3',
'@storybook/preset-server-webpack': '8.3.0-alpha.3',
'@storybook/preset-svelte-webpack': '8.3.0-alpha.3',
'@storybook/preset-vue3-webpack': '8.3.0-alpha.3',
'@storybook/html': '8.3.0-alpha.3',
'@storybook/preact': '8.3.0-alpha.3',
'@storybook/react': '8.3.0-alpha.3',
'@storybook/server': '8.3.0-alpha.3',
'@storybook/svelte': '8.3.0-alpha.3',
'@storybook/vue3': '8.3.0-alpha.3',
'@storybook/web-components': '8.3.0-alpha.3',
};

View File

@ -6,8 +6,8 @@ It contains:
- CLI arg parsing
- Storybook UI "manager" webpack configuration
- `start-storybook` dev server
- `build-storybook` static builder
- `storybook dev` dev server
- `storybook build` static builder
- presets handling
The "preview" (aka iframe) side is implemented in pluggable builders:

View File

@ -1 +1 @@
export const version = '8.3.0-alpha.2';
export const version = '8.3.0-alpha.3';

View File

@ -24,9 +24,6 @@ export const ForceRemount = {
*/
parameters: { chromatic: { disableSnapshot: true } },
play: async ({ canvasElement, id }: PlayFunctionContext<any>) => {
if (window?.navigator.userAgent.match(/StorybookTestRunner/)) {
return;
}
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
const button = await within(canvasElement).findByRole('button');
@ -39,6 +36,7 @@ export const ForceRemount = {
// By forcing the component to remount, we reset the focus state
await channel.emit(FORCE_REMOUNT, { storyId: id });
},
tags: ['!test'],
};
export const ChangeArgs = {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-manager",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook manager builder",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/channels",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/client-logger",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/components",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Core Storybook Components",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-common",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-events",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Event names used in storybook core",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-server",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/csf-tools",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Parse and manipulate CSF and Storybook config files",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/docs-tools",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Shared utility functions for frameworks to implement docs",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/manager-api",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Core Storybook Manager API & Context",
"keywords": [
"storybook"

View File

@ -0,0 +1 @@
module.exports = require('storybook/internal/manager/globals');

2
code/deprecated/manager/globals.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export * from 'storybook/internal/manager/globals';
export type * from 'storybook/internal/manager/globals';

View File

@ -1 +1 @@
module.exports = require('storybook/internal/manager/globals');
export * from 'storybook/internal/manager/globals';

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/manager",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Core Storybook UI",
"keywords": [
"storybook"
@ -20,6 +20,14 @@
},
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": {
"./globals": {
"type": "./globals.d.ts",
"import": "./globals.js",
"require": "./globals.cjs"
}
},
"files": [
"README.md",
"*.js",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/node-logger",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preview-api",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "",
"keywords": [
"storybook"

View File

@ -0,0 +1 @@
module.exports = require('storybook/internal/preview/globals');

2
code/deprecated/preview/globals.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export * from 'storybook/internal/preview/globals';
export type * from 'storybook/internal/preview/globals';

View File

@ -1 +1 @@
module.exports = require('storybook/internal/preview/globals');
export * from 'storybook/internal/preview/globals';

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preview",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "",
"keywords": [
"storybook"
@ -20,6 +20,14 @@
},
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": {
"./globals": {
"type": "./globals.d.ts",
"import": "./globals.js",
"require": "./globals.cjs"
}
},
"files": [
"README.md",
"*.js",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/router",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Core Storybook Router",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/telemetry",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Telemetry logging for crash reports and usage statistics",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/theming",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Core Storybook Components",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/types",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Core Storybook TS Types",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/angular",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
"keywords": [
"storybook",

View File

@ -1,11 +1,314 @@
import { Component } from '@angular/core';
import { ArgTypes } from 'storybook/internal/types';
import { describe, it, expect } from 'vitest';
import { computesTemplateSourceFromComponent } from './ComputesTemplateFromComponent';
import {
computesTemplateFromComponent,
computesTemplateSourceFromComponent,
} from './ComputesTemplateFromComponent';
import { ISomeInterface, ButtonAccent, InputComponent } from './__testfixtures__/input.component';
describe('angular template decorator', () => {
it('with props should generate tag with properties', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
accent: ButtonAccent.High,
counter: 4,
'aria-label': 'Hello world',
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(
`<doc-button [counter]="counter" [accent]="accent" [isDisabled]="isDisabled" [label]="label" [aria-label]="this['aria-label']"></doc-button>`
);
});
it('with props should generate tag with outputs', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
onClick: ($event: any) => {},
'dash-out': ($event: any) => {},
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(
`<doc-button [isDisabled]="isDisabled" [label]="label" (onClick)="onClick($event)" (dash-out)="this['dash-out']($event)"></doc-button>`
);
});
it('with no props should generate simple tag', () => {
const component = InputComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual('<doc-button></doc-button>');
});
describe('with component without selector', () => {
@Component({
template: `The content`,
})
class WithoutSelectorComponent {}
it('should add component ng-container', async () => {
const component = WithoutSelectorComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<ng-container *ngComponentOutlet="storyComponent"></ng-container>`);
});
});
describe('with component with attribute selector', () => {
@Component({
selector: 'doc-button[foo]',
template: '<button></button>',
})
class WithAttributeComponent {}
it('should add attribute to template', async () => {
const component = WithAttributeComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button foo></doc-button>`);
});
});
describe('with component with attribute and value selector', () => {
@Component({
selector: 'doc-button[foo="bar"]',
template: '<button></button>',
})
class WithAttributeValueComponent {}
it('should add attribute to template', async () => {
const component = WithAttributeValueComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button foo="bar"></doc-button>`);
});
});
describe('with component with attribute only selector', () => {
@Component({
selector: '[foo]',
template: '<button></button>',
})
class WithAttributeOnlyComponent {}
it('should create a div and add attribute to template', async () => {
const component = WithAttributeOnlyComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<div foo></div>`);
});
});
describe('with component with void element and attribute selector', () => {
@Component({
selector: 'input[foo]',
template: '<button></button>',
})
class VoidElementWithAttributeComponent {}
it('should create without separate closing tag', async () => {
const component = VoidElementWithAttributeComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<input foo />`);
});
});
describe('with component with attribute and value only selector', () => {
@Component({
selector: '[foo="bar"]',
template: '<button></button>',
})
class WithAttributeOnlyComponent {}
it('should create a div and add attribute to template', async () => {
const component = WithAttributeOnlyComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<div foo="bar"></div>`);
});
});
describe('with component with void element, attribute and value only selector', () => {
@Component({
selector: 'input[foo="bar"]',
template: '<button></button>',
})
class VoidElementWithAttributeComponent {}
it('should create and add attribute to template without separate closing tag', async () => {
const component = VoidElementWithAttributeComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<input foo="bar" />`);
});
});
describe('with component with class selector', () => {
@Component({
selector: 'doc-button.foo',
template: '<button></button>',
})
class WithClassComponent {}
it('should add class to template', async () => {
const component = WithClassComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button class="foo"></doc-button>`);
});
});
describe('with component with class only selector', () => {
@Component({
selector: '.foo',
template: '<button></button>',
})
class WithClassComponent {}
it('should create a div and add attribute to template', async () => {
const component = WithClassComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<div class="foo"></div>`);
});
});
describe('with component with multiple selectors', () => {
@Component({
selector: 'doc-button, doc-button2',
template: '<button></button>',
})
class WithMultipleSelectorsComponent {}
it('should use the first selector', async () => {
const component = WithMultipleSelectorsComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button></doc-button>`);
});
});
describe('with component with multiple selectors starting with attribute', () => {
@Component({
selector: 'doc-button[foo], doc-button2',
template: '<button></button>',
})
class WithMultipleSelectorsComponent {}
it('should use the first selector', async () => {
const component = WithMultipleSelectorsComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button foo></doc-button>`);
});
});
describe('with component with multiple selectors starting with attribute and value', () => {
@Component({
selector: 'doc-button[foo="bar"], doc-button2',
template: '<button></button>',
})
class WithMultipleSelectorsComponent {}
it('should use the first selector', async () => {
const component = WithMultipleSelectorsComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button foo="bar"></doc-button>`);
});
});
describe('with component with multiple selectors including 2 attributes and a class', () => {
@Component({
selector: 'doc-button, button[foo], .button[foo], button[baz]',
template: '<button></button>',
})
class WithMultipleSelectorsComponent {}
it('should use the first selector', async () => {
const component = WithMultipleSelectorsComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button></doc-button>`);
});
});
describe('with component with multiple selectors with line breaks', () => {
@Component({
selector: `doc-button,
doc-button2`,
template: '<button></button>',
})
class WithMultipleSelectorsComponent {}
it('should use the first selector', async () => {
const component = WithMultipleSelectorsComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button></doc-button>`);
});
});
describe('with component with multiple selectors starting with attribute only with line breaks', () => {
@Component({
selector: `[foo],
doc-button2`,
template: '<button></button>',
})
class WithMultipleSelectorsComponent {}
it('should use the first selector', async () => {
const component = WithMultipleSelectorsComponent;
const props = {};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<div foo></div>`);
});
});
it('with props should generate tag with properties', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
accent: ButtonAccent.High,
counter: 4,
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(
`<doc-button [counter]="counter" [accent]="accent" [isDisabled]="isDisabled" [label]="label"></doc-button>`
);
});
it('with props should generate tag with outputs', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
onClick: ($event: any) => {},
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(
`<doc-button [isDisabled]="isDisabled" [label]="label" (onClick)="onClick($event)"></doc-button>`
);
});
it('should generate correct property for overridden name for Input', () => {
const component = InputComponent;
const props = {
color: '#ffffff',
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(`<doc-button [color]="color"></doc-button>`);
});
});
describe('angular source decorator', () => {
it('With no props should generate simple tag', () => {
it('with no props should generate simple tag', () => {
const component = InputComponent;
const props = {};
const argTypes: ArgTypes = {};
@ -264,32 +567,34 @@ describe('angular source decorator', () => {
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(`<doc-button></doc-button>`);
});
it('With props should generate tag with properties', () => {
it('with props should generate tag with properties', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
accent: ButtonAccent.High,
counter: 4,
'aria-label': 'Hello world',
};
const argTypes: ArgTypes = {};
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(
`<doc-button [counter]="4" [accent]="'High'" [isDisabled]="true" [label]="'Hello world'"></doc-button>`
`<doc-button [counter]="4" [accent]="'High'" [isDisabled]="true" [label]="'Hello world'" [aria-label]="'Hello world'"></doc-button>`
);
});
it('With props should generate tag with outputs', () => {
it('with props should generate tag with outputs', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
onClick: ($event: any) => {},
'dash-out': ($event: any) => {},
};
const argTypes: ArgTypes = {};
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(
`<doc-button [isDisabled]="true" [label]="'Hello world'" (onClick)="onClick($event)"></doc-button>`
`<doc-button [isDisabled]="true" [label]="'Hello world'" (onClick)="onClick($event)" (dash-out)="this['dash-out']($event)"></doc-button>`
);
});
@ -305,7 +610,7 @@ describe('angular source decorator', () => {
});
describe('with argTypes (from compodoc)', () => {
it('Should handle enum as strongly typed enum', () => {
it('should handle enum as strongly typed enum', () => {
const component = InputComponent;
const props = {
isDisabled: false,
@ -335,7 +640,7 @@ describe('angular source decorator', () => {
);
});
it('Should handle enum without values as string', () => {
it('should handle enum without values as string', () => {
const component = InputComponent;
const props = {
isDisabled: false,
@ -365,7 +670,7 @@ describe('angular source decorator', () => {
);
});
it('Should handle objects correctly', () => {
it('should handle simple object as stringified', () => {
const component = InputComponent;
const someDataObject: ISomeInterface = {
@ -400,5 +705,42 @@ describe('angular source decorator', () => {
`<doc-button [isDisabled]="false" [label]="'Hello world'" [someDataObject]="{one: 'Hello world', two: true, three: ['a string literal with \\'double quotes\\'', 'a string literal with \\'single quotes\\'', 'a single quoted string with \\'double quotes\\'', 'a double quoted string with \\'single quotes\\'', 'a single quoted string with escaped \\'single quotes\\'', 'a double quoted string with escaped \\'double quotes\\'', 'a string literal with \\'escaped single quotes\\'', 'a string literal with \\'escaped double quotes\\'']}"></doc-button>`
);
});
it('should handle circular object as stringified', () => {
const component = InputComponent;
const someDataObject: ISomeInterface = {
one: 'Hello world',
two: true,
three: [
`a string literal with "double quotes"`,
`a string literal with 'single quotes'`,
'a single quoted string with "double quotes"',
"a double quoted string with 'single quotes'",
// eslint-disable-next-line prettier/prettier
'a single quoted string with escaped \'single quotes\'',
// eslint-disable-next-line prettier/prettier
"a double quoted string with escaped \"double quotes\"",
`a string literal with \'escaped single quotes\'`,
`a string literal with \"escaped double quotes\"`,
],
};
someDataObject.ref = someDataObject;
const props = {
isDisabled: false,
label: 'Hello world',
someDataObject,
};
const source = computesTemplateSourceFromComponent(component, props, null);
// Ideally we should stringify the object, but that could cause the story to break because of unescaped values in the JSON object.
// This will have to do for now
expect(source).toEqual(
`<doc-button [isDisabled]="false" [label]="'Hello world'" [someDataObject]="{one: 'Hello world', two: true, three: ['a string literal with \\'double quotes\\'', 'a string literal with \\'single quotes\\'', 'a single quoted string with \\'double quotes\\'', 'a double quoted string with \\'single quotes\\'', 'a single quoted string with escaped \\'single quotes\\'', 'a double quoted string with escaped \\'double quotes\\'', 'a string literal with \\'escaped single quotes\\'', 'a string literal with \\'escaped double quotes\\''], ref: '[Circular]'}"></doc-button>`
);
});
});
});

View File

@ -7,6 +7,20 @@ import {
getComponentInputsOutputs,
} from './utils/NgComponentAnalyzer';
/**
* Check if the name matches the criteria for a valid identifier.
* A valid identifier can only contain letters, digits, underscores, or dollar signs.
* It cannot start with a digit.
*/
const isValidIdentifier = (name: string): boolean => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
/**
* Returns the property name, if it can be accessed with dot notation. If not,
* it returns `this['propertyName']`.
*/
export const formatPropInTemplate = (propertyName: string) =>
isValidIdentifier(propertyName) ? propertyName : `this['${propertyName}']`;
const separateInputsOutputsAttributes = (
ngComponentInputsOutputs: ComponentInputsOutputs,
props: ICollection = {}
@ -50,10 +64,12 @@ export const computesTemplateFromComponent = (
);
const templateInputs =
initialInputs.length > 0 ? ` ${initialInputs.map((i) => `[${i}]="${i}"`).join(' ')}` : '';
initialInputs.length > 0
? ` ${initialInputs.map((i) => `[${i}]="${formatPropInTemplate(i)}"`).join(' ')}`
: '';
const templateOutputs =
initialOutputs.length > 0
? ` ${initialOutputs.map((i) => `(${i})="${i}($event)"`).join(' ')}`
? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
: '';
return buildTemplate(
@ -64,6 +80,22 @@ export const computesTemplateFromComponent = (
);
};
/**
* Stringify an object with a placholder in the circular references.
*/
function stringifyCircular(obj: any) {
const seen = new Set();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
return value;
});
}
const createAngularInputProperty = ({
propertyName,
value,
@ -79,7 +111,7 @@ const createAngularInputProperty = ({
templateValue = `'${value}'`;
break;
case 'object':
templateValue = JSON.stringify(value)
templateValue = stringifyCircular(value)
.replace(/'/g, '\u2019')
.replace(/\\"/g, '\u201D')
.replace(/"([^-"]+)":/g, '$1: ')
@ -137,7 +169,7 @@ export const computesTemplateSourceFromComponent = (
: '';
const templateOutputs =
initialOutputs.length > 0
? ` ${initialOutputs.map((i) => `(${i})="${i}($event)"`).join(' ')}`
? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
: '';
return buildTemplate(ngComponentMetadata.selector, '', templateInputs, templateOutputs);

View File

@ -11,6 +11,7 @@ export interface ISomeInterface {
one: string;
two: boolean;
three: any[];
ref?: ISomeInterface;
}
@Component({
@ -39,9 +40,13 @@ export class InputComponent<T> {
@Input()
public label: string;
@Input('aria-label') public ariaLabel: string;
/** Specifies some arbitrary object */
@Input() public someDataObject: ISomeInterface;
@Output()
public onClick = new EventEmitter<Event>();
@Output('dash-out') public dashOut = new EventEmitter<any>();
}

View File

@ -100,4 +100,10 @@ describe('argsToTemplate', () => {
const result = argsToTemplate(args, {});
expect(result).toEqual('[input]="input" (event1)="event1($event)"');
});
it('should format for non dot notation', () => {
const args = { 'non-dot': 'Value1', 'dash-out': () => {} };
const result = argsToTemplate(args, {});
expect(result).toEqual('[non-dot]="this[\'non-dot\']" (dash-out)="this[\'dash-out\']($event)"');
});
});

View File

@ -1,3 +1,5 @@
import { formatPropInTemplate } from './angular-beta/ComputesTemplateFromComponent';
/**
* Options for controlling the behavior of the argsToTemplate function.
*
@ -68,7 +70,9 @@ export function argsToTemplate<A extends Record<string, any>>(
return true;
})
.map(([key, value]) =>
typeof value === 'function' ? `(${key})="${key}($event)"` : `[${key}]="${key}"`
typeof value === 'function'
? `(${key})="${formatPropInTemplate(key)}($event)"`
: `[${key}]="${formatPropInTemplate(key)}"`
)
.join(' ');
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/ember",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember",
"bugs": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/html-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/html-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/nextjs",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Next.js",
"keywords": [
"storybook",

View File

@ -9,7 +9,7 @@ export default {
export const Default = {};
export const DisableRSC = {
tags: ['test-skip'],
tags: ['!test'],
parameters: {
chromatic: { disable: true },
nextjs: { rsc: false },
@ -17,7 +17,7 @@ export const DisableRSC = {
};
export const Error = {
tags: ['test-skip'],
tags: ['!test'],
parameters: {
chromatic: { disable: true },
},

View File

@ -44,6 +44,7 @@ export default {
},
},
},
tags: ['!test'],
} as Meta;
export const SingletonStateGetsInvalidatedAfterRedirecting: StoryObj = {

View File

@ -31,7 +31,7 @@ function Component() {
export default {
component: Component,
tags: ['test-skip'],
tags: ['!test'],
parameters: {
nextjs: {
appDirectory: true,

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preact-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preact-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Preact: Develop Preact Component in isolation.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/server-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/svelte-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/svelte-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/sveltekit",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for SvelteKit",
"keywords": [
"storybook",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/vue3-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/vue3-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/web-components-vite",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/web-components-webpack5",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.",
"keywords": [
"lit",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/blocks",
"version": "8.3.0-alpha.2",
"version": "8.3.0-alpha.3",
"description": "Storybook Doc Blocks",
"keywords": [
"storybook"
@ -44,7 +44,7 @@
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts"
},
"dependencies": {
"@storybook/csf": "0.1.11",
"@storybook/csf": "^0.1.11",
"@storybook/global": "^5.0.0",
"@storybook/icons": "^1.2.5",
"@types/lodash": "^4.14.167",

View File

@ -49,7 +49,7 @@ export const OfUndefined: Story = {
of: ExampleStories.NotDefined,
},
parameters: { chromatic: { disableSnapshot: true } },
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const OfStoryUnattached: Story = {

View File

@ -61,7 +61,7 @@ export const OfUndefined: Story = {
of: ButtonStories.NotDefined,
},
parameters: { chromatic: { disableSnapshot: true } },
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const PropWithToolbar: Story = {

View File

@ -46,7 +46,7 @@ export const OfUndefined: Story = {
of: ExampleStories.NotDefined,
},
parameters: { chromatic: { disableSnapshot: true } },
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const IncludeProp: Story = {

View File

@ -121,7 +121,7 @@ export const OfUndefinedAttached: Story = {
relativeCsfPaths: ['../examples/Button.stories'],
attached: true,
},
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const OfStringComponentAttached: Story = {
name: 'Of "component" Attached',

View File

@ -66,7 +66,7 @@ export const OfUndefined: Story = {
of: ParametersStories.NotDefined,
},
parameters: { chromatic: { disableSnapshot: true } },
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const OfTypeProp: Story = {

View File

@ -54,7 +54,7 @@ export const OfUndefined: Story = {
of: ButtonStories.NotDefined,
},
parameters: { chromatic: { disableSnapshot: true } },
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const Inline: Story = {

View File

@ -89,7 +89,7 @@ export const OfUndefinedAttached: Story = {
relativeCsfPaths: ['../examples/Button.stories'],
attached: true,
},
decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ? <div /> : s())],
tags: ['!test'],
};
export const OfStringMetaAttached: Story = {
name: 'Of "meta" Attached',

Some files were not shown because too many files have changed in this diff Show More