ngx-json-render 0.4.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-json-render",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Angular renderer for json-render — stream AI-generated JSON specs into real Angular components with signals.",
5
5
  "keywords": [
6
6
  "angular",
@@ -1,7 +1,7 @@
1
1
  import { Spec, StateStore, ActionHandler, ComputedFunction, ValidationFunction, DirectiveDefinition } from '@json-render/core';
2
- import { Type, Provider } from '@angular/core';
2
+ import { Provider, Type } from '@angular/core';
3
3
  import { ComponentFixture } from '@angular/core/testing';
4
- import { ComponentRegistry, StateModel, RegistryEntry, JsonRenderer, JsonRenderStateService, JsonRenderValidationService, StateChange } from 'ngx-json-render';
4
+ import { RenderContext, ComponentRegistry, StateModel, RegistryEntry, JsonRenderer, JsonRenderStateService, JsonRenderValidationService, StateChange } from 'ngx-json-render';
5
5
 
6
6
  /**
7
7
  * The lines of one recorded response body, in the order the server sent them,
@@ -85,6 +85,111 @@ declare function usageLine(usage: {
85
85
  totalTokens?: number;
86
86
  }): string;
87
87
 
88
+ /** The DOM half of a harness. */
89
+ interface DomQueries {
90
+ /** Trimmed text of the first match, or of everything when given nothing. */
91
+ text(selector?: string): string;
92
+ /** Trimmed text of every match. */
93
+ texts(selector: string): string[];
94
+ /** The first match. Throws when there is none. */
95
+ find<T extends HTMLElement>(selector: string): T;
96
+ /** Every match, possibly none. */
97
+ findAll<T extends HTMLElement>(selector: string): T[];
98
+ /** Click an element (or the first match of a selector) and settle. */
99
+ click(target: string | Element): Promise<void>;
100
+ /** Set an input's value, fire `input`, and settle. */
101
+ fill(target: string | Element, value: string): Promise<void>;
102
+ }
103
+
104
+ /** One `setBound` call that reached a bound prop, in order. */
105
+ interface BoundWrite {
106
+ prop: string;
107
+ value: unknown;
108
+ }
109
+ /** Options for {@link renderComponent}. Everything is optional. */
110
+ interface RenderComponentOptions<P extends object> {
111
+ /** The resolved props the component sees. Defaults to `{}`. */
112
+ props?: P;
113
+ /**
114
+ * Which props are two-way bound, as `prop → state path` — what
115
+ * `$bindState` / `$bindItem` would have resolved to. A `setBound` on a prop
116
+ * listed here is recorded and written back into `props`; on any other prop
117
+ * it is a no-op, exactly as under the renderer.
118
+ */
119
+ bindings?: Record<string, string>;
120
+ /**
121
+ * The events the spec would bind in `on`. Only affects what `ctx.on(event)`
122
+ * reports as `bound` — `emitted` records every event regardless.
123
+ */
124
+ on?: readonly string[];
125
+ /** The loading flag the component sees. */
126
+ loading?: boolean;
127
+ /** The spec key, for components that `injectElementKey()`. */
128
+ key?: string;
129
+ /** The element's catalog type name. */
130
+ type?: string;
131
+ /** Extra TestBed providers for anything else the component injects. */
132
+ providers?: Provider[];
133
+ }
134
+ /**
135
+ * A catalog component mounted on its own, and what it said to the renderer
136
+ * it thinks it is inside.
137
+ */
138
+ interface ComponentHarness<T, P extends object> extends DomQueries {
139
+ /** The fixture of the host the component is mounted in. */
140
+ readonly fixture: ComponentFixture<unknown>;
141
+ /** The host element, the root of the rendered DOM. */
142
+ readonly element: HTMLElement;
143
+ /** The component instance. */
144
+ readonly component: T;
145
+ /** The render context the component injected. */
146
+ readonly context: RenderContext<P>;
147
+ /** Every event the component emitted, bound or not, in order. */
148
+ readonly emitted: readonly string[];
149
+ /** Every `setBound` that reached a bound prop, in order. */
150
+ readonly writes: readonly BoundWrite[];
151
+ /** The props as the component currently sees them. */
152
+ props(): P;
153
+ /** Replace the props and settle. */
154
+ setProps(props: P): Promise<void>;
155
+ /** Merge into the props and settle. */
156
+ patchProps(patch: Partial<P>): Promise<void>;
157
+ /** Flip the loading flag and settle. */
158
+ setLoading(loading: boolean): Promise<void>;
159
+ /** Let effects, promises and change detection finish. */
160
+ settle(): Promise<void>;
161
+ /** Destroy the fixture. Rarely needed — TestBed resets between tests. */
162
+ destroy(): void;
163
+ }
164
+ /**
165
+ * Mount a catalog component with no renderer and no spec: hand it props, and
166
+ * read back the events it emitted and the values it wrote to bound props.
167
+ *
168
+ * This is the test of a component as a presentational one — what it draws
169
+ * for these props, and what it says when used. What the spec then does with
170
+ * that is the renderer's business, and `renderSpec` is where to test it.
171
+ *
172
+ * `props` is not inferred from the first value, so a later `patchProps` can
173
+ * set a prop the first render left out. Pass `P` to have the props typed.
174
+ *
175
+ * A component that renders `<jr-children>`, registers field validation or
176
+ * dispatches actions directly needs the renderer's services and fails with a
177
+ * message pointing at `renderSpec`.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const button = await renderComponent(MyButton, { props: { label: 'Save' } });
182
+ *
183
+ * expect(button.text('button')).toBe('Save');
184
+ * await button.click('button');
185
+ * expect(button.emitted).toEqual(['press']);
186
+ *
187
+ * await button.patchProps({ disabled: true });
188
+ * expect(button.find<HTMLButtonElement>('button').disabled).toBe(true);
189
+ * ```
190
+ */
191
+ declare function renderComponent<T, P extends object = Record<string, unknown>>(component: Type<T>, options?: RenderComponentOptions<NoInfer<P>>): Promise<ComponentHarness<T, P>>;
192
+
88
193
  /** One action the spec asked for, in the order it was dispatched. */
89
194
  interface DispatchedAction {
90
195
  name: string;
@@ -123,7 +228,7 @@ interface RenderSpecOptions {
123
228
  * `fixture` and `element` are there for everything this does not cover — the
124
229
  * harness is a shortcut, not a wall.
125
230
  */
126
- interface SpecHarness {
231
+ interface SpecHarness extends DomQueries {
127
232
  /** The fixture of the host that owns the `<json-render>`. */
128
233
  readonly fixture: ComponentFixture<unknown>;
129
234
  /** The host element, the root of the rendered DOM. */
@@ -150,18 +255,6 @@ interface SpecHarness {
150
255
  write(path: string, value: unknown): Promise<void>;
151
256
  /** The whole state model. */
152
257
  state(): StateModel;
153
- /** Trimmed text of the first match, or of everything when given nothing. */
154
- text(selector?: string): string;
155
- /** Trimmed text of every match. */
156
- texts(selector: string): string[];
157
- /** The first match. Throws when there is none. */
158
- find<T extends HTMLElement>(selector: string): T;
159
- /** Every match, possibly none. */
160
- findAll<T extends HTMLElement>(selector: string): T[];
161
- /** Click an element (or the first match of a selector) and settle. */
162
- click(target: string | Element): Promise<void>;
163
- /** Set an input's value, fire `input`, and settle. */
164
- fill(target: string | Element, value: string): Promise<void>;
165
258
  /** Destroy the fixture. Rarely needed — TestBed resets between tests. */
166
259
  destroy(): void;
167
260
  }
@@ -193,5 +286,5 @@ interface SpecHarness {
193
286
  */
194
287
  declare function renderSpec(spec: Spec | null, options: RenderSpecOptions): Promise<SpecHarness>;
195
288
 
196
- export { recordedTransport, renderSpec, specStream, usageLine };
197
- export type { DispatchedAction, RecordedFailure, RecordedTransportOptions, Recording, Recordings, RenderSpecOptions, SpecHarness };
289
+ export { recordedTransport, renderComponent, renderSpec, specStream, usageLine };
290
+ export type { BoundWrite, ComponentHarness, DispatchedAction, RecordedFailure, RecordedTransportOptions, Recording, Recordings, RenderComponentOptions, RenderSpecOptions, SpecHarness };
@@ -643,6 +643,12 @@ declare class JrElement {
643
643
  */
644
644
  private fireEvent;
645
645
  private emitEvent;
646
+ /**
647
+ * Run an `on` or `watch` binding's actions in order. Params are resolved
648
+ * per action, just before it runs, so a `$state` read in a later action of
649
+ * the chain sees what an earlier one wrote.
650
+ */
651
+ private dispatch;
646
652
  private eventHandle;
647
653
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<JrElement, never>;
648
654
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<JrElement, "jr-element", never, { "elementKey": { "alias": "elementKey"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;