@symbiote-native/test-utils 0.1.1 → 0.1.2
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/build/fake-fabric.d.ts +42 -0
- package/build/fake-fabric.js +100 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +4 -0
- package/package.json +1 -1
|
@@ -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 (ADR 0015).
|
|
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 (ADR 0025). `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, mirroring `clone_on_write_lives_in_engine`.
|
|
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
|
+
}
|
package/build/index.d.ts
ADDED
|
@@ -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 (ADR 0025). 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.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|