@symbiote-native/test-utils 0.1.1 → 0.1.3

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
@@ -1,6 +1,6 @@
1
1
  # @symbiote-native/test-utils
2
2
 
3
- The **shared fake-Fabric test harness** of [SymbioteNative](../../README.md) (ADR 0025) — one
3
+ The **shared fake-Fabric test harness** of [SymbioteNative](../../README.md) — one
4
4
  `installFabric()` that puts a fresh, faithful fake `nativeFabricUIManager` on `globalThis` and
5
5
  returns a handle to inspect what a renderer committed. It replaces the per-file fake slot every
6
6
  headless test used to copy-paste (×65 across the repo) with one implementation, shared by the
@@ -44,7 +44,7 @@ otherwise leak state into the next one.
44
44
  ## What `installFabric()` gives you
45
45
 
46
46
  - **`committed`** — the child set from the most recent `completeRoot`, and **`appRoot()`** — unwraps
47
- RN's synthetic `box-none` AppContainer root (ADR 0015) so a test doesn't re-check that invariant by
47
+ RN's synthetic `box-none` AppContainer root so a test doesn't re-check that invariant by
48
48
  hand.
49
49
  - **`created`** and **`find(predicate)`** — every node ever `createNode`'d this run (clones
50
50
  excluded), and a lookup by predicate (e.g. "the app's own `View` with this `testID`").
