@storybook/vue3 7.1.0-alpha.6 → 7.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-7CGPMEMZ.mjs +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +2 -2
- package/dist/config.mjs +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{render-c842c5d5.d.ts → render-189b3692.d.ts} +1 -1
- package/package.json +6 -6
- package/template/stories/BaseLayout.vue +18 -0
- package/template/stories/CustomRenderFunctionalComponent.stories.ts +34 -0
- package/template/stories/CustomRenderOptionsArgsFromData.stories.ts +47 -0
- package/template/stories/CustomRenderOptionsArgsFromProps.stories.ts +33 -0
- package/template/stories/{GlobalUsage.stories.js → GlobalUsage.stories.ts} +1 -1
- package/template/stories/MySlotComponent.vue +12 -0
- package/template/stories/OverrideArgs.vue +3 -3
- package/template/stories/ReactiveArgs.stories.ts +135 -0
- package/template/stories/ReactiveDecorators.stories.ts +112 -0
- package/template/stories/ReactiveSlots.stories.ts +127 -0
- package/template/stories/Reactivity.vue +44 -0
- package/template/stories/ScopedSlots.stories.ts +81 -0
- package/template/stories/decorators.stories.ts +84 -0
- package/dist/chunk-2GDW2BFM.mjs +0 -1
- package/template/stories/ReactiveArgs.stories.js +0 -44
- package/template/stories/decorators.stories.js +0 -66
@@ -0,0 +1,34 @@
|
|
1
|
+
import type { Meta } from '@storybook/vue3';
|
2
|
+
import { h } from 'vue';
|
3
|
+
import Reactivity from './Reactivity.vue';
|
4
|
+
import * as ReactiveDecorators from './ReactiveDecorators.stories';
|
5
|
+
|
6
|
+
const meta = {
|
7
|
+
...ReactiveDecorators.default,
|
8
|
+
component: Reactivity,
|
9
|
+
// storybook render function is not a functional component. it returns a functional component or a component options
|
10
|
+
render: (args) => {
|
11
|
+
// create the slot contents as a functional components
|
12
|
+
const header = ({ title }: { title: string }) => h('h3', `${args.header} - Title: ${title}`);
|
13
|
+
const defaultSlot = () => h('p', `${args.default}`);
|
14
|
+
const footer = () => h('p', `${args.footer}`);
|
15
|
+
// vue render function is a functional components
|
16
|
+
return () =>
|
17
|
+
h('div', [
|
18
|
+
`Custom render uses a functional component, and passes slots to the component:`,
|
19
|
+
h(Reactivity, args, { header, default: defaultSlot, footer }),
|
20
|
+
]);
|
21
|
+
},
|
22
|
+
} satisfies Meta<typeof Reactivity>;
|
23
|
+
|
24
|
+
export default meta;
|
25
|
+
|
26
|
+
export {
|
27
|
+
NoDecorators,
|
28
|
+
DecoratorFunctionalComponent,
|
29
|
+
DecoratorFunctionalComponentArgsFromContext,
|
30
|
+
DecoratorFunctionalComponentArgsFromProps,
|
31
|
+
DecoratorComponentOptions,
|
32
|
+
DecoratorComponentOptionsArgsFromData,
|
33
|
+
DecoratorComponentOptionsArgsFromProps,
|
34
|
+
} from './ReactiveDecorators.stories';
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import type { Meta } from '@storybook/vue3';
|
2
|
+
import { defineComponent, shallowReactive } from 'vue';
|
3
|
+
import Reactivity from './Reactivity.vue';
|
4
|
+
import * as ReactiveDecorators from './ReactiveDecorators.stories';
|
5
|
+
|
6
|
+
// when you use custom render, you can use any vue api to create your story and garanti reactivity, otherwise i can ease kill the reactivity.
|
7
|
+
const state = shallowReactive<{ header: any; default: any; footer: any }>({
|
8
|
+
header: '',
|
9
|
+
default: '',
|
10
|
+
footer: '',
|
11
|
+
}); // or reactive
|
12
|
+
|
13
|
+
const meta = {
|
14
|
+
...ReactiveDecorators.default,
|
15
|
+
component: Reactivity,
|
16
|
+
render: (args, { argTypes }) => {
|
17
|
+
state.header = args.header;
|
18
|
+
state.default = args.default;
|
19
|
+
state.footer = args.footer;
|
20
|
+
// return a component options
|
21
|
+
return defineComponent({
|
22
|
+
data: () => ({ args, header: state.header, default: state.default, footer: state.footer }),
|
23
|
+
components: {
|
24
|
+
Reactivity,
|
25
|
+
},
|
26
|
+
template: `<div>Custom render uses options api and binds args to data:
|
27
|
+
<Reactivity v-bind="args">
|
28
|
+
<template #header="{title}"><h3>{{ args.header }} - Title: {{ title }}</h3></template>
|
29
|
+
<template #default>{{ args.default }}</template>
|
30
|
+
<template #footer>{{ args.footer }} </template>
|
31
|
+
</Reactivity>
|
32
|
+
</div>`,
|
33
|
+
});
|
34
|
+
},
|
35
|
+
} satisfies Meta<typeof Reactivity>;
|
36
|
+
|
37
|
+
export default meta;
|
38
|
+
|
39
|
+
export {
|
40
|
+
NoDecorators,
|
41
|
+
DecoratorFunctionalComponent,
|
42
|
+
DecoratorFunctionalComponentArgsFromContext,
|
43
|
+
DecoratorFunctionalComponentArgsFromProps,
|
44
|
+
DecoratorComponentOptions,
|
45
|
+
DecoratorComponentOptionsArgsFromData,
|
46
|
+
DecoratorComponentOptionsArgsFromProps,
|
47
|
+
} from './ReactiveDecorators.stories';
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import type { Meta } from '@storybook/vue3';
|
2
|
+
import { defineComponent } from 'vue';
|
3
|
+
import Reactivity from './Reactivity.vue';
|
4
|
+
import * as ReactiveDecorators from './ReactiveDecorators.stories';
|
5
|
+
|
6
|
+
const meta = {
|
7
|
+
...ReactiveDecorators.default,
|
8
|
+
component: Reactivity,
|
9
|
+
render: (args, { argTypes }) => {
|
10
|
+
return defineComponent({
|
11
|
+
props: Object.keys(argTypes),
|
12
|
+
components: { Reactivity },
|
13
|
+
template: `<div>Custom render uses options api and binds args to props: <Reactivity v-bind="$props">
|
14
|
+
<template #header="{title}"><h3>{{ header }} - Title: {{ title }}</h3></template>
|
15
|
+
{{ $props.default }}
|
16
|
+
<template #footer>{{ footer }}</template>
|
17
|
+
</Reactivity>
|
18
|
+
</div>`,
|
19
|
+
});
|
20
|
+
},
|
21
|
+
} satisfies Meta<typeof Reactivity>;
|
22
|
+
|
23
|
+
export default meta;
|
24
|
+
|
25
|
+
export {
|
26
|
+
NoDecorators,
|
27
|
+
DecoratorFunctionalComponent,
|
28
|
+
DecoratorFunctionalComponentArgsFromContext,
|
29
|
+
DecoratorFunctionalComponentArgsFromProps,
|
30
|
+
DecoratorComponentOptions,
|
31
|
+
DecoratorComponentOptionsArgsFromData,
|
32
|
+
DecoratorComponentOptionsArgsFromProps,
|
33
|
+
} from './ReactiveDecorators.stories';
|
@@ -3,7 +3,7 @@ import GlobalUsage from './GlobalUsage.vue';
|
|
3
3
|
export default {
|
4
4
|
component: GlobalUsage,
|
5
5
|
argTypes: {},
|
6
|
-
render: (args) => ({
|
6
|
+
render: (args: any) => ({
|
7
7
|
// Components used in your story `template` are defined in the `components` object
|
8
8
|
components: { GlobalUsage },
|
9
9
|
// The story's `args` need to be mapped into the template through the `setup()` method
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<!-- <MyComponent> template -->
|
2
|
+
<script setup lang="ts">
|
3
|
+
const props = defineProps({
|
4
|
+
label: String,
|
5
|
+
year: Number,
|
6
|
+
});
|
7
|
+
</script>
|
8
|
+
<template>
|
9
|
+
<div data-testid="scoped-slot">
|
10
|
+
<slot :text="`Hello ${props.label} from the slot`" :year="props.year"></slot>
|
11
|
+
</div>
|
12
|
+
</template>
|
@@ -5,8 +5,8 @@
|
|
5
5
|
</button>
|
6
6
|
</template>
|
7
7
|
|
8
|
-
<script lang="
|
9
|
-
import {
|
8
|
+
<script lang="ts">
|
9
|
+
import { computed } from 'vue';
|
10
10
|
|
11
11
|
export default {
|
12
12
|
name: 'override-args',
|
@@ -22,7 +22,7 @@ export default {
|
|
22
22
|
},
|
23
23
|
},
|
24
24
|
|
25
|
-
|
25
|
+
|
26
26
|
setup(props, { emit }) {
|
27
27
|
const classes = {
|
28
28
|
'storybook-button': true,
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import { expect } from '@storybook/jest';
|
2
|
+
import { global as globalThis } from '@storybook/global';
|
3
|
+
import type { Meta, StoryObj, StoryFn } from '@storybook/vue3';
|
4
|
+
import { within, userEvent } from '@storybook/testing-library';
|
5
|
+
import { UPDATE_STORY_ARGS, STORY_ARGS_UPDATED, RESET_STORY_ARGS } from '@storybook/core-events';
|
6
|
+
|
7
|
+
import ReactiveArgs from './ReactiveArgs.vue';
|
8
|
+
|
9
|
+
const meta = {
|
10
|
+
component: ReactiveArgs,
|
11
|
+
argTypes: {
|
12
|
+
// To show that other props are passed through
|
13
|
+
backgroundColor: { control: 'color' },
|
14
|
+
},
|
15
|
+
} satisfies Meta<typeof ReactiveArgs>;
|
16
|
+
|
17
|
+
export default meta;
|
18
|
+
|
19
|
+
type Story = StoryObj<typeof meta>;
|
20
|
+
|
21
|
+
export const ReactiveTest: Story = {
|
22
|
+
args: {
|
23
|
+
label: 'Button',
|
24
|
+
},
|
25
|
+
// test that args are updated correctly in rective mode
|
26
|
+
play: async ({ canvasElement, id }) => {
|
27
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
28
|
+
const canvas = within(canvasElement);
|
29
|
+
|
30
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
31
|
+
await new Promise((resolve) => {
|
32
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
33
|
+
});
|
34
|
+
const reactiveButton = await canvas.getByRole('button');
|
35
|
+
await expect(reactiveButton).toHaveTextContent('Button 0');
|
36
|
+
|
37
|
+
await userEvent.click(reactiveButton); // click to update the label to increment the count + 1
|
38
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
39
|
+
storyId: id,
|
40
|
+
updatedArgs: { label: 'updated' },
|
41
|
+
});
|
42
|
+
await new Promise((resolve) => {
|
43
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
44
|
+
});
|
45
|
+
await expect(canvas.getByRole('button')).toHaveTextContent('updated 1');
|
46
|
+
|
47
|
+
await userEvent.click(reactiveButton); // click to update the label to increment the count + 1
|
48
|
+
await expect(reactiveButton).toHaveTextContent('updated 2');
|
49
|
+
},
|
50
|
+
};
|
51
|
+
|
52
|
+
export const ReactiveHtmlWrapper: Story = {
|
53
|
+
args: { label: 'Wrapped Button' },
|
54
|
+
|
55
|
+
decorators: [
|
56
|
+
() => ({
|
57
|
+
template: `
|
58
|
+
<div style="border: 5px solid red;">
|
59
|
+
<story/>
|
60
|
+
</div>
|
61
|
+
`,
|
62
|
+
}),
|
63
|
+
],
|
64
|
+
play: async ({ canvasElement, id }) => {
|
65
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
66
|
+
const canvas = within(canvasElement);
|
67
|
+
|
68
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
69
|
+
await new Promise((resolve) => {
|
70
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
71
|
+
});
|
72
|
+
const reactiveButton = await canvas.getByRole('button');
|
73
|
+
await expect(reactiveButton).toHaveTextContent('Wrapped Button 0');
|
74
|
+
|
75
|
+
await userEvent.click(reactiveButton); // click to update the label to increment the count + 1
|
76
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
77
|
+
storyId: id,
|
78
|
+
updatedArgs: { label: 'updated Wrapped Button' },
|
79
|
+
});
|
80
|
+
await new Promise((resolve) => {
|
81
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
82
|
+
});
|
83
|
+
await expect(canvas.getByRole('button')).toHaveTextContent('updated Wrapped Button 1');
|
84
|
+
|
85
|
+
await userEvent.click(reactiveButton); // click to update the label to increment the count + 1
|
86
|
+
await expect(reactiveButton).toHaveTextContent('updated Wrapped Button 2');
|
87
|
+
},
|
88
|
+
};
|
89
|
+
|
90
|
+
// to test that Simple html Decorators in CSF2 format are applied correctly in reactive mode
|
91
|
+
const ReactiveCSF2WrapperTempl: StoryFn = (args, { argTypes }) => ({
|
92
|
+
components: { ReactiveArgs },
|
93
|
+
props: Object.keys(argTypes),
|
94
|
+
template: '<ReactiveArgs v-bind="$props" />',
|
95
|
+
});
|
96
|
+
|
97
|
+
export const ReactiveCSF2Wrapper = ReactiveCSF2WrapperTempl.bind({});
|
98
|
+
|
99
|
+
ReactiveCSF2Wrapper.args = {
|
100
|
+
label: 'CSF2 Wrapped Button',
|
101
|
+
};
|
102
|
+
ReactiveCSF2Wrapper.decorators = [
|
103
|
+
() => ({
|
104
|
+
template: `
|
105
|
+
<div style="border: 5px solid red;">
|
106
|
+
<story/>
|
107
|
+
</div>
|
108
|
+
`,
|
109
|
+
}),
|
110
|
+
];
|
111
|
+
|
112
|
+
ReactiveCSF2Wrapper.play = async ({ canvasElement, id }) => {
|
113
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
114
|
+
const canvas = within(canvasElement);
|
115
|
+
|
116
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
117
|
+
await new Promise((resolve) => {
|
118
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
119
|
+
});
|
120
|
+
const reactiveButton = await canvas.getByRole('button');
|
121
|
+
await expect(reactiveButton).toHaveTextContent('CSF2 Wrapped Button 0');
|
122
|
+
|
123
|
+
await userEvent.click(reactiveButton); // click to update the label to increment the count + 1
|
124
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
125
|
+
storyId: id,
|
126
|
+
updatedArgs: { label: 'updated CSF2 Wrapped Button' },
|
127
|
+
});
|
128
|
+
await new Promise((resolve) => {
|
129
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
130
|
+
});
|
131
|
+
await expect(canvas.getByRole('button')).toHaveTextContent('updated CSF2 Wrapped Button 1');
|
132
|
+
|
133
|
+
await userEvent.click(reactiveButton); // click to update the label to increment the count + 1
|
134
|
+
await expect(reactiveButton).toHaveTextContent('updated CSF2 Wrapped Button 2');
|
135
|
+
};
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import { global as globalThis } from '@storybook/global';
|
2
|
+
import { userEvent, within } from '@storybook/testing-library';
|
3
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
4
|
+
import { h } from 'vue';
|
5
|
+
import { RESET_STORY_ARGS, STORY_ARGS_UPDATED, UPDATE_STORY_ARGS } from '@storybook/core-events';
|
6
|
+
import Reactivity from './Reactivity.vue';
|
7
|
+
|
8
|
+
const meta = {
|
9
|
+
component: Reactivity,
|
10
|
+
argTypes: {
|
11
|
+
header: { control: { type: 'text' } },
|
12
|
+
footer: { control: { type: 'text' } },
|
13
|
+
default: { control: { type: 'text' } },
|
14
|
+
},
|
15
|
+
args: {
|
16
|
+
label: 'If you see this then the label arg was not reactive.',
|
17
|
+
default: 'If you see this then the default slot was not reactive.',
|
18
|
+
header: 'If you see this, the header slot was not reactive.', // this can be useless if you have custom render function that overrides the slot
|
19
|
+
footer: 'If you see this, the footer slot was not reactive.',
|
20
|
+
},
|
21
|
+
play: async ({ canvasElement, id, args }) => {
|
22
|
+
const channel = (globalThis as any).__STORYBOOK_ADDONS_CHANNEL__;
|
23
|
+
|
24
|
+
const canvas = within(canvasElement);
|
25
|
+
|
26
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
27
|
+
await new Promise((resolve) => channel.once(STORY_ARGS_UPDATED, resolve));
|
28
|
+
|
29
|
+
const input = await canvas.findByLabelText<HTMLInputElement>('Some input:');
|
30
|
+
await userEvent.type(input, 'value');
|
31
|
+
|
32
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
33
|
+
storyId: id,
|
34
|
+
updatedArgs: {
|
35
|
+
label: 'updated label',
|
36
|
+
header: 'updated header slot', // this can be useless if you have custom render function that overrides the slot which the case here
|
37
|
+
footer: 'updated footer slot',
|
38
|
+
default: 'updated default slot',
|
39
|
+
},
|
40
|
+
});
|
41
|
+
await new Promise((resolve) => channel.once(STORY_ARGS_UPDATED, resolve));
|
42
|
+
},
|
43
|
+
} satisfies Meta<typeof Reactivity>;
|
44
|
+
|
45
|
+
export default meta;
|
46
|
+
type Story = StoryObj<typeof meta>;
|
47
|
+
|
48
|
+
export const NoDecorators: Story = {};
|
49
|
+
|
50
|
+
export const DecoratorFunctionalComponent: Story = {
|
51
|
+
decorators: [
|
52
|
+
(storyFn, context) => {
|
53
|
+
const story = storyFn();
|
54
|
+
return () => h('div', [h('h2', ['Decorator not using args']), [h(story, context.args)]]);
|
55
|
+
},
|
56
|
+
],
|
57
|
+
};
|
58
|
+
|
59
|
+
export const DecoratorFunctionalComponentArgsFromContext: Story = {
|
60
|
+
decorators: [
|
61
|
+
(storyFn, context) => {
|
62
|
+
const story = storyFn();
|
63
|
+
return () =>
|
64
|
+
h('div', [
|
65
|
+
h('h2', ['Decorator using args.label: ', context.args.label]),
|
66
|
+
[h(story, context.args)],
|
67
|
+
]);
|
68
|
+
},
|
69
|
+
],
|
70
|
+
};
|
71
|
+
|
72
|
+
export const DecoratorFunctionalComponentArgsFromProps: Story = {
|
73
|
+
decorators: [
|
74
|
+
(storyFn, context) => {
|
75
|
+
const story = storyFn();
|
76
|
+
return (args) =>
|
77
|
+
h('div', [h('h2', `Decorator using args.label: ${args.label}`), h(story, context.args)]);
|
78
|
+
},
|
79
|
+
],
|
80
|
+
};
|
81
|
+
|
82
|
+
export const DecoratorComponentOptions: Story = {
|
83
|
+
decorators: [
|
84
|
+
(storyFn, context) => {
|
85
|
+
return {
|
86
|
+
template: '<div><h2>Decorator not using args</h2><story/></div>',
|
87
|
+
};
|
88
|
+
},
|
89
|
+
],
|
90
|
+
};
|
91
|
+
|
92
|
+
export const DecoratorComponentOptionsArgsFromData: Story = {
|
93
|
+
decorators: [
|
94
|
+
(storyFn, context) => {
|
95
|
+
return {
|
96
|
+
data: () => ({ args: context.args }),
|
97
|
+
template: '<div><h2>Decorator using args.label: {{args.label}}</h2><story/></div>',
|
98
|
+
};
|
99
|
+
},
|
100
|
+
],
|
101
|
+
};
|
102
|
+
|
103
|
+
export const DecoratorComponentOptionsArgsFromProps: Story = {
|
104
|
+
decorators: [
|
105
|
+
(storyFn, context) => {
|
106
|
+
return {
|
107
|
+
props: ['label'],
|
108
|
+
template: '<div><h2>Decorator using label: {{label}}</h2><story /></div>',
|
109
|
+
};
|
110
|
+
},
|
111
|
+
],
|
112
|
+
};
|
@@ -0,0 +1,127 @@
|
|
1
|
+
import { expect } from '@storybook/jest';
|
2
|
+
import { global as globalThis } from '@storybook/global';
|
3
|
+
import { within } from '@storybook/testing-library';
|
4
|
+
import { STORY_ARGS_UPDATED, RESET_STORY_ARGS, UPDATE_STORY_ARGS } from '@storybook/core-events';
|
5
|
+
import { h } from 'vue';
|
6
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
7
|
+
import BaseLayout from './BaseLayout.vue';
|
8
|
+
|
9
|
+
const meta = {
|
10
|
+
component: BaseLayout,
|
11
|
+
args: {
|
12
|
+
label: 'Storybook Day',
|
13
|
+
default: () => 'Default Text Slot',
|
14
|
+
footer: h('p', 'Footer VNode Slot'),
|
15
|
+
},
|
16
|
+
tags: ['autodocs'],
|
17
|
+
} satisfies Meta<typeof BaseLayout>;
|
18
|
+
|
19
|
+
export default meta;
|
20
|
+
type Story = StoryObj<typeof meta>;
|
21
|
+
|
22
|
+
export const SimpleSlotTest: Story = {
|
23
|
+
args: {
|
24
|
+
label: 'Storybook Day',
|
25
|
+
header: () => h('h1', 'Header Text Slot'),
|
26
|
+
default: () => 'Default Text Slot',
|
27
|
+
footer: h('p', 'Footer VNode Slot'),
|
28
|
+
},
|
29
|
+
play: async ({ canvasElement, id }) => {
|
30
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
31
|
+
const canvas = within(canvasElement);
|
32
|
+
|
33
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
34
|
+
await new Promise((resolve) => {
|
35
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
36
|
+
});
|
37
|
+
await expect(canvas.getByTestId('footer-slot').innerText).toContain('Footer VNode Slot');
|
38
|
+
await expect(canvas.getByTestId('default-slot').innerText).toContain('Default Text Slot');
|
39
|
+
|
40
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
41
|
+
storyId: id,
|
42
|
+
updatedArgs: {
|
43
|
+
default: () => 'Default Text Slot Updated',
|
44
|
+
footer: h('p', 'Footer VNode Slot Updated'),
|
45
|
+
},
|
46
|
+
});
|
47
|
+
await new Promise((resolve) => {
|
48
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
49
|
+
});
|
50
|
+
await expect(canvas.getByTestId('default-slot').innerText).toContain(
|
51
|
+
'Default Text Slot Updated'
|
52
|
+
);
|
53
|
+
await expect(canvas.getByTestId('footer-slot').innerText).toContain(
|
54
|
+
'Footer VNode Slot Updated'
|
55
|
+
);
|
56
|
+
},
|
57
|
+
};
|
58
|
+
|
59
|
+
export const NamedSlotTest: Story = {
|
60
|
+
args: {
|
61
|
+
label: 'Storybook Day',
|
62
|
+
header: ({ title }: { title: string }) => h('h1', title),
|
63
|
+
default: () => 'Default Text Slot',
|
64
|
+
footer: h('p', 'Footer VNode Slot'),
|
65
|
+
},
|
66
|
+
// test that args are updated correctly in rective mode
|
67
|
+
play: async ({ canvasElement, id }) => {
|
68
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
69
|
+
const canvas = within(canvasElement);
|
70
|
+
|
71
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
72
|
+
await new Promise((resolve) => {
|
73
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
74
|
+
});
|
75
|
+
await expect(canvas.getByTestId('header-slot').innerText).toContain(
|
76
|
+
'Header title from the slot'
|
77
|
+
);
|
78
|
+
await expect(canvas.getByTestId('default-slot').innerText).toContain('Default Text Slot');
|
79
|
+
await expect(canvas.getByTestId('footer-slot').innerText).toContain('Footer VNode Slot');
|
80
|
+
|
81
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
82
|
+
storyId: id,
|
83
|
+
updatedArgs: {
|
84
|
+
default: () => 'Default Text Slot Updated',
|
85
|
+
footer: h('p', 'Footer VNode Slot Updated'),
|
86
|
+
},
|
87
|
+
});
|
88
|
+
await new Promise((resolve) => {
|
89
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
90
|
+
});
|
91
|
+
await expect(canvas.getByTestId('default-slot').innerText).toContain(
|
92
|
+
'Default Text Slot Updated'
|
93
|
+
);
|
94
|
+
await expect(canvas.getByTestId('footer-slot').innerText).toContain(
|
95
|
+
'Footer VNode Slot Updated'
|
96
|
+
);
|
97
|
+
},
|
98
|
+
};
|
99
|
+
|
100
|
+
export const SlotWithRenderFn: Story = {
|
101
|
+
args: {
|
102
|
+
label: 'Storybook Day',
|
103
|
+
header: ({ title }: { title: string }) => `${title}`,
|
104
|
+
default: () => 'Default Text Slot',
|
105
|
+
footer: h('p', 'Footer VNode Slot'),
|
106
|
+
},
|
107
|
+
render: (args) => ({
|
108
|
+
components: { BaseLayout },
|
109
|
+
setup() {
|
110
|
+
return { args };
|
111
|
+
},
|
112
|
+
template: `<BaseLayout :label="args.label" data-testid="layout">
|
113
|
+
{{args.default()}}
|
114
|
+
<template #header="{ title }"><h1>{{args.header({title})}}</h1></template>
|
115
|
+
</BaseLayout>`,
|
116
|
+
}),
|
117
|
+
play: async ({ canvasElement, id }) => {
|
118
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
119
|
+
const canvas = within(canvasElement);
|
120
|
+
|
121
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
122
|
+
await new Promise((resolve) => {
|
123
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
124
|
+
});
|
125
|
+
await expect(canvas.getByTestId('layout').innerText).toContain('Default Text Slot');
|
126
|
+
},
|
127
|
+
};
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<script setup lang="ts">
|
2
|
+
defineProps<{ label: string }>();
|
3
|
+
</script>
|
4
|
+
<template>
|
5
|
+
<div style="padding: 20px;background-color: pink;">
|
6
|
+
<header data-testid="header-slot">
|
7
|
+
<slot name="header" title="Header title from the slot">
|
8
|
+
If you see this, the header slot was not reactive.
|
9
|
+
</slot>
|
10
|
+
</header>
|
11
|
+
<div id="content">
|
12
|
+
<label>
|
13
|
+
Some input:
|
14
|
+
<input style='width: 400px' placeholder='If you see this, an args update caused the input field to loose state' />
|
15
|
+
</label>
|
16
|
+
<hr>
|
17
|
+
<button class="storybook-button storybook-button--primary storybook-button--medium"> {{ label
|
18
|
+
}}</button>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<main data-testid="default-slot">
|
22
|
+
<slot>Default slot placeholder</slot>
|
23
|
+
</main>
|
24
|
+
<footer data-testid="footer-slot">
|
25
|
+
<slot name="footer">
|
26
|
+
Footer slot placeholder
|
27
|
+
</slot>
|
28
|
+
</footer>
|
29
|
+
</div>
|
30
|
+
</template>
|
31
|
+
|
32
|
+
<style>
|
33
|
+
header,
|
34
|
+
footer {
|
35
|
+
background-color: #fff0ff;
|
36
|
+
padding: 20px;
|
37
|
+
}
|
38
|
+
|
39
|
+
main,
|
40
|
+
#content {
|
41
|
+
background-color: #f0f0f0;
|
42
|
+
padding: 20px;
|
43
|
+
}
|
44
|
+
</style>
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import { expect } from '@storybook/jest';
|
2
|
+
import { global as globalThis } from '@storybook/global';
|
3
|
+
import type { Channel } from '@storybook/channels';
|
4
|
+
import { within } from '@storybook/testing-library';
|
5
|
+
import { UPDATE_STORY_ARGS, STORY_ARGS_UPDATED, RESET_STORY_ARGS } from '@storybook/core-events';
|
6
|
+
|
7
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
8
|
+
import MySlotComponent from './MySlotComponent.vue';
|
9
|
+
|
10
|
+
declare global {
|
11
|
+
// eslint-disable-next-line no-var,vars-on-top,@typescript-eslint/naming-convention
|
12
|
+
var __STORYBOOK_ADDONS_CHANNEL__: Channel;
|
13
|
+
}
|
14
|
+
|
15
|
+
const meta = {
|
16
|
+
component: MySlotComponent,
|
17
|
+
args: {
|
18
|
+
label: 'Storybook Day',
|
19
|
+
year: 2022,
|
20
|
+
default: ({ text, year }) => `${text}, ${year}`,
|
21
|
+
},
|
22
|
+
tags: ['autodocs'],
|
23
|
+
} satisfies Meta<typeof MySlotComponent>;
|
24
|
+
|
25
|
+
export default meta;
|
26
|
+
type Story = StoryObj<typeof meta>;
|
27
|
+
|
28
|
+
export const Basic: Story = {
|
29
|
+
// test that args are updated correctly in reactive mode
|
30
|
+
play: async ({ canvasElement, id }) => {
|
31
|
+
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
|
32
|
+
const canvas = within(canvasElement);
|
33
|
+
|
34
|
+
await channel.emit(RESET_STORY_ARGS, { storyId: id });
|
35
|
+
await new Promise((resolve) => channel.once(STORY_ARGS_UPDATED, resolve));
|
36
|
+
await expect(canvas.getByTestId('scoped-slot').innerText).toMatch(
|
37
|
+
'Hello Storybook Day from the slot, 2022'
|
38
|
+
);
|
39
|
+
|
40
|
+
await channel.emit(UPDATE_STORY_ARGS, {
|
41
|
+
storyId: id,
|
42
|
+
updatedArgs: {
|
43
|
+
label: 'Storybook Day updated',
|
44
|
+
year: 2023,
|
45
|
+
},
|
46
|
+
});
|
47
|
+
await new Promise((resolve) => {
|
48
|
+
channel.once(STORY_ARGS_UPDATED, resolve);
|
49
|
+
});
|
50
|
+
|
51
|
+
await expect(canvas.getByTestId('scoped-slot').innerText).toMatch(
|
52
|
+
'Hello Storybook Day updated from the slot, 2023'
|
53
|
+
);
|
54
|
+
},
|
55
|
+
};
|
56
|
+
|
57
|
+
export const CustomRender: Story = {
|
58
|
+
render: (args) => ({
|
59
|
+
components: { MySlotComponent },
|
60
|
+
setup() {
|
61
|
+
return { args };
|
62
|
+
},
|
63
|
+
template: `<MySlotComponent :label="args.label" v-slot="slotProps">
|
64
|
+
{{ slotProps.text }}, {{ slotProps.year }}
|
65
|
+
</MySlotComponent>`,
|
66
|
+
}),
|
67
|
+
play: Basic.play,
|
68
|
+
};
|
69
|
+
|
70
|
+
export const CustomRenderUsingFunctionSlot: Story = {
|
71
|
+
render: (args: any) => ({
|
72
|
+
components: { MySlotComponent },
|
73
|
+
setup() {
|
74
|
+
return { args };
|
75
|
+
},
|
76
|
+
template: `<MySlotComponent :label="args.label" v-slot="slotProps">
|
77
|
+
{{args.default(slotProps)}}
|
78
|
+
</MySlotComponent>`,
|
79
|
+
}),
|
80
|
+
play: Basic.play,
|
81
|
+
};
|