@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.
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIhD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AAED,uEAAuE;AACvE,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,MAAM,CAAA;AAErF,yFAAyF;AACzF,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,IAAI,OAAO,CAC9E,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CACpC,CAAA;AAID,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAErF,MAAM,WAAW,YAAY,CAAC,IAAI;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAA;AAU1D;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,EACtE,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAM,GAC1C,MAAM,CAiBR;AA2BD;;;;;;;;;;;;GAYG;AACH,qBACa,SAAU,SAAQ,WAAW;;WAG3B,IAAI,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,EAC/D,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,SAAS,CAAC;IAOrB,IAAa,IAAI,IAAI,MAAM,CAE1B;IAED,wEAAwE;IACxE,OAAO,CAAC,CAAC,SAAS,aAAa,EAAE,YAAY,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,CAAC;cAI9D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB9C"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIhD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AAED,uEAAuE;AACvE,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,MAAM,CAAA;AAErF,yFAAyF;AACzF,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,IAAI,OAAO,CAC9E,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CACpC,CAAA;AAID,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAErF,MAAM,WAAW,YAAY,CAAC,IAAI;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAA;AAU1D;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,EACtE,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAM,GAC1C,MAAM,CAiBR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EAAE,IAAI,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAExF;AA2BD;;;;;;;;;;;;GAYG;AACH,qBACa,SAAU,SAAQ,WAAW;;WAG3B,IAAI,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,EAC/D,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,SAAS,CAAC;IAOrB,IAAa,IAAI,IAAI,MAAM,CAE1B;IAED,wEAAwE;IACxE,OAAO,CAAC,CAAC,SAAS,aAAa,EAAE,YAAY,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,CAAC;cAI9D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB9C"}
package/dist/page.js CHANGED
@@ -1,4 +1,5 @@
1
- import { STORY_READY_TEST_ID, __decoratorStart, __decorateElement, __runInitializers, PLAY_ERROR_ATTRIBUTE } from './chunk-J6DS34J4.js';
1
+ import { STORY_READY_TEST_ID, PLAY_ERROR_ATTRIBUTE } from './chunk-EBSULTWQ.js';
2
+ import { __decoratorStart, __decorateElement, __runInitializers } from './chunk-CGZBV6BD.js';
2
3
  import { Harness, testId, timeoutFor } from '@harnessed-ts/core';
3
4
  import { PageHarness } from '@harnessed-ts/page';
4
5
  import { toId, storyNameFromExport } from 'storybook/internal/csf';
@@ -21,6 +22,9 @@ function storyUrl(stories, name, options = {}) {
21
22
  if (options.play === false) params.push("embed=true");
22
23
  return `/iframe.html?${params.join("&")}`;
23
24
  }
25
+ function storiesModule(meta) {
26
+ return { default: meta };
27
+ }
24
28
  function encode(values) {
25
29
  return Object.entries(values).map(([key, value]) => `${key}:${encodeValue(key, value)}`).join(";");
26
30
  }
