Skip to content

Commit

Permalink
more typesafe CSF3 types for svelte
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperpeulen committed Oct 17, 2022
1 parent 5a2d028 commit c637e76
Show file tree
Hide file tree
Showing 25 changed files with 401 additions and 82 deletions.
6 changes: 3 additions & 3 deletions code/addons/highlight/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@
"@types/webpack-env": "^1.16.0",
"typescript": "~4.6.3"
},
"publishConfig": {
"access": "public"
},
"bundler": {
"entries": [
"./src/index.ts",
"./src/highlight.ts"
]
},
"publishConfig": {
"access": "public"
},
"gitHead": "438114fcf62a763f0e8c07e2c34890dd987ca431",
"sbmodern": "dist/modern/index.js",
"storybook": {
Expand Down
1 change: 1 addition & 0 deletions code/lib/core-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from './utils/normalize-stories';
export * from './utils/readTemplate';
export * from './utils/findDistEsm';
export * from './utils/symlinks';
export * from './utils/satisfies';

export * from './types';

Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
],
"resolutions": {
"@nrwl/cli": "14.6.1",
"@storybook/csf": "portal:/Users/kasperpeulen/code/storybook/csf",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/experimental-utils": "5.3.0",
"@typescript-eslint/parser": "^5.15.0",
Expand Down Expand Up @@ -336,7 +337,7 @@
"ts-jest": "^26.4.4",
"ts-node": "^10.4.0",
"tsup": "^6.2.2",
"typescript": "4.7.4",
"typescript": "~4.6.3",
"util": "^0.12.4",
"vite": "^3.1.7",
"wait-on": "^5.2.1",
Expand Down
9 changes: 4 additions & 5 deletions code/renderers/react/src/__test__/CSF3.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React, { KeyboardEventHandler, ReactNode } from 'react';
import { expectTypeOf } from 'expect-type';
import { describe, test } from '@jest/globals';
import { satisfies } from '@storybook/core-common';
import { StoryAnnotations } from '@storybook/csf';
import { expectTypeOf } from 'expect-type';
import React, { KeyboardEventHandler, ReactNode } from 'react';
import { SetOptional } from 'type-fest';

import { Meta, StoryObj } from '../public-types';
import { DecoratorFn } from '../public-api';
import { satisfies } from './utils';
import { Meta, StoryObj } from '../public-types';
import { ReactFramework } from '../types';

type ReactStory<Args, RequiredArgs> = StoryAnnotations<ReactFramework, Args, RequiredArgs>;
Expand Down
7 changes: 5 additions & 2 deletions code/renderers/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"*.d.ts"
],
"scripts": {
"check": "../../../scripts/node_modules/.bin/tsc --noEmit",
"check": "svelte-check --tsconfig ./tsconfig.json",
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
Expand All @@ -63,10 +63,13 @@
"global": "^4.4.0",
"react": "16.14.0",
"react-dom": "16.14.0",
"sveltedoc-parser": "^4.2.1"
"sveltedoc-parser": "^4.2.1",
"type-fest": "2.19.0"
},
"devDependencies": {
"expect-type": "^0.14.2",
"svelte": "^3.31.2",
"svelte-check": "^2.9.2",
"typescript": "~4.6.3"
},
"peerDependencies": {
Expand Down
12 changes: 12 additions & 0 deletions code/renderers/svelte/src/___test___/Button.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let disabled: boolean;
export let label: string;
const dispatch = createEventDispatcher();
</script>

<button on:click={(e) => dispatch('clicked', e)} on:dblclick on:mousemove {disabled}>
{label}
</button>
226 changes: 226 additions & 0 deletions code/renderers/svelte/src/___test___/CSF3.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { describe, test } from '@jest/globals';
import { satisfies } from '@storybook/core-common';
import { ComponentAnnotations, StoryAnnotations } from '@storybook/csf';
import { expectTypeOf } from 'expect-type';
import { ComponentProps, SvelteComponentTyped } from 'svelte';

import { DecoratorFn, Meta, StoryObj } from '../public-types';
import { SvelteFramework } from '../types';
import Button from './Button.svelte';
import Decorator from './Decorator.svelte';
import Decorator2 from './Decorator2.svelte';

type SvelteStory<Component extends SvelteComponentTyped, Args, RequiredArgs> = StoryAnnotations<
SvelteFramework<Component>,
Args,
RequiredArgs
>;

describe('Meta', () => {
test('Generic parameter of Meta can be a component', () => {
const meta: Meta<Button> = {
component: Button,
args: {
label: 'good',
disabled: false,
},
};

expectTypeOf(meta).toEqualTypeOf<
ComponentAnnotations<SvelteFramework<Button>, { disabled: boolean; label: string }>
>();
});

test('Generic parameter of Meta can be the props of the component', () => {
const meta: Meta<{ disabled: boolean; label: string }> = {
component: Button,
args: { label: 'good', disabled: false },
};

expectTypeOf(meta).toEqualTypeOf<
ComponentAnnotations<SvelteFramework, { disabled: boolean; label: string }>
>();
});

test('Events are inferred from component', () => {
const meta: Meta<Button> = {
component: Button,
args: {
label: 'good',
disabled: false,
},
render: (args) => ({
Component: Button,
props: args,
on: {
mousemove: (event) => {
expectTypeOf(event).toEqualTypeOf<MouseEvent>();
},
},
}),
};
});

test('Events fallback to custom events when no component is specified', () => {
const meta: Meta<{ disabled: boolean; label: string }> = {
component: Button,
args: { label: 'good', disabled: false },
render: (args) => ({
Component: Button,
props: args,
on: {
mousemove: (event) => {
expectTypeOf(event).toEqualTypeOf<CustomEvent>();
},
},
}),
};
});
});

describe('StoryObj', () => {
test('✅ Required args may be provided partial in meta and the story', () => {
const meta = satisfies<Meta<Button>>()({
component: Button,
args: { label: 'good' },
});

type Actual = StoryObj<typeof meta>;
type Expected = SvelteStory<
Button,
{ disabled: boolean; label: string },
{ disabled: boolean; label?: string }
>;
expectTypeOf<Actual>().toEqualTypeOf<Expected>();
});

test('❌ The combined shape of meta args and story args must match the required args.', () => {
{
const meta = satisfies<Meta<Button>>()({ component: Button });

type Expected = SvelteStory<
Button,
{ disabled: boolean; label: string },
{ disabled: boolean; label: string }
>;
expectTypeOf<StoryObj<typeof meta>>().toEqualTypeOf<Expected>();
}
{
const meta = satisfies<Meta<Button>>()({
component: Button,
args: { label: 'good' },
});
// @ts-expect-error disabled not provided ❌
const Basic: StoryObj<typeof meta> = {};

type Expected = SvelteStory<
Button,
{ disabled: boolean; label: string },
{ disabled: boolean; label?: string }
>;
expectTypeOf(Basic).toEqualTypeOf<Expected>();
}
{
const meta = satisfies<Meta<{ label: string; disabled: boolean }>>()({ component: Button });
const Basic: StoryObj<typeof meta> = {
// @ts-expect-error disabled not provided ❌
args: { label: 'good' },
};

type Expected = SvelteStory<
Button,
{ disabled: boolean; label: string },
{ disabled: boolean; label: string }
>;
expectTypeOf(Basic).toEqualTypeOf<Expected>();
}
});

test('Component can be used as generic parameter for StoryObj', () => {
type Actual = StoryObj<Button>;

type Expected = SvelteStory<
Button,
{ disabled: boolean; label: string },
{ disabled: boolean; label: string }
>;

expectTypeOf<Actual>().toEqualTypeOf<Expected>();
});
});

type ThemeData = 'light' | 'dark';

describe('Story args can be inferred', () => {
test('Correct args are inferred when type is widened for render function', () => {
const meta = satisfies<Meta<ComponentProps<Button> & { theme: ThemeData }>>()({
component: Button,
args: { disabled: false },
render: (args, { component }) => {
return {
Component: component,
props: args,
};
},
});

const Basic: StoryObj<typeof meta> = { args: { theme: 'light', label: 'good' } };

type Expected = SvelteStory<
Button,
{ theme: ThemeData; disabled: boolean; label: string },
{ theme: ThemeData; disabled?: boolean; label: string }
>;
expectTypeOf(Basic).toEqualTypeOf<Expected>();
});

const withDecorator: DecoratorFn<Decorator> = (storyFn, { args: { decoratorArg } }) => ({
Component: Decorator,
props: { decoratorArg },
});

test('Correct args are inferred when type is widened for decorators', () => {
type Props = ComponentProps<Button> & { decoratorArg: string };

const meta = satisfies<Meta<Props>>()({
component: Button,
args: { disabled: false },
decorators: [withDecorator],
});

const Basic: StoryObj<typeof meta> = { args: { decoratorArg: 'title', label: 'good' } };

type Expected = SvelteStory<
Button,
Props,
{ decoratorArg: string; disabled?: boolean; label: string }
>;
expectTypeOf(Basic).toEqualTypeOf<Expected>();
});

test('Correct args are inferred when type is widened for multiple decorators', () => {
type Props = ComponentProps<Button> & { decoratorArg: string; decoratorArg2: string };

const secondDecorator: DecoratorFn<Decorator2> = (storyFn, { args: { decoratorArg2 } }) => ({
Component: Decorator2,
props: { decoratorArg2 },
});

const meta = satisfies<Meta<Props>>()({
component: Button,
args: { disabled: false },
decorators: [withDecorator, secondDecorator],
});

const Basic: StoryObj<typeof meta> = {
args: { decoratorArg: '', decoratorArg2: '', label: 'good' },
};

type Expected = SvelteStory<
Button,
Props,
{ decoratorArg: string; decoratorArg2: string; disabled?: boolean; label: string }
>;
expectTypeOf(Basic).toEqualTypeOf<Expected>();
});
});
8 changes: 8 additions & 0 deletions code/renderers/svelte/src/___test___/Decorator.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
export let decoratorArg: string;
</script>

<div>
Decorator: {decoratorArg}
<slot />
</div>
8 changes: 8 additions & 0 deletions code/renderers/svelte/src/___test___/Decorator2.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
export let decoratorArg2: string;
</script>

<div>
Decorator: {decoratorArg2}
<slot />
</div>
1 change: 1 addition & 0 deletions code/renderers/svelte/src/docs/extractArgTypes.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, expect } from '@jest/globals';
import svelteDoc from 'sveltedoc-parser';
import * as fs from 'fs';
import { createArgTypes } from './extractArgTypes';
Expand Down
2 changes: 1 addition & 1 deletion code/renderers/svelte/src/docs/extractArgTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => {

/**
* Function to convert the type from sveltedoc-parser to a storybook type
* @param typeName
* @param type
* @returns string
*/
const parseTypeToControl = (type: JSDocType | undefined): any => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, expect, test } from '@jest/globals';
import { extractComponentDescription } from './extractComponentDescription';

