ngx-json-render 0.7.3 → 0.7.5
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
|
@@ -25,9 +25,16 @@ ng add ngx-json-render
|
|
|
25
25
|
npm install ngx-json-render @json-render/core zod
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Requires Angular ≥
|
|
29
|
-
library builds and its suite passes on
|
|
30
|
-
22 (the current release)
|
|
28
|
+
Requires Angular ≥ 19. CI proves both ends of that range on every push: the
|
|
29
|
+
library builds on Angular 19 (the floor), and its suite passes on 20 and on
|
|
30
|
+
22 (the current release) as well as on the 21 the workspace itself pins —
|
|
31
|
+
Angular 19's CLI has no unit-test builder to run it with. What npm serves is
|
|
32
|
+
checked too: the package built on 21 is installed into a fresh app on 19, 20
|
|
33
|
+
and 22, built with strict templates and driven in Chrome. On Angular 19 the
|
|
34
|
+
`ngx-json-render/testing` harness uses 19's
|
|
35
|
+
`provideExperimentalZonelessChangeDetection` by itself. The Material catalog,
|
|
36
|
+
[`ngx-json-render-material`](../ngx-json-render-material/README.md), needs
|
|
37
|
+
Angular ≥ 20.
|
|
31
38
|
The same goes for `@json-render/core`: 0.20 and 0.21 are both admitted
|
|
32
39
|
(`>=0.20.0 <0.22.0`), the workspace pins the newest and CI tests the floor.
|
|
33
40
|
|
|
@@ -654,6 +661,12 @@ const bound = injectBoundProp<string>(() => ctx.props().value, () => ctx.binding
|
|
|
654
661
|
|
|
655
662
|
Share one store across renderers (or drive it from your own state management) by passing a core `StateStore` — `createStateStore()`, or `createStoreAdapter()` over Redux/NgRx/etc. — via the `store` input. `createStoreSetState(store)` adapts a whole-state updater to fine-grained path writes.
|
|
656
663
|
|
|
664
|
+
`watch` also fires for writes made on that store from outside the renderer: on
|
|
665
|
+
every store notification the renderer compares the new snapshot with the last
|
|
666
|
+
one, branch by branch by reference, and reports each changed path. A store
|
|
667
|
+
that mutates its snapshot in place therefore re-renders but triggers no
|
|
668
|
+
`watch`; replace objects rather than editing them.
|
|
669
|
+
|
|
657
670
|
### What a state write re-renders
|
|
658
671
|
|
|
659
672
|
A write reaches only the components whose resolved props actually changed. Writing `/user/name` re-runs the template of the text that shows it, not the rest of the tree. So `ctx.props()` and `ctx.element()` are the signals to read: a template that reads anything else, such as a mutable object or `Date.now()`, is no longer refreshed by unrelated writes.
|
|
@@ -842,7 +855,9 @@ Spec checking: `checkSpec`, `formatSpecCheckIssues`, and the types
|
|
|
842
855
|
|
|
843
856
|
`injectActions().execute()` rejects when the user dismisses a `confirm`
|
|
844
857
|
dialog, which is a normal gesture rather than a failure — `isActionCancelled(error)`
|
|
845
|
-
is how you tell the two apart.
|
|
858
|
+
is how you tell the two apart. One dialog is open at a time: an action with a
|
|
859
|
+
`confirm` dispatched while another is waiting cancels the waiting one, so its
|
|
860
|
+
`execute()` rejects the same way.
|
|
846
861
|
|
|
847
862
|
Registry & schema: `defineRegistry`, `createStoreSetState`, `schema`.
|
|
848
863
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NgComponentOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import {
|
|
3
|
+
import { inject, Injector, signal, ChangeDetectionStrategy, Component, computed, isDevMode } from '@angular/core';
|
|
4
4
|
import { TestBed } from '@angular/core/testing';
|
|
5
5
|
import { RENDER_CONTEXT, ELEMENT_KEY, JsonRenderer, JsonRenderValidationService } from 'ngx-json-render';
|
|
6
6
|
|
|
@@ -170,6 +170,24 @@ function usageLine(usage) {
|
|
|
170
170
|
*
|
|
171
171
|
* @internal Not exported from the entry point.
|
|
172
172
|
*/
|
|
173
|
+
/**
|
|
174
|
+
* Zoneless change detection, under whichever name this Angular exports it.
|
|
175
|
+
*
|
|
176
|
+
* Angular 19 calls it `provideExperimentalZonelessChangeDetection`, 20 renamed
|
|
177
|
+
* it `provideZonelessChangeDetection`, and 21 dropped the old name. The peer
|
|
178
|
+
* range spans both, so neither can be a named import: whichever one the
|
|
179
|
+
* consumer's Angular lacks would fail their bundler with a missing export.
|
|
180
|
+
* `core` is a parameter only so the fallback can be tested on a newer Angular.
|
|
181
|
+
*/
|
|
182
|
+
function zonelessChangeDetection(core = i0) {
|
|
183
|
+
const named = core;
|
|
184
|
+
const provide = named['provideZonelessChangeDetection'] ??
|
|
185
|
+
named['provideExperimentalZonelessChangeDetection'];
|
|
186
|
+
if (!provide) {
|
|
187
|
+
throw new Error('ngx-json-render/testing: this @angular/core exports no zoneless change detection provider.');
|
|
188
|
+
}
|
|
189
|
+
return provide();
|
|
190
|
+
}
|
|
173
191
|
/**
|
|
174
192
|
* Configure the TestBed, unless the test already did.
|
|
175
193
|
*
|
|
@@ -181,7 +199,7 @@ function usageLine(usage) {
|
|
|
181
199
|
function configureTestBed(caller, providers) {
|
|
182
200
|
try {
|
|
183
201
|
TestBed.configureTestingModule({
|
|
184
|
-
providers: [
|
|
202
|
+
providers: [zonelessChangeDetection(), ...providers],
|
|
185
203
|
});
|
|
186
204
|
}
|
|
187
205
|
catch {
|
|
@@ -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/fixture-helpers.ts","../../../projects/ngx-json-render/testing/src/render-component.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 { type Provider, provideZonelessChangeDetection } from '@angular/core';\nimport { type ComponentFixture, TestBed } from '@angular/core/testing';\n\n/**\n * What `renderSpec` and `renderComponent` share: the TestBed setup, the settle\n * loop, and the handful of DOM queries a test runs against what rendered.\n *\n * @internal Not exported from the entry point.\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 time — 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 */\nexport function configureTestBed(caller: string, 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 `${caller}: 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/** Let effects, promises and change detection finish. */\nexport function settler(\n fixture: ComponentFixture<unknown>,\n): () => Promise<void> {\n return 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}\n\n/** The DOM half of a harness. */\nexport interface DomQueries {\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\nexport function domQueries(\n caller: string,\n element: HTMLElement,\n settle: () => Promise<void>,\n): DomQueries {\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 `${caller}: nothing matches ${JSON.stringify(target)}. Rendered HTML:\\n${element.innerHTML}`,\n );\n }\n return found;\n };\n\n return {\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}\n","import { NgComponentOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n Injector,\n type Provider,\n type Type,\n computed,\n inject,\n isDevMode,\n signal,\n} from '@angular/core';\nimport { type ComponentFixture, TestBed } from '@angular/core/testing';\nimport type { UIElement } from '@json-render/core';\nimport {\n ELEMENT_KEY,\n type EventHandle,\n RENDER_CONTEXT,\n type RenderContext,\n} from 'ngx-json-render';\nimport {\n type DomQueries,\n configureTestBed,\n domQueries,\n settler,\n} from './fixture-helpers';\n\n/** One `setBound` call that reached a bound prop, in order. */\nexport interface BoundWrite {\n prop: string;\n value: unknown;\n}\n\n/** Options for {@link renderComponent}. Everything is optional. */\nexport interface RenderComponentOptions<P extends object> {\n /** The resolved props the component sees. Defaults to `{}`. */\n props?: P;\n /**\n * Which props are two-way bound, as `prop → state path` — what\n * `$bindState` / `$bindItem` would have resolved to. A `setBound` on a prop\n * listed here is recorded and written back into `props`; on any other prop\n * it is a no-op, exactly as under the renderer.\n */\n bindings?: Record<string, string>;\n /**\n * The events the spec would bind in `on`. Only affects what `ctx.on(event)`\n * reports as `bound` — `emitted` records every event regardless.\n */\n on?: readonly string[];\n /** The loading flag the component sees. */\n loading?: boolean;\n /** The spec key, for components that `injectElementKey()`. */\n key?: string;\n /** The element's catalog type name. */\n type?: string;\n /** Extra TestBed providers for anything else the component injects. */\n providers?: Provider[];\n}\n\n/**\n * A catalog component mounted on its own, and what it said to the renderer\n * it thinks it is inside.\n */\nexport interface ComponentHarness<T, P extends object> extends DomQueries {\n /** The fixture of the host the component is mounted in. */\n readonly fixture: ComponentFixture<unknown>;\n /** The host element, the root of the rendered DOM. */\n readonly element: HTMLElement;\n /** The component instance. */\n readonly component: T;\n /** The render context the component injected. */\n readonly context: RenderContext<P>;\n /** Every event the component emitted, bound or not, in order. */\n readonly emitted: readonly string[];\n /** Every `setBound` that reached a bound prop, in order. */\n readonly writes: readonly BoundWrite[];\n\n /** The props as the component currently sees them. */\n props(): P;\n /** Replace the props and settle. */\n setProps(props: P): Promise<void>;\n /** Merge into the props and settle. */\n patchProps(patch: Partial<P>): Promise<void>;\n /** Flip the loading flag and settle. */\n setLoading(loading: boolean): Promise<void>;\n /** Let effects, promises and change detection finish. */\n settle(): Promise<void>;\n /** Destroy the fixture. Rarely needed — TestBed resets between tests. */\n destroy(): void;\n}\n\n/**\n * Mounted through `NgComponentOutlet` with an element injector, the way\n * `<jr-element>` mounts a catalog component — so the context is the\n * component's own, not the TestBed module's, and a test can mount twice.\n */\n@Component({\n selector: 'jr-component-test-host',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgComponentOutlet],\n template: `\n @if (component(); as type) {\n <ng-container *ngComponentOutlet=\"type; injector: injector()\" />\n }\n `,\n})\nclass JrComponentTestHost {\n readonly parent = inject(Injector);\n readonly component = signal<Type<unknown> | null>(null);\n readonly injector = signal<Injector>(this.parent);\n}\n\n/** The renderer-owned services a component can only have from a renderer. */\nconst RENDERER_ONLY =\n /JsonRender(RootContext|StateService|ValidationService|ActionsService)/;\n\n/**\n * Turn \"no provider for the renderer's services\" into what to do about it.\n * `<jr-children>`, `injectFieldValidation` and `injectActions` all need a\n * real renderer behind them; there is no honest fake for a subtree.\n */\nfunction explainMissingRenderer(error: unknown): never {\n const message = error instanceof Error ? error.message : String(error);\n if (RENDERER_ONLY.test(message)) {\n throw new Error(\n `renderComponent: this component needs a real <json-render> — it renders <jr-children>, validates a field or dispatches actions itself. Test it with renderSpec instead.\\n\\n${message}`,\n );\n }\n throw error;\n}\n\n/**\n * Mount a catalog component with no renderer and no spec: hand it props, and\n * read back the events it emitted and the values it wrote to bound props.\n *\n * This is the test of a component as a presentational one — what it draws\n * for these props, and what it says when used. What the spec then does with\n * that is the renderer's business, and `renderSpec` is where to test it.\n *\n * `props` is not inferred from the first value, so a later `patchProps` can\n * set a prop the first render left out. Pass `P` to have the props typed.\n *\n * A component that renders `<jr-children>`, registers field validation or\n * dispatches actions directly needs the renderer's services and fails with a\n * message pointing at `renderSpec`.\n *\n * @example\n * ```ts\n * const button = await renderComponent(MyButton, { props: { label: 'Save' } });\n *\n * expect(button.text('button')).toBe('Save');\n * await button.click('button');\n * expect(button.emitted).toEqual(['press']);\n *\n * await button.patchProps({ disabled: true });\n * expect(button.find<HTMLButtonElement>('button').disabled).toBe(true);\n * ```\n */\nexport async function renderComponent<\n T,\n P extends object = Record<string, unknown>,\n>(\n component: Type<T>,\n options: RenderComponentOptions<NoInfer<P>> = {},\n): Promise<ComponentHarness<T, P>> {\n const emitted: string[] = [];\n const writes: BoundWrite[] = [];\n\n const props = signal<P>(options.props ?? ({} as P));\n const loading = signal(options.loading ?? false);\n const bindings = options.bindings;\n const boundEvents = new Set(options.on ?? []);\n const key = signal(options.key ?? 'element');\n\n const element = computed(\n () =>\n ({\n type: options.type ?? component.name,\n props: props(),\n children: [],\n }) as UIElement<string, P>,\n );\n\n const emit = (event: string) => {\n emitted.push(event);\n };\n\n const context: RenderContext<P> = {\n element,\n props: props.asReadonly(),\n emit,\n on: (event): EventHandle => ({\n emit: () => emit(event),\n shouldPreventDefault: false,\n bound: boundEvents.has(event),\n }),\n bindings: signal(bindings).asReadonly(),\n loading: loading.asReadonly(),\n setBound: (prop, value) => {\n if (!bindings?.[prop]) {\n if (isDevMode()) {\n console.warn(\n `[ngx-json-render] setBound(\"${prop}\"): prop has no $bindState/$bindItem binding`,\n );\n }\n return;\n }\n writes.push({ prop, value });\n // What the renderer's round trip amounts to: the write lands in state,\n // and the bound prop resolves to it on the next read.\n props.update((current) => ({ ...current, [prop]: value }));\n },\n };\n\n configureTestBed('renderComponent', options.providers ?? []);\n\n const fixture = TestBed.createComponent(JrComponentTestHost);\n const host = fixture.componentInstance;\n host.injector.set(\n Injector.create({\n providers: [\n { provide: RENDER_CONTEXT, useValue: context },\n { provide: ELEMENT_KEY, useValue: key.asReadonly() },\n ],\n parent: host.parent,\n }),\n );\n host.component.set(component);\n\n const settle = settler(fixture);\n try {\n await settle();\n } catch (error) {\n explainMissingRenderer(error);\n }\n\n const mounted = fixture.debugElement.query(\n (candidate) => candidate.componentInstance instanceof component,\n );\n if (!mounted) {\n throw new Error(\n `renderComponent: ${component.name} did not mount. Is it a component?`,\n );\n }\n\n const root = fixture.nativeElement as HTMLElement;\n\n return {\n fixture,\n element: root,\n component: mounted.componentInstance as T,\n context,\n emitted,\n writes,\n\n props: () => props(),\n async setProps(next) {\n props.set(next);\n await settle();\n },\n async patchProps(patch) {\n props.update((current) => ({ ...current, ...patch }));\n await settle();\n },\n async setLoading(next) {\n loading.set(next);\n await settle();\n },\n settle,\n\n ...domQueries('renderComponent', root, settle),\n\n destroy: () => fixture.destroy(),\n };\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n type Provider,\n type Type,\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';\nimport {\n type DomQueries,\n configureTestBed,\n domQueries,\n settler,\n} from './fixture-helpers';\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<any>[];\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 extends DomQueries {\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 /** 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<any>[] | undefined>(\n undefined,\n );\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 * 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('renderSpec', 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 = settler(fixture);\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 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 ...domQueries('renderSpec', element, settle),\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 renderComponent,\n type BoundWrite,\n type ComponentHarness,\n type RenderComponentOptions,\n} from './render-component';\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/fixture-helpers.ts","../../../projects/ngx-json-render/testing/src/render-component.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 * as angularCore from '@angular/core';\nimport type { EnvironmentProviders, Provider } from '@angular/core';\nimport { type ComponentFixture, TestBed } from '@angular/core/testing';\n\n/**\n * What `renderSpec` and `renderComponent` share: the TestBed setup, the settle\n * loop, and the handful of DOM queries a test runs against what rendered.\n *\n * @internal Not exported from the entry point.\n */\n\n/**\n * Zoneless change detection, under whichever name this Angular exports it.\n *\n * Angular 19 calls it `provideExperimentalZonelessChangeDetection`, 20 renamed\n * it `provideZonelessChangeDetection`, and 21 dropped the old name. The peer\n * range spans both, so neither can be a named import: whichever one the\n * consumer's Angular lacks would fail their bundler with a missing export.\n * `core` is a parameter only so the fallback can be tested on a newer Angular.\n */\nexport function zonelessChangeDetection(\n core: object = angularCore,\n): EnvironmentProviders {\n const named = core as Record<\n string,\n (() => EnvironmentProviders) | undefined\n >;\n const provide =\n named['provideZonelessChangeDetection'] ??\n named['provideExperimentalZonelessChangeDetection'];\n if (!provide) {\n throw new Error(\n 'ngx-json-render/testing: this @angular/core exports no zoneless change detection provider.',\n );\n }\n return provide();\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 time — 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 */\nexport function configureTestBed(caller: string, providers: Provider[]): void {\n try {\n TestBed.configureTestingModule({\n providers: [zonelessChangeDetection(), ...providers],\n });\n } catch {\n if (providers.length > 0) {\n throw new Error(\n `${caller}: 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/** Let effects, promises and change detection finish. */\nexport function settler(\n fixture: ComponentFixture<unknown>,\n): () => Promise<void> {\n return 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}\n\n/** The DOM half of a harness. */\nexport interface DomQueries {\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\nexport function domQueries(\n caller: string,\n element: HTMLElement,\n settle: () => Promise<void>,\n): DomQueries {\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 `${caller}: nothing matches ${JSON.stringify(target)}. Rendered HTML:\\n${element.innerHTML}`,\n );\n }\n return found;\n };\n\n return {\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}\n","import { NgComponentOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n Injector,\n type Provider,\n type Type,\n computed,\n inject,\n isDevMode,\n signal,\n} from '@angular/core';\nimport { type ComponentFixture, TestBed } from '@angular/core/testing';\nimport type { UIElement } from '@json-render/core';\nimport {\n ELEMENT_KEY,\n type EventHandle,\n RENDER_CONTEXT,\n type RenderContext,\n} from 'ngx-json-render';\nimport {\n type DomQueries,\n configureTestBed,\n domQueries,\n settler,\n} from './fixture-helpers';\n\n/** One `setBound` call that reached a bound prop, in order. */\nexport interface BoundWrite {\n prop: string;\n value: unknown;\n}\n\n/** Options for {@link renderComponent}. Everything is optional. */\nexport interface RenderComponentOptions<P extends object> {\n /** The resolved props the component sees. Defaults to `{}`. */\n props?: P;\n /**\n * Which props are two-way bound, as `prop → state path` — what\n * `$bindState` / `$bindItem` would have resolved to. A `setBound` on a prop\n * listed here is recorded and written back into `props`; on any other prop\n * it is a no-op, exactly as under the renderer.\n */\n bindings?: Record<string, string>;\n /**\n * The events the spec would bind in `on`. Only affects what `ctx.on(event)`\n * reports as `bound` — `emitted` records every event regardless.\n */\n on?: readonly string[];\n /** The loading flag the component sees. */\n loading?: boolean;\n /** The spec key, for components that `injectElementKey()`. */\n key?: string;\n /** The element's catalog type name. */\n type?: string;\n /** Extra TestBed providers for anything else the component injects. */\n providers?: Provider[];\n}\n\n/**\n * A catalog component mounted on its own, and what it said to the renderer\n * it thinks it is inside.\n */\nexport interface ComponentHarness<T, P extends object> extends DomQueries {\n /** The fixture of the host the component is mounted in. */\n readonly fixture: ComponentFixture<unknown>;\n /** The host element, the root of the rendered DOM. */\n readonly element: HTMLElement;\n /** The component instance. */\n readonly component: T;\n /** The render context the component injected. */\n readonly context: RenderContext<P>;\n /** Every event the component emitted, bound or not, in order. */\n readonly emitted: readonly string[];\n /** Every `setBound` that reached a bound prop, in order. */\n readonly writes: readonly BoundWrite[];\n\n /** The props as the component currently sees them. */\n props(): P;\n /** Replace the props and settle. */\n setProps(props: P): Promise<void>;\n /** Merge into the props and settle. */\n patchProps(patch: Partial<P>): Promise<void>;\n /** Flip the loading flag and settle. */\n setLoading(loading: boolean): Promise<void>;\n /** Let effects, promises and change detection finish. */\n settle(): Promise<void>;\n /** Destroy the fixture. Rarely needed — TestBed resets between tests. */\n destroy(): void;\n}\n\n/**\n * Mounted through `NgComponentOutlet` with an element injector, the way\n * `<jr-element>` mounts a catalog component — so the context is the\n * component's own, not the TestBed module's, and a test can mount twice.\n */\n@Component({\n selector: 'jr-component-test-host',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgComponentOutlet],\n template: `\n @if (component(); as type) {\n <ng-container *ngComponentOutlet=\"type; injector: injector()\" />\n }\n `,\n})\nclass JrComponentTestHost {\n readonly parent = inject(Injector);\n readonly component = signal<Type<unknown> | null>(null);\n readonly injector = signal<Injector>(this.parent);\n}\n\n/** The renderer-owned services a component can only have from a renderer. */\nconst RENDERER_ONLY =\n /JsonRender(RootContext|StateService|ValidationService|ActionsService)/;\n\n/**\n * Turn \"no provider for the renderer's services\" into what to do about it.\n * `<jr-children>`, `injectFieldValidation` and `injectActions` all need a\n * real renderer behind them; there is no honest fake for a subtree.\n */\nfunction explainMissingRenderer(error: unknown): never {\n const message = error instanceof Error ? error.message : String(error);\n if (RENDERER_ONLY.test(message)) {\n throw new Error(\n `renderComponent: this component needs a real <json-render> — it renders <jr-children>, validates a field or dispatches actions itself. Test it with renderSpec instead.\\n\\n${message}`,\n );\n }\n throw error;\n}\n\n/**\n * Mount a catalog component with no renderer and no spec: hand it props, and\n * read back the events it emitted and the values it wrote to bound props.\n *\n * This is the test of a component as a presentational one — what it draws\n * for these props, and what it says when used. What the spec then does with\n * that is the renderer's business, and `renderSpec` is where to test it.\n *\n * `props` is not inferred from the first value, so a later `patchProps` can\n * set a prop the first render left out. Pass `P` to have the props typed.\n *\n * A component that renders `<jr-children>`, registers field validation or\n * dispatches actions directly needs the renderer's services and fails with a\n * message pointing at `renderSpec`.\n *\n * @example\n * ```ts\n * const button = await renderComponent(MyButton, { props: { label: 'Save' } });\n *\n * expect(button.text('button')).toBe('Save');\n * await button.click('button');\n * expect(button.emitted).toEqual(['press']);\n *\n * await button.patchProps({ disabled: true });\n * expect(button.find<HTMLButtonElement>('button').disabled).toBe(true);\n * ```\n */\nexport async function renderComponent<\n T,\n P extends object = Record<string, unknown>,\n>(\n component: Type<T>,\n options: RenderComponentOptions<NoInfer<P>> = {},\n): Promise<ComponentHarness<T, P>> {\n const emitted: string[] = [];\n const writes: BoundWrite[] = [];\n\n const props = signal<P>(options.props ?? ({} as P));\n const loading = signal(options.loading ?? false);\n const bindings = options.bindings;\n const boundEvents = new Set(options.on ?? []);\n const key = signal(options.key ?? 'element');\n\n const element = computed(\n () =>\n ({\n type: options.type ?? component.name,\n props: props(),\n children: [],\n }) as UIElement<string, P>,\n );\n\n const emit = (event: string) => {\n emitted.push(event);\n };\n\n const context: RenderContext<P> = {\n element,\n props: props.asReadonly(),\n emit,\n on: (event): EventHandle => ({\n emit: () => emit(event),\n shouldPreventDefault: false,\n bound: boundEvents.has(event),\n }),\n bindings: signal(bindings).asReadonly(),\n loading: loading.asReadonly(),\n setBound: (prop, value) => {\n if (!bindings?.[prop]) {\n if (isDevMode()) {\n console.warn(\n `[ngx-json-render] setBound(\"${prop}\"): prop has no $bindState/$bindItem binding`,\n );\n }\n return;\n }\n writes.push({ prop, value });\n // What the renderer's round trip amounts to: the write lands in state,\n // and the bound prop resolves to it on the next read.\n props.update((current) => ({ ...current, [prop]: value }));\n },\n };\n\n configureTestBed('renderComponent', options.providers ?? []);\n\n const fixture = TestBed.createComponent(JrComponentTestHost);\n const host = fixture.componentInstance;\n host.injector.set(\n Injector.create({\n providers: [\n { provide: RENDER_CONTEXT, useValue: context },\n { provide: ELEMENT_KEY, useValue: key.asReadonly() },\n ],\n parent: host.parent,\n }),\n );\n host.component.set(component);\n\n const settle = settler(fixture);\n try {\n await settle();\n } catch (error) {\n explainMissingRenderer(error);\n }\n\n const mounted = fixture.debugElement.query(\n (candidate) => candidate.componentInstance instanceof component,\n );\n if (!mounted) {\n throw new Error(\n `renderComponent: ${component.name} did not mount. Is it a component?`,\n );\n }\n\n const root = fixture.nativeElement as HTMLElement;\n\n return {\n fixture,\n element: root,\n component: mounted.componentInstance as T,\n context,\n emitted,\n writes,\n\n props: () => props(),\n async setProps(next) {\n props.set(next);\n await settle();\n },\n async patchProps(patch) {\n props.update((current) => ({ ...current, ...patch }));\n await settle();\n },\n async setLoading(next) {\n loading.set(next);\n await settle();\n },\n settle,\n\n ...domQueries('renderComponent', root, settle),\n\n destroy: () => fixture.destroy(),\n };\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n type Provider,\n type Type,\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';\nimport {\n type DomQueries,\n configureTestBed,\n domQueries,\n settler,\n} from './fixture-helpers';\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<any>[];\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 extends DomQueries {\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 /** 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<any>[] | undefined>(\n undefined,\n );\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 * 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('renderSpec', 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 = settler(fixture);\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 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 ...domQueries('renderSpec', element, settle),\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 renderComponent,\n type BoundWrite,\n type ComponentHarness,\n type RenderComponentOptions,\n} from './render-component';\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":["angularCore"],"mappings":"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"}
|
|
@@ -63,6 +63,10 @@ class JsonRenderStateService {
|
|
|
63
63
|
root = inject(JsonRenderRootContext);
|
|
64
64
|
internalStore = createStateStore({});
|
|
65
65
|
changeListeners = new Set();
|
|
66
|
+
/** The snapshot the change listeners last heard about. */
|
|
67
|
+
lastSnapshot = {};
|
|
68
|
+
/** True while this service writes, so the store's echo is not reported. */
|
|
69
|
+
writing = false;
|
|
66
70
|
_state = signal({}, { ...(ngDevMode ? { debugName: "_state" } : /* istanbul ignore next */ {}), equal: () => false });
|
|
67
71
|
/** The current state model as a signal. */
|
|
68
72
|
state = this._state.asReadonly();
|
|
@@ -82,9 +86,23 @@ class JsonRenderStateService {
|
|
|
82
86
|
console.warn(`[ngx-json-render] switching from ${initialMode} to ${mode} mode is not supported.`);
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
|
|
89
|
+
const controlled = store !== this.internalStore;
|
|
90
|
+
this.lastSnapshot = store.getSnapshot();
|
|
91
|
+
untracked(() => this._state.set(this.lastSnapshot));
|
|
86
92
|
const unsubscribe = store.subscribe(() => {
|
|
87
|
-
this.
|
|
93
|
+
const prev = this.lastSnapshot;
|
|
94
|
+
const next = store.getSnapshot();
|
|
95
|
+
// Recorded before the listeners run: a watch action may write again.
|
|
96
|
+
this.lastSnapshot = next;
|
|
97
|
+
this._state.set(next);
|
|
98
|
+
// A write the host made on its own store reaches no `set`/`update`
|
|
99
|
+
// here, so the diff is the only way `watch` hears about it. Our own
|
|
100
|
+
// writes report themselves, with the paths they were given.
|
|
101
|
+
if (controlled && !this.writing && this.changeListeners.size > 0) {
|
|
102
|
+
const changes = diffState(prev, next);
|
|
103
|
+
if (changes.length > 0)
|
|
104
|
+
this.notifyChanges(changes);
|
|
105
|
+
}
|
|
88
106
|
});
|
|
89
107
|
onCleanup(unsubscribe);
|
|
90
108
|
});
|
|
@@ -124,7 +142,7 @@ class JsonRenderStateService {
|
|
|
124
142
|
const store = untracked(this.currentStore);
|
|
125
143
|
const prev = store.getSnapshot();
|
|
126
144
|
const prevValue = getByPath(prev, path);
|
|
127
|
-
store.set(path, value);
|
|
145
|
+
this.write(store, () => store.set(path, value));
|
|
128
146
|
if (prevValue !== value) {
|
|
129
147
|
const changes = [{ path, value }];
|
|
130
148
|
this.notifyChanges(changes);
|
|
@@ -137,7 +155,7 @@ class JsonRenderStateService {
|
|
|
137
155
|
update(updates) {
|
|
138
156
|
const store = untracked(this.currentStore);
|
|
139
157
|
const prev = store.getSnapshot();
|
|
140
|
-
store.update(updates);
|
|
158
|
+
this.write(store, () => store.update(updates));
|
|
141
159
|
const changes = [];
|
|
142
160
|
for (const [path, value] of Object.entries(updates)) {
|
|
143
161
|
if (getByPath(prev, path) !== value) {
|
|
@@ -157,8 +175,9 @@ class JsonRenderStateService {
|
|
|
157
175
|
}
|
|
158
176
|
/**
|
|
159
177
|
* Register a listener called with the list of changed paths whenever state
|
|
160
|
-
* is written through this service
|
|
161
|
-
*
|
|
178
|
+
* is written through this service, or, in controlled mode, whenever the
|
|
179
|
+
* external store changes (element `watch` fields rely on this). Returns an
|
|
180
|
+
* unsubscribe function.
|
|
162
181
|
*/
|
|
163
182
|
subscribeChanges(listener) {
|
|
164
183
|
this.changeListeners.add(listener);
|
|
@@ -166,6 +185,17 @@ class JsonRenderStateService {
|
|
|
166
185
|
this.changeListeners.delete(listener);
|
|
167
186
|
};
|
|
168
187
|
}
|
|
188
|
+
write(store, apply) {
|
|
189
|
+
const wasWriting = this.writing;
|
|
190
|
+
this.writing = true;
|
|
191
|
+
try {
|
|
192
|
+
apply();
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
this.writing = wasWriting;
|
|
196
|
+
}
|
|
197
|
+
this.lastSnapshot = store.getSnapshot();
|
|
198
|
+
}
|
|
169
199
|
notifyChanges(changes) {
|
|
170
200
|
for (const listener of this.changeListeners) {
|
|
171
201
|
listener(changes);
|
|
@@ -177,6 +207,38 @@ class JsonRenderStateService {
|
|
|
177
207
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderStateService, decorators: [{
|
|
178
208
|
type: Injectable
|
|
179
209
|
}], ctorParameters: () => [] });
|
|
210
|
+
/**
|
|
211
|
+
* Every path whose value differs between two state snapshots, each container
|
|
212
|
+
* before what it holds, values taken from `next`. Subtrees are compared by
|
|
213
|
+
* reference, so an unchanged branch costs nothing, and a store that mutates a
|
|
214
|
+
* snapshot in place reports no change: the same rule as a signal's `set`.
|
|
215
|
+
*/
|
|
216
|
+
function diffState(prev, next) {
|
|
217
|
+
const changes = [];
|
|
218
|
+
const walk = (a, b, path) => {
|
|
219
|
+
if (a === b)
|
|
220
|
+
return;
|
|
221
|
+
if (path)
|
|
222
|
+
changes.push({ path, value: b });
|
|
223
|
+
const keys = new Set([...childKeys(a), ...childKeys(b)]);
|
|
224
|
+
for (const key of keys) {
|
|
225
|
+
walk(child(a, key), child(b, key), `${path}/${escapePointer(key)}`);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
walk(prev, next, '');
|
|
229
|
+
return changes;
|
|
230
|
+
}
|
|
231
|
+
function childKeys(value) {
|
|
232
|
+
return value !== null && typeof value === 'object' ? Object.keys(value) : [];
|
|
233
|
+
}
|
|
234
|
+
function child(value, key) {
|
|
235
|
+
return value !== null && typeof value === 'object'
|
|
236
|
+
? value[key]
|
|
237
|
+
: undefined;
|
|
238
|
+
}
|
|
239
|
+
function escapePointer(key) {
|
|
240
|
+
return key.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
241
|
+
}
|
|
180
242
|
/**
|
|
181
243
|
* Inject the state store of the nearest `<json-render>` renderer.
|
|
182
244
|
* Must be called from within the renderer's subtree (e.g. a catalog
|
|
@@ -665,21 +727,19 @@ class JsonRenderActionsService {
|
|
|
665
727
|
return;
|
|
666
728
|
}
|
|
667
729
|
if (resolved.confirm) {
|
|
730
|
+
// One question at a time: a confirm arriving while another is open
|
|
731
|
+
// cancels that one, so its caller settles instead of waiting forever.
|
|
732
|
+
untracked(this._pendingConfirmation)?.reject();
|
|
668
733
|
// Awaited, not returned: a returned promise would let the `finally`
|
|
669
734
|
// below settle the observers before the user has even answered.
|
|
670
735
|
await new Promise((resolve, reject) => {
|
|
671
|
-
|
|
736
|
+
const pending = {
|
|
672
737
|
action: resolved,
|
|
673
738
|
handler,
|
|
674
|
-
resolve: () =>
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
reject: () => {
|
|
679
|
-
this._pendingConfirmation.set(null);
|
|
680
|
-
reject(new ActionCancelledError());
|
|
681
|
-
},
|
|
682
|
-
});
|
|
739
|
+
resolve: () => this.answer(pending, resolve),
|
|
740
|
+
reject: () => this.answer(pending, () => reject(new ActionCancelledError())),
|
|
741
|
+
};
|
|
742
|
+
this._pendingConfirmation.set(pending);
|
|
683
743
|
});
|
|
684
744
|
}
|
|
685
745
|
await this.runHandler(resolved, handler);
|
|
@@ -709,6 +769,17 @@ class JsonRenderActionsService {
|
|
|
709
769
|
cancel() {
|
|
710
770
|
untracked(this._pendingConfirmation)?.reject();
|
|
711
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Close `pending` and run `settle`, but only while it is still the open
|
|
774
|
+
* question: a late answer to one that was replaced must not close its
|
|
775
|
+
* successor.
|
|
776
|
+
*/
|
|
777
|
+
answer(pending, settle) {
|
|
778
|
+
if (untracked(this._pendingConfirmation) !== pending)
|
|
779
|
+
return;
|
|
780
|
+
this._pendingConfirmation.set(null);
|
|
781
|
+
settle();
|
|
782
|
+
}
|
|
712
783
|
/**
|
|
713
784
|
* Validate every registered field and write `{ valid, errors }` to the
|
|
714
785
|
* action's `statePath` (default `/formValidation`). Returns null when there
|
|
@@ -1537,6 +1608,8 @@ class JrElement {
|
|
|
1537
1608
|
}
|
|
1538
1609
|
},
|
|
1539
1610
|
};
|
|
1611
|
+
// Typed as Injector for the peer range: see `confirmInjector` in
|
|
1612
|
+
// renderer.component.ts.
|
|
1540
1613
|
outletInjector = Injector.create({
|
|
1541
1614
|
providers: [
|
|
1542
1615
|
{ provide: RENDER_CONTEXT, useValue: this.renderCtx },
|
|
@@ -2406,6 +2479,9 @@ class JsonRenderer {
|
|
|
2406
2479
|
* one token wide instead of a component contract the renderer would have to
|
|
2407
2480
|
* bind to.
|
|
2408
2481
|
*/
|
|
2482
|
+
// Typed as Injector, not left to inference: Angular 20+ infers its
|
|
2483
|
+
// `DestroyableInjector`, which Angular 19's typings do not export, and the
|
|
2484
|
+
// inferred type would land in the published declarations.
|
|
2409
2485
|
confirmInjector = computed(() => {
|
|
2410
2486
|
const config = this.pendingConfirm();
|
|
2411
2487
|
if (!config)
|