@@ -76,6 +80,6 @@ _StoryPage = __decorateElement(_init, 0, "StoryPage", _StoryPage_decorators, _St
76
80
  __runInitializers(_init, 1, _StoryPage);
77
81
  var StoryPage = _StoryPage;
78
82
 
79
- export { StoryPage, storyUrl };
83
+ export { StoryPage, storiesModule, storyUrl };
80
84
  //# sourceMappingURL=page.js.map
81
85
  //# sourceMappingURL=page.js.map
package/dist/page.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/page.ts"],"names":[],"mappings":"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","file":"page.js","sourcesContent":["import { Harness, testId, timeoutFor } from '@harnessed-ts/core'\nimport type { EnvConfig, ScopedHarness, ScopedHarnessConstructor } from '@harnessed-ts/core'\nimport { PageHarness } from '@harnessed-ts/page'\nimport { storyNameFromExport, toId } from 'storybook/internal/csf'\nimport { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID } from './ready'\n\n/** A CSF module as imported with `import * as Stories from './X.stories'`. */\nexport interface StoriesModule {\n default: { title?: string; id?: string }\n}\n\n/** The export names in a CSF module that are stories, not the meta. */\nexport type StoryName<M extends StoriesModule> = Exclude<keyof M, 'default'> & string\n\n/** The args a story declares, all optional: a URL overrides some, never must set all. */\nexport type StoryArgs<M extends StoriesModule, K extends StoryName<M>> = Partial<\n ArgsOf<M[K]> & ArgsOf<M['default']>\n>\n\n// An empty record rather than `never` when there are none: a meta without\n// `args` must not erase the story's own.\ntype ArgsOf<T> = T extends { args?: infer A } ? NonNullable<A> : Record<never, never>\n\nexport interface StoryOptions<Args> {\n /**\n * Overrides for the story's args, encoded in Storybook's own URL syntax.\n * Storybook applies one only when the arg has an argType — inferred from the\n * component's props, or declared in `argTypes` — and drops it otherwise.\n */\n args?: Args\n /** Toolbar globals — theme, locale, viewport. */\n globals?: Record<string, Scalar>\n /**\n * Whether the story's play function runs. Defaults to `true`, as it does in\n * Storybook; `false` opens the story in its initial state for the test to drive.\n */\n play?: boolean\n}\n\ntype Scalar = string | number | boolean | null | undefined\n\n// Storybook's own rules (storybook/internal/router): it silently drops any arg\n// that fails them, so a test would run against the wrong state. Refuse instead.\nconst SAFE_KEY = /^[a-zA-Z0-9 _-]+$/\n// Storybook reads a numeric-looking string back as a number, so '02134' would\n// arrive as 2134: letters, digits, spaces, `_` and `-`, but not a number.\nconst SAFE_STRING = /^[a-zA-Z0-9 _-]*$/\nconst NUMERIC = /^-?[0-9]+(\\.[0-9]+)?$/\n\n/**\n * The iframe URL of one story — id from the meta and export name, exactly as\n * Storybook derives it — relative to the Storybook root.\n */\nexport function storyUrl<M extends StoriesModule, K extends StoryName<M>>(\n stories: M,\n name: K,\n options: StoryOptions<StoryArgs<M, K>> = {},\n): string {\n const title = stories.default.id ?? stories.default.title\n if (title === undefined) {\n throw new Error(\n `harnessed: the stories module for \"${name}\" has no \\`title\\` on its default export, ` +\n 'so its story id cannot be derived outside Storybook. Fix: set `title` on the meta ' +\n '(or `id`, if the story id must not follow the title).',\n )\n }\n const params = [`id=${toId(title, storyNameFromExport(name))}`, 'viewMode=story']\n const args = encode(options.args ?? {})\n if (args !== '') params.push(`args=${args}`)\n const globals = encode(options.globals ?? {})\n if (globals !== '') params.push(`globals=${globals}`)\n // The switch Storybook itself reads: an embedded story renders without autoplay.\n if (options.play === false) params.push('embed=true')\n return `/iframe.html?${params.join('&')}`\n}\n\nfunction encode(values: Record<string, unknown>): string {\n return Object.entries(values)\n .map(([key, value]) => `${key}:${encodeValue(key, value)}`)\n .join(';')\n}\n\nfunction encodeValue(key: string, value: unknown): string {\n if (SAFE_KEY.test(key)) {\n if (value === undefined) return '!undefined'\n if (value === null) return '!null'\n if (typeof value === 'boolean') return `!${value}`\n // NaN, Infinity and exponents (`1e+21`, whose `+` reads back as a space) do not survive.\n if (typeof value === 'number' && NUMERIC.test(String(value))) return String(value)\n if (typeof value === 'string' && SAFE_STRING.test(value) && !NUMERIC.test(value)) {\n return value.replaceAll(' ', '+')\n }\n }\n throw new Error(\n `harnessed: arg \\`${key}\\` = ${typeof value === 'number' ? value : JSON.stringify(value)} cannot travel in a Storybook URL — ` +\n 'Storybook drops anything but letters, digits, spaces, `_`, `-`, numbers and booleans ' +\n '(and reads a numeric string back as a number), so the story would render without it. ' +\n 'Fix: add a story with that state in its `args`.',\n )\n}\n\n/**\n * One Storybook story, opened as a fixture by any runner that can navigate.\n *\n * ```ts\n * import * as LoginStories from './LoginForm.stories'\n * const story = await StoryPage.goto(pw(page), LoginStories, 'Default', { play: false })\n * const form = story.harness(LoginFormHarness)\n * ```\n *\n * Ready means rendered *and* played: it waits for the node `harnessed()` adds\n * after the story's play function finishes, so `.storybook/preview.ts` must\n * spread `harnessed()`.\n */\n@Harness({ host: testId(STORY_READY_TEST_ID) })\nexport class StoryPage extends PageHarness {\n #url = ''\n\n static async goto<M extends StoriesModule, K extends StoryName<M>>(\n env: EnvConfig,\n stories: M,\n name: K,\n options?: StoryOptions<StoryArgs<M, K>>,\n ): Promise<StoryPage> {\n const story = new StoryPage(env)\n story.#url = storyUrl(stories, name, options)\n await story.goto()\n return story\n }\n\n override get path(): string {\n return this.#url\n }\n\n /** A harness for something in the story, scoped to the whole iframe. */\n harness<T extends ScopedHarness>(HarnessClass: ScopedHarnessConstructor<T>): T {\n return new HarnessClass(this._env)\n }\n\n protected async waitForReady(): Promise<void> {\n // The marker is an empty node, so \"visible\" means nothing; count it instead.\n const deadline = Date.now() + timeoutFor()\n while ((await this.self.count()) === 0) {\n if (Date.now() > deadline) {\n throw new Error(\n `harnessed: story ${this.#url} did not finish rendering. Either it threw ` +\n 'while rendering (open it in Storybook to see why), or ' +\n '`.storybook/preview.ts` does not spread `harnessed()`.',\n )\n }\n await new Promise(resolve => setTimeout(resolve, 50))\n }\n const playError = await this.self.attribute(PLAY_ERROR_ATTRIBUTE)\n if (playError !== null) {\n throw new Error(`harnessed: the play function of story ${this.#url} threw: ${playError}`)\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/page.ts"],"names":[],"mappings":"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","file":"page.js","sourcesContent":["import { Harness, testId, timeoutFor } from '@harnessed-ts/core'\nimport type { EnvConfig, ScopedHarness, ScopedHarnessConstructor } from '@harnessed-ts/core'\nimport { PageHarness } from '@harnessed-ts/page'\nimport { storyNameFromExport, toId } from 'storybook/internal/csf'\nimport { PLAY_ERROR_ATTRIBUTE, STORY_READY_TEST_ID } from './ready'\n\n/** A CSF module as imported with `import * as Stories from './X.stories'`. */\nexport interface StoriesModule {\n default: { title?: string; id?: string }\n}\n\n/** The export names in a CSF module that are stories, not the meta. */\nexport type StoryName<M extends StoriesModule> = Exclude<keyof M, 'default'> & string\n\n/** The args a story declares, all optional: a URL overrides some, never must set all. */\nexport type StoryArgs<M extends StoriesModule, K extends StoryName<M>> = Partial<\n ArgsOf<M[K]> & ArgsOf<M['default']>\n>\n\n// An empty record rather than `never` when there are none: a meta without\n// `args` must not erase the story's own.\ntype ArgsOf<T> = T extends { args?: infer A } ? NonNullable<A> : Record<never, never>\n\nexport interface StoryOptions<Args> {\n /**\n * Overrides for the story's args, encoded in Storybook's own URL syntax.\n * Storybook applies one only when the arg has an argType — inferred from the\n * component's props, or declared in `argTypes` — and drops it otherwise.\n */\n args?: Args\n /** Toolbar globals — theme, locale, viewport. */\n globals?: Record<string, Scalar>\n /**\n * Whether the story's play function runs. Defaults to `true`, as it does in\n * Storybook; `false` opens the story in its initial state for the test to drive.\n */\n play?: boolean\n}\n\ntype Scalar = string | number | boolean | null | undefined\n\n// Storybook's own rules (storybook/internal/router): it silently drops any arg\n// that fails them, so a test would run against the wrong state. Refuse instead.\nconst SAFE_KEY = /^[a-zA-Z0-9 _-]+$/\n// Storybook reads a numeric-looking string back as a number, so '02134' would\n// arrive as 2134: letters, digits, spaces, `_` and `-`, but not a number.\nconst SAFE_STRING = /^[a-zA-Z0-9 _-]*$/\nconst NUMERIC = /^-?[0-9]+(\\.[0-9]+)?$/\n\n/**\n * The iframe URL of one story — id from the meta and export name, exactly as\n * Storybook derives it — relative to the Storybook root.\n */\nexport function storyUrl<M extends StoriesModule, K extends StoryName<M>>(\n stories: M,\n name: K,\n options: StoryOptions<StoryArgs<M, K>> = {},\n): string {\n const title = stories.default.id ?? stories.default.title\n if (title === undefined) {\n throw new Error(\n `harnessed: the stories module for \"${name}\" has no \\`title\\` on its default export, ` +\n 'so its story id cannot be derived outside Storybook. Fix: set `title` on the meta ' +\n '(or `id`, if the story id must not follow the title).',\n )\n }\n const params = [`id=${toId(title, storyNameFromExport(name))}`, 'viewMode=story']\n const args = encode(options.args ?? {})\n if (args !== '') params.push(`args=${args}`)\n const globals = encode(options.globals ?? {})\n if (globals !== '') params.push(`globals=${globals}`)\n // The switch Storybook itself reads: an embedded story renders without autoplay.\n if (options.play === false) params.push('embed=true')\n return `/iframe.html?${params.join('&')}`\n}\n\n/**\n * A stand-in for a stories module the runner cannot import, typed as that\n * module: only its meta's `title` (or `id`), which is all `StoryPage` reads.\n * An Ember `.gts` stories file, or one importing a component, needs Ember's\n * build to load; Playwright's Node process has neither.\n *\n * ```ts\n * import type * as LoginStories from './LoginForm.stories.gts'\n * const Login = storiesModule<typeof LoginStories>({ title: 'Forms/Login Form' })\n * await StoryPage.goto(pw(page), Login, 'Submitted')\n * ```\n */\nexport function storiesModule<M extends StoriesModule>(meta: StoriesModule['default']): M {\n return { default: meta } as M\n}\n\nfunction encode(values: Record<string, unknown>): string {\n return Object.entries(values)\n .map(([key, value]) => `${key}:${encodeValue(key, value)}`)\n .join(';')\n}\n\nfunction encodeValue(key: string, value: unknown): string {\n if (SAFE_KEY.test(key)) {\n if (value === undefined) return '!undefined'\n if (value === null) return '!null'\n if (typeof value === 'boolean') return `!${value}`\n // NaN, Infinity and exponents (`1e+21`, whose `+` reads back as a space) do not survive.\n if (typeof value === 'number' && NUMERIC.test(String(value))) return String(value)\n if (typeof value === 'string' && SAFE_STRING.test(value) && !NUMERIC.test(value)) {\n return value.replaceAll(' ', '+')\n }\n }\n throw new Error(\n `harnessed: arg \\`${key}\\` = ${typeof value === 'number' ? value : JSON.stringify(value)} cannot travel in a Storybook URL — ` +\n 'Storybook drops anything but letters, digits, spaces, `_`, `-`, numbers and booleans ' +\n '(and reads a numeric string back as a number), so the story would render without it. ' +\n 'Fix: add a story with that state in its `args`.',\n )\n}\n\n/**\n * One Storybook story, opened as a fixture by any runner that can navigate.\n *\n * ```ts\n * import * as LoginStories from './LoginForm.stories'\n * const story = await StoryPage.goto(pw(page), LoginStories, 'Default', { play: false })\n * const form = story.harness(LoginFormHarness)\n * ```\n *\n * Ready means rendered *and* played: it waits for the node `harnessed()` adds\n * after the story's play function finishes, so `.storybook/preview.ts` must\n * spread `harnessed()`.\n */\n@Harness({ host: testId(STORY_READY_TEST_ID) })\nexport class StoryPage extends PageHarness {\n #url = ''\n\n static async goto<M extends StoriesModule, K extends StoryName<M>>(\n env: EnvConfig,\n stories: M,\n name: K,\n options?: StoryOptions<StoryArgs<M, K>>,\n ): Promise<StoryPage> {\n const story = new StoryPage(env)\n story.#url = storyUrl(stories, name, options)\n await story.goto()\n return story\n }\n\n override get path(): string {\n return this.#url\n }\n\n /** A harness for something in the story, scoped to the whole iframe. */\n harness<T extends ScopedHarness>(HarnessClass: ScopedHarnessConstructor<T>): T {\n return new HarnessClass(this._env)\n }\n\n protected async waitForReady(): Promise<void> {\n // The marker is an empty node, so \"visible\" means nothing; count it instead.\n const deadline = Date.now() + timeoutFor()\n while ((await this.self.count()) === 0) {\n if (Date.now() > deadline) {\n throw new Error(\n `harnessed: story ${this.#url} did not finish rendering. Either it threw ` +\n 'while rendering (open it in Storybook to see why), or ' +\n '`.storybook/preview.ts` does not spread `harnessed()`.',\n )\n }\n await new Promise(resolve => setTimeout(resolve, 50))\n }\n const playError = await this.self.attribute(PLAY_ERROR_ATTRIBUTE)\n if (playError !== null) {\n throw new Error(`harnessed: the play function of story ${this.#url} threw: ${playError}`)\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@harnessed-ts/storybook",
3
- "version": "0.6.0",
4
- "description": "Harnesses in Storybook: drive play() functions through a harness, and open stories as fixtures from any runner that can navigate.",
3
+ "version": "0.7.0",
4
+ "description": "Harnesses in Storybook: drive play() functions through a harness, and open stories as fixtures from any runner that can navigate. React and Ember.",
5
5
  "keywords": [
6
6
  "testing",
7
7
  "harness",
8
8
  "storybook",
9
9
  "play-function",
10
- "interactions"
10
+ "interactions",
11
+ "ember"
11
12
  ],
12
13
  "license": "MIT",
13
14
  "contributors": [
@@ -42,6 +43,16 @@
42
43
  "import": "./dist/page.js",
43
44
  "require": "./dist/page.cjs"
44
45
  },
46
+ "./ember": {
47
+ "types": "./dist/ember.d.ts",
48
+ "import": "./dist/ember.js",
49
+ "require": "./dist/ember.cjs"
50
+ },
51
+ "./ember/node": {
52
+ "types": "./dist/ember-node.d.ts",
53
+ "import": "./dist/ember-node.js",
54
+ "require": "./dist/ember-node.cjs"
55
+ },
45
56
  "./package.json": "./package.json"
46
57
  },