describe('extractComponentDescription', () => {
Expand Down
7 changes: 4 additions & 3 deletions code/renderers/svelte/src/docs/sourceDecorator.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { describe, expect, test } from '@jest/globals';
import type { Args } from '@storybook/api';
import { generateSvelteSource } from './sourceDecorator';

expect.addSnapshotSerializer({
print: (val: any) => val,
test: (val) => typeof val === 'string',
test: (val: unknown) => typeof val === 'string',
});

function generateForArgs(args: Args, slotProperty: string = null) {
function generateForArgs(args: Args, slotProperty: string | null = null) {
return generateSvelteSource({ name: 'Component' }, args, {}, slotProperty);
}

Expand Down Expand Up @@ -42,6 +43,6 @@ describe('generateSvelteSource', () => {
`);
});
test('component is not set', () => {
expect(generateSvelteSource(null, null, null, null)).toBeNull();
expect(generateSvelteSource(null, {}, {}, null)).toBeNull();
});
});
2 changes: 1 addition & 1 deletion code/renderers/svelte/src/docs/sourceDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function generateSvelteSource(
component: any,
args: Args,
argTypes: ArgTypes,
slotProperty?: string
slotProperty?: string | null
): string | null {
const name = getComponentName(component);

Expand Down
1 change: 0 additions & 1 deletion code/renderers/svelte/src/globals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-expect-error (Converted from ts-ignore)
import global from 'global';

const { window: globalWindow } = global;
Expand Down
Loading

0 comments on commit c637e76

Please sign in to comment.