@scaffold-ui-kit/preview 0.0.0-stage → 2.3.0
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/LICENSE +21 -0
- package/README.md +1868 -2
- package/application/host/bootstrap.ts +43 -0
- package/application/host/main.ts +31 -0
- package/application/host/shell-host.ts +47 -0
- package/application/index.html +12 -0
- package/application/lib/base-url.ts +15 -0
- package/application/lib/no-code-block.ts +7 -0
- package/application/lib/no-setup.ts +3 -0
- package/application/lib/no-views.ts +6 -0
- package/application/preview/code/code.html +12 -0
- package/application/preview/code/main.ts +89 -0
- package/application/preview/globals.ts +254 -0
- package/application/preview/html/main.ts +93 -0
- package/application/preview/html/preview.html +12 -0
- package/application/preview/react/main.tsx +146 -0
- package/application/preview/react/preview.html +12 -0
- package/application/preview/svelte/Preview.svelte +104 -0
- package/application/preview/svelte/main.ts +13 -0
- package/application/preview/svelte/preview.html +12 -0
- package/application/preview/svelte/story-props.ts +172 -0
- package/application/preview/view/main.ts +124 -0
- package/application/preview/view/view.html +12 -0
- package/application/preview/vue/main.ts +145 -0
- package/application/preview/vue/preview.html +12 -0
- package/application/shells/charcell/components/Application/index.css +37 -0
- package/application/shells/charcell/components/Application/index.tsx +65 -0
- package/application/shells/charcell/components/Bar/index.css +68 -0
- package/application/shells/charcell/components/Bar/index.tsx +60 -0
- package/application/shells/charcell/components/CodePane/index.css +60 -0
- package/application/shells/charcell/components/CodePane/index.tsx +113 -0
- package/application/shells/charcell/components/ComponentStage/index.css +26 -0
- package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
- package/application/shells/charcell/components/Control/index.css +20 -0
- package/application/shells/charcell/components/Control/index.tsx +118 -0
- package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
- package/application/shells/charcell/components/Description/index.css +6 -0
- package/application/shells/charcell/components/Description/index.tsx +24 -0
- package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
- package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
- package/application/shells/charcell/components/EditorPanel/index.css +48 -0
- package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
- package/application/shells/charcell/components/EventsRow/index.css +42 -0
- package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
- package/application/shells/charcell/components/HeadingRow/index.css +11 -0
- package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
- package/application/shells/charcell/components/Hint/index.css +16 -0
- package/application/shells/charcell/components/Hint/index.tsx +81 -0
- package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
- package/application/shells/charcell/components/KeyHints/index.css +27 -0
- package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
- package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
- package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
- package/application/shells/charcell/components/Markdown/index.tsx +16 -0
- package/application/shells/charcell/components/Navigation/index.css +13 -0
- package/application/shells/charcell/components/Navigation/index.tsx +98 -0
- package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
- package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
- package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
- package/application/shells/charcell/components/PaneStatus/index.css +7 -0
- package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
- package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
- package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
- package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
- package/application/shells/charcell/components/PreviewPane/index.css +127 -0
- package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
- package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
- package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
- package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
- package/application/shells/charcell/components/Select/index.tsx +48 -0
- package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
- package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
- package/application/shells/charcell/components/StageBar/index.css +7 -0
- package/application/shells/charcell/components/StageBar/index.tsx +109 -0
- package/application/shells/charcell/components/StageSplit/index.css +11 -0
- package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
- package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
- package/application/shells/charcell/components/ViewControls/index.css +15 -0
- package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
- package/application/shells/charcell/components/ViewHeading/index.css +13 -0
- package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
- package/application/shells/charcell/components/ViewStage/index.css +9 -0
- package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
- package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
- package/application/shells/charcell/configuration.ts +45 -0
- package/application/shells/charcell/controller-context.ts +27 -0
- package/application/shells/charcell/fit-labels.ts +42 -0
- package/application/shells/charcell/html-warnings.ts +18 -0
- package/application/shells/charcell/index.tsx +82 -0
- package/application/shells/charcell/page-loading.html +57 -0
- package/application/shells/charcell/placement.ts +24 -0
- package/application/shells/charcell/shell-options.ts +23 -0
- package/application/shells/charcell/shell.css +37 -0
- package/application/shells/charcell/shell.tsx +25 -0
- package/application/shells/charcell/split-reset.ts +47 -0
- package/application/shells/charcell/stacked-rows.ts +117 -0
- package/application/shells/charcell/stage-toggles.ts +93 -0
- package/application/types/manifest.d.ts +6 -0
- package/application/types/preview-configuration.d.ts +10 -0
- package/application/types/preview-shell.d.ts +7 -0
- package/application/types/react-registry.d.ts +15 -0
- package/application/types/vue-registry.d.ts +6 -0
- package/dist/build.d.ts +24 -0
- package/dist/build.js +34 -0
- package/dist/check.d.ts +48 -0
- package/dist/check.js +781 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +88 -0
- package/dist/context.d.ts +179 -0
- package/dist/context.js +783 -0
- package/dist/controller/catalog.d.ts +339 -0
- package/dist/controller/catalog.js +367 -0
- package/dist/controller/create-preview-controller.d.ts +532 -0
- package/dist/controller/create-preview-controller.js +1707 -0
- package/dist/controller/editor-tree.d.ts +161 -0
- package/dist/controller/editor-tree.js +373 -0
- package/dist/controller/html-source.d.ts +62 -0
- package/dist/controller/html-source.js +61 -0
- package/dist/controller/index.d.ts +9 -0
- package/dist/controller/index.js +12 -0
- package/dist/controller/persistence.d.ts +191 -0
- package/dist/controller/persistence.js +315 -0
- package/dist/controller/regeneration.d.ts +30 -0
- package/dist/controller/regeneration.js +27 -0
- package/dist/controller/source.d.ts +80 -0
- package/dist/controller/source.js +236 -0
- package/dist/controller/store.d.ts +27 -0
- package/dist/controller/store.js +40 -0
- package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
- package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
- package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
- package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
- package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
- package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
- package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
- package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
- package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
- package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
- package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
- package/dist/declarations/src/controller/catalog.d.ts +339 -0
- package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
- package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
- package/dist/declarations/src/controller/html-source.d.ts +62 -0
- package/dist/declarations/src/controller/index.d.ts +9 -0
- package/dist/declarations/src/controller/persistence.d.ts +191 -0
- package/dist/declarations/src/controller/regeneration.d.ts +30 -0
- package/dist/declarations/src/controller/source.d.ts +80 -0
- package/dist/declarations/src/controller/store.d.ts +27 -0
- package/dist/declarations/src/emit/common.d.ts +103 -0
- package/dist/declarations/src/emit/manifest.d.ts +138 -0
- package/dist/declarations/src/emit/source.d.ts +87 -0
- package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
- package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
- package/dist/declarations/src/lib/naming.d.ts +29 -0
- package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
- package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
- package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
- package/dist/declarations/src/lib/types.d.ts +741 -0
- package/dist/declarations/src/options.d.ts +224 -0
- package/dist/declarations/src/protocol.d.ts +541 -0
- package/dist/declarations/src/react-view.d.ts +22 -0
- package/dist/declarations/src/react.d.ts +17 -0
- package/dist/declarations/src/shell.d.ts +99 -0
- package/dist/declarations/src/views.d.ts +152 -0
- package/dist/development-server.d.ts +121 -0
- package/dist/development-server.js +331 -0
- package/dist/emit/common.d.ts +103 -0
- package/dist/emit/common.js +170 -0
- package/dist/emit/html-source.d.ts +46 -0
- package/dist/emit/html-source.js +322 -0
- package/dist/emit/manifest.d.ts +138 -0
- package/dist/emit/manifest.js +74 -0
- package/dist/emit/react.d.ts +16 -0
- package/dist/emit/react.js +210 -0
- package/dist/emit/scratch.d.ts +40 -0
- package/dist/emit/scratch.js +116 -0
- package/dist/emit/source.d.ts +87 -0
- package/dist/emit/source.js +789 -0
- package/dist/emit/svelte.d.ts +9 -0
- package/dist/emit/svelte.js +177 -0
- package/dist/emit/vue.d.ts +2 -0
- package/dist/emit/vue.js +152 -0
- package/dist/enumerate.d.ts +2 -0
- package/dist/enumerate.js +162 -0
- package/dist/generate.d.ts +43 -0
- package/dist/generate.js +302 -0
- package/dist/html-stylesheet.d.ts +43 -0
- package/dist/html-stylesheet.js +145 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +21 -0
- package/dist/landing.d.ts +16 -0
- package/dist/landing.js +45 -0
- package/dist/lib/exported-types.d.ts +42 -0
- package/dist/lib/exported-types.js +86 -0
- package/dist/lib/html-attributes.d.ts +2 -0
- package/dist/lib/html-attributes.js +111 -0
- package/dist/lib/html-warnings.d.ts +19 -0
- package/dist/lib/html-warnings.js +79 -0
- package/dist/lib/jsx-roots.d.ts +22 -0
- package/dist/lib/jsx-roots.js +243 -0
- package/dist/lib/module-of-source.d.ts +18 -0
- package/dist/lib/module-of-source.js +24 -0
- package/dist/lib/naming.d.ts +29 -0
- package/dist/lib/naming.js +56 -0
- package/dist/lib/parse-type.d.ts +32 -0
- package/dist/lib/parse-type.js +103 -0
- package/dist/lib/react-spelling.d.ts +68 -0
- package/dist/lib/react-spelling.js +197 -0
- package/dist/lib/scenario-story.d.ts +41 -0
- package/dist/lib/scenario-story.js +84 -0
- package/dist/lib/slot-fill.d.ts +15 -0
- package/dist/lib/slot-fill.js +92 -0
- package/dist/lib/template-files.d.ts +14 -0
- package/dist/lib/template-files.js +25 -0
- package/dist/lib/types.d.ts +741 -0
- package/dist/lib/types.js +13 -0
- package/dist/lib/void-elements.d.ts +7 -0
- package/dist/lib/void-elements.js +13 -0
- package/dist/load.d.ts +37 -0
- package/dist/load.js +82 -0
- package/dist/model.d.ts +44 -0
- package/dist/model.js +464 -0
- package/dist/options.d.ts +224 -0
- package/dist/options.js +223 -0
- package/dist/prop-types.d.ts +26 -0
- package/dist/prop-types.js +102 -0
- package/dist/props-from.d.ts +84 -0
- package/dist/props-from.js +510 -0
- package/dist/protocol.d.ts +541 -0
- package/dist/protocol.js +819 -0
- package/dist/react-view.d.ts +22 -0
- package/dist/react-view.js +36 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +23 -0
- package/dist/sass-importers.d.ts +26 -0
- package/dist/sass-importers.js +32 -0
- package/dist/shell-acceptance.d.ts +21 -0
- package/dist/shell-acceptance.js +37 -0
- package/dist/shell-page.d.ts +36 -0
- package/dist/shell-page.js +90 -0
- package/dist/shell-peers.d.ts +38 -0
- package/dist/shell-peers.js +61 -0
- package/dist/shell.d.ts +99 -0
- package/dist/shell.js +52 -0
- package/dist/shells/charcell/page.d.ts +8 -0
- package/dist/shells/charcell/page.js +69 -0
- package/dist/story-source.d.ts +113 -0
- package/dist/story-source.js +271 -0
- package/dist/story.d.ts +5 -0
- package/dist/story.js +28 -0
- package/dist/testing.d.ts +218 -0
- package/dist/testing.js +284 -0
- package/dist/views.d.ts +152 -0
- package/dist/views.js +7 -0
- package/dist/vite-configuration.d.ts +114 -0
- package/dist/vite-configuration.js +578 -0
- package/package.json +123 -3
- package/src/build.ts +51 -0
- package/src/check.ts +1062 -0
- package/src/cli.ts +123 -0
- package/src/context.ts +1168 -0
- package/src/controller/catalog.ts +719 -0
- package/src/controller/create-preview-controller.ts +2683 -0
- package/src/controller/editor-tree.ts +530 -0
- package/src/controller/html-source.ts +114 -0
- package/src/controller/index.ts +105 -0
- package/src/controller/persistence.ts +403 -0
- package/src/controller/regeneration.ts +55 -0
- package/src/controller/source.ts +333 -0
- package/src/controller/store.ts +55 -0
- package/src/development-server.ts +442 -0
- package/src/emit/common.ts +201 -0
- package/src/emit/html-source.ts +402 -0
- package/src/emit/manifest.ts +241 -0
- package/src/emit/react.ts +263 -0
- package/src/emit/scratch.ts +175 -0
- package/src/emit/source.ts +1094 -0
- package/src/emit/svelte.ts +230 -0
- package/src/emit/vue.ts +194 -0
- package/src/enumerate.ts +220 -0
- package/src/generate.ts +443 -0
- package/src/html-stylesheet.ts +173 -0
- package/src/index.ts +60 -0
- package/src/landing.ts +58 -0
- package/src/lib/exported-types.ts +114 -0
- package/src/lib/html-attributes.ts +113 -0
- package/src/lib/html-warnings.ts +81 -0
- package/src/lib/jsx-roots.ts +238 -0
- package/src/lib/module-of-source.ts +26 -0
- package/src/lib/naming.ts +65 -0
- package/src/lib/parse-type.ts +143 -0
- package/src/lib/react-spelling.ts +235 -0
- package/src/lib/scenario-story.ts +97 -0
- package/src/lib/slot-fill.ts +102 -0
- package/src/lib/template-files.ts +31 -0
- package/src/lib/types.ts +778 -0
- package/src/lib/void-elements.ts +15 -0
- package/src/load.ts +144 -0
- package/src/model.ts +568 -0
- package/src/options.ts +435 -0
- package/src/prop-types.ts +133 -0
- package/src/props-from.ts +678 -0
- package/src/protocol.ts +1135 -0
- package/src/react-view.ts +45 -0
- package/src/react.ts +34 -0
- package/src/sass-importers.ts +45 -0
- package/src/shell-acceptance.ts +45 -0
- package/src/shell-page.ts +133 -0
- package/src/shell-peers.ts +70 -0
- package/src/shell.ts +194 -0
- package/src/shells/charcell/page.ts +82 -0
- package/src/story-source.ts +391 -0
- package/src/story.ts +42 -0
- package/src/testing.ts +497 -0
- package/src/views.ts +183 -0
- package/src/vite-configuration.ts +675 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { ManifestComponent } from './emit/manifest.js';
|
|
2
|
+
import type { OptionValue } from './options.js';
|
|
3
|
+
import { PREVIEW_MOUNT_ELEMENT_ID, PREVIEW_RENDER_ERROR_FLAG, type PreviewScriptedTarget } from './protocol.js';
|
|
4
|
+
/** The id of the element a preview page mounts its story or view in. */
|
|
5
|
+
export { PREVIEW_MOUNT_ELEMENT_ID };
|
|
6
|
+
/** A component as the preview manifest lists it: the parts a browser test
|
|
7
|
+
* reads. */
|
|
8
|
+
export type PreviewManifestComponent = Pick<ManifestComponent, 'id' | 'name'> & Partial<Pick<ManifestComponent, 'defaultProps' | 'previewDefaults' | 'standalone' | 'targets' | 'bindings'>> & {
|
|
9
|
+
/** The story's scenarios, each with the props it sets over the defaults
|
|
10
|
+
* and, for one with slot content of its own, that content and the id
|
|
11
|
+
* its story mounts by. */
|
|
12
|
+
scenarios?: (Pick<ManifestComponent['scenarios'][number], 'id'> & Partial<Pick<ManifestComponent['scenarios'][number], 'label' | 'props' | 'content' | 'story'>>)[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Read the components of a kit's preview manifest,
|
|
16
|
+
* `.preview-generated/manifest.json`, which the preview writes when it
|
|
17
|
+
* starts.
|
|
18
|
+
*
|
|
19
|
+
* @param kitRoot - The kit's root directory.
|
|
20
|
+
* @returns The components the manifest lists.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* for (const component of readPreviewManifest('.')) {
|
|
24
|
+
* test(component.id, async ({ page }) => {});
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function readPreviewManifest(kitRoot: string): PreviewManifestComponent[];
|
|
29
|
+
/** The window property a story page sets to what its story threw; see
|
|
30
|
+
* {@link readRenderError}. */
|
|
31
|
+
export { PREVIEW_RENDER_ERROR_FLAG };
|
|
32
|
+
/** A target whose preview page the helpers load: `react`, `vue` or `svelte`. */
|
|
33
|
+
export type { PreviewScriptedTarget };
|
|
34
|
+
/** Preview option values keyed by option id, such as `{ mode: 'dark' }`. */
|
|
35
|
+
export type PreviewOptionValues = Record<string, OptionValue>;
|
|
36
|
+
/**
|
|
37
|
+
* The part of a browser page the helpers use. A Playwright `Page` satisfies
|
|
38
|
+
* it, as does any object with these three methods.
|
|
39
|
+
*/
|
|
40
|
+
export interface PreviewPage {
|
|
41
|
+
/** Navigate to `url`, resolving once the page has loaded. */
|
|
42
|
+
goto(url: string, options?: {
|
|
43
|
+
waitUntil?: 'load';
|
|
44
|
+
}): Promise<unknown>;
|
|
45
|
+
/** Resolve once `pageFunction`, run in the page with `argument`, returns a truthy value. */
|
|
46
|
+
waitForFunction<Argument>(pageFunction: (argument: Argument) => unknown, argument: Argument): Promise<unknown>;
|
|
47
|
+
/** Run `pageFunction` in the page with `argument` and resolve with its result. */
|
|
48
|
+
evaluate<Result, Argument>(pageFunction: (argument: Argument) => Result | Promise<Result>, argument: Argument): Promise<Result>;
|
|
49
|
+
}
|
|
50
|
+
/** Where the preview is served. */
|
|
51
|
+
export interface PreviewLocationOptions {
|
|
52
|
+
/** Prefixed to the preview page's path, such as `http://localhost:6100`.
|
|
53
|
+
* Omit it to navigate by path alone (resolved against the test runner's
|
|
54
|
+
* base URL, such as Playwright's `use.baseURL`). */
|
|
55
|
+
baseUrl?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Load a target's preview page and wait until it listens for messages. The
|
|
59
|
+
* page shows nothing until a story is set.
|
|
60
|
+
*
|
|
61
|
+
* @param page - The browser page.
|
|
62
|
+
* @param target - The target whose preview page to load.
|
|
63
|
+
* @param options - Where the preview is served.
|
|
64
|
+
* @throws When the target has no story page, the message naming those that
|
|
65
|
+
* do, or when the page never starts listening, such as after an import
|
|
66
|
+
* fails to load.
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* test.beforeEach(async ({ page }) => {
|
|
70
|
+
* await gotoPreview(page, 'vue');
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function gotoPreview(page: PreviewPage, target: PreviewScriptedTarget, options?: PreviewLocationOptions): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* The ids of the stories a preview page can mount.
|
|
77
|
+
*
|
|
78
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
79
|
+
* @returns The story ids.
|
|
80
|
+
* @throws When the page is not a preview page that mounts stories.
|
|
81
|
+
*/
|
|
82
|
+
export declare function readStoryIds(page: PreviewPage): Promise<string[]>;
|
|
83
|
+
/**
|
|
84
|
+
* What the story mounted on a preview page threw as it rendered, as the pane
|
|
85
|
+
* reports it: only a throw that reaches the pane counts, so a story with its
|
|
86
|
+
* own error boundary, or the word "error" in its content, reads as rendering.
|
|
87
|
+
*
|
|
88
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
89
|
+
* @returns The error's message, which may be empty, or `undefined` when the
|
|
90
|
+
* story renders.
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* await mountStory(page, 'components-button');
|
|
94
|
+
* await settle(page);
|
|
95
|
+
* expect(await readRenderError(page)).toBeUndefined();
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function readRenderError(page: PreviewPage): Promise<string | undefined>;
|
|
99
|
+
/**
|
|
100
|
+
* Mount a story by id with the given root props, replacing what the page
|
|
101
|
+
* shows. Setting the story already shown with other props keeps its component
|
|
102
|
+
* instance on some targets; use {@link mountStory} for a guaranteed fresh
|
|
103
|
+
* mount.
|
|
104
|
+
*
|
|
105
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
106
|
+
* @param id - The story's id.
|
|
107
|
+
* @param props - The story's root props.
|
|
108
|
+
* @throws When the page has no story with the id; the message lists those it
|
|
109
|
+
* has.
|
|
110
|
+
*/
|
|
111
|
+
export declare function setStory(page: PreviewPage, id: string, props?: Record<string, unknown>): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Mount a story from nothing: blur the focused element, unmount the current
|
|
114
|
+
* story and wait for the mount point to empty, then set the story. The new
|
|
115
|
+
* story inherits neither focus nor component state from the one before, which
|
|
116
|
+
* a sweep over a component's states needs (a state read at mount, such as an
|
|
117
|
+
* initially open panel, only applies to a fresh instance).
|
|
118
|
+
*
|
|
119
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
120
|
+
* @param id - The story's id.
|
|
121
|
+
* @param props - The story's root props.
|
|
122
|
+
* @throws When the page has no story with the id, the message listing those
|
|
123
|
+
* it has, or when the mount is not empty within 15 seconds of the unmount.
|
|
124
|
+
*/
|
|
125
|
+
export declare function mountStory(page: PreviewPage, id: string, props?: Record<string, unknown>): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Wait until a story set with {@link mountStory} has finished mounting,
|
|
128
|
+
* whatever it rendered: a story can legitimately render nothing, which an
|
|
129
|
+
* element to wait for would never show, and a story that throws counts as
|
|
130
|
+
* mounted. It follows `mountStory`, which clears the page's last report;
|
|
131
|
+
* after {@link setStory} that report can be the same story's from before.
|
|
132
|
+
*
|
|
133
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
134
|
+
* @param id - The story's id.
|
|
135
|
+
* @throws When the story does not finish mounting within 15 seconds.
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* await mountStory(page, 'components-banner', { visible: false });
|
|
139
|
+
* await waitForStory(page, 'components-banner');
|
|
140
|
+
* await expect(page.locator('#app > *')).toHaveCount(0);
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function waitForStory(page: PreviewPage, id: string): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Replace the mounted story's root props, keeping its component instance (and
|
|
146
|
+
* any state machine driving it) alive.
|
|
147
|
+
*
|
|
148
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
149
|
+
* @param props - The story's new root props.
|
|
150
|
+
*/
|
|
151
|
+
export declare function setProps(page: PreviewPage, props: Record<string, unknown>): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Apply preview option values (a color mode, a surface, a frame around the
|
|
154
|
+
* story) to the page, as the preview's controls do.
|
|
155
|
+
*
|
|
156
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
157
|
+
* @param options - Option values keyed by option id.
|
|
158
|
+
* @throws When an id is not one of the kit's options, or a value is not one
|
|
159
|
+
* the option takes, either of which the page would ignore; the message lists
|
|
160
|
+
* what it defines.
|
|
161
|
+
*/
|
|
162
|
+
export declare function setOptions(page: PreviewPage, options: PreviewOptionValues): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Wait until the frame after the next paint. A component can show its final
|
|
165
|
+
* markup before the effects that attach its event handling have run; call this
|
|
166
|
+
* after asserting a change has rendered and before sending input, such as a
|
|
167
|
+
* key press, that would otherwise be dropped.
|
|
168
|
+
*
|
|
169
|
+
* @param page - The browser page.
|
|
170
|
+
*/
|
|
171
|
+
export declare function settle(page: PreviewPage): Promise<void>;
|
|
172
|
+
/** What {@link openStory} loads and mounts. */
|
|
173
|
+
export interface OpenStoryOptions extends PreviewLocationOptions {
|
|
174
|
+
/** The target whose preview page to load. */
|
|
175
|
+
target: PreviewScriptedTarget;
|
|
176
|
+
/** The story's id, as the catalog and the address name it, such as
|
|
177
|
+
* `components-button` (`PreviewComponent.id`). */
|
|
178
|
+
id: string;
|
|
179
|
+
/** The story's root props. */
|
|
180
|
+
props?: Record<string, unknown>;
|
|
181
|
+
/** Preview option values applied before the story mounts. */
|
|
182
|
+
options?: PreviewOptionValues;
|
|
183
|
+
}
|
|
184
|
+
/** A story open in a page, with the page's controls bound to it. */
|
|
185
|
+
export interface PreviewStory<Page extends PreviewPage = PreviewPage> {
|
|
186
|
+
/** The page showing the story; the preview page is loaded at the top level,
|
|
187
|
+
* so locators and keyboard input go straight to it. */
|
|
188
|
+
page: Page;
|
|
189
|
+
/** The target the story renders in. */
|
|
190
|
+
target: PreviewScriptedTarget;
|
|
191
|
+
/** Replace the story's root props, keeping its component instance. */
|
|
192
|
+
setProps(props: Record<string, unknown>): Promise<void>;
|
|
193
|
+
/** Apply preview option values. */
|
|
194
|
+
setOptions(options: PreviewOptionValues): Promise<void>;
|
|
195
|
+
/** Show another story (see {@link setStory}). */
|
|
196
|
+
setStory(id: string, props?: Record<string, unknown>): Promise<void>;
|
|
197
|
+
/** Show another story from a fresh mount (see {@link mountStory}). */
|
|
198
|
+
mountStory(id: string, props?: Record<string, unknown>): Promise<void>;
|
|
199
|
+
/** Wait until the frame after the next paint (see {@link settle}). */
|
|
200
|
+
settle(): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Load a target's preview page and mount one story in it.
|
|
204
|
+
*
|
|
205
|
+
* @param page - The browser page.
|
|
206
|
+
* @param options - The target, the story and its props, and option values.
|
|
207
|
+
* @returns The open story.
|
|
208
|
+
* @throws When the page has no story with the id, or an option id is not one
|
|
209
|
+
* of the kit's; the message lists those it has.
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const story = await openStory(page, { target: 'vue', id: 'components-button', props: { variant: 'solid' } });
|
|
213
|
+
* await story.setProps({ variant: 'ghost' });
|
|
214
|
+
* await story.setOptions({ mode: 'dark' });
|
|
215
|
+
* await expect(story.page.getByRole('button')).toHaveClass(/ghost/);
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export declare function openStory<Page extends PreviewPage>(page: Page, options: OpenStoryOptions): Promise<PreviewStory<Page>>;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// Browser-test helpers for a kit's components: load one target's preview page
|
|
2
|
+
// on its own and drive it over the messages the preview host sends it. The page
|
|
3
|
+
// object is typed structurally, so a Playwright `Page` fits without the preview
|
|
4
|
+
// depending on Playwright. The functions handed to `evaluate` and
|
|
5
|
+
// `waitForFunction` run in the browser, so they read only their argument and
|
|
6
|
+
// browser globals, never this module's bindings.
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { componentFrameUrl, globalsMessage, PREVIEW_MOUNT_ELEMENT_ID, PREVIEW_MOUNTED_STORY_FLAG, PREVIEW_OPTIONS_FLAG, PREVIEW_READY_FLAG, PREVIEW_RENDER_ERROR_FLAG, PREVIEW_SCRIPTED_TARGETS, PREVIEW_STORIES_FLAG, propsMessage, setStoryMessage, } from './protocol.js';
|
|
10
|
+
/** The id of the element a preview page mounts its story or view in. */
|
|
11
|
+
export { PREVIEW_MOUNT_ELEMENT_ID };
|
|
12
|
+
/**
|
|
13
|
+
* Read the components of a kit's preview manifest,
|
|
14
|
+
* `.preview-generated/manifest.json`, which the preview writes when it
|
|
15
|
+
* starts.
|
|
16
|
+
*
|
|
17
|
+
* @param kitRoot - The kit's root directory.
|
|
18
|
+
* @returns The components the manifest lists.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* for (const component of readPreviewManifest('.')) {
|
|
22
|
+
* test(component.id, async ({ page }) => {});
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function readPreviewManifest(kitRoot) {
|
|
27
|
+
const manifest = JSON.parse(readFileSync(join(kitRoot, '.preview-generated', 'manifest.json'), 'utf8'));
|
|
28
|
+
return manifest.components;
|
|
29
|
+
}
|
|
30
|
+
/** The window property a story page sets to what its story threw; see
|
|
31
|
+
* {@link readRenderError}. */
|
|
32
|
+
export { PREVIEW_RENDER_ERROR_FLAG };
|
|
33
|
+
/** How long a loaded preview page has to start listening. The page's load
|
|
34
|
+
* has already waited for its modules, so only a page that failed to run
|
|
35
|
+
* them comes near this. */
|
|
36
|
+
const READY_TIMEOUT_MILLISECONDS = 15_000;
|
|
37
|
+
/** How long a story set on a loaded page has to finish mounting. Mounting
|
|
38
|
+
* needs no network, so only a page that has stopped comes near this. */
|
|
39
|
+
const STORY_TIMEOUT_MILLISECONDS = 15_000;
|
|
40
|
+
/** Wait for `wait`, or throw `message` past the deadline. A wait left past
|
|
41
|
+
* the deadline rejects when the page closes, which is ignored. */
|
|
42
|
+
async function withinDeadline(wait, milliseconds, message) {
|
|
43
|
+
wait.catch(() => { });
|
|
44
|
+
let timer;
|
|
45
|
+
const deadline = new Promise((_, reject) => {
|
|
46
|
+
timer = setTimeout(() => reject(new Error(message)), milliseconds);
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
await Promise.race([wait, deadline]);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load a target's preview page and wait until it listens for messages. The
|
|
57
|
+
* page shows nothing until a story is set.
|
|
58
|
+
*
|
|
59
|
+
* @param page - The browser page.
|
|
60
|
+
* @param target - The target whose preview page to load.
|
|
61
|
+
* @param options - Where the preview is served.
|
|
62
|
+
* @throws When the target has no story page, the message naming those that
|
|
63
|
+
* do, or when the page never starts listening, such as after an import
|
|
64
|
+
* fails to load.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* test.beforeEach(async ({ page }) => {
|
|
68
|
+
* await gotoPreview(page, 'vue');
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export async function gotoPreview(page, target, options = {}) {
|
|
73
|
+
if (!PREVIEW_SCRIPTED_TARGETS.includes(target)) {
|
|
74
|
+
throw new Error(`'${target}' has no story page; the story pages are ${PREVIEW_SCRIPTED_TARGETS.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
const baseUrl = (options.baseUrl ?? '').replace(/\/+$/, '');
|
|
77
|
+
const url = `${baseUrl}${componentFrameUrl(target)}`;
|
|
78
|
+
await page.goto(url, { waitUntil: 'load' });
|
|
79
|
+
await withinDeadline(page.waitForFunction((flag) => window[flag] === true, PREVIEW_READY_FLAG), READY_TIMEOUT_MILLISECONDS, `gotoPreview: the preview page at ${url} never announced itself: look for a page error (an import that fails to load) in the browser console or the web server log.`);
|
|
80
|
+
}
|
|
81
|
+
/** Post a preview message to the page's own window, where the preview listens. */
|
|
82
|
+
async function post(page, message) {
|
|
83
|
+
await page.evaluate((posted) => window.postMessage(posted, '*'), message);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What a preview page lists under a window property. A message posted to a
|
|
87
|
+
* page that lists nothing would reach nothing, so that throws, saying whether
|
|
88
|
+
* the page is a preview page at all.
|
|
89
|
+
*/
|
|
90
|
+
async function listedEntries(page, flag, helper) {
|
|
91
|
+
const read = (name) => window[name];
|
|
92
|
+
const ids = await page.evaluate(read, flag);
|
|
93
|
+
if (!Array.isArray(ids)) {
|
|
94
|
+
throw new Error((await page.evaluate(read, PREVIEW_READY_FLAG)) === true
|
|
95
|
+
? `${helper}: this is a preview page that mounts no story (its html or view page, or a preview older than these helpers).`
|
|
96
|
+
: `${helper} needs a loaded preview page, and this page is not one: load it with gotoPreview first.`);
|
|
97
|
+
}
|
|
98
|
+
return ids;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The ids of the stories a preview page can mount.
|
|
102
|
+
*
|
|
103
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
104
|
+
* @returns The story ids.
|
|
105
|
+
* @throws When the page is not a preview page that mounts stories.
|
|
106
|
+
*/
|
|
107
|
+
export function readStoryIds(page) {
|
|
108
|
+
return listedEntries(page, PREVIEW_STORIES_FLAG, 'readStoryIds');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* What the story mounted on a preview page threw as it rendered, as the pane
|
|
112
|
+
* reports it: only a throw that reaches the pane counts, so a story with its
|
|
113
|
+
* own error boundary, or the word "error" in its content, reads as rendering.
|
|
114
|
+
*
|
|
115
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
116
|
+
* @returns The error's message, which may be empty, or `undefined` when the
|
|
117
|
+
* story renders.
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* await mountStory(page, 'components-button');
|
|
121
|
+
* await settle(page);
|
|
122
|
+
* expect(await readRenderError(page)).toBeUndefined();
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export async function readRenderError(page) {
|
|
126
|
+
const message = await page.evaluate((flag) => window[flag], PREVIEW_RENDER_ERROR_FLAG);
|
|
127
|
+
return typeof message === 'string' ? message : undefined;
|
|
128
|
+
}
|
|
129
|
+
/** Throw unless the page can mount the story: a page shows nothing for an id
|
|
130
|
+
* it does not know, and every check on the empty mount would pass. */
|
|
131
|
+
async function requireStory(page, id, helper) {
|
|
132
|
+
const known = await listedEntries(page, PREVIEW_STORIES_FLAG, helper);
|
|
133
|
+
if (!known.includes(id)) {
|
|
134
|
+
throw new Error(`${helper}: this preview has no story with the id '${id}'. Its stories: ${known.join(', ') || 'none'}.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Mount a story by id with the given root props, replacing what the page
|
|
139
|
+
* shows. Setting the story already shown with other props keeps its component
|
|
140
|
+
* instance on some targets; use {@link mountStory} for a guaranteed fresh
|
|
141
|
+
* mount.
|
|
142
|
+
*
|
|
143
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
144
|
+
* @param id - The story's id.
|
|
145
|
+
* @param props - The story's root props.
|
|
146
|
+
* @throws When the page has no story with the id; the message lists those it
|
|
147
|
+
* has.
|
|
148
|
+
*/
|
|
149
|
+
export async function setStory(page, id, props = {}) {
|
|
150
|
+
await requireStory(page, id, 'setStory');
|
|
151
|
+
await post(page, setStoryMessage(id, props));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Mount a story from nothing: blur the focused element, unmount the current
|
|
155
|
+
* story and wait for the mount point to empty, then set the story. The new
|
|
156
|
+
* story inherits neither focus nor component state from the one before, which
|
|
157
|
+
* a sweep over a component's states needs (a state read at mount, such as an
|
|
158
|
+
* initially open panel, only applies to a fresh instance).
|
|
159
|
+
*
|
|
160
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
161
|
+
* @param id - The story's id.
|
|
162
|
+
* @param props - The story's root props.
|
|
163
|
+
* @throws When the page has no story with the id, the message listing those
|
|
164
|
+
* it has, or when the mount is not empty within 15 seconds of the unmount.
|
|
165
|
+
*/
|
|
166
|
+
export async function mountStory(page, id, props = {}) {
|
|
167
|
+
await requireStory(page, id, 'mountStory');
|
|
168
|
+
await page.evaluate(() => document.activeElement?.blur?.(), undefined);
|
|
169
|
+
// No story is registered under the empty id, so every target unmounts.
|
|
170
|
+
await post(page, setStoryMessage('', {}));
|
|
171
|
+
await withinDeadline(page.waitForFunction((mountId) => document.getElementById(mountId)?.childElementCount === 0, PREVIEW_MOUNT_ELEMENT_ID), STORY_TIMEOUT_MILLISECONDS, `mountStory: the page never cleared the mount ahead of the story '${id}': the story before it may have thrown as it unmounted; look for a page error in the browser console.`);
|
|
172
|
+
// The page's last report is cleared here, not awaited from the page: the
|
|
173
|
+
// pane reports its own unmount, but after its paint in React, which a mount
|
|
174
|
+
// already empty may be read before, and a story set twice in a row would
|
|
175
|
+
// then pass {@link waitForStory} on its first mount's report.
|
|
176
|
+
await page.evaluate((flag) => {
|
|
177
|
+
window[flag] = '';
|
|
178
|
+
}, PREVIEW_MOUNTED_STORY_FLAG);
|
|
179
|
+
await post(page, setStoryMessage(id, props));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Wait until a story set with {@link mountStory} has finished mounting,
|
|
183
|
+
* whatever it rendered: a story can legitimately render nothing, which an
|
|
184
|
+
* element to wait for would never show, and a story that throws counts as
|
|
185
|
+
* mounted. It follows `mountStory`, which clears the page's last report;
|
|
186
|
+
* after {@link setStory} that report can be the same story's from before.
|
|
187
|
+
*
|
|
188
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
189
|
+
* @param id - The story's id.
|
|
190
|
+
* @throws When the story does not finish mounting within 15 seconds.
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* await mountStory(page, 'components-banner', { visible: false });
|
|
194
|
+
* await waitForStory(page, 'components-banner');
|
|
195
|
+
* await expect(page.locator('#app > *')).toHaveCount(0);
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export async function waitForStory(page, id) {
|
|
199
|
+
await withinDeadline(page.waitForFunction((argument) => window[argument.flag] ===
|
|
200
|
+
argument.id, { id, flag: PREVIEW_MOUNTED_STORY_FLAG }), STORY_TIMEOUT_MILLISECONDS, `waitForStory: the story '${id}' never finished mounting: look for a page error in the browser console, or a page that has stopped responding.`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Replace the mounted story's root props, keeping its component instance (and
|
|
204
|
+
* any state machine driving it) alive.
|
|
205
|
+
*
|
|
206
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
207
|
+
* @param props - The story's new root props.
|
|
208
|
+
*/
|
|
209
|
+
export async function setProps(page, props) {
|
|
210
|
+
await post(page, propsMessage(props));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Apply preview option values (a color mode, a surface, a frame around the
|
|
214
|
+
* story) to the page, as the preview's controls do.
|
|
215
|
+
*
|
|
216
|
+
* @param page - A page {@link gotoPreview} loaded.
|
|
217
|
+
* @param options - Option values keyed by option id.
|
|
218
|
+
* @throws When an id is not one of the kit's options, or a value is not one
|
|
219
|
+
* the option takes, either of which the page would ignore; the message lists
|
|
220
|
+
* what it defines.
|
|
221
|
+
*/
|
|
222
|
+
export async function setOptions(page, options) {
|
|
223
|
+
const listed = await listedEntries(page, PREVIEW_OPTIONS_FLAG, 'setOptions');
|
|
224
|
+
const known = listed.map((option) => option.id);
|
|
225
|
+
const unknown = Object.keys(options).filter((id) => !known.includes(id));
|
|
226
|
+
if (unknown.length > 0) {
|
|
227
|
+
throw new Error(`setOptions: this preview defines no option ${unknown.map((id) => `'${id}'`).join(', ')}. Its options: ${known.join(', ') || 'none'}.`);
|
|
228
|
+
}
|
|
229
|
+
for (const [id, value] of Object.entries(options)) {
|
|
230
|
+
const { values } = listed.find((option) => option.id === id);
|
|
231
|
+
if (values ? !values.includes(value) : typeof value !== 'boolean') {
|
|
232
|
+
throw new Error(values
|
|
233
|
+
? `setOptions: option '${id}' has no value ${JSON.stringify(value)}. Its values: ${values.join(', ')}.`
|
|
234
|
+
: `setOptions: option '${id}' takes true or false, not ${JSON.stringify(value)}.`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
await post(page, globalsMessage(options));
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Wait until the frame after the next paint. A component can show its final
|
|
241
|
+
* markup before the effects that attach its event handling have run; call this
|
|
242
|
+
* after asserting a change has rendered and before sending input, such as a
|
|
243
|
+
* key press, that would otherwise be dropped.
|
|
244
|
+
*
|
|
245
|
+
* @param page - The browser page.
|
|
246
|
+
*/
|
|
247
|
+
export async function settle(page) {
|
|
248
|
+
await page.evaluate(() => new Promise((resolve) => {
|
|
249
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
250
|
+
}), undefined);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Load a target's preview page and mount one story in it.
|
|
254
|
+
*
|
|
255
|
+
* @param page - The browser page.
|
|
256
|
+
* @param options - The target, the story and its props, and option values.
|
|
257
|
+
* @returns The open story.
|
|
258
|
+
* @throws When the page has no story with the id, or an option id is not one
|
|
259
|
+
* of the kit's; the message lists those it has.
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* const story = await openStory(page, { target: 'vue', id: 'components-button', props: { variant: 'solid' } });
|
|
263
|
+
* await story.setProps({ variant: 'ghost' });
|
|
264
|
+
* await story.setOptions({ mode: 'dark' });
|
|
265
|
+
* await expect(story.page.getByRole('button')).toHaveClass(/ghost/);
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export async function openStory(page, options) {
|
|
269
|
+
const { target, id, props = {}, options: optionValues, baseUrl } = options;
|
|
270
|
+
await gotoPreview(page, target, { baseUrl });
|
|
271
|
+
if (optionValues)
|
|
272
|
+
await setOptions(page, optionValues);
|
|
273
|
+
await requireStory(page, id, 'openStory');
|
|
274
|
+
await post(page, setStoryMessage(id, props));
|
|
275
|
+
return {
|
|
276
|
+
page,
|
|
277
|
+
target,
|
|
278
|
+
setProps: (next) => setProps(page, next),
|
|
279
|
+
setOptions: (next) => setOptions(page, next),
|
|
280
|
+
setStory: (nextId, nextProps) => setStory(page, nextId, nextProps),
|
|
281
|
+
mountStory: (nextId, nextProps) => mountStory(page, nextId, nextProps),
|
|
282
|
+
settle: () => settle(page),
|
|
283
|
+
};
|
|
284
|
+
}
|
package/dist/views.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { OptionValue } from './options.js';
|
|
2
|
+
export { reactView, type ReactViewRender } from './react-view.js';
|
|
3
|
+
/** One entry of the component index a view receives: a browseable story the
|
|
4
|
+
* view can cross-reference or select. */
|
|
5
|
+
export interface ViewContextComponent {
|
|
6
|
+
/** The component's selection id - pass to `selectComponent` to open its story. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The full title (source label prefix included). */
|
|
9
|
+
title: string;
|
|
10
|
+
/** The preview source (navigation section) the component belongs to. */
|
|
11
|
+
source: string;
|
|
12
|
+
}
|
|
13
|
+
/** A source as a view sees it - the kit's own source plus any other package whose components it shows. */
|
|
14
|
+
export interface ViewContextSource {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
order: number;
|
|
18
|
+
}
|
|
19
|
+
/** What the preview passes a view. `options` is live (a new context arrives
|
|
20
|
+
* on every change, see `ViewContextUpdates`); the catalog fields are a
|
|
21
|
+
* read-only snapshot of the kit's catalog, so a view can cross-link into a
|
|
22
|
+
* component story. A view reads
|
|
23
|
+
* what it needs (a foundations view reads `options.theme` to pick its palette;
|
|
24
|
+
* a gallery resolves a component from `components` and deep-links via
|
|
25
|
+
* `selectComponent`). */
|
|
26
|
+
export interface ViewContext {
|
|
27
|
+
options: Record<string, OptionValue>;
|
|
28
|
+
/** The kit being previewed. Empty until the host handshake delivers the catalog. */
|
|
29
|
+
kitName: string;
|
|
30
|
+
/** The sources, ordered. Empty until the handshake delivers the catalog. */
|
|
31
|
+
sources: ViewContextSource[];
|
|
32
|
+
/** The browseable component stories (components that are not standalone and unlisted
|
|
33
|
+
* stories excluded; an unlisted story's id still selects it). Empty until
|
|
34
|
+
* the handshake delivers the catalog. */
|
|
35
|
+
components: ViewContextComponent[];
|
|
36
|
+
/**
|
|
37
|
+
* Ask the preview to select a component story, as choosing it in the
|
|
38
|
+
* preview's navigation does. With `props`, the story opens with those root props merged
|
|
39
|
+
* over the component's defaults (a gallery opens the clicked entry's variant);
|
|
40
|
+
* without, it opens on its baseline scenario. Unknown ids are ignored.
|
|
41
|
+
*/
|
|
42
|
+
selectComponent: (id: string, props?: Record<string, unknown>) => void;
|
|
43
|
+
}
|
|
44
|
+
/** Fields common to every navigation entry a kit registers. */
|
|
45
|
+
interface PreviewEntryBase {
|
|
46
|
+
/** Reserved selection id - also the view iframe's `?view=` parameter. */
|
|
47
|
+
id: string;
|
|
48
|
+
/** The entry's name in the navigation. */
|
|
49
|
+
label: string;
|
|
50
|
+
/** The source whose entries list this one (a source id from
|
|
51
|
+
* `preview.sources`); defaults to the kit's own (first) source. */
|
|
52
|
+
source?: string;
|
|
53
|
+
/** Position among the source's entries - lower first. Views and the
|
|
54
|
+
* auto-generated component tree interleave by this. */
|
|
55
|
+
order?: number;
|
|
56
|
+
}
|
|
57
|
+
/** The framework a view mounts with. The view iframe loads the kit's setup
|
|
58
|
+
* module for that framework (`preview.setupModules`) before it
|
|
59
|
+
* mounts the view; `'dom'` loads none. */
|
|
60
|
+
export type ViewFramework = 'react' | 'vue' | 'svelte' | 'dom';
|
|
61
|
+
/** How a mounted view hears of a changed context: the options as the
|
|
62
|
+
* preview applies them, and the catalog once the handshake delivers it. */
|
|
63
|
+
export interface ViewContextUpdates {
|
|
64
|
+
/**
|
|
65
|
+
* Call `listener` with each new context.
|
|
66
|
+
*
|
|
67
|
+
* @returns A function that stops the calls.
|
|
68
|
+
*/
|
|
69
|
+
subscribe(listener: (context: ViewContext) => void): () => void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Mount a view into `element` with the context as it stands. The view stays
|
|
73
|
+
* mounted for the life of the view iframe and follows later changes through
|
|
74
|
+
* `updates`; a view that ignores them shows the context it mounted with.
|
|
75
|
+
*
|
|
76
|
+
* @param element - The element the view renders into.
|
|
77
|
+
* @param context - The context as it stands when the view mounts.
|
|
78
|
+
* @param updates - Delivers each new context.
|
|
79
|
+
* @returns A function that unmounts the view, or nothing.
|
|
80
|
+
*/
|
|
81
|
+
export type ViewMount = (element: HTMLElement, context: ViewContext, updates: ViewContextUpdates) => (() => void) | void;
|
|
82
|
+
/** A consumer-authored panel: the preview lists it beside the component stories
|
|
83
|
+
* and mounts it in the generic view iframe (`?view=<id>`) under the live
|
|
84
|
+
* options. */
|
|
85
|
+
export interface RenderedView extends PreviewEntryBase {
|
|
86
|
+
/** Discriminant - omit (or `false`) for a rendered view. */
|
|
87
|
+
componentTree?: false;
|
|
88
|
+
/** The framework `mount` renders with. Default `'dom'`. */
|
|
89
|
+
framework?: ViewFramework;
|
|
90
|
+
/** Mount the view inside the view iframe. */
|
|
91
|
+
mount: ViewMount;
|
|
92
|
+
}
|
|
93
|
+
/** The "magic" entry standing in for the source's auto-generated component tree (its
|
|
94
|
+
* stories) at a chosen position. It mounts no iframe, so it carries no `mount`. */
|
|
95
|
+
export interface ComponentTreeEntry extends PreviewEntryBase {
|
|
96
|
+
componentTree: true;
|
|
97
|
+
mount?: never;
|
|
98
|
+
}
|
|
99
|
+
/** A navigation entry a kit's `viewsModule` registers: either a rendered view or the
|
|
100
|
+
* component-tree marker. The two interleave by `order`. */
|
|
101
|
+
export type PreviewView = RenderedView | ComponentTreeEntry;
|
|
102
|
+
/** What the kit's `viewsModule` must export. */
|
|
103
|
+
export interface PreviewViewsModule {
|
|
104
|
+
views: PreviewView[];
|
|
105
|
+
viewById: (id: string) => PreviewView | undefined;
|
|
106
|
+
}
|
|
107
|
+
/** The language of a code block's source, as a highlighter names it: a
|
|
108
|
+
* scripted target's file (`tsx`, `vue`, `svelte`) or the html target's
|
|
109
|
+
* markup. */
|
|
110
|
+
export type CodeBlockLanguage = 'tsx' | 'vue' | 'svelte' | 'html';
|
|
111
|
+
/** What a code block shows: one target's source of the story, or, while it
|
|
112
|
+
* has none, nothing (it is being printed) or why there is none. */
|
|
113
|
+
export interface CodeBlockContent {
|
|
114
|
+
/** The target the source is for: a scripted target, or `html`. */
|
|
115
|
+
target: 'react' | 'vue' | 'svelte' | 'html';
|
|
116
|
+
language: CodeBlockLanguage;
|
|
117
|
+
/** The source, as a consumer writes it. */
|
|
118
|
+
source?: string;
|
|
119
|
+
/** Why the target has no source, when it has none to be had. */
|
|
120
|
+
reason?: string;
|
|
121
|
+
/** Whether the shell wraps its code panes' long lines: the block wraps
|
|
122
|
+
* them when true and scrolls sideways when false. A shell with no such
|
|
123
|
+
* choice sends false. */
|
|
124
|
+
wrap: boolean;
|
|
125
|
+
}
|
|
126
|
+
/** How a mounted code block hears of a new source: each time the story, its
|
|
127
|
+
* props or its content change, or the shell's wrapping does. */
|
|
128
|
+
export interface CodeBlockUpdates {
|
|
129
|
+
/**
|
|
130
|
+
* Call `listener` with each new content.
|
|
131
|
+
*
|
|
132
|
+
* @returns A function that stops the calls.
|
|
133
|
+
*/
|
|
134
|
+
subscribe(listener: (content: CodeBlockContent) => void): () => void;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Mount a kit's code block into `element`, showing `content`, the default
|
|
138
|
+
* export of the module the kit names as its `codeBlock`. The block stays
|
|
139
|
+
* mounted for the life of its iframe, one per code pane, and follows later
|
|
140
|
+
* sources through `updates`. It wraps long lines while `content.wrap` is
|
|
141
|
+
* true and scrolls sideways while it is false, as the shell's own panes do.
|
|
142
|
+
* The iframe's page is the block's alone: its root carries
|
|
143
|
+
* `data-preview-frame="code"`, so the kit's stylesheet may style the frame's
|
|
144
|
+
* `html` and `body` by it, say in the block's background so an overscroll
|
|
145
|
+
* shows no other color.
|
|
146
|
+
*
|
|
147
|
+
* @param element - The element the block renders into.
|
|
148
|
+
* @param content - The source as it stands when the block mounts.
|
|
149
|
+
* @param updates - Delivers each new source.
|
|
150
|
+
* @returns A function that unmounts the block, or nothing.
|
|
151
|
+
*/
|
|
152
|
+
export type CodeBlockMount = (element: HTMLElement, content: CodeBlockContent, updates: CodeBlockUpdates) => (() => void) | void;
|
package/dist/views.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The custom-view contract: what a kit's `viewsModule` exports. A view is a
|
|
2
|
+
// consumer-authored panel the preview lists beside the component stories and
|
|
3
|
+
// mounts in the dedicated view iframe (`?view=<id>`) under the live options
|
|
4
|
+
// context - tokens pages, galleries, composed demos. A view mounts itself into
|
|
5
|
+
// an element in any of the preview's frameworks, or in plain DOM; `reactView`
|
|
6
|
+
// adapts a React render function. Browser-safe.
|
|
7
|
+
export { reactView } from './react-view.js';
|