@harnessed-ts/storybook 0.6.0 → 0.7.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/CHANGELOG.md +20 -0
- package/README.md +14 -1
- package/dist/{chunk-J6DS34J4.js → chunk-CGZBV6BD.js} +3 -7
- package/dist/chunk-CGZBV6BD.js.map +1 -0
- package/dist/chunk-EBSULTWQ.js +7 -0
- package/dist/chunk-EBSULTWQ.js.map +1 -0
- package/dist/chunk-XYTGUTWH.js +134 -0
- package/dist/chunk-XYTGUTWH.js.map +1 -0
- package/dist/ember-node.cjs +19 -0
- package/dist/ember-node.cjs.map +1 -0
- package/dist/ember-node.d.ts +18 -0
- package/dist/ember-node.d.ts.map +1 -0
- package/dist/ember-node.js +17 -0
- package/dist/ember-node.js.map +1 -0
- package/dist/ember.cjs +239 -0
- package/dist/ember.cjs.map +1 -0
- package/dist/ember.d.ts +77 -0
- package/dist/ember.d.ts.map +1 -0
- package/dist/ember.js +99 -0
- package/dist/ember.js.map +1 -0
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -132
- package/dist/index.js.map +1 -1
- package/dist/page.cjs +4 -0
- package/dist/page.cjs.map +1 -1
- package/dist/page.d.ts +13 -0
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +6 -2
- package/dist/page.js.map +1 -1
- package/package.json +34 -6
- package/dist/chunk-J6DS34J4.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @harnessed-ts/storybook
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3f53eb6: Ember stories. `@harnessed-ts/storybook/ember` renders stories as Ember components under `@storybook/html-vite`, through Ember's public `renderComponent` (Ember 6.8 or later, Storybook 10): spread `emberRenderer` and its `harnessed()` into `.storybook/preview.ts`, and a story renders its meta's `component` with the story's args, or whatever its `render` returns, `<template>` included. Its `harnessed()` resolves every harness interaction once Ember has settled (rendering, run loop, timers and `@ember/test-waiters`), as `@harnessed-ts/ember` does; `settled()` is exported too. `parameters.ember.owner` sets the owner a story renders with. `composeStories`, `composeStory` and `setProjectAnnotations` from the same entry run Ember stories as portable stories, and `Meta`, `StoryObj` and `Preview` type them. `emberIndexer` from `@harnessed-ts/storybook/ember/node` indexes `.stories.gjs` and `.stories.gts` files, whose `<template>` tags Storybook's own indexer cannot parse; it needs `content-tag`. The conformance catalog runs against Ember stories all three ways React's do: as portable stories, through `StoryPage` under Playwright, and through the Vitest addon.
|
|
8
|
+
|
|
9
|
+
`storiesModule()` from `@harnessed-ts/storybook/page` stands in for a stories module a Node runner cannot import (one that imports Ember components), typed as that module: `StoryPage.goto` only reads its meta's `title` or `id`.
|
|
10
|
+
|
|
11
|
+
`harnessed()` takes an `afterAction` option, awaited after every interaction a harness makes, and `dom()` takes the same option for any framework that renders asynchronously.
|
|
12
|
+
|
|
13
|
+
The ESLint plugin treats `.stories.gjs` and `.stories.gts` files as stories files, so `no-raw-locator-in-test` flags raw canvas queries in Ember play functions.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [ec06bdf]
|
|
18
|
+
- Updated dependencies [3f53eb6]
|
|
19
|
+
- @harnessed-ts/core@0.7.0
|
|
20
|
+
- @harnessed-ts/dom@0.7.0
|
|
21
|
+
- @harnessed-ts/page@0.7.0
|
|
22
|
+
|
|
3
23
|
## 0.6.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -37,4 +37,17 @@ const story = await StoryPage.goto(pw(page), LoginStories, 'WrongPassword', { pl
|
|
|
37
37
|
await story.harness(LoginFormHarness).signInAs('ada@example.com', 'hunter2')
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
**Ember.** `@harnessed-ts/storybook/ember` renders stories as Ember components
|
|
41
|
+
under `@storybook/html-vite`, through Ember's `renderComponent`, and its
|
|
42
|
+
`harnessed()` settles Ember after every interaction. `emberIndexer` from
|
|
43
|
+
`/ember/node` indexes `.gts` stories.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// .storybook/preview.ts
|
|
47
|
+
import { emberRenderer, harnessed } from '@harnessed-ts/storybook/ember'
|
|
48
|
+
export default { ...emberRenderer, ...harnessed() } satisfies Preview
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Storybook 9 or later; 10 or later, with Ember 6.8 or later, for Ember. See
|
|
52
|
+
[Using with Storybook](https://github.com/joegaudet/harnessed#using-with-storybook)
|
|
53
|
+
and [Ember stories](https://github.com/joegaudet/harnessed#ember-stories).
|
|
@@ -46,10 +46,6 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
46
46
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
47
47
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID, __decorateElement, __decoratorStart, __runInitializers };
|
|
54
|
-
//# sourceMappingURL=chunk-J6DS34J4.js.map
|
|
55
|
-
//# sourceMappingURL=chunk-J6DS34J4.js.map
|
|
49
|
+
export { __decorateElement, __decoratorStart, __runInitializers };
|
|
50
|
+
//# sourceMappingURL=chunk-CGZBV6BD.js.map
|
|
51
|
+
//# sourceMappingURL=chunk-CGZBV6BD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-CGZBV6BD.js"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/ready.ts
|
|
2
|
+
var STORY_READY_TEST_ID = "harnessed-story-ready";
|
|
3
|
+
var PLAY_ERROR_ATTRIBUTE = "data-harnessed-play-error";
|
|
4
|
+
|
|
5
|
+
export { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID };
|
|
6
|
+
//# sourceMappingURL=chunk-EBSULTWQ.js.map
|
|
7
|
+
//# sourceMappingURL=chunk-EBSULTWQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ready.ts"],"names":[],"mappings":";AAMO,IAAM,mBAAA,GAAsB;AAG5B,IAAM,oBAAA,GAAuB","file":"chunk-EBSULTWQ.js","sourcesContent":["/**\n * The test id of the node `harnessed()` adds once a story has rendered and its\n * play function has finished. `StoryPage` waits for it: Storybook's own\n * \"rendered\" signal is a channel event, which a runner outside the iframe's\n * realm cannot listen for, while a node is something every driver can count.\n */\nexport const STORY_READY_TEST_ID = 'harnessed-story-ready'\n\n/** Set on that node when the play function threw, holding the error's message. */\nexport const PLAY_ERROR_ATTRIBUTE = 'data-harnessed-play-error'\n"]}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { STORY_READY_TEST_ID, PLAY_ERROR_ATTRIBUTE } from './chunk-EBSULTWQ.js';
|
|
2
|
+
import { ScopedHarness, harnessMatchers, getConfig, testIdPresenceSelector } from '@harnessed-ts/core';
|
|
3
|
+
import { dom } from '@harnessed-ts/dom';
|
|
4
|
+
import { PLAY_FUNCTION_THREW_EXCEPTION } from 'storybook/internal/core-events';
|
|
5
|
+
import { addons } from 'storybook/preview-api';
|
|
6
|
+
import { expect, userEvent } from 'storybook/test';
|
|
7
|
+
|
|
8
|
+
var SECRET = /pass|secret|token|credential/i;
|
|
9
|
+
var SECRET_WORD = /^(key|pin|otp|auth)$/i;
|
|
10
|
+
var WORD_BREAK = /[^a-zA-Z0-9]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/;
|
|
11
|
+
var REDACTED = "\u2022\u2022\u2022";
|
|
12
|
+
var MAX_ARG = 40;
|
|
13
|
+
var AsyncFunction = (async () => {
|
|
14
|
+
}).constructor;
|
|
15
|
+
var proxies = /* @__PURE__ */ new WeakMap();
|
|
16
|
+
function withSteps(harness, step) {
|
|
17
|
+
const cached = proxies.get(harness);
|
|
18
|
+
if (cached !== void 0) return cached;
|
|
19
|
+
const className = harness.constructor.name;
|
|
20
|
+
const wrap = (value) => {
|
|
21
|
+
if (value instanceof ScopedHarness) return withSteps(value, step);
|
|
22
|
+
if (Array.isArray(value) && value.some((item) => item instanceof ScopedHarness)) {
|
|
23
|
+
return value.map(wrap);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
};
|
|
27
|
+
const proxy = new Proxy(harness, {
|
|
28
|
+
get(target, prop) {
|
|
29
|
+
const value = Reflect.get(target, prop, target);
|
|
30
|
+
if (typeof value !== "function") return wrap(value);
|
|
31
|
+
if (typeof prop !== "string" || prop === "constructor" || prop.startsWith("_")) {
|
|
32
|
+
return value.bind(target);
|
|
33
|
+
}
|
|
34
|
+
const method = value;
|
|
35
|
+
if (method instanceof AsyncFunction) {
|
|
36
|
+
return async (...args) => {
|
|
37
|
+
let result;
|
|
38
|
+
await step(stepLabel(className, method, args, prop), async () => {
|
|
39
|
+
result = await method.apply(target, args);
|
|
40
|
+
});
|
|
41
|
+
return wrap(result);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return (...args) => wrap(method.apply(target, args));
|
|
45
|
+
},
|
|
46
|
+
// An accessor's setter writes a private slot only the real instance has.
|
|
47
|
+
set: (target, prop, value) => Reflect.set(target, prop, value, target)
|
|
48
|
+
});
|
|
49
|
+
proxies.set(harness, proxy);
|
|
50
|
+
return proxy;
|
|
51
|
+
}
|
|
52
|
+
function stepLabel(className, method, args, name = method.name) {
|
|
53
|
+
const params = parameterNames(method);
|
|
54
|
+
const shown = args.map(
|
|
55
|
+
(arg, index) => isSecret(params[index] ?? "") ? JSON.stringify(REDACTED) : formatArg(arg)
|
|
56
|
+
);
|
|
57
|
+
return `${className}.${name}(${shown.join(", ")})`;
|
|
58
|
+
}
|
|
59
|
+
function isSecret(name) {
|
|
60
|
+
return SECRET.test(name) || name.split(WORD_BREAK).some((word) => SECRET_WORD.test(word));
|
|
61
|
+
}
|
|
62
|
+
function formatArg(arg) {
|
|
63
|
+
if (typeof arg === "function") return "\u0192";
|
|
64
|
+
if (arg instanceof ScopedHarness) return arg.constructor.name;
|
|
65
|
+
if (typeof arg === "string") {
|
|
66
|
+
return JSON.stringify(arg.length > MAX_ARG ? `${arg.slice(0, MAX_ARG)}\u2026` : arg);
|
|
67
|
+
}
|
|
68
|
+
const json = JSON.stringify(arg, (key, value) => isSecret(key) ? REDACTED : value) ?? String(arg);
|
|
69
|
+
return json.length > MAX_ARG ? `${json.slice(0, MAX_ARG)}\u2026` : json;
|
|
70
|
+
}
|
|
71
|
+
function parameterNames(method) {
|
|
72
|
+
const list = /\(([^)]*)\)/.exec(Function.prototype.toString.call(method))?.[1] ?? "";
|
|
73
|
+
if (/[{[]/.test(list)) return [];
|
|
74
|
+
return list.split(",").map((param) => param.replace(/[=:].*$/s, "").trim());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/index.ts
|
|
78
|
+
var matchersRegistered = false;
|
|
79
|
+
var listening = false;
|
|
80
|
+
var playError;
|
|
81
|
+
function listenForPlayErrors() {
|
|
82
|
+
if (listening || !addons.hasChannel()) return;
|
|
83
|
+
addons.getChannel().on(PLAY_FUNCTION_THREW_EXCEPTION, (error) => {
|
|
84
|
+
playError = error?.message ?? "the play function threw";
|
|
85
|
+
});
|
|
86
|
+
listening = true;
|
|
87
|
+
}
|
|
88
|
+
function harnessed(options = {}) {
|
|
89
|
+
if (!matchersRegistered) {
|
|
90
|
+
expect.extend(harnessMatchers);
|
|
91
|
+
matchersRegistered = true;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
async beforeEach(context) {
|
|
95
|
+
readyMarker(context.canvasElement.ownerDocument)?.remove();
|
|
96
|
+
listenForPlayErrors();
|
|
97
|
+
playError = void 0;
|
|
98
|
+
const HarnessClass = context.parameters.harness;
|
|
99
|
+
if (HarnessClass === void 0) return;
|
|
100
|
+
const env = dom({
|
|
101
|
+
// Storybook's own instance, so each keystroke shows up in the panel. It
|
|
102
|
+
// is only set where a clipboard exists; fall back to a plain one.
|
|
103
|
+
user: hasClick(context.userEvent) ? context.userEvent : userEvent.setup(),
|
|
104
|
+
container: context.canvasElement,
|
|
105
|
+
...options.afterAction === void 0 ? {} : { afterAction: options.afterAction }
|
|
106
|
+
});
|
|
107
|
+
context.harness = withSteps(new HarnessClass(env), context.step);
|
|
108
|
+
},
|
|
109
|
+
async afterEach(context) {
|
|
110
|
+
const document = context.canvasElement.ownerDocument;
|
|
111
|
+
if (readyMarker(document) !== null) return;
|
|
112
|
+
const marker = document.createElement("div");
|
|
113
|
+
const attribute = getConfig().testIdAttribute;
|
|
114
|
+
if (attribute.endsWith("*"))
|
|
115
|
+
marker.setAttribute(attribute.slice(0, -1) + STORY_READY_TEST_ID, "");
|
|
116
|
+
else marker.setAttribute(attribute, STORY_READY_TEST_ID);
|
|
117
|
+
marker.hidden = true;
|
|
118
|
+
if (playError !== void 0) marker.setAttribute(PLAY_ERROR_ATTRIBUTE, playError);
|
|
119
|
+
document.body.append(marker);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function readyMarker(document) {
|
|
124
|
+
const attribute = getConfig().testIdAttribute;
|
|
125
|
+
const presence = testIdPresenceSelector(attribute, STORY_READY_TEST_ID);
|
|
126
|
+
return document.querySelector(presence ?? `[${attribute}="${STORY_READY_TEST_ID}"]`);
|
|
127
|
+
}
|
|
128
|
+
function hasClick(value) {
|
|
129
|
+
return typeof value?.click === "function";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { harnessed, stepLabel, withSteps };
|
|
133
|
+
//# sourceMappingURL=chunk-XYTGUTWH.js.map
|
|
134
|
+
//# sourceMappingURL=chunk-XYTGUTWH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/steps.ts","../src/index.ts"],"names":[],"mappings":"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","file":"chunk-XYTGUTWH.js","sourcesContent":["import { ScopedHarness } from '@harnessed-ts/core'\n\n/** Storybook's `context.step`, narrowed to what the proxy needs. */\nexport type StepRunner = (label: string, play: () => Promise<void>) => Promise<unknown> | unknown\n\nconst SECRET = /pass|secret|token|credential/i\n// Short names match whole words only, so `author`, `keyboard` and `typing` stay visible.\nconst SECRET_WORD = /^(key|pin|otp|auth)$/i\n// camelCase, PascalCase acronyms (`APIKey`) and snake/kebab-case word breaks.\nconst WORD_BREAK = /[^a-zA-Z0-9]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/\nconst REDACTED = '•••'\nconst MAX_ARG = 40\n\nconst AsyncFunction = (async () => {}).constructor\n\nconst proxies = new WeakMap<object, unknown>()\n\n/**\n * The harness, with every public async method run inside `step()` — so the\n * Interactions panel reads `LoginFormHarness.signInAs(\"ada@…\", \"•••\")` with the\n * raw clicks and keystrokes nested under it, rather than a flat list of events.\n *\n * Methods run against the real instance, not the proxy: a harness calling its\n * own methods does not log a step per call, and decorated `accessor` fields\n * (backed by private slots a proxy cannot reach) keep working. Sync methods stay\n * sync; a harness they return — `nth()`, a child — comes back wrapped too.\n *\n * shortcut: async is detected by `AsyncFunction`, so a plain method that returns\n * a promise, or one down-levelled to a generator, logs no step. Upgrade if a\n * harness or a build ever needs that.\n */\nexport function withSteps<T extends ScopedHarness>(harness: T, step: StepRunner): T {\n const cached = proxies.get(harness)\n if (cached !== undefined) return cached as T\n const className = harness.constructor.name\n\n const wrap = (value: unknown): unknown => {\n if (value instanceof ScopedHarness) return withSteps(value, step)\n if (Array.isArray(value) && value.some(item => item instanceof ScopedHarness)) {\n return value.map(wrap)\n }\n return value\n }\n\n const proxy = new Proxy(harness, {\n get(target, prop) {\n const value: unknown = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return wrap(value)\n if (typeof prop !== 'string' || prop === 'constructor' || prop.startsWith('_')) {\n return (value as (...args: unknown[]) => unknown).bind(target)\n }\n const method = value as (...args: unknown[]) => unknown\n if (method instanceof AsyncFunction) {\n return async (...args: unknown[]) => {\n let result: unknown\n await step(stepLabel(className, method, args, prop), async () => {\n result = await method.apply(target, args)\n })\n return wrap(result)\n }\n }\n return (...args: unknown[]) => wrap(method.apply(target, args))\n },\n // An accessor's setter writes a private slot only the real instance has.\n set: (target, prop, value) => Reflect.set(target, prop, value, target),\n })\n proxies.set(harness, proxy)\n return proxy\n}\n\n/**\n * `Harness.method(args…)`, each argument shortened, and any argument whose\n * parameter name — or object key — looks like a secret replaced with `•••`.\n *\n * Names are read from the source, so a minified `storybook build` shows mangled\n * class names and masks only object keys, not positional arguments.\n */\nexport function stepLabel(\n className: string,\n method: (...args: never[]) => unknown,\n args: readonly unknown[],\n name: string = method.name,\n): string {\n const params = parameterNames(method)\n const shown = args.map((arg, index) =>\n isSecret(params[index] ?? '') ? JSON.stringify(REDACTED) : formatArg(arg),\n )\n return `${className}.${name}(${shown.join(', ')})`\n}\n\nfunction isSecret(name: string): boolean {\n return SECRET.test(name) || name.split(WORD_BREAK).some(word => SECRET_WORD.test(word))\n}\n\nfunction formatArg(arg: unknown): string {\n if (typeof arg === 'function') return 'ƒ'\n if (arg instanceof ScopedHarness) return arg.constructor.name\n if (typeof arg === 'string') {\n return JSON.stringify(arg.length > MAX_ARG ? `${arg.slice(0, MAX_ARG)}…` : arg)\n }\n const json =\n JSON.stringify(arg, (key, value: unknown) => (isSecret(key) ? REDACTED : value)) ?? String(arg)\n return json.length > MAX_ARG ? `${json.slice(0, MAX_ARG)}…` : json\n}\n\n/** Plain parameter names from the source; none when they are destructured. */\nfunction parameterNames(method: (...args: never[]) => unknown): string[] {\n const list = /\\(([^)]*)\\)/.exec(Function.prototype.toString.call(method))?.[1] ?? ''\n if (/[{[]/.test(list)) return []\n return list.split(',').map(param => param.replace(/[=:].*$/s, '').trim())\n}\n","import { getConfig, harnessMatchers, testIdPresenceSelector } from '@harnessed-ts/core'\nimport type { Assertable, ScopedHarness, ScopedHarnessConstructor } from '@harnessed-ts/core'\nimport { dom } from '@harnessed-ts/dom'\nimport type { UserEvent } from '@testing-library/user-event'\nimport { PLAY_FUNCTION_THREW_EXCEPTION } from 'storybook/internal/core-events'\nimport { addons } from 'storybook/preview-api'\nimport { expect, userEvent } from 'storybook/test'\nimport { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID } from './ready'\nimport { withSteps } from './steps'\nimport type { StepRunner } from './steps'\n\nexport { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID } from './ready'\nexport { stepLabel, withSteps } from './steps'\nexport type { StepRunner } from './steps'\n\n/** What `harnessed()` reads from, and adds to, Storybook's story context. */\nexport interface HarnessedContext {\n canvasElement: HTMLElement\n parameters: { harness?: ScopedHarnessConstructor }\n userEvent?: unknown\n step: StepRunner\n harness?: ScopedHarness\n}\n\nexport interface HarnessedOptions {\n /**\n * Awaited after every interaction a harness makes, before the next step reads\n * the canvas. A renderer that updates the DOM asynchronously settles here;\n * `@harnessed-ts/storybook/ember` passes Ember's.\n */\n afterAction?: () => Promise<void>\n}\n\nlet matchersRegistered = false\nlet listening = false\n/** The message of the current story's play error, if it threw. */\nlet playError: string | undefined\n\n/**\n * Storybook catches a play function's error, reports it on its channel, and\n * then finishes the render (running `afterEach`) as if it had passed, so the\n * error has to be caught here to reach the ready mark.\n */\nfunction listenForPlayErrors(): void {\n if (listening || !addons.hasChannel()) return\n addons.getChannel().on(PLAY_FUNCTION_THREW_EXCEPTION, (error?: { message?: string }) => {\n playError = error?.message ?? 'the play function threw'\n })\n listening = true\n}\n\n/**\n * Preview annotations that put a harness in every play function. Spread them\n * into `.storybook/preview.ts`:\n *\n * ```ts\n * import { harnessed } from '@harnessed-ts/storybook'\n * export default { ...harnessed() } satisfies Preview\n * ```\n *\n * A story (or its meta) names its harness in `parameters.harness`, and `play`\n * receives it as `context.harness`, driving the canvas through Storybook's\n * instrumented `userEvent` with each method logged as a step. The harness\n * matchers are added to `storybook/test`'s `expect`.\n */\nexport function harnessed(options: HarnessedOptions = {}): {\n beforeEach: (context: HarnessedContext) => Promise<void>\n afterEach: (context: HarnessedContext) => Promise<void>\n} {\n if (!matchersRegistered) {\n expect.extend(harnessMatchers)\n matchersRegistered = true\n }\n return {\n async beforeEach(context: HarnessedContext) {\n readyMarker(context.canvasElement.ownerDocument)?.remove()\n listenForPlayErrors()\n playError = undefined\n const HarnessClass = context.parameters.harness\n if (HarnessClass === undefined) return\n const env = dom({\n // Storybook's own instance, so each keystroke shows up in the panel. It\n // is only set where a clipboard exists; fall back to a plain one.\n user: (hasClick(context.userEvent) ? context.userEvent : userEvent.setup()) as UserEvent,\n container: context.canvasElement,\n ...(options.afterAction === undefined ? {} : { afterAction: options.afterAction }),\n })\n context.harness = withSteps(new HarnessClass(env), context.step)\n },\n async afterEach(context: HarnessedContext) {\n const document = context.canvasElement.ownerDocument\n if (readyMarker(document) !== null) return\n const marker = document.createElement('div')\n const attribute = getConfig().testIdAttribute\n // Under a presence pattern the id is part of the name: `data-test-<id>`.\n if (attribute.endsWith('*'))\n marker.setAttribute(attribute.slice(0, -1) + STORY_READY_TEST_ID, '')\n else marker.setAttribute(attribute, STORY_READY_TEST_ID)\n marker.hidden = true\n if (playError !== undefined) marker.setAttribute(PLAY_ERROR_ATTRIBUTE, playError)\n document.body.append(marker)\n },\n }\n}\n\n/** Found the way `testId(STORY_READY_TEST_ID)` finds it, which is what `StoryPage` waits on. */\nfunction readyMarker(document: Document): Element | null {\n const attribute = getConfig().testIdAttribute\n const presence = testIdPresenceSelector(attribute, STORY_READY_TEST_ID)\n return document.querySelector(presence ?? `[${attribute}=\"${STORY_READY_TEST_ID}\"]`)\n}\n\nfunction hasClick(value: unknown): boolean {\n return typeof (value as { click?: unknown } | undefined)?.click === 'function'\n}\n\n/**\n * A story type whose `play` receives `harness` typed as `H`.\n *\n * ```ts\n * type Story = WithHarness<StoryObj<typeof meta>, LoginFormHarness>\n * ```\n */\nexport type WithHarness<S, H extends ScopedHarness> = Omit<S, 'play'> & {\n play?: S extends { play?: infer P }\n ? P extends (context: infer C) => infer R\n ? (context: C & { harness: H }) => R\n : never\n : never\n}\n\ndeclare module 'storybook/test' {\n // The type parameter must keep Storybook's own name. Gating on `T` makes calling\n // these on something that is not a target or a harness a type error.\n interface Assertion<T> {\n toBeSelected(): T extends Assertable ? Promise<void> : never\n toBeAbsent(): T extends Assertable ? Promise<void> : never\n toReadAs(expected: string | RegExp): T extends Assertable ? Promise<void> : never\n }\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
var contentTag = require('content-tag');
|
|
5
|
+
var csfTools = require('storybook/internal/csf-tools');
|
|
6
|
+
|
|
7
|
+
// src/ember-node.ts
|
|
8
|
+
var emberIndexer = {
|
|
9
|
+
test: /\.stories\.g[jt]s$/,
|
|
10
|
+
async createIndex(fileName, options) {
|
|
11
|
+
const source = await promises.readFile(fileName, "utf-8");
|
|
12
|
+
const { code } = new contentTag.Preprocessor().process(source, { filename: fileName });
|
|
13
|
+
return csfTools.loadCsf(code, { ...options, fileName }).parse().indexInputs;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
exports.emberIndexer = emberIndexer;
|
|
18
|
+
//# sourceMappingURL=ember-node.cjs.map
|
|
19
|
+
//# sourceMappingURL=ember-node.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ember-node.ts"],"names":["readFile","Preprocessor","loadCsf"],"mappings":";;;;;;;AAoBO,IAAM,YAAA,GAAwB;AAAA,EACnC,IAAA,EAAM,oBAAA;AAAA,EACN,MAAM,WAAA,CAAY,QAAA,EAAU,OAAA,EAAS;AACnC,IAAA,MAAM,MAAA,GAAS,MAAMA,iBAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAC/C,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,IAAIC,uBAAA,EAAa,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,CAAA;AAC1E,IAAA,OAAOC,gBAAA,CAAQ,MAAM,EAAE,GAAG,SAAS,QAAA,EAAU,CAAA,CAAE,KAAA,EAAM,CAAE,WAAA;AAAA,EACzD;AACF","file":"ember-node.cjs","sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { Preprocessor } from 'content-tag'\nimport { loadCsf } from 'storybook/internal/csf-tools'\nimport type { Indexer } from 'storybook/internal/types'\n\n/**\n * Indexes `.stories.gjs` and `.stories.gts` files, which Storybook's own CSF\n * indexer cannot parse: their `<template>` tags are not JavaScript. Each file is\n * read with the tags compiled away, as `content-tag` does for Embroider, and\n * indexed as ordinary CSF.\n *\n * ```ts\n * // .storybook/main.ts\n * import { emberIndexer } from '@harnessed-ts/storybook/ember/node'\n * export default {\n * stories: ['../stories/*.stories.@(ts|gts)'],\n * experimental_indexers: existing => [emberIndexer, ...(existing ?? [])],\n * }\n * ```\n */\nexport const emberIndexer: Indexer = {\n test: /\\.stories\\.g[jt]s$/,\n async createIndex(fileName, options) {\n const source = await readFile(fileName, 'utf-8')\n const { code } = new Preprocessor().process(source, { filename: fileName })\n return loadCsf(code, { ...options, fileName }).parse().indexInputs\n },\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Indexer } from 'storybook/internal/types';
|
|
2
|
+
/**
|
|
3
|
+
* Indexes `.stories.gjs` and `.stories.gts` files, which Storybook's own CSF
|
|
4
|
+
* indexer cannot parse: their `<template>` tags are not JavaScript. Each file is
|
|
5
|
+
* read with the tags compiled away, as `content-tag` does for Embroider, and
|
|
6
|
+
* indexed as ordinary CSF.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // .storybook/main.ts
|
|
10
|
+
* import { emberIndexer } from '@harnessed-ts/storybook/ember/node'
|
|
11
|
+
* export default {
|
|
12
|
+
* stories: ['../stories/*.stories.@(ts|gts)'],
|
|
13
|
+
* experimental_indexers: existing => [emberIndexer, ...(existing ?? [])],
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const emberIndexer: Indexer;
|
|
18
|
+
//# sourceMappingURL=ember-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ember-node.d.ts","sourceRoot":"","sources":["../src/ember-node.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAEvD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,EAAE,OAO1B,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import './chunk-CGZBV6BD.js';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { Preprocessor } from 'content-tag';
|
|
4
|
+
import { loadCsf } from 'storybook/internal/csf-tools';
|
|
5
|
+
|
|
6
|
+
var emberIndexer = {
|
|
7
|
+
test: /\.stories\.g[jt]s$/,
|
|
8
|
+
async createIndex(fileName, options) {
|
|
9
|
+
const source = await readFile(fileName, "utf-8");
|
|
10
|
+
const { code } = new Preprocessor().process(source, { filename: fileName });
|
|
11
|
+
return loadCsf(code, { ...options, fileName }).parse().indexInputs;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export { emberIndexer };
|
|
16
|
+
//# sourceMappingURL=ember-node.js.map
|
|
17
|
+
//# sourceMappingURL=ember-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ember-node.ts"],"names":[],"mappings":";;;;;AAoBO,IAAM,YAAA,GAAwB;AAAA,EACnC,IAAA,EAAM,oBAAA;AAAA,EACN,MAAM,WAAA,CAAY,QAAA,EAAU,OAAA,EAAS;AACnC,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAC/C,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,IAAI,YAAA,EAAa,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,CAAA;AAC1E,IAAA,OAAO,OAAA,CAAQ,MAAM,EAAE,GAAG,SAAS,QAAA,EAAU,CAAA,CAAE,KAAA,EAAM,CAAE,WAAA;AAAA,EACzD;AACF","file":"ember-node.js","sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { Preprocessor } from 'content-tag'\nimport { loadCsf } from 'storybook/internal/csf-tools'\nimport type { Indexer } from 'storybook/internal/types'\n\n/**\n * Indexes `.stories.gjs` and `.stories.gts` files, which Storybook's own CSF\n * indexer cannot parse: their `<template>` tags are not JavaScript. Each file is\n * read with the tags compiled away, as `content-tag` does for Embroider, and\n * indexed as ordinary CSF.\n *\n * ```ts\n * // .storybook/main.ts\n * import { emberIndexer } from '@harnessed-ts/storybook/ember/node'\n * export default {\n * stories: ['../stories/*.stories.@(ts|gts)'],\n * experimental_indexers: existing => [emberIndexer, ...(existing ?? [])],\n * }\n * ```\n */\nexport const emberIndexer: Indexer = {\n test: /\\.stories\\.g[jt]s$/,\n async createIndex(fileName, options) {\n const source = await readFile(fileName, 'utf-8')\n const { code } = new Preprocessor().process(source, { filename: fileName })\n return loadCsf(code, { ...options, fileName }).parse().indexInputs\n },\n}\n"]}
|
package/dist/ember.cjs
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var renderer = require('@ember/renderer');
|
|
4
|
+
var runloop = require('@ember/runloop');
|
|
5
|
+
var testWaiters = require('@ember/test-waiters');
|
|
6
|
+
var core = require('@harnessed-ts/core');
|
|
7
|
+
var previewApi = require('storybook/preview-api');
|
|
8
|
+
var dom = require('@harnessed-ts/dom');
|
|
9
|
+
var coreEvents = require('storybook/internal/core-events');
|
|
10
|
+
var test = require('storybook/test');
|
|
11
|
+
|
|
12
|
+
// src/ember.ts
|
|
13
|
+
|
|
14
|
+
// src/ready.ts
|
|
15
|
+
var STORY_READY_TEST_ID = "harnessed-story-ready";
|
|
16
|
+
var PLAY_ERROR_ATTRIBUTE = "data-harnessed-play-error";
|
|
17
|
+
var SECRET = /pass|secret|token|credential/i;
|
|
18
|
+
var SECRET_WORD = /^(key|pin|otp|auth)$/i;
|
|
19
|
+
var WORD_BREAK = /[^a-zA-Z0-9]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/;
|
|
20
|
+
var REDACTED = "\u2022\u2022\u2022";
|
|
21
|
+
var MAX_ARG = 40;
|
|
22
|
+
var AsyncFunction = (async () => {
|
|
23
|
+
}).constructor;
|
|
24
|
+
var proxies = /* @__PURE__ */ new WeakMap();
|
|
25
|
+
function withSteps(harness, step) {
|
|
26
|
+
const cached = proxies.get(harness);
|
|
27
|
+
if (cached !== void 0) return cached;
|
|
28
|
+
const className = harness.constructor.name;
|
|
29
|
+
const wrap = (value) => {
|
|
30
|
+
if (value instanceof core.ScopedHarness) return withSteps(value, step);
|
|
31
|
+
if (Array.isArray(value) && value.some((item) => item instanceof core.ScopedHarness)) {
|
|
32
|
+
return value.map(wrap);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
const proxy = new Proxy(harness, {
|
|
37
|
+
get(target, prop) {
|
|
38
|
+
const value = Reflect.get(target, prop, target);
|
|
39
|
+
if (typeof value !== "function") return wrap(value);
|
|
40
|
+
if (typeof prop !== "string" || prop === "constructor" || prop.startsWith("_")) {
|
|
41
|
+
return value.bind(target);
|
|
42
|
+
}
|
|
43
|
+
const method = value;
|
|
44
|
+
if (method instanceof AsyncFunction) {
|
|
45
|
+
return async (...args) => {
|
|
46
|
+
let result;
|
|
47
|
+
await step(stepLabel(className, method, args, prop), async () => {
|
|
48
|
+
result = await method.apply(target, args);
|
|
49
|
+
});
|
|
50
|
+
return wrap(result);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return (...args) => wrap(method.apply(target, args));
|
|
54
|
+
},
|
|
55
|
+
// An accessor's setter writes a private slot only the real instance has.
|
|
56
|
+
set: (target, prop, value) => Reflect.set(target, prop, value, target)
|
|
57
|
+
});
|
|
58
|
+
proxies.set(harness, proxy);
|
|
59
|
+
return proxy;
|
|
60
|
+
}
|
|
61
|
+
function stepLabel(className, method, args, name = method.name) {
|
|
62
|
+
const params = parameterNames(method);
|
|
63
|
+
const shown = args.map(
|
|
64
|
+
(arg, index) => isSecret(params[index] ?? "") ? JSON.stringify(REDACTED) : formatArg(arg)
|
|
65
|
+
);
|
|
66
|
+
return `${className}.${name}(${shown.join(", ")})`;
|
|
67
|
+
}
|
|
68
|
+
function isSecret(name) {
|
|
69
|
+
return SECRET.test(name) || name.split(WORD_BREAK).some((word) => SECRET_WORD.test(word));
|
|
70
|
+
}
|
|
71
|
+
function formatArg(arg) {
|
|
72
|
+
if (typeof arg === "function") return "\u0192";
|
|
73
|
+
if (arg instanceof core.ScopedHarness) return arg.constructor.name;
|
|
74
|
+
if (typeof arg === "string") {
|
|
75
|
+
return JSON.stringify(arg.length > MAX_ARG ? `${arg.slice(0, MAX_ARG)}\u2026` : arg);
|
|
76
|
+
}
|
|
77
|
+
const json = JSON.stringify(arg, (key, value) => isSecret(key) ? REDACTED : value) ?? String(arg);
|
|
78
|
+
return json.length > MAX_ARG ? `${json.slice(0, MAX_ARG)}\u2026` : json;
|
|
79
|
+
}
|
|
80
|
+
function parameterNames(method) {
|
|
81
|
+
const list = /\(([^)]*)\)/.exec(Function.prototype.toString.call(method))?.[1] ?? "";
|
|
82
|
+
if (/[{[]/.test(list)) return [];
|
|
83
|
+
return list.split(",").map((param) => param.replace(/[=:].*$/s, "").trim());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/index.ts
|
|
87
|
+
var matchersRegistered = false;
|
|
88
|
+
var listening = false;
|
|
89
|
+
var playError;
|
|
90
|
+
function listenForPlayErrors() {
|
|
91
|
+
if (listening || !previewApi.addons.hasChannel()) return;
|
|
92
|
+
previewApi.addons.getChannel().on(coreEvents.PLAY_FUNCTION_THREW_EXCEPTION, (error) => {
|
|
93
|
+
playError = error?.message ?? "the play function threw";
|
|
94
|
+
});
|
|
95
|
+
listening = true;
|
|
96
|
+
}
|
|
97
|
+
function harnessed(options = {}) {
|
|
98
|
+
if (!matchersRegistered) {
|
|
99
|
+
test.expect.extend(core.harnessMatchers);
|
|
100
|
+
matchersRegistered = true;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
async beforeEach(context) {
|
|
104
|
+
readyMarker(context.canvasElement.ownerDocument)?.remove();
|
|
105
|
+
listenForPlayErrors();
|
|
106
|
+
playError = void 0;
|
|
107
|
+
const HarnessClass = context.parameters.harness;
|
|
108
|
+
if (HarnessClass === void 0) return;
|
|
109
|
+
const env = dom.dom({
|
|
110
|
+
// Storybook's own instance, so each keystroke shows up in the panel. It
|
|
111
|
+
// is only set where a clipboard exists; fall back to a plain one.
|
|
112
|
+
user: hasClick(context.userEvent) ? context.userEvent : test.userEvent.setup(),
|
|
113
|
+
container: context.canvasElement,
|
|
114
|
+
...options.afterAction === void 0 ? {} : { afterAction: options.afterAction }
|
|
115
|
+
});
|
|
116
|
+
context.harness = withSteps(new HarnessClass(env), context.step);
|
|
117
|
+
},
|
|
118
|
+
async afterEach(context) {
|
|
119
|
+
const document = context.canvasElement.ownerDocument;
|
|
120
|
+
if (readyMarker(document) !== null) return;
|
|
121
|
+
const marker = document.createElement("div");
|
|
122
|
+
const attribute = core.getConfig().testIdAttribute;
|
|
123
|
+
if (attribute.endsWith("*"))
|
|
124
|
+
marker.setAttribute(attribute.slice(0, -1) + STORY_READY_TEST_ID, "");
|
|
125
|
+
else marker.setAttribute(attribute, STORY_READY_TEST_ID);
|
|
126
|
+
marker.hidden = true;
|
|
127
|
+
if (playError !== void 0) marker.setAttribute(PLAY_ERROR_ATTRIBUTE, playError);
|
|
128
|
+
document.body.append(marker);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function readyMarker(document) {
|
|
133
|
+
const attribute = core.getConfig().testIdAttribute;
|
|
134
|
+
const presence = core.testIdPresenceSelector(attribute, STORY_READY_TEST_ID);
|
|
135
|
+
return document.querySelector(presence ?? `[${attribute}="${STORY_READY_TEST_ID}"]`);
|
|
136
|
+
}
|
|
137
|
+
function hasClick(value) {
|
|
138
|
+
return typeof value?.click === "function";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/ember.ts
|
|
142
|
+
function busy() {
|
|
143
|
+
return Boolean(runloop._backburner.currentInstance) || runloop._backburner.hasTimers() || testWaiters.hasPendingWaiters();
|
|
144
|
+
}
|
|
145
|
+
async function settled(timeout = core.timeoutFor()) {
|
|
146
|
+
const deadline = Date.now() + timeout;
|
|
147
|
+
for (; ; ) {
|
|
148
|
+
await renderer.renderSettled();
|
|
149
|
+
if (!busy()) return;
|
|
150
|
+
if (Date.now() > deadline) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Ember did not settle within ${timeout}ms: a timer or test waiter is still pending`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function harnessed2() {
|
|
159
|
+
return harnessed({ afterAction: () => settled() });
|
|
160
|
+
}
|
|
161
|
+
var mounted = /* @__PURE__ */ new WeakMap();
|
|
162
|
+
function unmount(canvasElement) {
|
|
163
|
+
mounted.get(canvasElement)?.destroy();
|
|
164
|
+
mounted.delete(canvasElement);
|
|
165
|
+
}
|
|
166
|
+
function ownerFor(parameters) {
|
|
167
|
+
const owner = parameters.ember?.owner;
|
|
168
|
+
return typeof owner === "function" ? owner() : owner;
|
|
169
|
+
}
|
|
170
|
+
function render(_args, context) {
|
|
171
|
+
if (context.component === void 0) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Unable to render story ${context.id}: the meta names no \`component\` and the story has no \`render\``
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return context.component;
|
|
177
|
+
}
|
|
178
|
+
async function renderToCanvas({ storyContext, unboundStoryFn, showMain }, canvasElement) {
|
|
179
|
+
const component = unboundStoryFn(storyContext);
|
|
180
|
+
unmount(canvasElement);
|
|
181
|
+
canvasElement.replaceChildren();
|
|
182
|
+
const owner = ownerFor(storyContext.parameters);
|
|
183
|
+
mounted.set(
|
|
184
|
+
canvasElement,
|
|
185
|
+
renderer.renderComponent(component, {
|
|
186
|
+
into: canvasElement,
|
|
187
|
+
args: { ...storyContext.args },
|
|
188
|
+
...owner === void 0 ? {} : { owner }
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
await renderer.renderSettled();
|
|
192
|
+
showMain();
|
|
193
|
+
return () => unmount(canvasElement);
|
|
194
|
+
}
|
|
195
|
+
function mount(context) {
|
|
196
|
+
return async () => {
|
|
197
|
+
await context.renderToCanvas();
|
|
198
|
+
return context.canvas;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
var emberRenderer = {
|
|
202
|
+
render,
|
|
203
|
+
renderToCanvas,
|
|
204
|
+
mount,
|
|
205
|
+
parameters: { renderer: "ember" }
|
|
206
|
+
};
|
|
207
|
+
var DEFAULT_ANNOTATIONS = previewApi.composeConfigs([emberRenderer]);
|
|
208
|
+
function setProjectAnnotations(projectAnnotations) {
|
|
209
|
+
previewApi.setDefaultProjectAnnotations(DEFAULT_ANNOTATIONS);
|
|
210
|
+
return previewApi.setProjectAnnotations(
|
|
211
|
+
projectAnnotations
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
function composeStory(story, componentAnnotations, projectAnnotations, exportsName) {
|
|
215
|
+
return previewApi.composeStory(
|
|
216
|
+
// Storybook's own signature takes untyped args; the typed ones are a subtype.
|
|
217
|
+
story,
|
|
218
|
+
componentAnnotations,
|
|
219
|
+
projectAnnotations,
|
|
220
|
+
globalThis.globalProjectAnnotations ?? DEFAULT_ANNOTATIONS,
|
|
221
|
+
exportsName
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function composeStories(csfExports, projectAnnotations) {
|
|
225
|
+
const project = projectAnnotations;
|
|
226
|
+
return previewApi.composeStories(csfExports, project, composeStory);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
exports.composeStories = composeStories;
|
|
230
|
+
exports.composeStory = composeStory;
|
|
231
|
+
exports.emberRenderer = emberRenderer;
|
|
232
|
+
exports.harnessed = harnessed2;
|
|
233
|
+
exports.mount = mount;
|
|
234
|
+
exports.render = render;
|
|
235
|
+
exports.renderToCanvas = renderToCanvas;
|
|
236
|
+
exports.setProjectAnnotations = setProjectAnnotations;
|
|
237
|
+
exports.settled = settled;
|
|
238
|
+
//# sourceMappingURL=ember.cjs.map
|
|
239
|
+
//# sourceMappingURL=ember.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ready.ts","../src/steps.ts","../src/index.ts","../src/ember.ts"],"names":["ScopedHarness","addons","PLAY_FUNCTION_THREW_EXCEPTION","expect","harnessMatchers","dom","userEvent","getConfig","testIdPresenceSelector","_backburner","hasPendingWaiters","timeoutFor","renderSettled","harnessed","renderComponent","composeConfigs","setDefaultProjectAnnotations","originalSetProjectAnnotations","originalComposeStory","originalComposeStories"],"mappings":";;;;;;;;;;;;;;AAMO,IAAM,mBAAA,GAAsB,uBAAA;AAG5B,IAAM,oBAAA,GAAuB,2BAAA;ACJpC,IAAM,MAAA,GAAS,+BAAA;AAEf,IAAM,WAAA,GAAc,uBAAA;AAEpB,IAAM,UAAA,GAAa,+DAAA;AACnB,IAAM,QAAA,GAAW,oBAAA;AACjB,IAAM,OAAA,GAAU,EAAA;AAEhB,IAAM,iBAAiB,YAAY;AAAC,CAAA,EAAG,WAAA;AAEvC,IAAM,OAAA,uBAAc,OAAA,EAAyB;AAgBtC,SAAS,SAAA,CAAmC,SAAY,IAAA,EAAqB;AAClF,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA;AAClC,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,MAAA;AACjC,EAAA,MAAM,SAAA,GAAY,QAAQ,WAAA,CAAY,IAAA;AAEtC,EAAA,MAAM,IAAA,GAAO,CAAC,KAAA,KAA4B;AACxC,IAAA,IAAI,KAAA,YAAiBA,kBAAA,EAAe,OAAO,SAAA,CAAU,OAAO,IAAI,CAAA;AAChE,IAAA,IAAI,KAAA,CAAM,QAAQ,KAAK,CAAA,IAAK,MAAM,IAAA,CAAK,CAAA,IAAA,KAAQ,IAAA,YAAgBA,kBAAa,CAAA,EAAG;AAC7E,MAAA,OAAO,KAAA,CAAM,IAAI,IAAI,CAAA;AAAA,IACvB;AACA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,OAAA,EAAS;AAAA,IAC/B,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,MAAM,KAAA,GAAiB,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,MAAM,MAAM,CAAA;AACvD,MAAA,IAAI,OAAO,KAAA,KAAU,UAAA,EAAY,OAAO,KAAK,KAAK,CAAA;AAClD,MAAA,IAAI,OAAO,SAAS,QAAA,IAAY,IAAA,KAAS,iBAAiB,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAC9E,QAAA,OAAQ,KAAA,CAA0C,KAAK,MAAM,CAAA;AAAA,MAC/D;AACA,MAAA,MAAM,MAAA,GAAS,KAAA;AACf,MAAA,IAAI,kBAAkB,aAAA,EAAe;AACnC,QAAA,OAAO,UAAU,IAAA,KAAoB;AACnC,UAAA,IAAI,MAAA;AACJ,UAAA,MAAM,KAAK,SAAA,CAAU,SAAA,EAAW,QAAQ,IAAA,EAAM,IAAI,GAAG,YAAY;AAC/D,YAAA,MAAA,GAAS,MAAM,MAAA,CAAO,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAA;AAAA,UAC1C,CAAC,CAAA;AACD,UAAA,OAAO,KAAK,MAAM,CAAA;AAAA,QACpB,CAAA;AAAA,MACF;AACA,MAAA,OAAO,IAAI,IAAA,KAAoB,IAAA,CAAK,OAAO,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,IAChE,CAAA;AAAA;AAAA,IAEA,GAAA,EAAK,CAAC,MAAA,EAAQ,IAAA,EAAM,KAAA,KAAU,QAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAO,MAAM;AAAA,GACtE,CAAA;AACD,EAAA,OAAA,CAAQ,GAAA,CAAI,SAAS,KAAK,CAAA;AAC1B,EAAA,OAAO,KAAA;AACT;AASO,SAAS,UACd,SAAA,EACA,MAAA,EACA,IAAA,EACA,IAAA,GAAe,OAAO,IAAA,EACd;AACR,EAAA,MAAM,MAAA,GAAS,eAAe,MAAM,CAAA;AACpC,EAAA,MAAM,QAAQ,IAAA,CAAK,GAAA;AAAA,IAAI,CAAC,GAAA,EAAK,KAAA,KAC3B,QAAA,CAAS,OAAO,KAAK,CAAA,IAAK,EAAE,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA,GAAI,UAAU,GAAG;AAAA,GAC1E;AACA,EAAA,OAAO,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,IAAI,IAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAA;AACjD;AAEA,SAAS,SAAS,IAAA,EAAuB;AACvC,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,IAAK,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,CAAE,IAAA,CAAK,CAAA,IAAA,KAAQ,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA;AACxF;AAEA,SAAS,UAAU,GAAA,EAAsB;AACvC,EAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,OAAO,QAAA;AACtC,EAAA,IAAI,GAAA,YAAeA,kBAAA,EAAe,OAAO,GAAA,CAAI,WAAA,CAAY,IAAA;AACzD,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,MAAA,GAAS,OAAA,GAAU,CAAA,EAAG,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,OAAO,CAAC,CAAA,MAAA,CAAA,GAAM,GAAG,CAAA;AAAA,EAChF;AACA,EAAA,MAAM,IAAA,GACJ,IAAA,CAAK,SAAA,CAAU,GAAA,EAAK,CAAC,GAAA,EAAK,KAAA,KAAoB,QAAA,CAAS,GAAG,CAAA,GAAI,QAAA,GAAW,KAAM,CAAA,IAAK,OAAO,GAAG,CAAA;AAChG,EAAA,OAAO,IAAA,CAAK,SAAS,OAAA,GAAU,CAAA,EAAG,KAAK,KAAA,CAAM,CAAA,EAAG,OAAO,CAAC,CAAA,MAAA,CAAA,GAAM,IAAA;AAChE;AAGA,SAAS,eAAe,MAAA,EAAiD;AACvE,EAAA,MAAM,IAAA,GAAO,aAAA,CAAc,IAAA,CAAK,QAAA,CAAS,SAAA,CAAU,QAAA,CAAS,IAAA,CAAK,MAAM,CAAC,CAAA,GAAI,CAAC,CAAA,IAAK,EAAA;AAClF,EAAA,IAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,SAAU,EAAC;AAC/B,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAA,KAAA,KAAS,KAAA,CAAM,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA,CAAE,IAAA,EAAM,CAAA;AAC1E;;;AC7EA,IAAI,kBAAA,GAAqB,KAAA;AACzB,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAI,SAAA;AAOJ,SAAS,mBAAA,GAA4B;AACnC,EAAA,IAAI,SAAA,IAAa,CAACC,iBAAA,CAAO,UAAA,EAAW,EAAG;AACvC,EAAAA,iBAAA,CAAO,UAAA,EAAW,CAAE,EAAA,CAAGC,wCAAA,EAA+B,CAAC,KAAA,KAAiC;AACtF,IAAA,SAAA,GAAY,OAAO,OAAA,IAAW,yBAAA;AAAA,EAChC,CAAC,CAAA;AACD,EAAA,SAAA,GAAY,IAAA;AACd;AAgBO,SAAS,SAAA,CAAU,OAAA,GAA4B,EAAC,EAGrD;AACA,EAAA,IAAI,CAAC,kBAAA,EAAoB;AACvB,IAAAC,WAAA,CAAO,OAAOC,oBAAe,CAAA;AAC7B,IAAA,kBAAA,GAAqB,IAAA;AAAA,EACvB;AACA,EAAA,OAAO;AAAA,IACL,MAAM,WAAW,OAAA,EAA2B;AAC1C,MAAA,WAAA,CAAY,OAAA,CAAQ,aAAA,CAAc,aAAa,CAAA,EAAG,MAAA,EAAO;AACzD,MAAA,mBAAA,EAAoB;AACpB,MAAA,SAAA,GAAY,MAAA;AACZ,MAAA,MAAM,YAAA,GAAe,QAAQ,UAAA,CAAW,OAAA;AACxC,MAAA,IAAI,iBAAiB,MAAA,EAAW;AAChC,MAAA,MAAM,MAAMC,OAAA,CAAI;AAAA;AAAA;AAAA,QAGd,IAAA,EAAO,SAAS,OAAA,CAAQ,SAAS,IAAI,OAAA,CAAQ,SAAA,GAAYC,eAAU,KAAA,EAAM;AAAA,QACzE,WAAW,OAAA,CAAQ,aAAA;AAAA,QACnB,GAAI,QAAQ,WAAA,KAAgB,MAAA,GAAY,EAAC,GAAI,EAAE,WAAA,EAAa,OAAA,CAAQ,WAAA;AAAY,OACjF,CAAA;AACD,MAAA,OAAA,CAAQ,UAAU,SAAA,CAAU,IAAI,aAAa,GAAG,CAAA,EAAG,QAAQ,IAAI,CAAA;AAAA,IACjE,CAAA;AAAA,IACA,MAAM,UAAU,OAAA,EAA2B;AACzC,MAAA,MAAM,QAAA,GAAW,QAAQ,aAAA,CAAc,aAAA;AACvC,MAAA,IAAI,WAAA,CAAY,QAAQ,CAAA,KAAM,IAAA,EAAM;AACpC,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC3C,MAAA,MAAM,SAAA,GAAYC,gBAAU,CAAE,eAAA;AAE9B,MAAA,IAAI,SAAA,CAAU,SAAS,GAAG,CAAA;AACxB,QAAA,MAAA,CAAO,aAAa,SAAA,CAAU,KAAA,CAAM,GAAG,EAAE,CAAA,GAAI,qBAAqB,EAAE,CAAA;AAAA,WACjE,MAAA,CAAO,YAAA,CAAa,SAAA,EAAW,mBAAmB,CAAA;AACvD,MAAA,MAAA,CAAO,MAAA,GAAS,IAAA;AAChB,MAAA,IAAI,SAAA,KAAc,MAAA,EAAW,MAAA,CAAO,YAAA,CAAa,sBAAsB,SAAS,CAAA;AAChF,MAAA,QAAA,CAAS,IAAA,CAAK,OAAO,MAAM,CAAA;AAAA,IAC7B;AAAA,GACF;AACF;AAGA,SAAS,YAAY,QAAA,EAAoC;AACvD,EAAA,MAAM,SAAA,GAAYA,gBAAU,CAAE,eAAA;AAC9B,EAAA,MAAM,QAAA,GAAWC,2BAAA,CAAuB,SAAA,EAAW,mBAAmB,CAAA;AACtE,EAAA,OAAO,SAAS,aAAA,CAAc,QAAA,IAAY,IAAI,SAAS,CAAA,EAAA,EAAK,mBAAmB,CAAA,EAAA,CAAI,CAAA;AACrF;AAEA,SAAS,SAAS,KAAA,EAAyB;AACzC,EAAA,OAAO,OAAQ,OAA2C,KAAA,KAAU,UAAA;AACtE;;;AClDA,SAAS,IAAA,GAAgB;AACvB,EAAA,OAAO,QAAQC,mBAAA,CAAY,eAAe,KAAKA,mBAAA,CAAY,SAAA,MAAeC,6BAAA,EAAkB;AAC9F;AAMA,eAAsB,OAAA,CAAQ,OAAA,GAAUC,eAAA,EAAW,EAAkB;AACnE,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,OAAA;AAC9B,EAAA,WAAS;AACP,IAAA,MAAMC,sBAAA,EAAc;AACpB,IAAA,IAAI,CAAC,MAAK,EAAG;AACb,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,QAAA,EAAU;AACzB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,+BAA+B,OAAO,CAAA,2CAAA;AAAA,OACxC;AAAA,IACF;AACA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAAA,EACtD;AACF;AAYO,SAASC,UAAAA,GAGd;AACA,EAAA,OAAO,UAAa,EAAE,WAAA,EAAa,MAAM,OAAA,IAAW,CAAA;AACtD;AAGA,IAAM,OAAA,uBAAc,OAAA,EAA0C;AAE9D,SAAS,QAAQ,aAAA,EAAkC;AACjD,EAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA,EAAG,OAAA,EAAQ;AACpC,EAAA,OAAA,CAAQ,OAAO,aAAa,CAAA;AAC9B;AAEA,SAAS,SAAS,UAAA,EAA6D;AAC7E,EAAA,MAAM,KAAA,GAAQ,WAAW,KAAA,EAAO,KAAA;AAChC,EAAA,OAAO,OAAO,KAAA,KAAU,UAAA,GAAa,KAAA,EAAM,GAAI,KAAA;AACjD;AAGO,SAAS,MAAA,CAAO,OAAa,OAAA,EAA8C;AAChF,EAAA,IAAI,OAAA,CAAQ,cAAc,MAAA,EAAW;AACnC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,uBAAA,EAA0B,QAAQ,EAAE,CAAA,iEAAA;AAAA,KACtC;AAAA,EACF;AACA,EAAA,OAAO,OAAA,CAAQ,SAAA;AACjB;AAOA,eAAsB,eACpB,EAAE,YAAA,EAAc,cAAA,EAAgB,QAAA,IAChC,aAAA,EACqB;AACrB,EAAA,MAAM,SAAA,GAAY,eAAe,YAAY,CAAA;AAC7C,EAAA,OAAA,CAAQ,aAAa,CAAA;AACrB,EAAA,aAAA,CAAc,eAAA,EAAgB;AAC9B,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,YAAA,CAAa,UAAU,CAAA;AAC9C,EAAA,OAAA,CAAQ,GAAA;AAAA,IACN,aAAA;AAAA,IACAC,yBAAgB,SAAA,EAAW;AAAA,MACzB,IAAA,EAAM,aAAA;AAAA,MACN,IAAA,EAAM,EAAE,GAAG,YAAA,CAAa,IAAA,EAAK;AAAA,MAC7B,GAAI,KAAA,KAAU,MAAA,GAAY,EAAC,GAAI,EAAE,KAAA;AAAM,KACxC;AAAA,GACH;AACA,EAAA,MAAMF,sBAAA,EAAc;AACpB,EAAA,QAAA,EAAS;AACT,EAAA,OAAO,MAAM,QAAQ,aAAa,CAAA;AACpC;AAGO,SAAS,MAAM,OAAA,EAAsC;AAC1D,EAAA,OAAO,YAA4D;AACjE,IAAA,MAAM,QAAQ,cAAA,EAAe;AAC7B,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,EACjB,CAAA;AACF;AAOO,IAAM,aAAA,GAAgB;AAAA,EAC3B,MAAA;AAAA,EACA,cAAA;AAAA,EACA,KAAA;AAAA,EACA,UAAA,EAAY,EAAE,QAAA,EAAU,OAAA;AAC1B;AAEA,IAAM,mBAAA,GAAyDG,yBAAA,CAAe,CAAC,aAAa,CAAC,CAAA;AAMtF,SAAS,sBACd,kBAAA,EAG6C;AAC7C,EAAAC,uCAAA,CAA6B,mBAAmB,CAAA;AAChD,EAAA,OAAOC,gCAAA;AAAA,IACL;AAAA,GACF;AACF;AAGO,SAAS,YAAA,CACd,KAAA,EACA,oBAAA,EACA,kBAAA,EACA,WAAA,EACgD;AAChD,EAAA,OAAOC,uBAAA;AAAA;AAAA,IAEL,KAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACC,WACE,wBAAA,IAA4B,mBAAA;AAAA,IAC/B;AAAA,GACF;AACF;AAMO,SAAS,cAAA,CACd,YACA,kBAAA,EAC+E;AAE/E,EAAA,MAAM,OAAA,GAAU,kBAAA;AAChB,EAAA,OAAOC,yBAAA,CAAuB,UAAA,EAAY,OAAA,EAAS,YAAY,CAAA;AAIjE","file":"ember.cjs","sourcesContent":["/**\n * The test id of the node `harnessed()` adds once a story has rendered and its\n * play function has finished. `StoryPage` waits for it: Storybook's own\n * \"rendered\" signal is a channel event, which a runner outside the iframe's\n * realm cannot listen for, while a node is something every driver can count.\n */\nexport const STORY_READY_TEST_ID = 'harnessed-story-ready'\n\n/** Set on that node when the play function threw, holding the error's message. */\nexport const PLAY_ERROR_ATTRIBUTE = 'data-harnessed-play-error'\n","import { ScopedHarness } from '@harnessed-ts/core'\n\n/** Storybook's `context.step`, narrowed to what the proxy needs. */\nexport type StepRunner = (label: string, play: () => Promise<void>) => Promise<unknown> | unknown\n\nconst SECRET = /pass|secret|token|credential/i\n// Short names match whole words only, so `author`, `keyboard` and `typing` stay visible.\nconst SECRET_WORD = /^(key|pin|otp|auth)$/i\n// camelCase, PascalCase acronyms (`APIKey`) and snake/kebab-case word breaks.\nconst WORD_BREAK = /[^a-zA-Z0-9]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/\nconst REDACTED = '•••'\nconst MAX_ARG = 40\n\nconst AsyncFunction = (async () => {}).constructor\n\nconst proxies = new WeakMap<object, unknown>()\n\n/**\n * The harness, with every public async method run inside `step()` — so the\n * Interactions panel reads `LoginFormHarness.signInAs(\"ada@…\", \"•••\")` with the\n * raw clicks and keystrokes nested under it, rather than a flat list of events.\n *\n * Methods run against the real instance, not the proxy: a harness calling its\n * own methods does not log a step per call, and decorated `accessor` fields\n * (backed by private slots a proxy cannot reach) keep working. Sync methods stay\n * sync; a harness they return — `nth()`, a child — comes back wrapped too.\n *\n * shortcut: async is detected by `AsyncFunction`, so a plain method that returns\n * a promise, or one down-levelled to a generator, logs no step. Upgrade if a\n * harness or a build ever needs that.\n */\nexport function withSteps<T extends ScopedHarness>(harness: T, step: StepRunner): T {\n const cached = proxies.get(harness)\n if (cached !== undefined) return cached as T\n const className = harness.constructor.name\n\n const wrap = (value: unknown): unknown => {\n if (value instanceof ScopedHarness) return withSteps(value, step)\n if (Array.isArray(value) && value.some(item => item instanceof ScopedHarness)) {\n return value.map(wrap)\n }\n return value\n }\n\n const proxy = new Proxy(harness, {\n get(target, prop) {\n const value: unknown = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return wrap(value)\n if (typeof prop !== 'string' || prop === 'constructor' || prop.startsWith('_')) {\n return (value as (...args: unknown[]) => unknown).bind(target)\n }\n const method = value as (...args: unknown[]) => unknown\n if (method instanceof AsyncFunction) {\n return async (...args: unknown[]) => {\n let result: unknown\n await step(stepLabel(className, method, args, prop), async () => {\n result = await method.apply(target, args)\n })\n return wrap(result)\n }\n }\n return (...args: unknown[]) => wrap(method.apply(target, args))\n },\n // An accessor's setter writes a private slot only the real instance has.\n set: (target, prop, value) => Reflect.set(target, prop, value, target),\n })\n proxies.set(harness, proxy)\n return proxy\n}\n\n/**\n * `Harness.method(args…)`, each argument shortened, and any argument whose\n * parameter name — or object key — looks like a secret replaced with `•••`.\n *\n * Names are read from the source, so a minified `storybook build` shows mangled\n * class names and masks only object keys, not positional arguments.\n */\nexport function stepLabel(\n className: string,\n method: (...args: never[]) => unknown,\n args: readonly unknown[],\n name: string = method.name,\n): string {\n const params = parameterNames(method)\n const shown = args.map((arg, index) =>\n isSecret(params[index] ?? '') ? JSON.stringify(REDACTED) : formatArg(arg),\n )\n return `${className}.${name}(${shown.join(', ')})`\n}\n\nfunction isSecret(name: string): boolean {\n return SECRET.test(name) || name.split(WORD_BREAK).some(word => SECRET_WORD.test(word))\n}\n\nfunction formatArg(arg: unknown): string {\n if (typeof arg === 'function') return 'ƒ'\n if (arg instanceof ScopedHarness) return arg.constructor.name\n if (typeof arg === 'string') {\n return JSON.stringify(arg.length > MAX_ARG ? `${arg.slice(0, MAX_ARG)}…` : arg)\n }\n const json =\n JSON.stringify(arg, (key, value: unknown) => (isSecret(key) ? REDACTED : value)) ?? String(arg)\n return json.length > MAX_ARG ? `${json.slice(0, MAX_ARG)}…` : json\n}\n\n/** Plain parameter names from the source; none when they are destructured. */\nfunction parameterNames(method: (...args: never[]) => unknown): string[] {\n const list = /\\(([^)]*)\\)/.exec(Function.prototype.toString.call(method))?.[1] ?? ''\n if (/[{[]/.test(list)) return []\n return list.split(',').map(param => param.replace(/[=:].*$/s, '').trim())\n}\n","import { getConfig, harnessMatchers, testIdPresenceSelector } from '@harnessed-ts/core'\nimport type { Assertable, ScopedHarness, ScopedHarnessConstructor } from '@harnessed-ts/core'\nimport { dom } from '@harnessed-ts/dom'\nimport type { UserEvent } from '@testing-library/user-event'\nimport { PLAY_FUNCTION_THREW_EXCEPTION } from 'storybook/internal/core-events'\nimport { addons } from 'storybook/preview-api'\nimport { expect, userEvent } from 'storybook/test'\nimport { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID } from './ready'\nimport { withSteps } from './steps'\nimport type { StepRunner } from './steps'\n\nexport { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID } from './ready'\nexport { stepLabel, withSteps } from './steps'\nexport type { StepRunner } from './steps'\n\n/** What `harnessed()` reads from, and adds to, Storybook's story context. */\nexport interface HarnessedContext {\n canvasElement: HTMLElement\n parameters: { harness?: ScopedHarnessConstructor }\n userEvent?: unknown\n step: StepRunner\n harness?: ScopedHarness\n}\n\nexport interface HarnessedOptions {\n /**\n * Awaited after every interaction a harness makes, before the next step reads\n * the canvas. A renderer that updates the DOM asynchronously settles here;\n * `@harnessed-ts/storybook/ember` passes Ember's.\n */\n afterAction?: () => Promise<void>\n}\n\nlet matchersRegistered = false\nlet listening = false\n/** The message of the current story's play error, if it threw. */\nlet playError: string | undefined\n\n/**\n * Storybook catches a play function's error, reports it on its channel, and\n * then finishes the render (running `afterEach`) as if it had passed, so the\n * error has to be caught here to reach the ready mark.\n */\nfunction listenForPlayErrors(): void {\n if (listening || !addons.hasChannel()) return\n addons.getChannel().on(PLAY_FUNCTION_THREW_EXCEPTION, (error?: { message?: string }) => {\n playError = error?.message ?? 'the play function threw'\n })\n listening = true\n}\n\n/**\n * Preview annotations that put a harness in every play function. Spread them\n * into `.storybook/preview.ts`:\n *\n * ```ts\n * import { harnessed } from '@harnessed-ts/storybook'\n * export default { ...harnessed() } satisfies Preview\n * ```\n *\n * A story (or its meta) names its harness in `parameters.harness`, and `play`\n * receives it as `context.harness`, driving the canvas through Storybook's\n * instrumented `userEvent` with each method logged as a step. The harness\n * matchers are added to `storybook/test`'s `expect`.\n */\nexport function harnessed(options: HarnessedOptions = {}): {\n beforeEach: (context: HarnessedContext) => Promise<void>\n afterEach: (context: HarnessedContext) => Promise<void>\n} {\n if (!matchersRegistered) {\n expect.extend(harnessMatchers)\n matchersRegistered = true\n }\n return {\n async beforeEach(context: HarnessedContext) {\n readyMarker(context.canvasElement.ownerDocument)?.remove()\n listenForPlayErrors()\n playError = undefined\n const HarnessClass = context.parameters.harness\n if (HarnessClass === undefined) return\n const env = dom({\n // Storybook's own instance, so each keystroke shows up in the panel. It\n // is only set where a clipboard exists; fall back to a plain one.\n user: (hasClick(context.userEvent) ? context.userEvent : userEvent.setup()) as UserEvent,\n container: context.canvasElement,\n ...(options.afterAction === undefined ? {} : { afterAction: options.afterAction }),\n })\n context.harness = withSteps(new HarnessClass(env), context.step)\n },\n async afterEach(context: HarnessedContext) {\n const document = context.canvasElement.ownerDocument\n if (readyMarker(document) !== null) return\n const marker = document.createElement('div')\n const attribute = getConfig().testIdAttribute\n // Under a presence pattern the id is part of the name: `data-test-<id>`.\n if (attribute.endsWith('*'))\n marker.setAttribute(attribute.slice(0, -1) + STORY_READY_TEST_ID, '')\n else marker.setAttribute(attribute, STORY_READY_TEST_ID)\n marker.hidden = true\n if (playError !== undefined) marker.setAttribute(PLAY_ERROR_ATTRIBUTE, playError)\n document.body.append(marker)\n },\n }\n}\n\n/** Found the way `testId(STORY_READY_TEST_ID)` finds it, which is what `StoryPage` waits on. */\nfunction readyMarker(document: Document): Element | null {\n const attribute = getConfig().testIdAttribute\n const presence = testIdPresenceSelector(attribute, STORY_READY_TEST_ID)\n return document.querySelector(presence ?? `[${attribute}=\"${STORY_READY_TEST_ID}\"]`)\n}\n\nfunction hasClick(value: unknown): boolean {\n return typeof (value as { click?: unknown } | undefined)?.click === 'function'\n}\n\n/**\n * A story type whose `play` receives `harness` typed as `H`.\n *\n * ```ts\n * type Story = WithHarness<StoryObj<typeof meta>, LoginFormHarness>\n * ```\n */\nexport type WithHarness<S, H extends ScopedHarness> = Omit<S, 'play'> & {\n play?: S extends { play?: infer P }\n ? P extends (context: infer C) => infer R\n ? (context: C & { harness: H }) => R\n : never\n : never\n}\n\ndeclare module 'storybook/test' {\n // The type parameter must keep Storybook's own name. Gating on `T` makes calling\n // these on something that is not a target or a harness a type error.\n interface Assertion<T> {\n toBeSelected(): T extends Assertable ? Promise<void> : never\n toBeAbsent(): T extends Assertable ? Promise<void> : never\n toReadAs(expected: string | RegExp): T extends Assertable ? Promise<void> : never\n }\n}\n","/// <reference types=\"ember-source/types\" />\nimport { renderComponent, renderSettled } from '@ember/renderer'\nimport { _backburner } from '@ember/runloop'\nimport { hasPendingWaiters } from '@ember/test-waiters'\nimport { timeoutFor } from '@harnessed-ts/core'\nimport {\n composeConfigs,\n composeStories as originalComposeStories,\n composeStory as originalComposeStory,\n setDefaultProjectAnnotations,\n setProjectAnnotations as originalSetProjectAnnotations,\n} from 'storybook/preview-api'\nimport type {\n Args,\n ComponentAnnotations,\n ComposedStoryFn,\n NamedOrDefaultProjectAnnotations,\n NormalizedProjectAnnotations,\n ProjectAnnotations,\n RenderContext,\n Store_CSFExports,\n StoriesWithPartialProps,\n StoryAnnotations,\n StoryAnnotationsOrFn,\n StoryContext,\n WebRenderer,\n} from 'storybook/internal/types'\nimport { harnessed as harnessedFor } from './index'\nimport type { HarnessedContext } from './index'\n\n/**\n * Storybook's renderer type for Ember: a story's `component`, and what its\n * `render` returns, is a component — a Glimmer class or a `<template>`.\n */\nexport interface EmberRenderer extends WebRenderer {\n component: object\n storyResult: object\n}\n\n/** A CSF meta for an Ember component. */\nexport type Meta<TArgs = Args> = ComponentAnnotations<EmberRenderer, TArgs>\n\n/** A CSF story for an Ember component. */\nexport type StoryObj<TArgs = Args> = StoryAnnotations<EmberRenderer, TArgs>\n\n/** The `.storybook/preview.ts` default export. */\nexport type Preview = ProjectAnnotations<EmberRenderer>\n\n/** What a story can set under `parameters.ember`. */\nexport interface EmberParameters {\n /**\n * The owner the story renders with, for components that inject services. A\n * function is called once per render. Without one, Ember's default applies:\n * an empty object, enough for components that look nothing up.\n */\n owner?: object | (() => object)\n}\n\n/**\n * Whether Ember has work in flight: a run loop, a scheduled timer, a render,\n * or a test waiter (an `@ember/test-waiters` waiter, as a data layer or\n * `ember-concurrency` registers). The same signals `settled()` in\n * `@ember/test-helpers` reads, without its test context.\n */\nfunction busy(): boolean {\n return Boolean(_backburner.currentInstance) || _backburner.hasTimers() || hasPendingWaiters()\n}\n\n/**\n * Resolves once Ember is settled: rendering done, no run loop or timers left,\n * and no test waiter pending. Rejects if it is still busy after `timeout`.\n */\nexport async function settled(timeout = timeoutFor()): Promise<void> {\n const deadline = Date.now() + timeout\n for (;;) {\n await renderSettled()\n if (!busy()) return\n if (Date.now() > deadline) {\n throw new Error(\n `Ember did not settle within ${timeout}ms: a timer or test waiter is still pending`,\n )\n }\n await new Promise(resolve => setTimeout(resolve, 10))\n }\n}\n\n/**\n * `harnessed()` for Ember stories: every interaction a harness makes resolves\n * once Ember has settled, as it does under `@harnessed-ts/ember`, so the next\n * read sees the re-rendered canvas.\n *\n * ```ts\n * import { emberRenderer, harnessed } from '@harnessed-ts/storybook/ember'\n * export default { ...emberRenderer, ...harnessed() } satisfies Preview\n * ```\n */\nexport function harnessed(): {\n beforeEach: (context: HarnessedContext) => Promise<void>\n afterEach: (context: HarnessedContext) => Promise<void>\n} {\n return harnessedFor({ afterAction: () => settled() })\n}\n\n/** What each canvas has on it, so the next render can tear it down first. */\nconst mounted = new WeakMap<HTMLElement, { destroy(): void }>()\n\nfunction unmount(canvasElement: HTMLElement): void {\n mounted.get(canvasElement)?.destroy()\n mounted.delete(canvasElement)\n}\n\nfunction ownerFor(parameters: { ember?: EmberParameters }): object | undefined {\n const owner = parameters.ember?.owner\n return typeof owner === 'function' ? owner() : owner\n}\n\n/** The default `render`: the meta's `component`, with the story's args. */\nexport function render(_args: Args, context: StoryContext<EmberRenderer>): object {\n if (context.component === undefined) {\n throw new Error(\n `Unable to render story ${context.id}: the meta names no \\`component\\` and the story has no \\`render\\``,\n )\n }\n return context.component\n}\n\n/**\n * Renders the story's component into the canvas with Ember's public\n * `renderComponent`, passing the story's args. Every render starts fresh, so\n * changing an arg in the Controls panel remounts the component.\n */\nexport async function renderToCanvas(\n { storyContext, unboundStoryFn, showMain }: RenderContext<EmberRenderer>,\n canvasElement: HTMLElement,\n): Promise<() => void> {\n const component = unboundStoryFn(storyContext)\n unmount(canvasElement)\n canvasElement.replaceChildren()\n const owner = ownerFor(storyContext.parameters)\n mounted.set(\n canvasElement,\n renderComponent(component, {\n into: canvasElement,\n args: { ...storyContext.args },\n ...(owner === undefined ? {} : { owner }),\n }),\n )\n await renderSettled()\n showMain()\n return () => unmount(canvasElement)\n}\n\n/** Storybook's `mount`: renders the story, and resolves with its canvas. */\nexport function mount(context: StoryContext<EmberRenderer>) {\n return async (): Promise<StoryContext<EmberRenderer>['canvas']> => {\n await context.renderToCanvas()\n return context.canvas\n }\n}\n\n/**\n * The renderer, as preview annotations. A Storybook built with a renderer\n * that is not Ember's — `@storybook/html-vite` — spreads these into\n * `.storybook/preview.ts`, and its stories render as Ember components.\n */\nexport const emberRenderer = {\n render,\n renderToCanvas,\n mount,\n parameters: { renderer: 'ember' },\n} satisfies ProjectAnnotations<EmberRenderer>\n\nconst DEFAULT_ANNOTATIONS: ProjectAnnotations<EmberRenderer> = composeConfigs([emberRenderer])\n\n/**\n * Portable stories: applies `.storybook/preview.ts` to every story composed\n * after it. Call once, in a Vitest setup file or at the top of a test.\n */\nexport function setProjectAnnotations(\n projectAnnotations:\n | NamedOrDefaultProjectAnnotations<EmberRenderer>\n | NamedOrDefaultProjectAnnotations<EmberRenderer>[],\n): NormalizedProjectAnnotations<EmberRenderer> {\n setDefaultProjectAnnotations(DEFAULT_ANNOTATIONS)\n return originalSetProjectAnnotations(\n projectAnnotations,\n ) as NormalizedProjectAnnotations<EmberRenderer>\n}\n\n/** One story, composed with its meta and the project's annotations: `await story.run()` renders it. */\nexport function composeStory<TArgs extends Args = Args>(\n story: StoryAnnotationsOrFn<EmberRenderer, TArgs>,\n componentAnnotations: Meta<TArgs>,\n projectAnnotations?: ProjectAnnotations<EmberRenderer>,\n exportsName?: string,\n): ComposedStoryFn<EmberRenderer, Partial<TArgs>> {\n return originalComposeStory(\n // Storybook's own signature takes untyped args; the typed ones are a subtype.\n story as StoryAnnotationsOrFn<EmberRenderer>,\n componentAnnotations as Meta,\n projectAnnotations,\n (globalThis as { globalProjectAnnotations?: ProjectAnnotations<EmberRenderer> })\n .globalProjectAnnotations ?? DEFAULT_ANNOTATIONS,\n exportsName,\n ) as ComposedStoryFn<EmberRenderer, Partial<TArgs>>\n}\n\n/** Every story in a CSF module, composed: `const stories = composeStories(LoginStories)`. */\n// `any` args, as in Storybook's own renderers: a module whose stories take typed\n// args is not assignable to one whose stories take `Args`.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function composeStories<TModule extends Store_CSFExports<EmberRenderer, any>>(\n csfExports: TModule,\n projectAnnotations?: ProjectAnnotations<EmberRenderer>,\n): Omit<StoriesWithPartialProps<EmberRenderer, TModule>, keyof Store_CSFExports> {\n // Storybook passes it through to `composeStory`, where `undefined` is allowed.\n const project = projectAnnotations as ProjectAnnotations<EmberRenderer>\n return originalComposeStories(csfExports, project, composeStory) as Omit<\n StoriesWithPartialProps<EmberRenderer, TModule>,\n keyof Store_CSFExports\n >\n}\n"]}
|