@uniflowed/story 0.0.0-alpha.18 → 0.14.1
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/collect.js +29 -21
- package/index.js +15 -22
- package/package.json +6 -6
- package/runner.js +8 -6
package/collect.js
CHANGED
|
@@ -2,41 +2,39 @@
|
|
|
2
2
|
//
|
|
3
3
|
// `@uniflowed/story/collect`: which files are stories, and what is in them.
|
|
4
4
|
//
|
|
5
|
-
// A story file is
|
|
5
|
+
// A story file is `$story.js`, beside the component it describes.
|
|
6
6
|
//
|
|
7
7
|
// src/components/Button.js
|
|
8
|
-
// src/components
|
|
8
|
+
// src/components/$story.js
|
|
9
9
|
//
|
|
10
10
|
// # The name is the repository's own grammar, not a second one
|
|
11
11
|
//
|
|
12
|
-
// uf already reserves
|
|
12
|
+
// uf already reserves `$<role>[.<variant>].js` for the files the framework
|
|
13
13
|
// gives meaning to, and `crates/uf_router/src/reserved.rs` is its single
|
|
14
14
|
// source of truth: `uf create` generates those names, the router looks for
|
|
15
15
|
// them, and `uf lint`'s `router/reserved-files` rejects the ones that do not
|
|
16
16
|
// fit. A story file is exactly such a file — a name uf assigns meaning to, in
|
|
17
|
-
// a project's own source tree — so it is spelled
|
|
17
|
+
// a project's own source tree — so it is spelled `$story.js` and not
|
|
18
18
|
// `*.stories.js`.
|
|
19
19
|
//
|
|
20
20
|
// The variants are the same vocabulary for the same reason, and the rule
|
|
21
21
|
// about them is the router's too: only the default variant is the thing the
|
|
22
|
-
// runner renders.
|
|
23
|
-
// build, the way
|
|
22
|
+
// runner renders. `$story.native.js` is a companion for a React Native
|
|
23
|
+
// build, the way `$page.native.js` is, and [`findStoryFiles`] leaves it
|
|
24
24
|
// alone until there is a renderer that could mount it.
|
|
25
25
|
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// **Readiness** rather than leaving it to be discovered by whoever writes the
|
|
32
|
-
// first story file.
|
|
26
|
+
// `story` is one of the roles `ReservedRole` in
|
|
27
|
+
// `crates/uf_router/src/reserved.rs` defines, so `uf lint`'s
|
|
28
|
+
// `router/reserved-files` accepts `$story.js` and its variants and still
|
|
29
|
+
// reports a misspelling such as `$stori.js`. The variant list below mirrors
|
|
30
|
+
// `ReservedVariant`, `client` included; a test pins it.
|
|
33
31
|
//
|
|
34
32
|
// # What makes a file a story file is the name, and what makes it valid is
|
|
35
33
|
// the export
|
|
36
34
|
//
|
|
37
35
|
// Discovery is by name alone: a walk that had to read every file to find out
|
|
38
36
|
// whether it declared stories would be a parse of the whole tree. Loading is
|
|
39
|
-
// where a file is judged, and a
|
|
37
|
+
// where a file is judged, and a `$story.js` that exports no story set is
|
|
40
38
|
// an error rather than an empty result — the name is a claim, and a file that
|
|
41
39
|
// does not honour it is a mistake somebody made, not a fact about the
|
|
42
40
|
// project.
|
|
@@ -72,17 +70,24 @@ import { isStorySet } from "./story.js";
|
|
|
72
70
|
export const STORY_ROLE: "story" = "story";
|
|
73
71
|
|
|
74
72
|
/** The name of a story file with no variant: the one the runner renders. */
|
|
75
|
-
export const STORY_FILE: "
|
|
73
|
+
export const STORY_FILE: "$story.js" = "$story.js";
|
|
76
74
|
|
|
77
75
|
/**
|
|
78
76
|
* Which build a story file applies to.
|
|
79
77
|
*
|
|
80
78
|
* The router's vocabulary, exactly. `"default"` has no segment in the name.
|
|
81
79
|
*/
|
|
82
|
-
export type StoryVariant = "default" | "native" | "ios" | "android" | "web" | "test";
|
|
80
|
+
export type StoryVariant = "default" | "native" | "ios" | "android" | "web" | "test" | "client";
|
|
83
81
|
|
|
84
|
-
/** The variants, in the order `uf_router::ReservedVariant`
|
|
85
|
-
const VARIANTS: $ReadOnlyArray<StoryVariant> = [
|
|
82
|
+
/** The variants, in the order `uf_router::ReservedVariant::all` lists them. */
|
|
83
|
+
const VARIANTS: $ReadOnlyArray<StoryVariant> = [
|
|
84
|
+
"native",
|
|
85
|
+
"ios",
|
|
86
|
+
"android",
|
|
87
|
+
"web",
|
|
88
|
+
"test",
|
|
89
|
+
"client",
|
|
90
|
+
];
|
|
86
91
|
|
|
87
92
|
/** Directory names the walk never descends into. */
|
|
88
93
|
const IGNORED: $ReadOnlyArray<string> = [
|
|
@@ -102,10 +107,10 @@ const IGNORED: $ReadOnlyArray<string> = [
|
|
|
102
107
|
* a caller with a path can normalise it two ways and get two answers.
|
|
103
108
|
*/
|
|
104
109
|
export function classifyStoryFile(fileName: string): StoryVariant | null {
|
|
105
|
-
if (!fileName.startsWith("
|
|
110
|
+
if (!fileName.startsWith("$") || !fileName.endsWith(".js")) {
|
|
106
111
|
return null;
|
|
107
112
|
}
|
|
108
|
-
const segments = fileName.slice("
|
|
113
|
+
const segments = fileName.slice("$".length, -".js".length).split(".");
|
|
109
114
|
if (segments[0] !== STORY_ROLE) {
|
|
110
115
|
return null;
|
|
111
116
|
}
|
|
@@ -113,7 +118,7 @@ export function classifyStoryFile(fileName: string): StoryVariant | null {
|
|
|
113
118
|
return "default";
|
|
114
119
|
}
|
|
115
120
|
if (segments.length > 2) {
|
|
116
|
-
//
|
|
121
|
+
// `$story.native.test.js`: one variant, not a stack of them.
|
|
117
122
|
return null;
|
|
118
123
|
}
|
|
119
124
|
const variant = VARIANTS.find((each) => each === segments[1]);
|
|
@@ -202,6 +207,9 @@ export async function findStoryFiles(root: string, options?: FindOptions): Promi
|
|
|
202
207
|
*/
|
|
203
208
|
export async function loadStoryFile(file: string): Promise<Array<StorySet>> {
|
|
204
209
|
const absolute = path.resolve(file);
|
|
210
|
+
// The user's story file, known only at run time; Flow types only a literal
|
|
211
|
+
// specifier, and `isStorySet` checks every export before it is kept.
|
|
212
|
+
// $FlowFixMe[unsupported-syntax]
|
|
205
213
|
const module = await import(pathToFileURL(absolute).href);
|
|
206
214
|
const sets: Array<StorySet> = [];
|
|
207
215
|
for (const name of Object.keys(module)) {
|
package/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// they are declared as data, the same declaration serves the assertion and
|
|
18
18
|
// the picture.
|
|
19
19
|
//
|
|
20
|
-
// // src/components
|
|
20
|
+
// // src/components/$story.js
|
|
21
21
|
// export const stories = defineStories({
|
|
22
22
|
// title: "Button",
|
|
23
23
|
// component: Button,
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
// - `story.js` — **declaring**: `defineStories`, what a story inherits from
|
|
64
64
|
// its set, and where the `Props` type parameter is checked and why it is
|
|
65
65
|
// then erased. Pure data; declaring a story runs nothing.
|
|
66
|
-
// - `collect.js` — **finding**:
|
|
66
|
+
// - `collect.js` — **finding**: `$story.js`, the repository's own reserved
|
|
67
67
|
// name grammar, the walk, and what makes a story file valid.
|
|
68
68
|
// - `render.js` — **rendering one**: the mock lifetime, the decorators, the
|
|
69
69
|
// mount, and the markup. Everything here is about *time*.
|
|
@@ -84,7 +84,8 @@
|
|
|
84
84
|
// props on the set and a partial override per story, with per-story and
|
|
85
85
|
// per-set decorators, mocks and play functions; names defaulting to the
|
|
86
86
|
// declaration key; stable `title--name` ids. Collecting them: the
|
|
87
|
-
//
|
|
87
|
+
// `$story.js` reserved name — a role in `crates/uf_router/src/reserved.rs`,
|
|
88
|
+
// so `uf lint` accepts it — with the router's variant vocabulary, a
|
|
88
89
|
// bounded walk that skips `node_modules` and symlinks, loading every story
|
|
89
90
|
// set a file exports, and rejecting two stories that share an id. Rendering
|
|
90
91
|
// one into `@uniflowed/react-testing`'s DOM, with the story's handlers
|
|
@@ -94,19 +95,10 @@
|
|
|
94
95
|
// path and the original error. `renderStoryToHtml` for something that is not
|
|
95
96
|
// a test. One `it` per story through `@uniflowed/story/runner`.
|
|
96
97
|
//
|
|
97
|
-
// **Experimental.**
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// linter. Until a `story` role is added there, **`uf lint` reports
|
|
102
|
-
// `router/reserved-files` on every `_uf.story.js`**: the name is right and
|
|
103
|
-
// the linter has not been told. The alternative was to invent a second
|
|
104
|
-
// convention (`*.stories.js`) that no uf tool knows about, which is worse.
|
|
105
|
-
//
|
|
106
|
-
// `describeStories` is experimental for a different reason, and it is a
|
|
107
|
-
// property of `uf test` rather than of this package: discovery scans source
|
|
108
|
-
// text for `it(` with a string-literal name, so a file whose only content is
|
|
109
|
-
// a `describeStories` call is skipped — silently, reporting zero files and
|
|
98
|
+
// **Experimental.** `describeStories`, and for a reason that is a property of
|
|
99
|
+
// `uf test` rather than of this package: discovery scans source text for
|
|
100
|
+
// `it(` or `test(` with a string-literal name, so a file whose only content
|
|
101
|
+
// is a `describeStories` call is skipped — silently, reporting zero files and
|
|
110
102
|
// exiting 0. `runner.js` documents the shape that works today and
|
|
111
103
|
// `storyTest` is the escape hatch.
|
|
112
104
|
//
|
|
@@ -114,11 +106,12 @@
|
|
|
114
106
|
// no browser canvas and no static story site: this package produces the index
|
|
115
107
|
// and the markup those would need, and nothing renders them for a person yet
|
|
116
108
|
// beyond a string. `withBrowser` is deliberately gone rather than carried
|
|
117
|
-
// over
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
109
|
+
// over. `@uniflowed/test/browser` now wraps `@uniflowed/test/browser`, which drives
|
|
110
|
+
// a real page under `uf test --browser`, but no story API is built on it yet:
|
|
111
|
+
// a story renders into `@uniflowed/react-testing`'s DOM, and a story test that
|
|
112
|
+
// wants a real browser opens one itself. Nothing here talks to
|
|
113
|
+
// `@uniflowed/vrt` either; `storyId` is exported so that a baseline can be
|
|
114
|
+
// filed under the same name when something does.
|
|
122
115
|
//
|
|
123
116
|
// Also absent, and each for a reason rather than by oversight: no Storybook
|
|
124
117
|
// CSF compatibility, no `argTypes`, controls or knobs (a control panel needs
|
|
@@ -128,7 +121,7 @@
|
|
|
128
121
|
// remote catalogues, and no story-level snapshot testing —
|
|
129
122
|
// `@uniflowed/test`'s snapshots work on the string `renderStoryToHtml`
|
|
130
123
|
// returns. Only the default variant of the reserved name is rendered:
|
|
131
|
-
//
|
|
124
|
+
// `$story.native.js` is recognised and skipped, because a React Native
|
|
132
125
|
// renderer does not exist here either. Nothing renders a story through RSC or
|
|
133
126
|
// server rendering; a story mounts on the client, which is what
|
|
134
127
|
// `@uniflowed/react-testing` provides.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniflowed/story",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Named, rendered states of a component that a person and a test can both reach, part of the Unified Toolchain for Flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/ubugeeei-prod/uf.git",
|
|
11
|
-
"directory": "
|
|
11
|
+
"directory": "npm/story"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
".": "./index.js",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"!*.test.js"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@uniflowed/mock": "0.
|
|
27
|
-
"@uniflowed/react": "0.
|
|
28
|
-
"@uniflowed/react-testing": "0.
|
|
29
|
-
"@uniflowed/test": "0.
|
|
26
|
+
"@uniflowed/mock": "0.14.1",
|
|
27
|
+
"@uniflowed/react": "0.14.1",
|
|
28
|
+
"@uniflowed/react-testing": "0.14.1",
|
|
29
|
+
"@uniflowed/test": "0.14.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": ">=19"
|
package/runner.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
// does not write the same mount-play-unmount block once per story.
|
|
8
8
|
//
|
|
9
9
|
// import { describe, it } from "@uniflowed/test";
|
|
10
|
+
// import { findStory } from "@uniflowed/story";
|
|
10
11
|
// import { describeStories, storyTest } from "@uniflowed/story/runner";
|
|
11
|
-
// import { stories } from "
|
|
12
|
+
// import { stories } from "./$story.js";
|
|
12
13
|
//
|
|
13
14
|
// it("renders every Button story", storyTest(findStory(stories, "Primary")));
|
|
14
15
|
// describe("Button", () => {
|
|
@@ -37,11 +38,12 @@
|
|
|
37
38
|
// # What `uf test` can and cannot see here
|
|
38
39
|
//
|
|
39
40
|
// `uf test` discovers test declarations by scanning source text for `it(` and
|
|
40
|
-
// `
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
41
|
+
// `test(` with a **string literal** first argument, and a file with no such
|
|
42
|
+
// declaration is not run at all. `describe(` is read too, for the names of the
|
|
43
|
+
// cases inside it, but a `describe` alone does not make a file runnable.
|
|
44
|
+
// [`describeStories`] registers its cases from the set, so their names are not
|
|
45
|
+
// literals — which means a file whose only content is a `describeStories` call
|
|
46
|
+
// is skipped silently, reported as zero files and zero tests, and exits 0.
|
|
45
47
|
//
|
|
46
48
|
// So a story test file must contain at least one literal declaration, and the
|
|
47
49
|
// shape above is the recommended one: a literal `describe` is not enough,
|