47
58
  "files": [
@@ -54,21 +65,38 @@
54
65
  "node": ">=22.12.0"
55
66
  },
56
67
  "dependencies": {
57
- "@harnessed-ts/core": "^0.6.0",
58
- "@harnessed-ts/dom": "^0.6.0",
59
- "@harnessed-ts/page": "^0.6.0"
68
+ "@harnessed-ts/core": "^0.7.0",
69
+ "@harnessed-ts/dom": "^0.7.0",
70
+ "@harnessed-ts/page": "^0.7.0"
60
71
  },
61
72
  "peerDependencies": {
73
+ "@ember/test-waiters": ">=4",
62
74
  "@testing-library/dom": ">=10",
63
75
  "@testing-library/user-event": ">=14",
76
+ "content-tag": ">=3",
77
+ "ember-source": ">=6.8",
64
78
  "storybook": ">=9"
65
79
  },
80
+ "peerDependenciesMeta": {
81
+ "@ember/test-waiters": {
82
+ "optional": true
83
+ },
84
+ "content-tag": {
85
+ "optional": true
86
+ },
87
+ "ember-source": {
88
+ "optional": true
89
+ }
90
+ },
66
91
  "publishConfig": {
67
92
  "access": "public"
68
93
  },
69
94
  "devDependencies": {
95
+ "@ember/test-waiters": "^4.1.2",
70
96
  "@testing-library/dom": "^10.4.2",
71
97
  "@testing-library/user-event": "^14.6.7",
98
+ "content-tag": "^4.2.1",
99
+ "ember-source": "~7.3.0",
72
100
  "jsdom": "^30.1.2",
73
101
  "storybook": "10.6.1",
74
102
  "vitest": "^5.0.3"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ready.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMO,IAAM,mBAAA,GAAsB;AAG5B,IAAM,oBAAA,GAAuB","file":"chunk-J6DS34J4.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"]}