@@ -0,0 +1,42 @@
1
+ export interface IFakeNode {
2
+ tag: number;
3
+ viewName: string;
4
+ props: Record<string, unknown>;
5
+ children: IFakeNode[];
6
+ instanceHandle: unknown;
7
+ /** Fabric family parent. Clones keep the same tag/family, so reparenting a family is illegal. */
8
+ parentFamilyTag?: number;
9
+ }
10
+ export type IEventHandler = (instanceHandle: unknown, topLevelType: string, nativeEvent: Record<string, unknown>) => void;
11
+ export interface IFabricRecorder {
12
+ /** The child set handed to the most recent `completeRoot`. */
13
+ committed: IFakeNode[];
14
+ /** Every node ever `createNode`'d this run (clones excluded). */
15
+ created: IFakeNode[];
16
+ /** Every imperative command dispatched at a committed Fabric node. */
17
+ commands: Array<{
18
+ node: IFakeNode;
19
+ commandName: string;
20
+ args: readonly unknown[];
21
+ }>;
22
+ /** Call counters, for tests that assert "exactly N native nodes were created". */
23
+ counts: {
24
+ createNode: number;
25
+ completeRoot: number;
26
+ };
27
+ /**
28
+ * RN wraps every commit in a synthetic `box-none` AppContainer root.
29
+ * Returns it, asserting it is the single expected root, so each test unwraps the
30
+ * AppContainer the same way instead of re-checking the invariant by hand.
31
+ */
32
+ appRoot(): IFakeNode;
33
+ /** Find the first `createNode`'d node matching a predicate (e.g. the app's own View). */
34
+ find(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined;
35
+ /** Deliver a native event to the renderer's registered handler. */
36
+ fireEvent(handle: unknown, topLevelType: string, nativeEvent?: Record<string, unknown>): void;
37
+ /** Serialize a node list to `RCTView(RCTText(RCTRawText "text"))` shorthand. */
38
+ serialize(nodes: IFakeNode[]): string;
39
+ /** Zero the counters and clear `committed` / `created` (the event handler survives). */
40
+ reset(): void;
41
+ }
42
+ export declare function installFabric(): IFabricRecorder;
@@ -0,0 +1,100 @@
1
+ // One shared fake `nativeFabricUIManager` for the unit suite. `installFabric()`
2
+ // puts a fresh recording slot on `globalThis` and returns a handle to inspect what the
3
+ // renderer committed. It replaces the per-file slot the smokes each copy-pasted (×65).
4
+ //
5
+ // Faithful persistent (clone-on-write) semantics, identical to what the engine drives
6
+ // against real Fabric: every clone is a NEW identity; `*NewProps` MERGES the payload onto
7
+ // the previous props (the engine always sends a minimal diff — see `diffProps` in
8
+ // commit.ts — and relies on native Fabric to merge it onto the retained props; a removed
9
+ // key arrives as literal `null` and is kept as `null`, not deleted, so a test can still see
10
+ // "explicitly reset" distinct from "never set"); the `*Children` variants reset children
11
+ // (the engine re-appends). A persistence bug in the fake is now fixed once, here, for every
12
+ // test, matching how the real engine's clone-on-write commit path behaves.
13
+ // Mirrors real Fabric's clone*WithNewProps merge: `diff` is a minimal payload (only changed
14
+ // keys, plus a removed key sent as literal `null` — kept as `null` here, not deleted, so a
15
+ // test can tell "explicitly reset to default" apart from "never set").
16
+ function mergeFabricProps(previous, diff) {
17
+ return { ...previous, ...diff };
18
+ }
19
+ export function installFabric() {
20
+ let committed = [];
21
+ const created = [];
22
+ const commands = [];
23
+ const counts = { createNode: 0, completeRoot: 0 };
24
+ let eventHandler;
25
+ const slot = {
26
+ createNode(tag, viewName, _rootTag, props, instanceHandle) {
27
+ counts.createNode += 1;
28
+ const node = { tag, viewName, props, children: [], instanceHandle };
29
+ created.push(node);
30
+ return node;
31
+ },
32
+ cloneNodeWithNewProps: (node, newProps) => ({
33
+ ...node,
34
+ props: mergeFabricProps(node.props, newProps),
35
+ }),
36
+ cloneNodeWithNewChildren: (node) => ({ ...node, children: [] }),
37
+ cloneNodeWithNewChildrenAndProps: (node, newProps) => ({ ...node, props: mergeFabricProps(node.props, newProps), children: [] }),
38
+ createChildSet: () => [],
39
+ appendChild(parent, child) {
40
+ if (child.parentFamilyTag !== undefined && child.parentFamilyTag !== parent.tag) {
41
+ throw new Error(`Fabric family reparent: child ${child.viewName}#${child.tag} already belongs to parent #${child.parentFamilyTag}, cannot append to ${parent.viewName}#${parent.tag}`);
42
+ }
43
+ child.parentFamilyTag = parent.tag;
44
+ parent.children.push(child);
45
+ return parent;
46
+ },
47
+ appendChildToSet(childSet, child) {
48
+ childSet.push(child);
49
+ },
50
+ completeRoot(_rootTag, childSet) {
51
+ counts.completeRoot += 1;
52
+ committed = childSet;
53
+ },
54
+ registerEventHandler(handler) {
55
+ eventHandler = handler;
56
+ },
57
+ dispatchCommand(node, commandName, args) {
58
+ commands.push({ node, commandName, args });
59
+ },
60
+ };
61
+ Object.assign(globalThis, { nativeFabricUIManager: slot });
62
+ const serializeNode = (node) => {
63
+ const text = node.viewName === 'RCTRawText' ? ` "${String(node.props.text)}"` : '';
64
+ const kids = node.children.length ? `(${node.children.map(serializeNode).join('')})` : '';
65
+ return `${node.viewName}${text}${kids}`;
66
+ };
67
+ return {
68
+ get committed() {
69
+ return committed;
70
+ },
71
+ created,
72
+ commands,
73
+ counts,
74
+ appRoot() {
75
+ const root = committed[0];
76
+ if (committed.length !== 1 || root?.props.pointerEvents !== 'box-none') {
77
+ throw new Error(`expected a single box-none AppContainer root, got ${committed.length} node(s)`);
78
+ }
79
+ return root;
80
+ },
81
+ find(predicate) {
82
+ return created.find(predicate);
83
+ },
84
+ fireEvent(handle, topLevelType, nativeEvent = {}) {
85
+ if (!eventHandler)
86
+ throw new Error('no event handler registered by the renderer');
87
+ eventHandler(handle, topLevelType, nativeEvent);
88
+ },
89
+ serialize(nodes) {
90
+ return nodes.map(serializeNode).join('');
91
+ },
92
+ reset() {
93
+ committed = [];
94
+ created.length = 0;
95
+ commands.length = 0;
96
+ counts.createNode = 0;
97
+ counts.completeRoot = 0;
98
+ },
99
+ };
100
+ }
@@ -0,0 +1 @@
1
+ export * from './fake-fabric';
package/build/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/test-utils: shared, framework-agnostic test harness. Imported by
2
+ // the co-located tests across engine, adapters, and the example apps so the fake-Fabric
3
+ // recorder and its helpers live in exactly one place.
4
+ export * from './fake-fabric';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/test-utils",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Shared, framework-agnostic fake-Fabric test harness for SymbioteNative — the fake nativeFabricUIManager recorder used by the co-located test suites across the engine, every adapter, and the example apps.",
5
5
  "repository": {
6
6
  "type": "git",