@flighthq/node 0.1.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.
Files changed (75) hide show
  1. package/dist/boundsRectangle.d.ts +26 -0
  2. package/dist/boundsRectangle.d.ts.map +1 -0
  3. package/dist/boundsRectangle.js +160 -0
  4. package/dist/boundsRectangle.js.map +1 -0
  5. package/dist/hasAppearance.d.ts +3 -0
  6. package/dist/hasAppearance.d.ts.map +1 -0
  7. package/dist/hasAppearance.js +6 -0
  8. package/dist/hasAppearance.js.map +1 -0
  9. package/dist/hasBoundsRectangle.d.ts +5 -0
  10. package/dist/hasBoundsRectangle.d.ts.map +1 -0
  11. package/dist/hasBoundsRectangle.js +9 -0
  12. package/dist/hasBoundsRectangle.js.map +1 -0
  13. package/dist/hasClip.d.ts +3 -0
  14. package/dist/hasClip.d.ts.map +1 -0
  15. package/dist/hasClip.js +4 -0
  16. package/dist/hasClip.js.map +1 -0
  17. package/dist/hasMaterial.d.ts +3 -0
  18. package/dist/hasMaterial.d.ts.map +1 -0
  19. package/dist/hasMaterial.js +5 -0
  20. package/dist/hasMaterial.js.map +1 -0
  21. package/dist/hasTransform2d.d.ts +4 -0
  22. package/dist/hasTransform2d.d.ts.map +1 -0
  23. package/dist/hasTransform2d.js +19 -0
  24. package/dist/hasTransform2d.js.map +1 -0
  25. package/dist/hasTransform3d.d.ts +5 -0
  26. package/dist/hasTransform3d.d.ts.map +1 -0
  27. package/dist/hasTransform3d.js +8 -0
  28. package/dist/hasTransform3d.js.map +1 -0
  29. package/dist/hierarchy.d.ts +127 -0
  30. package/dist/hierarchy.d.ts.map +1 -0
  31. package/dist/hierarchy.js +396 -0
  32. package/dist/hierarchy.js.map +1 -0
  33. package/dist/index.d.ts +15 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +15 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/node.d.ts +21 -0
  38. package/dist/node.d.ts.map +1 -0
  39. package/dist/node.js +109 -0
  40. package/dist/node.js.map +1 -0
  41. package/dist/revision.d.ts +40 -0
  42. package/dist/revision.d.ts.map +1 -0
  43. package/dist/revision.js +85 -0
  44. package/dist/revision.js.map +1 -0
  45. package/dist/transform2d.d.ts +16 -0
  46. package/dist/transform2d.d.ts.map +1 -0
  47. package/dist/transform2d.js +99 -0
  48. package/dist/transform2d.js.map +1 -0
  49. package/dist/transform3d.d.ts +6 -0
  50. package/dist/transform3d.d.ts.map +1 -0
  51. package/dist/transform3d.js +45 -0
  52. package/dist/transform3d.js.map +1 -0
  53. package/dist/traversal.d.ts +51 -0
  54. package/dist/traversal.d.ts.map +1 -0
  55. package/dist/traversal.js +130 -0
  56. package/dist/traversal.js.map +1 -0
  57. package/dist/viewport.d.ts +8 -0
  58. package/dist/viewport.d.ts.map +1 -0
  59. package/dist/viewport.js +78 -0
  60. package/dist/viewport.js.map +1 -0
  61. package/package.json +40 -0
  62. package/src/boundsRectangle.test.ts +497 -0
  63. package/src/hasAppearance.test.ts +47 -0
  64. package/src/hasBoundsRectangle.test.ts +57 -0
  65. package/src/hasClip.test.ts +27 -0
  66. package/src/hasMaterial.test.ts +30 -0
  67. package/src/hasTransform2d.test.ts +67 -0
  68. package/src/hasTransform3d.test.ts +37 -0
  69. package/src/hierarchy.test.ts +973 -0
  70. package/src/node.test.ts +265 -0
  71. package/src/revision.test.ts +211 -0
  72. package/src/transform2d.test.ts +391 -0
  73. package/src/transform3d.test.ts +202 -0
  74. package/src/traversal.test.ts +233 -0
  75. package/src/viewport.test.ts +228 -0
