ngx-json-render 0.3.0 → 0.4.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/README.md CHANGED
@@ -371,6 +371,66 @@ The check waits for the spec to settle. While `loading` is true a missing child
371
371
  is a patch that has not arrived yet, not a defect, so nothing is reported and
372
372
  `strict` keeps rendering; the hooks check once, when the generation completes.
373
373
 
374
+ Pass the catalog too, and the same check covers what structure alone cannot
375
+ see — every `type` is one the catalog defines, and the props match its schema:
376
+
377
+ ```html
378
+ <json-render [spec]="spec()" [registry]="registry" [catalog]="catalog" validate="strict" />
379
+ ```
380
+
381
+ ### Capping what a spec may cost
382
+
383
+ `validate` describes a spec. `renderLimits` constrains it:
384
+
385
+ ```html
386
+ <json-render
387
+ [spec]="spec()"
388
+ [registry]="registry"
389
+ [renderLimits]="{ maxElements: 500, maxDepth: 16, maxRepeatItems: 200 }"
390
+ />
391
+ ```
392
+
393
+ ```ts
394
+ readonly ui = injectUIStream({
395
+ api: '/api/generate',
396
+ renderLimits: { maxElements: 500, maxDepth: 16 },
397
+ });
398
+ ```
399
+
400
+ | Limit | What it caps | When it is passed |
401
+ | ---------------- | ------------------------------- | --------------------------------------------------------------- |
402
+ | `maxElements` | Elements in the spec | Nothing renders — there is no useful part of an oversized graph |
403
+ | `maxDepth` | Nesting, counting the root as 1 | Elements below the cap do not render; what fits still does |
404
+ | `maxRepeatItems` | Items one `repeat` expands | The surplus items do not render |
405
+
406
+ Every limit is opt-in and unset means unlimited; there are no defaults, so
407
+ upgrading changes nothing about what your app renders today.
408
+
409
+ An element a cap refuses does not act either. `watch` is the one thing an
410
+ element does without being on screen, and it stays unwired while the element is
411
+ capped — otherwise a spec could keep dispatching actions from behind a limit
412
+ that was supposed to have stopped it. The same holds for the element that
413
+ closes a cycle.
414
+
415
+ Two things follow from limits being a control rather than a report. They are
416
+ enforced in **every** mode, `validate="off"` included — the app already made
417
+ the decision by setting a number. And they apply while `loading`: a partial
418
+ spec is a subset of the finished one, so a cap can only ever fire early, never
419
+ falsely. Under `strict`, a spec over `maxDepth` is refused outright rather than
420
+ truncated, which is how an app says it would rather draw nothing than draw the
421
+ first sixteen levels of something hostile.
422
+
423
+ In the hooks, a limit fails the generation instead of calling `onComplete`, in
424
+ any mode — the same reasoning as `strict`: `onComplete` is where apps persist a
425
+ spec, and this is one the app has already refused.
426
+
427
+ One order is deliberate rather than incidental. The cheap caps run before the
428
+ structural check, because core's `validateSpec` walks the tree by recursion:
429
+ the specs that most need a limit are exactly the ones that would overflow the
430
+ stack proving they exceed it. If you accept specs you did not generate, set
431
+ `maxDepth` — with no cap there is nothing to stop the check from recursing as
432
+ deep as the spec asks.
433
+
374
434
  ## Testing
375
435
 
376
436
  `ngx-json-render/testing` is a separate entry point, so nothing in it can reach
@@ -616,10 +676,36 @@ string to the `navigate` callback you provide, verbatim. Treat it as untrusted:
616
676
  match it against known routes, and never hand it to `window.location` or
617
677
  `router.navigateByUrl` unchecked.
618
678
 
