Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue3: Bring back new tests to main #22685

Merged
merged 2 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Meta } from '@storybook/vue3';
import { h } from 'vue';
import Reactivity from './Reactivity.vue';
import * as ReactiveDecorators from './ReactiveDecorators.stories';

const meta = {
...ReactiveDecorators.default,
component: Reactivity,
// storybook render function is not a functional component. it returns a functional component or a component options
render: (args) => {
// create the slot contents as a functional components
const header = ({ title }: { title: string }) => h('h3', `${args.header} - Title: ${title}`);
const defaultSlot = () => h('p', `${args.default}`);
const footer = () => h('p', `${args.footer}`);
// vue render function is a functional components
return () =>
h('div', [
`Custom render uses a functional component, and passes slots to the component:`,
h(Reactivity, args, { header, default: defaultSlot, footer }),
]);
},
} satisfies Meta<typeof Reactivity>;

export default meta;

export {
NoDecorators,
DecoratorFunctionalComponent,
DecoratorFunctionalComponentArgsFromContext,
DecoratorComponentOptions,
DecoratorComponentOptionsArgsFromData,
} from './ReactiveDecorators.stories';
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Meta } from '@storybook/vue3';
import { defineComponent, shallowReactive } from 'vue';
import Reactivity from './Reactivity.vue';
import * as ReactiveDecorators from './ReactiveDecorators.stories';

// 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.
const state = shallowReactive<{ header: any; default: any; footer: any }>({
header: '',
default: '',
footer: '',
}); // or reactive

const meta = {
...ReactiveDecorators.default,
component: Reactivity,
render: (args, { argTypes }) => {
state.header = args.header;
state.default = args.default;
state.footer = args.footer;
// return a component options
return defineComponent({
data: () => ({ args, header: state.header, default: state.default, footer: state.footer }),
components: {
Reactivity,
},
template: `<div>Custom render uses options api and binds args to data:
<Reactivity v-bind="args">
<template #header="{title}"><h3>{{ args.header }} - Title: {{ title }}</h3></template>
<template #default>{{ args.default }}</template>
<template #footer>{{ args.footer }} </template>
</Reactivity>
</div>`,
});
},
} satisfies Meta<typeof Reactivity>;

export default meta;

export {
NoDecorators,
DecoratorFunctionalComponent,
DecoratorFunctionalComponentArgsFromContext,
DecoratorComponentOptions,
DecoratorComponentOptionsArgsFromData,
} from './ReactiveDecorators.stories';
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { global as globalThis } from '@storybook/global';
import { userEvent, within } from '@storybook/testing-library';
import type { Meta, StoryObj } from '@storybook/vue3';
import { h } from 'vue';
import { RESET_STORY_ARGS, STORY_ARGS_UPDATED, UPDATE_STORY_ARGS } from '@storybook/core-events';
import Reactivity from './Reactivity.vue';

const meta = {
component: Reactivity,
argTypes: {
header: { control: { type: 'text' } },
footer: { control: { type: 'text' } },
default: { control: { type: 'text' } },
},
args: {
label: 'If you see this then the label arg was not reactive.',
default: 'If you see this then the default slot was not reactive.',
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
footer: 'If you see this, the footer slot was not reactive.',
},
play: async ({ canvasElement, id, args }) => {
const channel = (globalThis as any).__STORYBOOK_ADDONS_CHANNEL__;

const canvas = within(canvasElement);

await channel.emit(RESET_STORY_ARGS, { storyId: id });
await new Promise((resolve) => channel.once(STORY_ARGS_UPDATED, resolve));

const input = await canvas.findByLabelText<HTMLInputElement>('Some input:');
await userEvent.type(input, 'value');

await channel.emit(UPDATE_STORY_ARGS, {
storyId: id,
updatedArgs: {
label: 'updated label',
header: 'updated header slot', // this can be useless if you have custom render function that overrides the slot which the case here
footer: 'updated footer slot',
default: 'updated default slot',
},
});
await new Promise((resolve) => channel.once(STORY_ARGS_UPDATED, resolve));
},
} satisfies Meta<typeof Reactivity>;

export default meta;
type Story = StoryObj<typeof meta>;

export const NoDecorators: Story = {};

export const DecoratorFunctionalComponent: Story = {
decorators: [
(storyFn, context) => {
const story = storyFn();
return () => h('div', [h('h2', ['Decorator not using args']), [h(story)]]);
},
],
};

export const DecoratorFunctionalComponentArgsFromContext: Story = {
decorators: [
(storyFn, context) => {
const story = storyFn();
return () =>
h('div', [h('h2', ['Decorator using args.label: ', context.args.label]), [h(story)]]);
},
],
};