@@ -0,0 +1,265 @@
1
+ import { connectSignal } from '@flighthq/signals';
2
+ import type { Node, NodeData, NodeRuntime, PartialNode } from '@flighthq/types';
3
+ import { NodeKind } from '@flighthq/types';
4
+
5
+ import { addNodeChild, getNodeChildCount, getNodeParent } from './hierarchy';
6
+ import {
7
+ createNode,
8
+ createNodeRuntime,
9
+ createNodeSignals,
10
+ defaultNodeRuntimeCanAddChild,
11
+ disposeNode,
12
+ enableNodeSignals,
13
+ getNodeRuntime,
14
+ getNodeSignals,
15
+ setNodeEnabled,
16
+ } from './node';
17
+
18
+ describe('createNode', () => {
19
+ let node: Node;
20
+
21
+ beforeEach(() => {
22
+ node = createNode(NodeTestKind);
23
+ });
24
+
25
+ it('initializes default values', () => {
26
+ expect(node.enabled).toBe(true);
27
+ });
28
+
29
+ it('allows pre-defined values', () => {
30
+ const base = {
31
+ parent: createNode(NodeTestKind),
32
+ children: [],
33
+ enabled: false,
34
+ };
35
+ node = createNode(NodeTestKind, base);
36
+ expect(node.enabled).toStrictEqual(base.enabled);
37
+ });
38
+
39
+ it('returns a new object for better hidden-class performance', () => {
40
+ const base = {};
41
+ node = createNode(NodeTestKind, base);
42
+ expect(node).not.toStrictEqual(base);
43
+ });
44
+
45
+ it('allows creation of a type without a data field', () => {
46
+ const node = createNode(NodeTestKind);
47
+ expect(node.data).toBeNull();
48
+ });
49
+
50
+ it('makes a default runtime object if none passed in', () => {
51
+ const node = createNode(NodeTestKind);
52
+ const runtime = getNodeRuntime(node);
53
+ expect(runtime).not.toBeNull();
54
+ });
55
+
56
+ it('allows a custom type', () => {
57
+ const node = createNode(NodeTestKind);
58
+ expect(node.kind).toBe(NodeTestKind);
59
+ });
60
+
61
+ it('returns a new object', () => {
62
+ const obj: PartialNode<NodeTest> = {};
63
+ const node: NodeTest = createNode(NodeTestKind, obj) as NodeTest;
64
+ expect(node).not.toStrictEqual(obj);
65
+ });
66
+
67
+ it('allows use of a data initializer', () => {
68
+ const obj: PartialNode<NodeTest> = {};
69
+ const node: NodeTest = createNode(NodeTestKind, obj, createGraphNodeTestData) as NodeTest;
70
+ expect((node.data as NodeTestData).testDataField).toBe('testDataField');
71
+ });
72
+
73
+ it('allows use of a runtime initializer', () => {
74
+ const obj: PartialNode<NodeTest> = {};
75
+ const node = createNode(NodeTestKind, obj, undefined, createGraphNodeTestRuntime);
76
+ const runtime = getNodeRuntime(node);
77
+ expect((runtime as NodeTestRuntime).testRuntimeField).toBe('testRuntimeField');
78
+ });
79
+ });
80
+
81
+ describe('createNodeRuntime', () => {
82
+ let runtime: NodeRuntime;
83
+
84
+ beforeEach(() => {
85
+ runtime = createNodeRuntime();
86
+ });
87
+
88
+ it('initializes default values', () => {
89
+ expect(runtime.appearanceId).toStrictEqual(0);
90
+ expect(runtime.boundsUsingLocalBoundsId).toStrictEqual(-1);
91
+ expect(runtime.boundsUsingLocalTransformId).toStrictEqual(-1);
92
+ expect(runtime.children).toBeNull();
93
+ expect(runtime.nodeSignals).toBeNull();
94
+ expect(runtime.localBoundsId).toStrictEqual(0);
95
+ expect(runtime.localBoundsUsingLocalBoundsId).toStrictEqual(-1);
96
+ expect(runtime.localTransformId).toStrictEqual(0);
97
+ expect(runtime.localTransformUsingLocalTransformId).toStrictEqual(-1);
98
+ expect(runtime.parent).toBeNull();
99
+ expect(runtime.worldBoundsUsingLocalBoundsId).toStrictEqual(-1);
100
+ expect(runtime.worldBoundsUsingWorldTransformId).toStrictEqual(-1);
101
+ expect(runtime.worldTransformId).toStrictEqual(0);
102
+ expect(runtime.worldTransformUsingLocalTransformId).toStrictEqual(-1);
103
+ expect(runtime.worldTransformUsingParentTransformId).toStrictEqual(-1);
104
+ expect(runtime.canAddChild).toStrictEqual(defaultNodeRuntimeCanAddChild);
105
+ });
106
+
107
+ it('does not set traits — concrete graph runtimes set it', () => {
108
+ expect(runtime.traits).toBeUndefined();
109
+ });
110
+
111
+ it('allows custom canAddChild', () => {
112
+ const methods = {
113
+ canAddChild: (_parent: Node, _child: Node) => true,
114
+ };
115
+ runtime = createNodeRuntime(methods);
116
+ expect(runtime.canAddChild).toStrictEqual(methods.canAddChild);
117
+ });
118
+ });
119
+
120
+ describe('createNodeSignals', () => {
121
+ it('returns an object with all signal properties', () => {
122
+ const signals = createNodeSignals();
123
+ expect(signals.onChildAdded).toBeDefined();
124
+ expect(signals.onChildRemoved).toBeDefined();
125
+ expect(signals.onChildrenChanged).toBeDefined();
126
+ expect(signals.onChildrenOrderChanged).toBeDefined();
127
+ expect(signals.onParentChanged).toBeDefined();
128
+ });
129
+ });
130
+
131
+ describe('defaultNodeRuntimeCanAddChild', () => {
132
+ it('always returns true', () => {
133
+ const parent = createNode(NodeTestKind);
134
+ const child = createNode(NodeTestKind);
135
+ expect(defaultNodeRuntimeCanAddChild(parent, child)).toBe(true);
136
+ });
137
+ });
138
+
139
+ describe('disposeNode', () => {
140
+ it('detaches the node from its parent', () => {
141
+ const parent = createNode(NodeKind);
142
+ const child = createNode(NodeKind);
143
+ addNodeChild(parent, child);
144
+ disposeNode(child);
145
+ expect(getNodeParent(child)).toBeNull();
146
+ expect(getNodeChildCount(parent)).toBe(0);
147
+ });
148
+
149
+ it('recursively removes all descendants', () => {
150
+ const parent = createNode(NodeKind);
151
+ const child = createNode(NodeKind);
152
+ const grandchild = createNode(NodeKind);
153
+ addNodeChild(parent, child);
154
+ addNodeChild(child, grandchild);
155
+ disposeNode(parent);
156
+ expect(getNodeChildCount(parent)).toBe(0);
157
+ expect(getNodeParent(child)).toBeNull();
158
+ expect(getNodeParent(grandchild)).toBeNull();
159
+ });
160
+
161
+ it('clears nodeSignals after disposal', () => {
162
+ const node = createNode(NodeKind);
163
+ enableNodeSignals(node);
164
+ disposeNode(node);
165
+ expect(getNodeSignals(node)).toBeNull();
166
+ });
167
+
168
+ it('is safe to call on a root node with no parent', () => {
169
+ const node = createNode(NodeKind);
170
+ expect(() => disposeNode(node)).not.toThrow();
171
+ });
172
+
173
+ it('is safe to call on a leaf node with no children', () => {
174
+ const node = createNode(NodeKind);
175
+ expect(() => disposeNode(node)).not.toThrow();
176
+ });
177
+ });
178
+
179
+ describe('enableNodeSignals', () => {
180
+ it('creates and returns signals on first call', () => {
181
+ const node = createNode(NodeTestKind);
182
+ const signals = enableNodeSignals(node);
183
+ expect(signals).toBeDefined();
184
+ expect(signals.onChildrenChanged).toBeDefined();
185
+ });
186
+
187
+ it('returns the same object on subsequent calls', () => {
188
+ const node = createNode(NodeTestKind);
189
+ expect(enableNodeSignals(node)).toBe(enableNodeSignals(node));
190
+ });
191
+
192
+ it('makes getNodeSignals return the enabled object', () => {
193
+ const node = createNode(NodeTestKind);
194
+ const signals = enableNodeSignals(node);
195
+ expect(getNodeSignals(node)).toBe(signals);
196
+ });
197
+ });
198
+
199
+ describe('getNodeRuntime', () => {
200
+ it('assumes runtime is defined', () => {
201
+ const node = { kind: NodeTestKind };
202
+ const runtime = getNodeRuntime(node as Node);
203
+ expect(runtime).toBeUndefined();
204
+ });
205
+
206
+ it('returns runtime when defined', () => {
207
+ const node = createNode(NodeTestKind);
208
+ const runtime = getNodeRuntime(node);
209
+ expect(runtime).not.toBeUndefined();
210
+ });
211
+ });
212
+
213
+ describe('getNodeSignals', () => {
214
+ it('returns null before signals are enabled', () => {
215
+ const node = createNode(NodeTestKind);
216
+ expect(getNodeSignals(node)).toBeNull();
217
+ });
218
+
219
+ it('returns the signals after enableNodeSignals', () => {
220
+ const node = createNode(NodeTestKind);
221
+ const signals = enableNodeSignals(node);
222
+ expect(getNodeSignals(node)).toBe(signals);
223
+ });
224
+ });
225
+
226
+ describe('setNodeEnabled', () => {
227
+ it('sets enabled to false', () => {
228
+ const node = createNode(NodeTestKind);
229
+ setNodeEnabled(node, false);
230
+ expect(node.enabled).toBe(false);
231
+ });
232
+
233
+ it('sets enabled back to true', () => {
234
+ const node = createNode(NodeTestKind);
235
+ setNodeEnabled(node, false);
236
+ setNodeEnabled(node, true);
237
+ expect(node.enabled).toBe(true);
238
+ });
239
+ });
240
+
241
+ const NodeTestKind = 'NodeTest';
242
+
243
+ interface NodeTest extends Node {
244
+ data: NodeTestData;
245
+ }
246
+
247
+ interface NodeTestData extends NodeData {
248
+ testDataField: string;
249
+ }
250
+
251
+ interface NodeTestRuntime extends NodeRuntime {
252
+ testRuntimeField: string;
253
+ }
254
+
255
+ function createGraphNodeTestData(data?: Partial<NodeTestData>): NodeTestData {
256
+ return {
257
+ testDataField: data?.testDataField ?? 'testDataField',
258
+ };
259
+ }
260
+
261
+ function createGraphNodeTestRuntime(): NodeTestRuntime {
262
+ const obj = createNodeRuntime() as NodeTestRuntime;
263
+ obj.testRuntimeField = 'testRuntimeField';
264
+ return obj;
265
+ }
@@ -0,0 +1,211 @@
1
+ import { createNode, getNodeRuntime } from '@flighthq/node';
2
+ import type { Node, NodeRuntime } from '@flighthq/types';
3
+
4
+ import {
5
+ computeNodeWorldTransformRevision,
6
+ getNodeAppearanceRevision,
7
+ getNodeLocalBoundsRevision,
8
+ getNodeLocalContentRevision,
9
+ getNodeLocalTransformRevision,
10
+ getNodeWorldTransformRevision,
11
+ invalidateNode,
12
+ invalidateNodeAppearance,
13
+ invalidateNodeLocalBounds,
14
+ invalidateNodeLocalContent,
15
+ invalidateNodeLocalTransform,
16
+ invalidateNodeParentReference,
17
+ invalidateNodeRender,
18
+ invalidateNodeWorldBounds,
19
+ } from './revision';
20
+
21
+ function createTestNode(): TestNode {
22
+ return createNode(TestKind) as TestNode;
23
+ }
24
+
25
+ let node: TestNode;
26
+ beforeEach(() => {
27
+ node = createTestNode();
28
+ });
29
+
30
+ function getEntityRuntime(source: TestNode) {
31
+ return getNodeRuntime(source) as NodeRuntime;
32
+ }
33
+
34
+ describe('computeNodeWorldTransformRevision', () => {
35
+ it('updates worldTransformId based on local and parent transform IDs', () => {
36
+ const runtime = getEntityRuntime(node);
37
+ runtime.localTransformId = 3;
38
+ computeNodeWorldTransformRevision(runtime);
39
+ expect(runtime.worldTransformUsingLocalTransformId).toBe(3);
40
+ expect(runtime.worldTransformUsingParentTransformId).toBe(0);
41
+ });
42
+
43
+ it('incorporates parent worldTransformId when provided', () => {
44
+ const parentNode = createTestNode();
45
+ const parentRuntime = getEntityRuntime(parentNode);
46
+ parentRuntime.worldTransformId = 7;
47
+ const runtime = getEntityRuntime(node);
48
+ computeNodeWorldTransformRevision(runtime, parentRuntime);
49
+ expect(runtime.worldTransformUsingParentTransformId).toBe(7);
50
+ });
51
+ });
52
+
53
+ describe('getNodeAppearanceRevision', () => {
54
+ it('returns appearanceId', () => {
55
+ const runtime = getEntityRuntime(node);
56
+ runtime.appearanceId = 100;
57
+ expect(getNodeAppearanceRevision(node)).toStrictEqual(runtime.appearanceId);
58
+ });
59
+ });
60
+
61
+ describe('getNodeLocalBoundsRevision', () => {
62
+ it('returns localBoundsId', () => {
63
+ const runtime = getEntityRuntime(node);
64
+ runtime.localBoundsId = 100;
65
+ expect(getNodeLocalBoundsRevision(node)).toStrictEqual(runtime.localBoundsId);
66
+ });
67
+ });
68
+
69
+ describe('getNodeLocalContentRevision', () => {
70
+ it('returns localContentId', () => {
71
+ const runtime = getEntityRuntime(node);
72
+ runtime.localContentId = 100;
73
+ expect(getNodeLocalContentRevision(node)).toStrictEqual(runtime.localContentId);
74
+ });
75
+ });
76
+
77
+ describe('getNodeLocalTransformRevision', () => {
78
+ it('returns localTransformId', () => {
79
+ const runtime = getEntityRuntime(node);
80
+ runtime.localTransformId = 100;
81
+ expect(getNodeLocalTransformRevision(node)).toStrictEqual(runtime.localTransformId);
82
+ });
83
+ });
84
+
85
+ describe('getNodeWorldTransformRevision', () => {
86
+ it('returns worldTransformId', () => {
87
+ const runtime = getEntityRuntime(node);
88
+ runtime.worldTransformId = 100;
89
+ expect(getNodeWorldTransformRevision(node)).toStrictEqual(runtime.worldTransformId);
90
+ });
91
+ });
92
+
93
+ describe('invalidateNode', () => {
94
+ it('increments appearanceId, localBoundsId, localContentId, localTransformId', () => {
95
+ const appearanceId = getEntityRuntime(node).appearanceId;
96
+ const localBoundsId = getEntityRuntime(node).localBoundsId;
97
+ const localContentId = getEntityRuntime(node).localContentId;
98
+ const localTransformId = getEntityRuntime(node).localTransformId;
99
+ invalidateNode(node);
100
+ expect(getEntityRuntime(node).appearanceId).toBe(appearanceId + 1);
101
+ expect(getEntityRuntime(node).localBoundsId).toBe(localBoundsId + 1);
102
+ expect(getEntityRuntime(node).localContentId).toBe(localContentId + 1);
103
+ expect(getEntityRuntime(node).localTransformId).toBe(localTransformId + 1);
104
+ });
105
+
106
+ it('invalidates parent reference', () => {
107
+ invalidateNode(node);
108
+ expect(getEntityRuntime(node).worldTransformUsingParentTransformId).toBe(-1);
109
+ });
110
+
111
+ it('invalidates world bounds', () => {
112
+ invalidateNode(node);
113
+ expect(getEntityRuntime(node).worldBoundsUsingWorldTransformId).toBe(-1);
114
+ expect(getEntityRuntime(node).worldBoundsUsingLocalBoundsId).toBe(-1);
115
+ });
116
+ });
117
+
118
+ describe('invalidateNodeAppearance', () => {
119
+ it('increments appearanceId', () => {
120
+ const appearanceId = getEntityRuntime(node).appearanceId;
121
+ invalidateNodeAppearance(node);
122
+ expect(getEntityRuntime(node).appearanceId).toBe(appearanceId + 1);
123
+ });
124
+
125
+ it('should wrap around appearanceId correctly using >>> 0', () => {
126
+ const runtime = getEntityRuntime(node);
127
+ runtime.appearanceId = 0xffffffff; // max 32-bit uint
128
+ invalidateNodeAppearance(node);
129
+ expect(getEntityRuntime(node).appearanceId).toBe(0);
130
+ });
131
+ });
132
+
133
+ describe('invalidateNodeLocalBounds', () => {
134
+ it('increments localBoundsId', () => {
135
+ const localBoundsId = getEntityRuntime(node).localBoundsId;
136
+ invalidateNodeLocalBounds(node);
137
+ expect(getEntityRuntime(node).localBoundsId).toBe(localBoundsId + 1);
138
+ });
139
+
140
+ it('should wrap around localBoundsId correctly using >>> 0', () => {
141
+ const runtime = getEntityRuntime(node);
142
+ runtime.localBoundsId = 0xffffffff; // max 32-bit uint
143
+ invalidateNodeLocalBounds(node);
144
+ expect(getEntityRuntime(node).localBoundsId).toBe(0);
145
+ });
146
+ });
147
+
148
+ describe('invalidateNodeLocalContent', () => {
149
+ it('increments localContentId', () => {
150
+ const localContentId = getEntityRuntime(node).localContentId;
151
+ invalidateNodeLocalContent(node);
152
+ expect(getEntityRuntime(node).localContentId).toBe(localContentId + 1);
153
+ });
154
+
155
+ it('should wrap around localContentId correctly using >>> 0', () => {
156
+ const runtime = getEntityRuntime(node);
157
+ runtime.localContentId = 0xffffffff; // max 32-bit uint
158
+ invalidateNodeLocalContent(node);
159
+ expect(getEntityRuntime(node).localContentId).toBe(0);
160
+ });
161
+ });
162
+
163
+ describe('invalidateNodeLocalTransform', () => {
164
+ it('increments localTransformId', () => {
165
+ const localTransformId = getEntityRuntime(node).localTransformId;
166
+ invalidateNodeLocalTransform(node);
167
+ expect(getEntityRuntime(node).localTransformId).toBe(localTransformId + 1);
168
+ });
169
+
170
+ it('should wrap around localTransformId correctly using >>> 0', () => {
171
+ const runtime = getEntityRuntime(node);
172
+ runtime.localTransformId = 0xffffffff; // max 32-bit uint
173
+ invalidateNodeLocalTransform(node);
174
+ expect(getEntityRuntime(node).localTransformId).toBe(0);
175
+ });
176
+ });
177
+
178
+ describe('invalidateNodeParentReference', () => {
179
+ it('invalidates the world transform parent transform cached ID', () => {
180
+ const runtime = getEntityRuntime(node);
181
+ runtime.worldTransformUsingParentTransformId = 1;
182
+ invalidateNodeParentReference(node);
183
+ expect(runtime.worldTransformUsingParentTransformId).toBe(-1);
184
+ });
185
+ });
186
+
187
+ describe('invalidateNodeRender', () => {
188
+ it('increments both appearanceId and localTransformId', () => {
189
+ const runtime = getEntityRuntime(node);
190
+ const prevAppearance = runtime.appearanceId;
191
+ const prevLocalTransform = runtime.localTransformId;
192
+ invalidateNodeRender(node);
193
+ expect(runtime.appearanceId).toBe(prevAppearance + 1);
194
+ expect(runtime.localTransformId).toBe(prevLocalTransform + 1);
195
+ });
196
+ });
197
+
198
+ describe('invalidateNodeWorldBounds', () => {
199
+ it('invalidates supporting values for world bounds calculations', () => {
200
+ const runtime = getEntityRuntime(node);
201
+ runtime.worldBoundsUsingWorldTransformId = 1;
202
+ runtime.worldBoundsUsingLocalBoundsId = 1;
203
+ invalidateNodeWorldBounds(node);
204
+ expect(runtime.worldBoundsUsingWorldTransformId).toBe(-1);
205
+ expect(runtime.worldBoundsUsingLocalBoundsId).toBe(-1);
206
+ });
207
+ });
208
+
209
+ type TestNode = Node;
210
+
211
+ const TestKind = 'Test';