619
- **A spec sizes its own render tree.** `repeat` iterates a state array the spec
620
- may itself have supplied, so specs are a denial-of-service surface against the
621
- browser tab. Cap spec size and array lengths at the boundary where you accept
622
- one.
679
+ **A spec cannot render forever.** Two elements naming each other as children,
680
+ or one naming itself, would recurse until the tab died. The renderer refuses to
681
+ draw an element that is rendering itself again _without reading any deeper into
682
+ state_, so the cycle is broken where it closes and everything above it still
683
+ renders. This holds in every mode, including `validate="off"` and mid-stream:
684
+ it is a crash guard, not an opinion about spec quality. Core's `validateSpec`
685
+ does not report cycles, so nothing else in the stack catches this for you.
686
+
687
+ Recursion that goes somewhere is untouched. A tree — a comment thread, a file
688
+ browser, a nested menu — is an element repeating over a path relative to the
689
+ item it is already inside (`{"$item": "children"}`) and rendering itself for
690
+ each one, so every pass reads one level further in and the drawing ends where
691
+ the data does. What the guard stops is the pass that reads the same array
692
+ again: `repeat` over a fixed `/items` inside itself never runs out, however
693
+ much data there is.
694
+
695
+ **A spec sizes its own render tree, up to the caps you set.** `repeat` iterates
696
+ a state array the spec may itself have supplied, and nesting costs a component
697
+ per level, so specs are a denial-of-service surface against the browser tab.
698
+ `renderLimits` is the cap: `maxElements`, `maxDepth` and `maxRepeatItems`,
699
+ enforced in every mode. They are unset by default — a renderer cannot guess
700
+ what your catalog considers a reasonable page — so an app taking specs it did
701
+ not generate should set all three. See
702
+ [Capping what a spec may cost](#capping-what-a-spec-may-cost).
703
+
704
+ **A spec can name components you never built.** `validate` with a `catalog`
705
+ reports every `type` the catalog does not define and every prop its schema
706
+ rejects; `strict` refuses such a spec outright. Without a catalog the renderer
707
+ only warns and draws nothing in that element's place, which degrades well but
708
+ tells you nothing until you read the console.
623
709
 
624
710
  **`confirm` is a UX affordance, not a security control.** It routes an action
625
711
  through the confirmation dialog before the handler runs, but it is set on the
@@ -635,6 +721,10 @@ Injectables/helpers: `injectRenderContext`, `injectElementKey`, `injectRepeatSco
635
721
 
636
722
  Tokens: `JR_CONFIRM_DIALOG` (replace the confirmation dialog), `JR_CONFIRM_LABELS` (its two words), `CONFIRM_CONTEXT`, `RENDER_CONTEXT`, `REPEAT_SCOPE`.
637
723
 
724
+ Spec checking: `checkSpec`, `formatSpecCheckIssues`, and the types
725
+ `RenderLimits`, `SpecCheck`, `SpecCheckIssue`, `SpecCheckIssueCode`,
726
+ `SpecCheckOptions`, `SpecCatalog`, `SpecValidationMode`.
727
+
638
728
  `injectActions().execute()` rejects when the user dismisses a `confirm`
639
729
  dialog, which is a normal gesture rather than a failure — `isActionCancelled(error)`
640
730
  is how you tell the two apart.
@@ -647,21 +737,23 @@ Everything from `@json-render/core` (types, `createStateStore`, `nestedToFlat`,
647
737
 
648
738
  ## Renderer inputs
649
739
 
650
- | Input | Type | Purpose |
651
- | --------------------- | ------------------------------------ | ------------------------------------------------------ |
652
- | `spec` | `Spec \| null` | The UI spec (may be partial while streaming) |
653
- | `registry` | `ComponentRegistry` | Catalog type → Angular component |
654
- | `loading` | `boolean` | Suppress missing-element warnings while streaming |
655
- | `fallback` | `Type<unknown>` | Component for unknown types |
656
- | `validate` | `'off' \| 'warn' \| 'strict'` | Check the settled spec's structure (default `'off'`) |
657
- | `state` | `StateModel` | Initial state (uncontrolled; defaults to `spec.state`) |
658
- | `store` | `StateStore` | External store (controlled mode) |
659
- | `handlers` | `Record<string, ActionHandler>` | Action handlers |
660
- | `onAction` | `(name, params) => unknown` | Catch-all action handler |
661
- | `navigate` | `(path) => void` | Used by `onSuccess: { navigate }` |
662
- | `validationFunctions` | `Record<string, ValidationFunction>` | Custom validation |
663
- | `functions` | `Record<string, ComputedFunction>` | `$computed` functions |
664
- | `directives` | `DirectiveDefinition[]` | Custom `$`-prefixed expressions |
740
+ | Input | Type | Purpose |
741
+ | --------------------- | ------------------------------------ | ------------------------------------------------------- |
742
+ | `spec` | `Spec \| null` | The UI spec (may be partial while streaming) |
743
+ | `registry` | `ComponentRegistry` | Catalog type → Angular component |
744
+ | `loading` | `boolean` | Suppress missing-element warnings while streaming |
745
+ | `fallback` | `Type<unknown>` | Component for unknown types |
746
+ | `validate` | `'off' \| 'warn' \| 'strict'` | Check the settled spec's structure (default `'off'`) |
747
+ | `renderLimits` | `RenderLimits` | Cap elements, depth and repeat expansion (default none) |
748
+ | `catalog` | `Catalog` | Also check types and props against the catalog |
749
+ | `state` | `StateModel` | Initial state (uncontrolled; defaults to `spec.state`) |
750
+ | `store` | `StateStore` | External store (controlled mode) |
751
+ | `handlers` | `Record<string, ActionHandler>` | Action handlers |
752
+ | `onAction` | `(name, params) => unknown` | Catch-all action handler |
753
+ | `navigate` | `(path) => void` | Used by `onSuccess: { navigate }` |
754
+ | `validationFunctions` | `Record<string, ValidationFunction>` | Custom validation |
755
+ | `functions` | `Record<string, ComputedFunction>` | `$computed` functions |
756
+ | `directives` | `DirectiveDefinition[]` | Custom `$`-prefixed expressions |
665
757
 
666
758
  Output: `(stateChange)` — batched `{ path, value }[]` in uncontrolled mode.
667
759
 
@@ -194,7 +194,7 @@ class JrTestHost {
194
194
  [directives]="directives()"
195
195
  (stateChange)="record($event)"
196
196
  />
197
- `, isInline: true, dependencies: [{ kind: "component", type: JsonRenderer, selector: "json-render", inputs: ["spec", "registry", "loading", "fallback", "validate", "state", "store", "handlers", "onAction", "navigate", "validationFunctions", "functions", "directives"], outputs: ["stateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
197
+ `, isInline: true, dependencies: [{ kind: "component", type: JsonRenderer, selector: "json-render", inputs: ["spec", "registry", "loading", "fallback", "validate", "renderLimits", "catalog", "state", "store", "handlers", "onAction", "navigate", "validationFunctions", "functions", "directives"], outputs: ["stateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
198
198
  }
199
199
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrTestHost, decorators: [{
200
200
  type: Component,
@@ -1 +1 @@
1
- {"version":3,"file":"ngx-json-render-testing.mjs","sources":["../../../projects/ngx-json-render/testing/src/recorded-transport.ts","../../../projects/ngx-json-render/testing/src/render-spec.ts","../../../projects/ngx-json-render/testing/src/public-api.ts","../../../projects/ngx-json-render/testing/src/ngx-json-render-testing.ts"],"sourcesContent":["import type { Spec } from '@json-render/core';\n\n/**\n * The lines of one recorded response body, in the order the server sent them,\n * without their trailing newlines.\n */\nexport type Recording = readonly string[];\n\n/**\n * What to replay for a request. Either one recording for every request, a\n * lookup by prompt, or a function for anything more involved.\n */\nexport type Recordings =\n | Recording\n | Readonly<Record<string, Recording>>\n | ((\n prompt: string,\n body: Readonly<Record<string, unknown>>,\n ) => Recording | undefined);\n\n/** An error the recorded server answers with instead of a stream. */\nexport interface RecordedFailure {\n /** HTTP status. Defaults to 500. */\n status?: number;\n /** The `message` field of the JSON error body. */\n message?: string;\n}\n\n/** Options for {@link recordedTransport}. */\nexport interface RecordedTransportOptions {\n /**\n * Milliseconds between lines — the pace a model would emit them at.\n * Defaults to 0, which still yields to the event loop between lines, so a\n * test can observe the stream in flight.\n */\n delayMs?: number;\n /**\n * Answer with an error instead of a stream. A function is called per\n * request, so a test can turn the failure on and off between sends.\n */\n fail?: RecordedFailure | (() => RecordedFailure | null | undefined);\n /**\n * Pull the prompt out of the request body. The default reads `prompt`\n * (what `injectUIStream` sends) and falls back to the last message's\n * content (what `injectChatUI` sends).\n */\n promptOf?: (body: Readonly<Record<string, unknown>>) => string;\n}\n\n/** The rejection `fetch` raises when its signal fires. */\nfunction abortError(): Error {\n const error = new Error('The operation was aborted.');\n error.name = 'AbortError';\n return error;\n}\n\n/** A JSON error body, shaped the way the streaming hooks read it. */\nfunction errorResponse(status: number, message: string): Response {\n return new Response(JSON.stringify({ message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nfunction parseBody(body: BodyInit | null | undefined): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(String(body ?? '{}'));\n return parsed && typeof parsed === 'object'\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\n/**\n * The prompt of a request, from either hook's body shape. Both are the\n * package's own, so the default knows them and a test does not have to.\n */\nfunction defaultPromptOf(body: Readonly<Record<string, unknown>>): string {\n const prompt = body['prompt'];\n if (typeof prompt === 'string') return prompt;\n\n const messages = body['messages'];\n if (!Array.isArray(messages) || messages.length === 0) return '';\n const last: unknown = messages[messages.length - 1];\n const content = (last as { content?: unknown } | null)?.content;\n return typeof content === 'string' ? content : '';\n}\n\nfunction toFinder(\n recordings: Recordings,\n): (prompt: string, body: Record<string, unknown>) => Recording | undefined {\n if (typeof recordings === 'function') return recordings;\n if (Array.isArray(recordings)) return () => recordings as Recording;\n const byPrompt = recordings as Readonly<Record<string, Recording>>;\n return (prompt) => byPrompt[prompt];\n}\n\nfunction replay(\n lines: Recording,\n delayMs: number,\n signal: AbortSignal | null,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n return new ReadableStream<Uint8Array>({\n start(controller) {\n let index = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n // A superseded or stopped generation has to die mid-flight the way a\n // real request does, or a test of that behaviour proves nothing.\n const onAbort = () => {\n if (timer) clearTimeout(timer);\n controller.error(abortError());\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n const push = () => {\n if (index >= lines.length) {\n signal?.removeEventListener('abort', onAbort);\n controller.close();\n return;\n }\n controller.enqueue(encoder.encode(`${lines[index++]}\\n`));\n timer = setTimeout(push, delayMs);\n };\n timer = setTimeout(push, delayMs);\n },\n });\n}\n\n/**\n * A `fetch` that answers the streaming hooks from a recording instead of a\n * server.\n *\n * Both {@link injectUIStream} and {@link injectChatUI} take any function with\n * fetch's shape, so a test runs the real client — request body, streamed\n * lines, usage metadata, abort on supersede — with no server, no API key and\n * no global patched behind the test's back. Only the transport is recorded.\n *\n * @example\n * ```ts\n * const ui = injectUIStream({\n * api: '/api/generate',\n * fetch: recordedTransport(specStream(spec)),\n * });\n * ```\n *\n * @example Different answers per prompt, at a visible pace:\n * ```ts\n * recordedTransport(\n * { 'a dashboard': specStream(dashboard), 'a form': specStream(form) },\n * { delayMs: 20 },\n * );\n * ```\n */\nexport function recordedTransport(\n recordings: Recordings,\n options: RecordedTransportOptions = {},\n): typeof globalThis.fetch {\n const { delayMs = 0, promptOf = defaultPromptOf } = options;\n const find = toFinder(recordings);\n\n return async (_input, init) => {\n const failure =\n typeof options.fail === 'function' ? options.fail() : options.fail;\n if (failure) {\n return errorResponse(\n failure.status ?? 500,\n failure.message ?? 'The recorded server failed this request.',\n );\n }\n\n const body = parseBody(init?.body);\n const prompt = promptOf(body);\n const lines = find(prompt, body);\n if (!lines) {\n return errorResponse(404, `No recording for ${JSON.stringify(prompt)}.`);\n }\n\n const signal = init?.signal ?? null;\n if (signal?.aborted) throw abortError();\n\n return new Response(replay(lines, delayMs, signal), { status: 200 });\n };\n}\n\n/** Escape a JSON Pointer segment (RFC 6901). */\nfunction pointer(segment: string): string {\n return segment.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\n/**\n * The JSONL patch lines a model would emit to build `spec`: the root, then\n * the state, then one line per element in declaration order.\n *\n * Recording a stream by hand means writing patches by hand; this writes them\n * from the spec a test already has, so the test says what it renders rather\n * than how the wire spells it.\n */\nexport function specStream(spec: Spec): string[] {\n const lines = [\n JSON.stringify({ op: 'add', path: '/root', value: spec.root }),\n ];\n if (spec.state) {\n lines.push(\n JSON.stringify({ op: 'add', path: '/state', value: spec.state }),\n );\n }\n for (const [key, element] of Object.entries(spec.elements ?? {})) {\n lines.push(\n JSON.stringify({\n op: 'add',\n path: `/elements/${pointer(key)}`,\n value: element,\n }),\n );\n }\n return lines;\n}\n\n/**\n * The usage metadata line `injectUIStream` reads into its `usage` signal.\n * Append it to a recording to exercise that path.\n */\nexport function usageLine(usage: {\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n}): string {\n return JSON.stringify({\n __meta: 'usage',\n promptTokens: usage.promptTokens ?? 0,\n completionTokens: usage.completionTokens ?? 0,\n totalTokens: usage.totalTokens ?? 0,\n });\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n type Provider,\n type Type,\n provideZonelessChangeDetection,\n signal,\n} from '@angular/core';\nimport { type ComponentFixture, TestBed } from '@angular/core/testing';\nimport type {\n ActionHandler,\n ComputedFunction,\n DirectiveDefinition,\n Spec,\n StateStore,\n ValidationFunction,\n} from '@json-render/core';\nimport {\n type ComponentRegistry,\n JsonRenderStateService,\n JsonRenderValidationService,\n JsonRenderer,\n type RegistryEntry,\n type StateChange,\n type StateModel,\n} from 'ngx-json-render';\n\n/** One action the spec asked for, in the order it was dispatched. */\nexport interface DispatchedAction {\n name: string;\n params?: Record<string, unknown>;\n}\n\n/** Options for {@link renderSpec}. Everything but `registry` is optional. */\nexport interface RenderSpecOptions {\n /** Catalog type name → Angular component. The one required option. */\n registry: ComponentRegistry;\n /** Initial state model. Defaults to the spec's own `state`. */\n state?: StateModel;\n /** External store, for controlled mode. */\n store?: StateStore | null;\n /** Action handlers by name. Dispatches are recorded either way. */\n handlers?: Record<string, ActionHandler>;\n /** Catch-all handler for actions with no dedicated handler. */\n onAction?: (name: string, params?: Record<string, unknown>) => unknown;\n /** Navigation function for `onSuccess: { navigate }`. */\n navigate?: (path: string) => void;\n /** Fallback component for unknown types. */\n fallback?: Type<unknown> | RegistryEntry | null;\n /** Named functions for `$computed` props. */\n functions?: Record<string, ComputedFunction>;\n /** Custom validation functions. */\n validationFunctions?: Record<string, ValidationFunction>;\n /** Custom directives for `$`-prefixed values. */\n directives?: DirectiveDefinition[];\n /** Render as if the spec were still streaming. */\n loading?: boolean;\n /** Extra TestBed providers, e.g. a `JR_CONFIRM_DIALOG` replacement. */\n providers?: Provider[];\n}\n\n/**\n * A rendered spec, and the handful of things a test does to one.\n *\n * `fixture` and `element` are there for everything this does not cover — the\n * harness is a shortcut, not a wall.\n */\nexport interface SpecHarness {\n /** The fixture of the host that owns the `<json-render>`. */\n readonly fixture: ComponentFixture<unknown>;\n /** The host element, the root of the rendered DOM. */\n readonly element: HTMLElement;\n /** The renderer instance. */\n readonly renderer: JsonRenderer;\n /** The renderer's state store. */\n readonly store: JsonRenderStateService;\n /** The renderer's validation service. */\n readonly validation: JsonRenderValidationService;\n /** Every action the spec dispatched, handled or not, in order. */\n readonly dispatched: readonly DispatchedAction[];\n /** Every state change the renderer emitted, flattened, in order. */\n readonly changes: readonly StateChange[];\n\n /** Render a different spec (a later streaming frame, say). */\n setSpec(spec: Spec | null): Promise<void>;\n /** Flip the loading flag catalog components see. */\n setLoading(loading: boolean): Promise<void>;\n /** Let effects, promises and change detection finish. */\n settle(): Promise<void>;\n\n /** Read a state path, e.g. `/form/email`. */\n read(path: string): unknown;\n /** Write a state path and settle. */\n write(path: string, value: unknown): Promise<void>;\n /** The whole state model. */\n state(): StateModel;\n\n /** Trimmed text of the first match, or of everything when given nothing. */\n text(selector?: string): string;\n /** Trimmed text of every match. */\n texts(selector: string): string[];\n /** The first match. Throws when there is none. */\n find<T extends HTMLElement>(selector: string): T;\n /** Every match, possibly none. */\n findAll<T extends HTMLElement>(selector: string): T[];\n /** Click an element (or the first match of a selector) and settle. */\n click(target: string | Element): Promise<void>;\n /** Set an input's value, fire `input`, and settle. */\n fill(target: string | Element, value: string): Promise<void>;\n\n /** Destroy the fixture. Rarely needed — TestBed resets between tests. */\n destroy(): void;\n}\n\n@Component({\n selector: 'jr-test-host',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [JsonRenderer],\n template: `\n <json-render\n [spec]=\"spec()\"\n [registry]=\"registry()\"\n [loading]=\"loading()\"\n [state]=\"state()\"\n [store]=\"store()\"\n [handlers]=\"handlers()\"\n [onAction]=\"onAction()\"\n [navigate]=\"navigate()\"\n [fallback]=\"fallback()\"\n [functions]=\"functions()\"\n [validationFunctions]=\"validationFunctions()\"\n [directives]=\"directives()\"\n (stateChange)=\"record($event)\"\n />\n `,\n})\nclass JrTestHost {\n readonly spec = signal<Spec | null>(null);\n readonly registry = signal<ComponentRegistry>({});\n readonly loading = signal(false);\n readonly state = signal<StateModel | undefined>(undefined);\n readonly store = signal<StateStore | null>(null);\n readonly handlers = signal<Record<string, ActionHandler> | undefined>(\n undefined,\n );\n readonly onAction = signal<\n ((name: string, params?: Record<string, unknown>) => unknown) | null\n >(null);\n readonly navigate = signal<((path: string) => void) | null>(null);\n readonly fallback = signal<Type<unknown> | RegistryEntry | null>(null);\n readonly functions = signal<Record<string, ComputedFunction> | undefined>(\n undefined,\n );\n readonly validationFunctions = signal<\n Record<string, ValidationFunction> | undefined\n >(undefined);\n readonly directives = signal<DirectiveDefinition[] | undefined>(undefined);\n\n record: (changes: StateChange[]) => void = () => {};\n}\n\n/**\n * Wrap every handler so a dispatch is recorded whether or not the action is\n * handled. Without this, `dispatched` would silently omit exactly the actions\n * a test bothered to wire up.\n */\nfunction recordingHandlers(\n handlers: Record<string, ActionHandler> | undefined,\n dispatched: DispatchedAction[],\n): Record<string, ActionHandler> | undefined {\n if (!handlers) return undefined;\n const wrapped: Record<string, ActionHandler> = {};\n for (const [name, handler] of Object.entries(handlers)) {\n wrapped[name] = (params: Record<string, unknown>) => {\n dispatched.push({ name, params });\n return handler(params);\n };\n }\n return wrapped;\n}\n\n/**\n * Configure the TestBed, unless the test already did.\n *\n * A test that called `TestBed.runInInjectionContext` first — or that renders\n * a second spec — has an instantiated module, and configuring it again\n * throws. Its own module stands; the only thing that cannot be salvaged is\n * this call's `providers`, so that is the one case worth failing over.\n */\nfunction configureTestBed(providers: Provider[]): void {\n try {\n TestBed.configureTestingModule({\n providers: [provideZonelessChangeDetection(), ...providers],\n });\n } catch {\n if (providers.length > 0) {\n throw new Error(\n 'renderSpec: the TestBed was already instantiated, so `providers` cannot be applied. Pass them to your own TestBed.configureTestingModule() call instead.',\n );\n }\n }\n}\n\n/**\n * Render a spec against a registry and hand back the few things a test does\n * to it.\n *\n * Testing a catalog component otherwise means a host component, a TestBed\n * module, a zoneless provider, two settle passes and a `querySelector` — the\n * same twenty lines in every project that adopts this package, none of them\n * about the component under test.\n *\n * @example\n * ```ts\n * const ui = await renderSpec(\n * {\n * root: 'save',\n * elements: {\n * save: { type: 'Button', props: { label: 'Save' }, on: { press: { action: 'save' } } },\n * },\n * },\n * { registry: { Button: MyButton } },\n * );\n *\n * expect(ui.text('button')).toBe('Save');\n * await ui.click('button');\n * expect(ui.dispatched).toEqual([{ name: 'save', params: {} }]);\n * ```\n */\nexport async function renderSpec(\n spec: Spec | null,\n options: RenderSpecOptions,\n): Promise<SpecHarness> {\n const dispatched: DispatchedAction[] = [];\n const changes: StateChange[] = [];\n\n configureTestBed(options.providers ?? []);\n\n const fixture = TestBed.createComponent(JrTestHost);\n const host = fixture.componentInstance;\n\n host.registry.set(options.registry);\n host.loading.set(options.loading ?? false);\n host.state.set(options.state);\n host.store.set(options.store ?? null);\n host.handlers.set(recordingHandlers(options.handlers, dispatched));\n host.onAction.set((name, params) => {\n dispatched.push({ name, params });\n return options.onAction?.(name, params);\n });\n host.navigate.set(options.navigate ?? null);\n host.fallback.set(options.fallback ?? null);\n host.functions.set(options.functions);\n host.validationFunctions.set(options.validationFunctions);\n host.directives.set(options.directives);\n host.record = (batch) => changes.push(...batch);\n host.spec.set(spec);\n\n const settle = async () => {\n // Twice: the first pass runs the effects that a render schedules, the\n // second renders what those effects changed. One pass leaves a test\n // asserting against a frame that no browser would ever show.\n await fixture.whenStable();\n fixture.detectChanges();\n await fixture.whenStable();\n };\n await settle();\n\n const element = fixture.nativeElement as HTMLElement;\n // Found by predicate rather than with `By.directive`, which would make\n // @angular/platform-browser a dependency of this entry point for one line.\n const rendererElement = fixture.debugElement.query(\n (candidate) => candidate.componentInstance instanceof JsonRenderer,\n );\n const renderer = rendererElement.componentInstance as JsonRenderer;\n const store = renderer.stateStore;\n // From the renderer's own injector: these services are provided by the\n // component, so the root TestBed injector has never heard of them.\n const validation = rendererElement.injector.get(JsonRenderValidationService);\n\n const resolve = (target: string | Element): HTMLElement => {\n if (typeof target !== 'string') return target as HTMLElement;\n const found = element.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(\n `renderSpec: nothing matches ${JSON.stringify(target)}. Rendered HTML:\\n${element.innerHTML}`,\n );\n }\n return found;\n };\n\n return {\n fixture,\n element,\n renderer,\n store,\n validation,\n dispatched,\n changes,\n\n async setSpec(next) {\n host.spec.set(next);\n await settle();\n },\n async setLoading(loading) {\n host.loading.set(loading);\n await settle();\n },\n settle,\n\n read: (path) => store.get(path),\n async write(path, value) {\n store.set(path, value);\n await settle();\n },\n state: () => store.state(),\n\n text: (selector) =>\n ((selector ? resolve(selector) : element).textContent ?? '').trim(),\n texts: (selector) =>\n Array.from(element.querySelectorAll(selector)).map((el) =>\n (el.textContent ?? '').trim(),\n ),\n find: <T extends HTMLElement>(selector: string) => resolve(selector) as T,\n findAll: <T extends HTMLElement>(selector: string) =>\n Array.from(element.querySelectorAll<T>(selector)),\n async click(target) {\n resolve(target).click();\n await settle();\n },\n async fill(target, value) {\n const input = resolve(target) as HTMLInputElement;\n input.value = value;\n input.dispatchEvent(new Event('input', { bubbles: true }));\n await settle();\n },\n\n destroy: () => fixture.destroy(),\n };\n}\n","/*\n * Public API Surface of ngx-json-render/testing\n *\n * Test utilities, shipped as a separate entry point so nothing here can be\n * pulled into an application bundle by accident.\n */\n\nexport {\n recordedTransport,\n specStream,\n usageLine,\n type Recording,\n type Recordings,\n type RecordedFailure,\n type RecordedTransportOptions,\n} from './recorded-transport';\nexport {\n renderSpec,\n type DispatchedAction,\n type RenderSpecOptions,\n type SpecHarness,\n} from './render-spec';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngx-json-render-testing.mjs","sources":["../../../projects/ngx-json-render/testing/src/recorded-transport.ts","../../../projects/ngx-json-render/testing/src/render-spec.ts","../../../projects/ngx-json-render/testing/src/public-api.ts","../../../projects/ngx-json-render/testing/src/ngx-json-render-testing.ts"],"sourcesContent":["import type { Spec } from '@json-render/core';\n\n/**\n * The lines of one recorded response body, in the order the server sent them,\n * without their trailing newlines.\n */\nexport type Recording = readonly string[];\n\n/**\n * What to replay for a request. Either one recording for every request, a\n * lookup by prompt, or a function for anything more involved.\n */\nexport type Recordings =\n | Recording\n | Readonly<Record<string, Recording>>\n | ((\n prompt: string,\n body: Readonly<Record<string, unknown>>,\n ) => Recording | undefined);\n\n/** An error the recorded server answers with instead of a stream. */\nexport interface RecordedFailure {\n /** HTTP status. Defaults to 500. */\n status?: number;\n /** The `message` field of the JSON error body. */\n message?: string;\n}\n\n/** Options for {@link recordedTransport}. */\nexport interface RecordedTransportOptions {\n /**\n * Milliseconds between lines — the pace a model would emit them at.\n * Defaults to 0, which still yields to the event loop between lines, so a\n * test can observe the stream in flight.\n */\n delayMs?: number;\n /**\n * Answer with an error instead of a stream. A function is called per\n * request, so a test can turn the failure on and off between sends.\n */\n fail?: RecordedFailure | (() => RecordedFailure | null | undefined);\n /**\n * Pull the prompt out of the request body. The default reads `prompt`\n * (what `injectUIStream` sends) and falls back to the last message's\n * content (what `injectChatUI` sends).\n */\n promptOf?: (body: Readonly<Record<string, unknown>>) => string;\n}\n\n/** The rejection `fetch` raises when its signal fires. */\nfunction abortError(): Error {\n const error = new Error('The operation was aborted.');\n error.name = 'AbortError';\n return error;\n}\n\n/** A JSON error body, shaped the way the streaming hooks read it. */\nfunction errorResponse(status: number, message: string): Response {\n return new Response(JSON.stringify({ message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nfunction parseBody(body: BodyInit | null | undefined): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(String(body ?? '{}'));\n return parsed && typeof parsed === 'object'\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\n/**\n * The prompt of a request, from either hook's body shape. Both are the\n * package's own, so the default knows them and a test does not have to.\n */\nfunction defaultPromptOf(body: Readonly<Record<string, unknown>>): string {\n const prompt = body['prompt'];\n if (typeof prompt === 'string') return prompt;\n\n const messages = body['messages'];\n if (!Array.isArray(messages) || messages.length === 0) return '';\n const last: unknown = messages[messages.length - 1];\n const content = (last as { content?: unknown } | null)?.content;\n return typeof content === 'string' ? content : '';\n}\n\nfunction toFinder(\n recordings: Recordings,\n): (prompt: string, body: Record<string, unknown>) => Recording | undefined {\n if (typeof recordings === 'function') return recordings;\n if (Array.isArray(recordings)) return () => recordings as Recording;\n const byPrompt = recordings as Readonly<Record<string, Recording>>;\n return (prompt) => byPrompt[prompt];\n}\n\nfunction replay(\n lines: Recording,\n delayMs: number,\n signal: AbortSignal | null,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n return new ReadableStream<Uint8Array>({\n start(controller) {\n let index = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n // A superseded or stopped generation has to die mid-flight the way a\n // real request does, or a test of that behaviour proves nothing.\n const onAbort = () => {\n if (timer) clearTimeout(timer);\n controller.error(abortError());\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n const push = () => {\n if (index >= lines.length) {\n signal?.removeEventListener('abort', onAbort);\n controller.close();\n return;\n }\n controller.enqueue(encoder.encode(`${lines[index++]}\\n`));\n timer = setTimeout(push, delayMs);\n };\n timer = setTimeout(push, delayMs);\n },\n });\n}\n\n/**\n * A `fetch` that answers the streaming hooks from a recording instead of a\n * server.\n *\n * Both {@link injectUIStream} and {@link injectChatUI} take any function with\n * fetch's shape, so a test runs the real client — request body, streamed\n * lines, usage metadata, abort on supersede — with no server, no API key and\n * no global patched behind the test's back. Only the transport is recorded.\n *\n * @example\n * ```ts\n * const ui = injectUIStream({\n * api: '/api/generate',\n * fetch: recordedTransport(specStream(spec)),\n * });\n * ```\n *\n * @example Different answers per prompt, at a visible pace:\n * ```ts\n * recordedTransport(\n * { 'a dashboard': specStream(dashboard), 'a form': specStream(form) },\n * { delayMs: 20 },\n * );\n * ```\n */\nexport function recordedTransport(\n recordings: Recordings,\n options: RecordedTransportOptions = {},\n): typeof globalThis.fetch {\n const { delayMs = 0, promptOf = defaultPromptOf } = options;\n const find = toFinder(recordings);\n\n return async (_input, init) => {\n const failure =\n typeof options.fail === 'function' ? options.fail() : options.fail;\n if (failure) {\n return errorResponse(\n failure.status ?? 500,\n failure.message ?? 'The recorded server failed this request.',\n );\n }\n\n const body = parseBody(init?.body);\n const prompt = promptOf(body);\n const lines = find(prompt, body);\n if (!lines) {\n return errorResponse(404, `No recording for ${JSON.stringify(prompt)}.`);\n }\n\n const signal = init?.signal ?? null;\n if (signal?.aborted) throw abortError();\n\n return new Response(replay(lines, delayMs, signal), { status: 200 });\n };\n}\n\n/** Escape a JSON Pointer segment (RFC 6901). */\nfunction pointer(segment: string): string {\n return segment.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\n/**\n * The JSONL patch lines a model would emit to build `spec`: the root, then\n * the state, then one line per element in declaration order.\n *\n * Recording a stream by hand means writing patches by hand; this writes them\n * from the spec a test already has, so the test says what it renders rather\n * than how the wire spells it.\n */\nexport function specStream(spec: Spec): string[] {\n const lines = [\n JSON.stringify({ op: 'add', path: '/root', value: spec.root }),\n ];\n if (spec.state) {\n lines.push(\n JSON.stringify({ op: 'add', path: '/state', value: spec.state }),\n );\n }\n for (const [key, element] of Object.entries(spec.elements ?? {})) {\n lines.push(\n JSON.stringify({\n op: 'add',\n path: `/elements/${pointer(key)}`,\n value: element,\n }),\n );\n }\n return lines;\n}\n\n/**\n * The usage metadata line `injectUIStream` reads into its `usage` signal.\n * Append it to a recording to exercise that path.\n */\nexport function usageLine(usage: {\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n}): string {\n return JSON.stringify({\n __meta: 'usage',\n promptTokens: usage.promptTokens ?? 0,\n completionTokens: usage.completionTokens ?? 0,\n totalTokens: usage.totalTokens ?? 0,\n });\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n type Provider,\n type Type,\n provideZonelessChangeDetection,\n signal,\n} from '@angular/core';\nimport { type ComponentFixture, TestBed } from '@angular/core/testing';\nimport type {\n ActionHandler,\n ComputedFunction,\n DirectiveDefinition,\n Spec,\n StateStore,\n ValidationFunction,\n} from '@json-render/core';\nimport {\n type ComponentRegistry,\n JsonRenderStateService,\n JsonRenderValidationService,\n JsonRenderer,\n type RegistryEntry,\n type StateChange,\n type StateModel,\n} from 'ngx-json-render';\n\n/** One action the spec asked for, in the order it was dispatched. */\nexport interface DispatchedAction {\n name: string;\n params?: Record<string, unknown>;\n}\n\n/** Options for {@link renderSpec}. Everything but `registry` is optional. */\nexport interface RenderSpecOptions {\n /** Catalog type name → Angular component. The one required option. */\n registry: ComponentRegistry;\n /** Initial state model. Defaults to the spec's own `state`. */\n state?: StateModel;\n /** External store, for controlled mode. */\n store?: StateStore | null;\n /** Action handlers by name. Dispatches are recorded either way. */\n handlers?: Record<string, ActionHandler>;\n /** Catch-all handler for actions with no dedicated handler. */\n onAction?: (name: string, params?: Record<string, unknown>) => unknown;\n /** Navigation function for `onSuccess: { navigate }`. */\n navigate?: (path: string) => void;\n /** Fallback component for unknown types. */\n fallback?: Type<unknown> | RegistryEntry | null;\n /** Named functions for `$computed` props. */\n functions?: Record<string, ComputedFunction>;\n /** Custom validation functions. */\n validationFunctions?: Record<string, ValidationFunction>;\n /** Custom directives for `$`-prefixed values. */\n directives?: DirectiveDefinition[];\n /** Render as if the spec were still streaming. */\n loading?: boolean;\n /** Extra TestBed providers, e.g. a `JR_CONFIRM_DIALOG` replacement. */\n providers?: Provider[];\n}\n\n/**\n * A rendered spec, and the handful of things a test does to one.\n *\n * `fixture` and `element` are there for everything this does not cover — the\n * harness is a shortcut, not a wall.\n */\nexport interface SpecHarness {\n /** The fixture of the host that owns the `<json-render>`. */\n readonly fixture: ComponentFixture<unknown>;\n /** The host element, the root of the rendered DOM. */\n readonly element: HTMLElement;\n /** The renderer instance. */\n readonly renderer: JsonRenderer;\n /** The renderer's state store. */\n readonly store: JsonRenderStateService;\n /** The renderer's validation service. */\n readonly validation: JsonRenderValidationService;\n /** Every action the spec dispatched, handled or not, in order. */\n readonly dispatched: readonly DispatchedAction[];\n /** Every state change the renderer emitted, flattened, in order. */\n readonly changes: readonly StateChange[];\n\n /** Render a different spec (a later streaming frame, say). */\n setSpec(spec: Spec | null): Promise<void>;\n /** Flip the loading flag catalog components see. */\n setLoading(loading: boolean): Promise<void>;\n /** Let effects, promises and change detection finish. */\n settle(): Promise<void>;\n\n /** Read a state path, e.g. `/form/email`. */\n read(path: string): unknown;\n /** Write a state path and settle. */\n write(path: string, value: unknown): Promise<void>;\n /** The whole state model. */\n state(): StateModel;\n\n /** Trimmed text of the first match, or of everything when given nothing. */\n text(selector?: string): string;\n /** Trimmed text of every match. */\n texts(selector: string): string[];\n /** The first match. Throws when there is none. */\n find<T extends HTMLElement>(selector: string): T;\n /** Every match, possibly none. */\n findAll<T extends HTMLElement>(selector: string): T[];\n /** Click an element (or the first match of a selector) and settle. */\n click(target: string | Element): Promise<void>;\n /** Set an input's value, fire `input`, and settle. */\n fill(target: string | Element, value: string): Promise<void>;\n\n /** Destroy the fixture. Rarely needed — TestBed resets between tests. */\n destroy(): void;\n}\n\n@Component({\n selector: 'jr-test-host',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [JsonRenderer],\n template: `\n <json-render\n [spec]=\"spec()\"\n [registry]=\"registry()\"\n [loading]=\"loading()\"\n [state]=\"state()\"\n [store]=\"store()\"\n [handlers]=\"handlers()\"\n [onAction]=\"onAction()\"\n [navigate]=\"navigate()\"\n [fallback]=\"fallback()\"\n [functions]=\"functions()\"\n [validationFunctions]=\"validationFunctions()\"\n [directives]=\"directives()\"\n (stateChange)=\"record($event)\"\n />\n `,\n})\nclass JrTestHost {\n readonly spec = signal<Spec | null>(null);\n readonly registry = signal<ComponentRegistry>({});\n readonly loading = signal(false);\n readonly state = signal<StateModel | undefined>(undefined);\n readonly store = signal<StateStore | null>(null);\n readonly handlers = signal<Record<string, ActionHandler> | undefined>(\n undefined,\n );\n readonly onAction = signal<\n ((name: string, params?: Record<string, unknown>) => unknown) | null\n >(null);\n readonly navigate = signal<((path: string) => void) | null>(null);\n readonly fallback = signal<Type<unknown> | RegistryEntry | null>(null);\n readonly functions = signal<Record<string, ComputedFunction> | undefined>(\n undefined,\n );\n readonly validationFunctions = signal<\n Record<string, ValidationFunction> | undefined\n >(undefined);\n readonly directives = signal<DirectiveDefinition[] | undefined>(undefined);\n\n record: (changes: StateChange[]) => void = () => {};\n}\n\n/**\n * Wrap every handler so a dispatch is recorded whether or not the action is\n * handled. Without this, `dispatched` would silently omit exactly the actions\n * a test bothered to wire up.\n */\nfunction recordingHandlers(\n handlers: Record<string, ActionHandler> | undefined,\n dispatched: DispatchedAction[],\n): Record<string, ActionHandler> | undefined {\n if (!handlers) return undefined;\n const wrapped: Record<string, ActionHandler> = {};\n for (const [name, handler] of Object.entries(handlers)) {\n wrapped[name] = (params: Record<string, unknown>) => {\n dispatched.push({ name, params });\n return handler(params);\n };\n }\n return wrapped;\n}\n\n/**\n * Configure the TestBed, unless the test already did.\n *\n * A test that called `TestBed.runInInjectionContext` first — or that renders\n * a second spec — has an instantiated module, and configuring it again\n * throws. Its own module stands; the only thing that cannot be salvaged is\n * this call's `providers`, so that is the one case worth failing over.\n */\nfunction configureTestBed(providers: Provider[]): void {\n try {\n TestBed.configureTestingModule({\n providers: [provideZonelessChangeDetection(), ...providers],\n });\n } catch {\n if (providers.length > 0) {\n throw new Error(\n 'renderSpec: the TestBed was already instantiated, so `providers` cannot be applied. Pass them to your own TestBed.configureTestingModule() call instead.',\n );\n }\n }\n}\n\n/**\n * Render a spec against a registry and hand back the few things a test does\n * to it.\n *\n * Testing a catalog component otherwise means a host component, a TestBed\n * module, a zoneless provider, two settle passes and a `querySelector` — the\n * same twenty lines in every project that adopts this package, none of them\n * about the component under test.\n *\n * @example\n * ```ts\n * const ui = await renderSpec(\n * {\n * root: 'save',\n * elements: {\n * save: { type: 'Button', props: { label: 'Save' }, on: { press: { action: 'save' } } },\n * },\n * },\n * { registry: { Button: MyButton } },\n * );\n *\n * expect(ui.text('button')).toBe('Save');\n * await ui.click('button');\n * expect(ui.dispatched).toEqual([{ name: 'save', params: {} }]);\n * ```\n */\nexport async function renderSpec(\n spec: Spec | null,\n options: RenderSpecOptions,\n): Promise<SpecHarness> {\n const dispatched: DispatchedAction[] = [];\n const changes: StateChange[] = [];\n\n configureTestBed(options.providers ?? []);\n\n const fixture = TestBed.createComponent(JrTestHost);\n const host = fixture.componentInstance;\n\n host.registry.set(options.registry);\n host.loading.set(options.loading ?? false);\n host.state.set(options.state);\n host.store.set(options.store ?? null);\n host.handlers.set(recordingHandlers(options.handlers, dispatched));\n host.onAction.set((name, params) => {\n dispatched.push({ name, params });\n return options.onAction?.(name, params);\n });\n host.navigate.set(options.navigate ?? null);\n host.fallback.set(options.fallback ?? null);\n host.functions.set(options.functions);\n host.validationFunctions.set(options.validationFunctions);\n host.directives.set(options.directives);\n host.record = (batch) => changes.push(...batch);\n host.spec.set(spec);\n\n const settle = async () => {\n // Twice: the first pass runs the effects that a render schedules, the\n // second renders what those effects changed. One pass leaves a test\n // asserting against a frame that no browser would ever show.\n await fixture.whenStable();\n fixture.detectChanges();\n await fixture.whenStable();\n };\n await settle();\n\n const element = fixture.nativeElement as HTMLElement;\n // Found by predicate rather than with `By.directive`, which would make\n // @angular/platform-browser a dependency of this entry point for one line.\n const rendererElement = fixture.debugElement.query(\n (candidate) => candidate.componentInstance instanceof JsonRenderer,\n );\n const renderer = rendererElement.componentInstance as JsonRenderer;\n const store = renderer.stateStore;\n // From the renderer's own injector: these services are provided by the\n // component, so the root TestBed injector has never heard of them.\n const validation = rendererElement.injector.get(JsonRenderValidationService);\n\n const resolve = (target: string | Element): HTMLElement => {\n if (typeof target !== 'string') return target as HTMLElement;\n const found = element.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(\n `renderSpec: nothing matches ${JSON.stringify(target)}. Rendered HTML:\\n${element.innerHTML}`,\n );\n }\n return found;\n };\n\n return {\n fixture,\n element,\n renderer,\n store,\n validation,\n dispatched,\n changes,\n\n async setSpec(next) {\n host.spec.set(next);\n await settle();\n },\n async setLoading(loading) {\n host.loading.set(loading);\n await settle();\n },\n settle,\n\n read: (path) => store.get(path),\n async write(path, value) {\n store.set(path, value);\n await settle();\n },\n state: () => store.state(),\n\n text: (selector) =>\n ((selector ? resolve(selector) : element).textContent ?? '').trim(),\n texts: (selector) =>\n Array.from(element.querySelectorAll(selector)).map((el) =>\n (el.textContent ?? '').trim(),\n ),\n find: <T extends HTMLElement>(selector: string) => resolve(selector) as T,\n findAll: <T extends HTMLElement>(selector: string) =>\n Array.from(element.querySelectorAll<T>(selector)),\n async click(target) {\n resolve(target).click();\n await settle();\n },\n async fill(target, value) {\n const input = resolve(target) as HTMLInputElement;\n input.value = value;\n input.dispatchEvent(new Event('input', { bubbles: true }));\n await settle();\n },\n\n destroy: () => fixture.destroy(),\n };\n}\n","/*\n * Public API Surface of ngx-json-render/testing\n *\n * Test utilities, shipped as a separate entry point so nothing here can be\n * pulled into an application bundle by accident.\n */\n\nexport {\n recordedTransport,\n specStream,\n usageLine,\n type Recording,\n type Recordings,\n type RecordedFailure,\n type RecordedTransportOptions,\n} from './recorded-transport';\nexport {\n renderSpec,\n type DispatchedAction,\n type RenderSpecOptions,\n type SpecHarness,\n} from './render-spec';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}