export const DecoratorComponentOptions: Story = {
decorators: [
(storyFn, context) => {
return {
template: '<div><h2>Decorator not using args</h2><story/></div>',
};
},
],
};

export const DecoratorComponentOptionsArgsFromData: Story = {
decorators: [
(storyFn, context) => {
return {
data: () => ({ args: context.args }),
template: '<div><h2>Decorator using args.label: {{args.label}}</h2><story/></div>',
};
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup lang="ts">
defineProps<{ label: string }>();
</script>
<template>
<div style="padding: 20px;background-color: pink;">
<header data-testid="header-slot">
<slot name="header" title="Header title from the slot">
If you see this, the header slot was not reactive.
</slot>
</header>
<div id="content">
<label>
Some input:
<input style='width: 400px' placeholder='If you see this, an args update caused the input field to loose state' />
</label>
<hr>
<button class="storybook-button storybook-button--primary storybook-button--medium"> {{ label
}}</button>
</div>

<main data-testid="default-slot">
<slot>Default slot placeholder</slot>
</main>
<footer data-testid="footer-slot">
<slot name="footer">
Footer slot placeholder
</slot>
</footer>
</div>
</template>

<style>
header,
footer {
background-color: #fff0ff;
padding: 20px;
}

main,
#content {
background-color: #f0f0f0;
padding: 20px;
}
</style>
35 changes: 25 additions & 10 deletions scripts/tasks/sandbox-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,17 @@ function addStoriesEntry(mainConfig: ConfigFile, path: string) {
mainConfig.setFieldValue(['stories'], [...stories, entry]);
}

function addVariantToFolder(variant?: string, folder = 'stories') {
function getStoriesFolderWithVariant(variant?: string, folder = 'stories') {
return variant ? `${folder}_${variant}` : folder;
}

// packageDir is eg 'renderers/react', 'addons/actions'
async function linkPackageStories(
packageDir: string,
{ mainConfig, cwd, linkInDir }: { mainConfig: ConfigFile; cwd: string; linkInDir?: string },
frameworkVariant?: string
variant?: string
) {
const storiesFolderName = frameworkVariant ? addVariantToFolder(frameworkVariant) : 'stories';
const storiesFolderName = variant ? getStoriesFolderWithVariant(variant) : 'stories';
const source = join(CODE_DIRECTORY, packageDir, 'template', storiesFolderName);
// By default we link `stories` directories
// e.g '../../../code/lib/store/template/stories' to 'template-stories/lib/store'
Expand All @@ -299,10 +299,7 @@ async function linkPackageStories(
// The files must be linked in the cwd, in order to ensure that any dependencies they
// reference are resolved in the cwd. In particular 'react' resolved by MDX files.
const target = linkInDir
? resolve(
linkInDir,
frameworkVariant ? addVariantToFolder(frameworkVariant, packageDir) : packageDir
)
? resolve(linkInDir, variant ? getStoriesFolderWithVariant(variant, packageDir) : packageDir)
: resolve(cwd, 'template-stories', packageDir);

await ensureSymlink(source, target);
Expand Down Expand Up @@ -373,6 +370,9 @@ export const addStories: Task['run'] = async (
template.expected.renderer.startsWith('@storybook/') &&
template.expected.renderer !== '@storybook/server';

const sandboxSpecificStoriesFolder = key.replaceAll('/', '-');
const storiesVariantFolder = getStoriesFolderWithVariant(sandboxSpecificStoriesFolder);

if (isCoreRenderer) {
// Link in the template/components/index.js from store, the renderer and the addons
const rendererPath = await workspacePath('renderer', template.expected.renderer);
Expand All @@ -388,6 +388,22 @@ export const addStories: Task['run'] = async (
cwd,
linkInDir: resolve(cwd, storiesPath),
});

if (
await pathExists(
resolve(CODE_DIRECTORY, rendererPath, join('template', storiesVariantFolder))
)
) {
await linkPackageStories(
rendererPath,
{
mainConfig,
cwd,
linkInDir: resolve(cwd, storiesPath),
},
sandboxSpecificStoriesFolder
);
}
}

const isCoreFramework = template.expected.framework.startsWith('@storybook/');
Expand All @@ -404,8 +420,7 @@ export const addStories: Task['run'] = async (
});
}

const frameworkVariant = key.split('/')[1];
const storiesVariantFolder = addVariantToFolder(frameworkVariant);
console.log({ sandboxSpecificStoriesFolder, storiesVariantFolder });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)


if (
await pathExists(
Expand All @@ -419,7 +434,7 @@ export const addStories: Task['run'] = async (
cwd,
linkInDir: resolve(cwd, storiesPath),
},
frameworkVariant
sandboxSpecificStoriesFolder
);
}
}
Expand Down