-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
SvelteKit: Add support for mocking $app/state
#31369
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
Changes from all commits
9f4dc00
c1e6a35
f6aba2b
8b52a44
f30d13c
7860caa
ebde853
515b043
cb2457b
acd137a
11f812e
277dcbc
82f287e
7af29a2
99f5109
58dadc8
5f9bc5f
2a9c6f5
2c95bb4
f909bf7
1062424
b3cf465
19ebc0c
b800518
a6ac560
038fb8e
25c6d70
9d316ee
210036e
e043fa8
91017dc
5ef0ff8
e873a2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,10 @@ | ||
| import type { Decorator } from '@storybook/svelte'; | ||
| import type { Decorator, Preview } from '@storybook/svelte'; | ||
| import MockProvider from '@storybook/sveltekit/internal/MockProvider.svelte'; | ||
| import { | ||
| setAppStateNavigating, | ||
| setAppStatePage, | ||
| setAppStateUpdated, // @ts-expect-error no declaration file for this JS module | ||
| } from '@storybook/sveltekit/internal/mocks/app/state.svelte.js'; | ||
|
Comment on lines
+3
to
+7
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix @ts-expect-error placement; TypeScript ignores inline trailing annotations
Apply: -import {
- setAppStateNavigating,
- setAppStatePage,
- setAppStateUpdated, // @ts-expect-error no declaration file for this JS module
-} from '@storybook/sveltekit/internal/mocks/app/state.svelte.js';
+// @ts-expect-error no declaration file for this JS module
+import {
+ setAppStateNavigating,
+ setAppStatePage,
+ setAppStateUpdated,
+} from '@storybook/sveltekit/internal/mocks/app/state.svelte.js';Optional (preferred): add a declaration file to remove the need for ts-expect-error: // code/frameworks/sveltekit/src/mocks/app/state.svelte.d.ts
declare module '@storybook/sveltekit/internal/mocks/app/state.svelte.js' {
import type { Page, Navigation } from '../../types'; // adjust path if needed
export function setAppStatePage(page?: Partial<Page> | undefined): void;
export function setAppStateNavigating(navigating?: Partial<Navigation> | undefined): void;
export function setAppStateUpdated(updated?: { current: boolean } | undefined): void;
}🤖 Prompt for AI Agents |
||
|
|
||
| import type { SvelteKitParameters } from './types'; | ||
|
|
||
|
|
@@ -15,3 +20,11 @@ const svelteKitMocksDecorator: Decorator = (Story, ctx) => { | |
| }; | ||
|
|
||
| export const decorators: Decorator[] = [svelteKitMocksDecorator]; | ||
|
|
||
| export const beforeEach: Preview['beforeEach'] = async (ctx) => { | ||
| const svelteKitParameters: SvelteKitParameters = ctx.parameters?.sveltekit_experimental ?? {}; | ||
|
|
||
| setAppStatePage(svelteKitParameters?.state?.page); | ||
| setAppStateNavigating(svelteKitParameters?.state?.navigating); | ||
| setAppStateUpdated(svelteKitParameters?.state?.updated); | ||
| }; | ||
JReinhold marked this conversation as resolved.
Show resolved
Hide resolved
JReinhold marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| /** | ||
| * Inspired by the the code: | ||
| * {@link https://github.com/sveltejs/kit/blob/main/packages/kit/src/runtime/client/state.svelte.js} | ||
| * | ||
| * The differences: | ||
| * | ||
| * - Legacy Svelte support is not included | ||
| * - Not using classes (internal coding style preference) | ||
| * | ||
| * @module | ||
| */ | ||
| import { fn } from 'storybook/test'; | ||
|
|
||
| /** | ||
| * @typedef {Object} App | ||
| * @property {Object} Error | ||
| * @property {string} Error.message | ||
| * @property {Object} Locals | ||
| * @property {Object} PageData | ||
| * @property {Object} PageState | ||
| * @property {Object} Platform | ||
| */ | ||
|
|
||
| /** | ||
| * @typedef {Object} Page | ||
| * @property {URL} url | ||
| * @property {Record<string, string>} params | ||
| * @property {Object} route | ||
| * @property {string | null} route.id | ||
| * @property {number} status | ||
| * @property {App.Error | null} error | ||
| * @property {App.PageData & Record<string, any>} data | ||
| * @property {App.PageState} state | ||
| * @property {any} form | ||
| */ | ||
|
|
||
| const defaultStatePageValues = { | ||
| data: {}, | ||
| form: null, | ||
| error: null, | ||
| params: {}, | ||
| route: { id: null }, | ||
| state: {}, | ||
| status: -1, | ||
| url: new URL(location.origin), | ||
| }; | ||
JReinhold marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** @type {Page['data']} */ | ||
| let pageData = $state.raw(defaultStatePageValues.data); | ||
| /** @type {Page['form']} */ | ||
| let pageForm = $state.raw(defaultStatePageValues.form); | ||
| /** @type {Page['error']} */ | ||
| let pageError = $state.raw(defaultStatePageValues.error); | ||
| /** @type {Page['params']} */ | ||
| let pageParams = $state.raw(defaultStatePageValues.params); | ||
| /** @type {Page['route']} */ | ||
| let pageRoute = $state.raw(defaultStatePageValues.route); | ||
| /** @type {Page['state']} */ | ||
| let pageState = $state.raw(defaultStatePageValues.state); | ||
| /** @type {Page['status']} */ | ||
| let pageStatus = $state.raw(defaultStatePageValues.status); | ||
| /** @type {Page['url']} */ | ||
| let pageUrl = $state.raw(defaultStatePageValues.url); | ||
|
|
||
| /** @type {Page} */ | ||
| export let page = { | ||
| get data() { | ||
| return pageData; | ||
| }, | ||
| set data(newPageData) { | ||
| pageData = newPageData; | ||
| }, | ||
| get form() { | ||
| return pageForm; | ||
| }, | ||
| set form(newPageForm) { | ||
| pageForm = newPageForm; | ||
| }, | ||
| get error() { | ||
| return pageError; | ||
| }, | ||
| set error(newPageError) { | ||
| pageError = newPageError; | ||
| }, | ||
| get params() { | ||
| return pageParams; | ||
| }, | ||
| set params(newPageParams) { | ||
| pageParams = newPageParams; | ||
| }, | ||
| get route() { | ||
| return pageRoute; | ||
| }, | ||
| set route(newPageRoute) { | ||
| pageRoute = newPageRoute; | ||
| }, | ||
| get state() { | ||
| return pageState; | ||
| }, | ||
| set state(newPageState) { | ||
| pageState = newPageState; | ||
| }, | ||
| get status() { | ||
| return pageStatus; | ||
| }, | ||
| set status(newPageStatus) { | ||
| pageStatus = newPageStatus; | ||
| }, | ||
| get url() { | ||
| return pageUrl; | ||
| }, | ||
| set url(newPageUrl) { | ||
| pageUrl = newPageUrl; | ||
| }, | ||
| }; | ||
|
|
||
| export function setAppStatePage(params = {}) { | ||
| page.data = params.data ?? defaultStatePageValues.data; | ||
| page.form = params.form ?? defaultStatePageValues.form; | ||
| page.error = params.error ?? defaultStatePageValues.error; | ||
| page.params = params.params ?? defaultStatePageValues.params; | ||
| page.route = params.route ?? defaultStatePageValues.route; | ||
| page.state = params.state ?? defaultStatePageValues.state; | ||
| page.status = params.status ?? defaultStatePageValues.status; | ||
| page.url = params.url ?? defaultStatePageValues.url; | ||
| } | ||
|
|
||
| /** | ||
| * @typedef {Object} NavigationTarget | ||
| * @property {Record<string, string> | null} params | ||
| * @property {Object} route | ||
| * @property {string | null} route.id | ||
| * @property {URL} url | ||
| */ | ||
|
|
||
| /** @typedef {'enter' | 'form' | 'leave' | 'link' | 'goto' | 'popstate'} NavigationType */ | ||
|
|
||
| /** | ||
| * @typedef {Object} Navigation | ||
| * @property {NavigationTarget | null} from | ||
| * @property {NavigationTarget | null} to | ||
| * @property {Exclude<NavigationType, 'enter'>} type | ||
| * @property {boolean} willUnload | ||
| * @property {number} [delta] | ||
| * @property {Promise<void>} complete | ||
| */ | ||
|
|
||
| const defaultStateNavigatingValues = { | ||
| from: null, | ||
| to: null, | ||
| type: null, | ||
| willUnload: null, | ||
| delta: null, | ||
| complete: null, | ||
| }; | ||
|
|
||
| /** @type {Navigation['from'] | null} */ | ||
| let navigatingFrom = $state.raw(defaultStateNavigatingValues.from); | ||
| /** @type {Navigation['to'] | null} */ | ||
| let navigatingTo = $state.raw(defaultStateNavigatingValues.to); | ||
| /** @type {Navigation['type'] | null} */ | ||
| let navigatingType = $state.raw(defaultStateNavigatingValues.type); | ||
| /** @type {Navigation['willUnload'] | null} */ | ||
| let navigatingWillUnload = $state.raw(defaultStateNavigatingValues.willUnload); | ||
| /** @type {Navigation['delta'] | null} */ | ||
| let navigatingDelta = $state.raw(defaultStateNavigatingValues.delta); | ||
| /** @type {Navigation['complete'] | null} */ | ||
| let navigatingComplete = $state.raw(defaultStateNavigatingValues.complete); | ||
|
|
||
| /** @type {Navigation} */ | ||
| export let navigating = { | ||
| get from() { | ||
| return navigatingFrom; | ||
| }, | ||
| set from(newNavigatingFrom) { | ||
| navigatingFrom = newNavigatingFrom; | ||
| }, | ||
| get to() { | ||
| return navigatingTo; | ||
| }, | ||
| set to(newNavigatingTo) { | ||
| navigatingTo = newNavigatingTo; | ||
| }, | ||
| get type() { | ||
| return navigatingType; | ||
| }, | ||
| set type(newNavigatingType) { | ||
| navigatingType = newNavigatingType; | ||
| }, | ||
| get willUnload() { | ||
| return navigatingWillUnload; | ||
| }, | ||
| set willUnload(newNavigatingWillUnload) { | ||
| navigatingWillUnload = newNavigatingWillUnload; | ||
| }, | ||
| get delta() { | ||
| return navigatingDelta; | ||
| }, | ||
| set delta(newNavigatingDelta) { | ||
| navigatingDelta = newNavigatingDelta; | ||
| }, | ||
| get complete() { | ||
| return navigatingComplete; | ||
| }, | ||
| set complete(newNavigatingComplete) { | ||
| navigatingComplete = newNavigatingComplete; | ||
| }, | ||
| }; | ||
|
|
||
| export function setAppStateNavigating(params = {}) { | ||
| navigating.from = params.from ?? defaultStateNavigatingValues.from; | ||
| navigating.to = params.to ?? defaultStateNavigatingValues.to; | ||
| navigating.type = params.type ?? defaultStateNavigatingValues.type; | ||
| navigating.willUnload = params.willUnload ?? defaultStateNavigatingValues.willUnload; | ||
| navigating.delta = params.delta ?? defaultStateNavigatingValues.delta; | ||
| navigating.complete = params.complete ?? defaultStateNavigatingValues.complete; | ||
| } | ||
|
|
||
| /** @type {boolean} */ | ||
| let updatedCurrent = $state.raw(false); | ||
|
|
||
| export let updated = { | ||
| get current() { | ||
| return updatedCurrent; | ||
| }, | ||
| set current(newCurrent) { | ||
| updatedCurrent = newCurrent; | ||
| }, | ||
| check: fn(() => Promise.resolve(updatedCurrent)), | ||
| }; | ||
|
|
||
| export function setAppStateUpdated(params = {}) { | ||
| updated.current = params.current ?? false; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.