-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
505 additions
and
0 deletions.
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
code/renderers/svelte/src/__test__/composeStories/Button.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { userEvent, within } from '@storybook/testing-library'; | ||
import type { Meta, StoryFn as CSF2Story, StoryObj } from '../..'; | ||
|
||
import Button from './Button.svelte'; | ||
|
||
const meta = { | ||
title: 'Example/Button', | ||
component: Button, | ||
argTypes: { | ||
size: { control: 'select', options: ['small', 'medium', 'large'] }, | ||
backgroundColor: { control: 'color' }, | ||
onClick: { action: 'clicked' }, | ||
}, | ||
args: { primary: false }, | ||
excludeStories: /.*ImNotAStory$/, | ||
} as Meta<typeof Button>; | ||
|
||
export default meta; | ||
type CSF3Story = StoryObj<typeof meta>; | ||
|
||
// For testing purposes. Should be ignored in ComposeStories | ||
export const ImNotAStory = 123; | ||
|
||
const Template: CSF2Story = (args) => ({ | ||
components: { Button }, | ||
setup() { | ||
return { args }; | ||
}, | ||
template: '<Button v-bind="args" />', | ||
}); | ||
|
||
export const CSF2Secondary = Template.bind({}); | ||
CSF2Secondary.args = { | ||
label: 'label coming from story args!', | ||
primary: false, | ||
}; | ||
|
||
const getCaptionForLocale = (locale: string) => { | ||
switch (locale) { | ||
case 'es': | ||
return 'Hola!'; | ||
case 'fr': | ||
return 'Bonjour!'; | ||
case 'kr': | ||
return '안녕하세요!'; | ||
case 'pt': | ||
return 'Olá!'; | ||
default: | ||
return 'Hello!'; | ||
} | ||
}; | ||
|
||
export const CSF2StoryWithLocale: CSF2Story = (args, { globals }) => ({ | ||
components: { Button }, | ||
setup() { | ||
console.log({ globals }); | ||
const label = getCaptionForLocale(globals.locale); | ||
return { args: { ...args, label } }; | ||
}, | ||
template: `<div> | ||
<p>locale: ${globals.locale}</p> | ||
<Button v-bind="args" /> | ||
</div>`, | ||
}); | ||
CSF2StoryWithLocale.storyName = 'WithLocale'; | ||
|
||
export const CSF2StoryWithParamsAndDecorator = Template.bind({}); | ||
CSF2StoryWithParamsAndDecorator.args = { | ||
label: 'foo', | ||
}; | ||
CSF2StoryWithParamsAndDecorator.parameters = { | ||
layout: 'centered', | ||
}; | ||
CSF2StoryWithParamsAndDecorator.decorators = [ | ||
() => ({ template: '<div style="margin: 3em;"><story/></div>' }), | ||
]; | ||
|
||
export const CSF3Primary: CSF3Story = { | ||
args: { | ||
label: 'foo', | ||
size: 'large', | ||
primary: true, | ||
}, | ||
}; | ||
|
||
export const CSF3Button: CSF3Story = { | ||
args: { label: 'foo' }, | ||
}; | ||
|
||
export const CSF3ButtonWithRender: CSF3Story = { | ||
...CSF3Button, | ||
render: (args) => ({ | ||
components: { Button }, | ||
setup() { | ||
return { args }; | ||
}, | ||
template: ` | ||
<div> | ||
<p data-testid="custom-render">I am a custom render function</p> | ||
<Button v-bind="args" /> | ||
</div> | ||
`, | ||
}), | ||
}; | ||
|
||
export const CSF3InputFieldFilled: CSF3Story = { | ||
...CSF3Button, | ||
render: (args) => ({ | ||
components: { Button }, | ||
setup() { | ||
return { args }; | ||
}, | ||
template: '<input data-testid="input" />', | ||
}), | ||
play: async ({ canvasElement, step }) => { | ||
const canvas = within(canvasElement); | ||
await step('Step label', async () => { | ||
await userEvent.type(canvas.getByTestId('input'), 'Hello world!'); | ||
}); | ||
}, | ||
}; |
32 changes: 32 additions & 0 deletions
32
code/renderers/svelte/src/__test__/composeStories/Button.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<script lang="ts"> | ||
/** | ||
* Is this the principal call to action on the page? | ||
*/ | ||
export let primary = false; | ||
/** | ||
* What background color to use | ||
*/ | ||
export let backgroundColor: string | undefined = undefined; | ||
/** | ||
* How large should the button be? | ||
*/ | ||
export let size: 'small' | 'medium' | 'large' = 'medium'; | ||
/** | ||
* Button contents | ||
*/ | ||
export let label: string = ''; | ||
$: mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; | ||
$: style = backgroundColor ? `background-color: ${backgroundColor}` : ''; | ||
</script> | ||
|
||
<button | ||
type="button" | ||
class={['storybook-button', `storybook-button--${size}`, mode].join(' ')} | ||
{style} | ||
on:click | ||
> | ||
{label} | ||
</button> |
111 changes: 111 additions & 0 deletions
111
code/renderers/svelte/src/__test__/composeStories/composeStories.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/// <reference types="@testing-library/jest-dom" />; | ||
import { it, expect, vi, describe } from 'vitest'; | ||
import { render, screen } from '@testing-library/svelte'; | ||
import '@testing-library/svelte/vitest'; | ||
import { expectTypeOf } from 'expect-type'; | ||
import type { Meta } from '../../'; | ||
import * as stories from './Button.stories'; | ||
// import type Button from './Button.svelte'; | ||
import Button from './Button.svelte'; | ||
import { composeStories, composeStory, setProjectAnnotations } from '../../portable-stories'; | ||
import { SvelteComponent } from 'svelte'; | ||
|
||
// example with composeStories, returns an object with all stories composed with args/decorators | ||
const { CSF3Primary } = composeStories(stories); | ||
|
||
// example with composeStory, returns a single story composed with args/decorators | ||
const Secondary = composeStory(stories.CSF2Secondary, stories.default); | ||
|
||
it('renders primary button', () => { | ||
render(CSF3Primary); | ||
// const buttonElement = screen.getByText(/Hello world/i); | ||
// expect(buttonElement).toBeInTheDocument(); | ||
}); | ||
|
||
// it('reuses args from composed story', () => { | ||
// render(Secondary()); | ||
// const buttonElement = screen.getByRole('button'); | ||
// expect(buttonElement.textContent).toEqual(Secondary.args.label); | ||
// }); | ||
|
||
// it('myClickEvent handler is called', async () => { | ||
// const myClickEventSpy = vi.fn(); | ||
// render(Secondary({ onMyClickEvent: myClickEventSpy })); | ||
// const buttonElement = screen.getByRole('button'); | ||
// buttonElement.click(); | ||
// expect(myClickEventSpy).toHaveBeenCalled(); | ||
// }); | ||
|
||
// it('reuses args from composeStories', () => { | ||
// const { getByText } = render(CSF3Primary()); | ||
// const buttonElement = getByText(/foo/i); | ||
// expect(buttonElement).toBeInTheDocument(); | ||
// }); | ||
|
||
// describe('projectAnnotations', () => { | ||
// it('renders with default projectAnnotations', () => { | ||
// const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); | ||
// const { getByText } = render(WithEnglishText()); | ||
// const buttonElement = getByText('Hello!'); | ||
// expect(buttonElement).toBeInTheDocument(); | ||
// }); | ||
|
||
// it('renders with custom projectAnnotations via composeStory params', () => { | ||
// const WithPortugueseText = composeStory(stories.CSF2StoryWithLocale, stories.default, { | ||
// globalTypes: { locale: { defaultValue: 'pt' } } as any, | ||
// }); | ||
// const { getByText } = render(WithPortugueseText()); | ||
// const buttonElement = getByText('Olá!'); | ||
// expect(buttonElement).toBeInTheDocument(); | ||
// }); | ||
|
||
// it('renders with custom projectAnnotations via setProjectAnnotations', () => { | ||
// setProjectAnnotations([{ parameters: { injected: true } }]); | ||
// const Story = composeStory(stories.CSF2StoryWithLocale, stories.default); | ||
// expect(Story.parameters?.injected).toBe(true); | ||
// }); | ||
// }); | ||
|
||
// describe('CSF3', () => { | ||
// it('renders with inferred globalRender', () => { | ||
// const Primary = composeStory(stories.CSF3Button, stories.default); | ||
|
||
// render(Primary({ label: 'Hello world' })); | ||
// const buttonElement = screen.getByText(/Hello world/i); | ||
// expect(buttonElement).toBeInTheDocument(); | ||
// }); | ||
|
||
// it('renders with custom render function', () => { | ||
// const Primary = composeStory(stories.CSF3ButtonWithRender, stories.default); | ||
|
||
// render(Primary()); | ||
// expect(screen.getByTestId('custom-render')).toBeInTheDocument(); | ||
// }); | ||
|
||
// it('renders with play function', async () => { | ||
// const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); | ||
|
||
// const { container } = render(CSF3InputFieldFilled()); | ||
|
||
// await CSF3InputFieldFilled.play({ canvasElement: container as HTMLElement }); | ||
|
||
// const input = screen.getByTestId('input') as HTMLInputElement; | ||
// expect(input.value).toEqual('Hello world!'); | ||
// }); | ||
// }); | ||
|
||
// describe('ComposeStories types', () => { | ||
// it('Should support typescript operators', () => { | ||
// type ComposeStoriesParam = Parameters<typeof composeStories>[0]; | ||
|
||
// expectTypeOf({ | ||
// ...stories, | ||
// default: stories.default as Meta<typeof Button>, | ||
// }).toMatchTypeOf<ComposeStoriesParam>(); | ||
|
||
// expectTypeOf({ | ||
// ...stories, | ||
// default: stories.default satisfies Meta<typeof Button>, | ||
// }).toMatchTypeOf<ComposeStoriesParam>(); | ||
// }); | ||
// }); |
123 changes: 123 additions & 0 deletions
123
code/renderers/svelte/src/__test__/composeStories/internals.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import React from 'react'; | ||
// import { addons } from '@storybook/preview-api'; | ||
// import { render, screen } from '@testing-library/svelte'; | ||
import { describe, it, expect } from 'vitest'; | ||
|
||
import { composeStories, composeStory } from '../../portable-stories'; | ||
|
||
import * as stories from './Button.stories'; | ||
|
||
const { CSF2StoryWithParamsAndDecorator } = composeStories(stories); | ||
|
||
it('returns composed args including default values from argtypes', () => { | ||
expect({ | ||
...stories.default.args, | ||
...CSF2StoryWithParamsAndDecorator.args, | ||
}).toEqual(expect.objectContaining(CSF2StoryWithParamsAndDecorator.args)); | ||
}); | ||
|
||
it('returns composed parameters from story', () => { | ||
expect(CSF2StoryWithParamsAndDecorator.parameters).toEqual( | ||
expect.objectContaining({ | ||
...stories.CSF2StoryWithParamsAndDecorator.parameters, | ||
}) | ||
); | ||
}); | ||
|
||
describe('Id of the story', () => { | ||
it('is exposed correctly when composeStories is used', () => { | ||
expect(CSF2StoryWithParamsAndDecorator.id).toBe( | ||
'example-button--csf-2-story-with-params-and-decorator' | ||
); | ||
}); | ||
it('is exposed correctly when composeStory is used and exportsName is passed', () => { | ||
const exportName = Object.entries(stories).filter( | ||
([_, story]) => story === stories.CSF3Primary | ||
)[0][0]; | ||
const Primary = composeStory(stories.CSF3Primary, stories.default, {}, exportName); | ||
expect(Primary.id).toBe('example-button--csf-3-primary'); | ||
}); | ||
it("is not unique when composeStory is used and exportsName isn't passed", () => { | ||
const Primary = composeStory(stories.CSF3Primary, stories.default); | ||
expect(Primary.id).toContain('unknown'); | ||
}); | ||
}); | ||
|
||
// TODO: Bring this back | ||
// common in addons that need to communicate between manager and preview | ||
// it('should pass with decorators that need addons channel', () => { | ||
// const PrimaryWithChannels = composeStory(stories.CSF3Primary, stories.default, { | ||
// decorators: [ | ||
// (StoryFn: any) => { | ||
// addons.getChannel(); | ||
// return StoryFn(); | ||
// }, | ||
// ], | ||
// }); | ||
// render(PrimaryWithChannels({ label: 'Hello world' })); | ||
// const buttonElement = screen.getByText(/Hello world/i); | ||
// expect(buttonElement).not.toBeNull(); | ||
// }); | ||
|
||
describe('Unsupported formats', () => { | ||
it('should throw error if story is undefined', () => { | ||
const UnsupportedStory = () => <div>hello world</div>; | ||
UnsupportedStory.story = { parameters: {} }; | ||
|
||
const UnsupportedStoryModule: any = { | ||
default: {}, | ||
UnsupportedStory: undefined, | ||
}; | ||
|
||
expect(() => { | ||
composeStories(UnsupportedStoryModule); | ||
}).toThrow(); | ||
}); | ||
}); | ||
|
||
describe('non-story exports', () => { | ||
it('should filter non-story exports with excludeStories', () => { | ||
const StoryModuleWithNonStoryExports = { | ||
default: { | ||
title: 'Some/Component', | ||
excludeStories: /.*Data/, | ||
}, | ||
LegitimateStory: () => <div>hello world</div>, | ||
mockData: {}, | ||
}; | ||
|
||
const result = composeStories(StoryModuleWithNonStoryExports); | ||
expect(Object.keys(result)).not.toContain('mockData'); | ||
}); | ||
|
||
it('should filter non-story exports with includeStories', () => { | ||
const StoryModuleWithNonStoryExports = { | ||
default: { | ||
title: 'Some/Component', | ||
includeStories: /.*Story/, | ||
}, | ||
LegitimateStory: () => <div>hello world</div>, | ||
mockData: {}, | ||
}; | ||
|
||
const result = composeStories(StoryModuleWithNonStoryExports); | ||
expect(Object.keys(result)).not.toContain('mockData'); | ||
}); | ||
}); | ||
|
||
// // Batch snapshot testing | ||
// const testCases = Object.values(composeStories(stories)).map((Story) => [ | ||
// // The ! is necessary in Typescript only, as the property is part of a partial type | ||
// Story.storyName!, | ||
// Story, | ||
// ]); | ||
// it.each(testCases)('Renders %s story', async (_storyName, Story) => { | ||
// if (typeof Story === 'string' || _storyName === 'CSF2StoryWithParamsAndDecorator') { | ||
// return; | ||
// } | ||
|
||
// await new Promise((resolve) => setTimeout(resolve, 0)); | ||
|
||
// const tree = await render(Story()); | ||
// expect(tree.baseElement).toMatchSnapshot(); | ||
// }); |
Oops, something went wrong.