@flighthq/node 0.3.1-next.861.5a56d7a → 0.3.1-next.948.85d9f30
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/dist/boundsRectangle.d.ts +3 -3
- package/dist/boundsRectangle.d.ts.map +1 -1
- package/dist/boundsRectangle.js +63 -11
- package/dist/boundsRectangle.js.map +1 -1
- package/dist/hasAppearance.d.ts +2 -1
- package/dist/hasAppearance.d.ts.map +1 -1
- package/dist/hasAppearance.js +10 -0
- package/dist/hasAppearance.js.map +1 -1
- package/dist/hasBoundsRectangle.d.ts.map +1 -1
- package/dist/hasBoundsRectangle.js +4 -0
- package/dist/hasBoundsRectangle.js.map +1 -1
- package/dist/hasTransform3d.d.ts.map +1 -1
- package/dist/hasTransform3d.js +11 -4
- package/dist/hasTransform3d.js.map +1 -1
- package/dist/hierarchy.d.ts +3 -3
- package/dist/hierarchy.d.ts.map +1 -1
- package/dist/hierarchy.js +20 -20
- package/dist/hierarchy.js.map +1 -1
- package/dist/nodeOrderList.d.ts.map +1 -1
- package/dist/nodeOrderList.js +9 -2
- package/dist/nodeOrderList.js.map +1 -1
- package/dist/nodeTransform2d.d.ts.map +1 -1
- package/dist/nodeTransform2d.js +1 -1
- package/dist/nodeTransform2d.js.map +1 -1
- package/dist/revision.d.ts +2 -1
- package/dist/revision.d.ts.map +1 -1
- package/dist/revision.js +9 -4
- package/dist/revision.js.map +1 -1
- package/dist/stageFit.d.ts.map +1 -1
- package/dist/stageFit.js +8 -2
- package/dist/stageFit.js.map +1 -1
- package/package.json +8 -7
- package/src/boundsRectangle.test.ts +228 -5
- package/src/hasAppearance.test.ts +33 -3
- package/src/hasTransform2d.test.ts +6 -0
- package/src/hasTransform3d.test.ts +40 -8
- package/src/hierarchy.test.ts +67 -3
- package/src/nodeOrderList.test.ts +75 -18
- package/src/nodeTransform2d.test.ts +23 -0
- package/src/revision.test.ts +26 -0
- package/src/stageFit.test.ts +56 -1
- package/src/traversal.test.ts +6 -0
package/src/hierarchy.test.ts
CHANGED
|
@@ -67,6 +67,15 @@ describe('addNodeChild', () => {
|
|
|
67
67
|
expect(() => addNodeChild(container, container)).toThrow(TypeError);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
it('throws rather than creating a cycle', () => {
|
|
71
|
+
addNodeChild(container, childA);
|
|
72
|
+
addNodeChild(childA, childB);
|
|
73
|
+
|
|
74
|
+
expect(() => addNodeChild(childB, container)).toThrow(TypeError);
|
|
75
|
+
expect(getNodeParent(container)).toBeNull();
|
|
76
|
+
expect(getNodeRoot(childB)).toBe(container);
|
|
77
|
+
});
|
|
78
|
+
|
|
70
79
|
it('rejects a child disallowed by the parent runtime', () => {
|
|
71
80
|
getEntityRuntime(container).canAddChild = () => false;
|
|
72
81
|
|
|
@@ -771,27 +780,81 @@ describe('reparentNode', () => {
|
|
|
771
780
|
expect(after.ty).toBeCloseTo(before.ty, 10);
|
|
772
781
|
});
|
|
773
782
|
|
|
774
|
-
it('preserves
|
|
783
|
+
it('preserves shear induced by rotation across non-uniformly scaled parents', () => {
|
|
784
|
+
const parentA = createTransformNode();
|
|
785
|
+
parentA.rotation = -15;
|
|
786
|
+
parentA.scaleX = 0.75;
|
|
787
|
+
parentA.scaleY = 2.5;
|
|
788
|
+
invalidateNodeLocalTransform(parentA);
|
|
789
|
+
|
|
790
|
+
const child = createTransformNode();
|
|
791
|
+
child.x = 10;
|
|
792
|
+
child.y = 5;
|
|
793
|
+
child.rotation = 37;
|
|
794
|
+
child.scaleX = 1.2;
|
|
795
|
+
child.scaleY = 0.9;
|
|
796
|
+
invalidateNodeLocalTransform(child);
|
|
797
|
+
addNodeChild(parentA, child);
|
|
798
|
+
|
|
799
|
+
expect(child.skewX).toBe(0);
|
|
800
|
+
expect(child.skewY).toBe(0);
|
|
801
|
+
const before = cloneMatrix(getNodeWorldMatrix(child));
|
|
802
|
+
|
|
803
|
+
const parentB = createTransformNode();
|
|
804
|
+
parentB.rotation = 22;
|
|
805
|
+
parentB.scaleX = 2.4;
|
|
806
|
+
parentB.scaleY = 0.6;
|
|
807
|
+
invalidateNodeLocalTransform(parentB);
|
|
808
|
+
|
|
809
|
+
reparentNode(child, parentB);
|
|
810
|
+
const after = getNodeWorldMatrix(child);
|
|
811
|
+
|
|
812
|
+
expect(after.a).toBeCloseTo(before.a, 10);
|
|
813
|
+
expect(after.b).toBeCloseTo(before.b, 10);
|
|
814
|
+
expect(after.c).toBeCloseTo(before.c, 10);
|
|
815
|
+
expect(after.d).toBeCloseTo(before.d, 10);
|
|
816
|
+
expect(after.tx).toBeCloseTo(before.tx, 10);
|
|
817
|
+
expect(after.ty).toBeCloseTo(before.ty, 10);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it('preserves a skewed world transform across asymmetric parents', () => {
|
|
775
821
|
const parentA = createTransformNode();
|
|
776
822
|
parentA.x = 50;
|
|
777
823
|
parentA.rotation = 45;
|
|
824
|
+
parentA.scaleX = 2;
|
|
825
|
+
parentA.scaleY = 3;
|
|
778
826
|
invalidateNodeLocalTransform(parentA);
|
|
779
827
|
|
|
780
828
|
const child = createTransformNode();
|
|
781
829
|
child.x = 10;
|
|
830
|
+
child.y = 5;
|
|
831
|
+
child.rotation = 12;
|
|
832
|
+
child.scaleX = 1.4;
|
|
833
|
+
child.scaleY = 0.8;
|
|
782
834
|
child.skewX = 15;
|
|
783
835
|
child.skewY = 20;
|
|
784
836
|
invalidateNodeLocalTransform(child);
|
|
785
837
|
addNodeChild(parentA, child);
|
|
786
838
|
|
|
839
|
+
const before = cloneMatrix(getNodeWorldMatrix(child));
|
|
840
|
+
|
|
787
841
|
const parentB = createTransformNode();
|
|
788
842
|
parentB.x = 100;
|
|
843
|
+
parentB.y = -20;
|
|
844
|
+
parentB.rotation = -10;
|
|
845
|
+
parentB.scaleX = 0.7;
|
|
846
|
+
parentB.scaleY = 1.6;
|
|
789
847
|
invalidateNodeLocalTransform(parentB);
|
|
790
848
|
|
|
791
849
|
reparentNode(child, parentB);
|
|
850
|
+
const after = getNodeWorldMatrix(child);
|
|
792
851
|
|
|
793
|
-
expect(
|
|
794
|
-
expect(
|
|
852
|
+
expect(after.a).toBeCloseTo(before.a, 10);
|
|
853
|
+
expect(after.b).toBeCloseTo(before.b, 10);
|
|
854
|
+
expect(after.c).toBeCloseTo(before.c, 10);
|
|
855
|
+
expect(after.d).toBeCloseTo(before.d, 10);
|
|
856
|
+
expect(after.tx).toBeCloseTo(before.tx, 10);
|
|
857
|
+
expect(after.ty).toBeCloseTo(before.ty, 10);
|
|
795
858
|
});
|
|
796
859
|
|
|
797
860
|
it('preserves world position with pivot', () => {
|
|
@@ -873,6 +936,7 @@ describe('reparentNode', () => {
|
|
|
873
936
|
reparentNode(child, parentB);
|
|
874
937
|
const after = getNodeWorldMatrix(child);
|
|
875
938
|
|
|
939
|
+
expect(child.scaleY).toBeLessThan(0);
|
|
876
940
|
expect(after.a).toBeCloseTo(before.a, 10);
|
|
877
941
|
expect(after.b).toBeCloseTo(before.b, 10);
|
|
878
942
|
expect(after.c).toBeCloseTo(before.c, 10);
|
|
@@ -38,6 +38,12 @@ function getChildren(source: Node) {
|
|
|
38
38
|
return getNodeRuntime(source).children as Node[];
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function expectChildren(source: Node, expected: readonly Node[]): void {
|
|
42
|
+
const actual = getChildren(source);
|
|
43
|
+
expect(actual).toHaveLength(expected.length);
|
|
44
|
+
for (let i = 0; i < expected.length; i++) expect(actual[i]).toBe(expected[i]);
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
function attachAll() {
|
|
42
48
|
addNodeChild(container, childA);
|
|
43
49
|
addNodeChild(container, childB);
|
|
@@ -91,7 +97,7 @@ describe('applyNodeOrderList', () => {
|
|
|
91
97
|
|
|
92
98
|
applyNodeOrderList(container, list);
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
expectChildren(container, [childC, childB, childA]);
|
|
95
101
|
});
|
|
96
102
|
|
|
97
103
|
it('never moves a child the list does not name', () => {
|
|
@@ -107,7 +113,7 @@ describe('applyNodeOrderList', () => {
|
|
|
107
113
|
|
|
108
114
|
// The members swap across the slots they held (0 and 2); the foreign child keeps slot 1, so it
|
|
109
115
|
// still sits between them rather than being compacted to one side.
|
|
110
|
-
|
|
116
|
+
expectChildren(container, [childB, foreign, childA]);
|
|
111
117
|
});
|
|
112
118
|
|
|
113
119
|
it('ignores entries that are not children of the target', () => {
|
|
@@ -121,7 +127,7 @@ describe('applyNodeOrderList', () => {
|
|
|
121
127
|
|
|
122
128
|
applyNodeOrderList(container, list);
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
expectChildren(container, [childB, childA]);
|
|
125
131
|
});
|
|
126
132
|
|
|
127
133
|
it('ignores entries attached to a different parent', () => {
|
|
@@ -136,8 +142,8 @@ describe('applyNodeOrderList', () => {
|
|
|
136
142
|
|
|
137
143
|
applyNodeOrderList(container, list);
|
|
138
144
|
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
expectChildren(container, [childB, childA]);
|
|
146
|
+
expectChildren(other, [childC]);
|
|
141
147
|
});
|
|
142
148
|
|
|
143
149
|
it('is a no-op once every member has been removed', () => {
|
|
@@ -152,7 +158,7 @@ describe('applyNodeOrderList', () => {
|
|
|
152
158
|
|
|
153
159
|
applyNodeOrderList(container, list);
|
|
154
160
|
|
|
155
|
-
|
|
161
|
+
expectChildren(container, [foreign]);
|
|
156
162
|
expect(getNodeChildrenRevision(container)).toBe(revision);
|
|
157
163
|
});
|
|
158
164
|
|
|
@@ -166,7 +172,7 @@ describe('applyNodeOrderList', () => {
|
|
|
166
172
|
|
|
167
173
|
applyNodeOrderList(container, list);
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
expectChildren(container, [childC, childA]);
|
|
170
176
|
});
|
|
171
177
|
|
|
172
178
|
it('breaks equal sort keys by the order the entries were added', () => {
|
|
@@ -180,7 +186,7 @@ describe('applyNodeOrderList', () => {
|
|
|
180
186
|
|
|
181
187
|
applyNodeOrderList(container, list);
|
|
182
188
|
|
|
183
|
-
|
|
189
|
+
expectChildren(container, [childA, childB, childC]);
|
|
184
190
|
});
|
|
185
191
|
|
|
186
192
|
it('resolves a node entered twice to its last entry', () => {
|
|
@@ -193,7 +199,7 @@ describe('applyNodeOrderList', () => {
|
|
|
193
199
|
|
|
194
200
|
applyNodeOrderList(container, list);
|
|
195
201
|
|
|
196
|
-
|
|
202
|
+
expectChildren(container, [childB, childA]);
|
|
197
203
|
});
|
|
198
204
|
|
|
199
205
|
it('is idempotent', () => {
|
|
@@ -207,7 +213,7 @@ describe('applyNodeOrderList', () => {
|
|
|
207
213
|
|
|
208
214
|
applyNodeOrderList(container, list);
|
|
209
215
|
|
|
210
|
-
|
|
216
|
+
expectChildren(container, [childB, childA]);
|
|
211
217
|
expect(getNodeChildrenRevision(container)).toBe(revision);
|
|
212
218
|
});
|
|
213
219
|
|
|
@@ -263,9 +269,31 @@ describe('applyNodeOrderList', () => {
|
|
|
263
269
|
|
|
264
270
|
applyNodeOrderList(container, createNodeOrderList());
|
|
265
271
|
|
|
266
|
-
|
|
272
|
+
expectChildren(container, [childA, childB]);
|
|
267
273
|
expect(getNodeChildrenRevision(container)).toBe(revision);
|
|
268
274
|
});
|
|
275
|
+
|
|
276
|
+
it('leaves a childless target alone when the list is nonempty', () => {
|
|
277
|
+
const list = createNodeOrderList();
|
|
278
|
+
addNodeOrderListEntry(list, childA, 1);
|
|
279
|
+
|
|
280
|
+
expect(() => applyNodeOrderList(container, list)).not.toThrow();
|
|
281
|
+
expect(getNodeRuntime(container).children).toBeNull();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('does not apply retained entries outside the valid window', () => {
|
|
285
|
+
addNodeChild(container, childC);
|
|
286
|
+
addNodeChild(container, childB);
|
|
287
|
+
const list = createNodeOrderList();
|
|
288
|
+
addNodeOrderListEntry(list, childC, 10);
|
|
289
|
+
addNodeOrderListEntry(list, childB, 0);
|
|
290
|
+
clearNodeOrderList(list);
|
|
291
|
+
addNodeOrderListEntry(list, childC, 10);
|
|
292
|
+
|
|
293
|
+
applyNodeOrderList(container, list);
|
|
294
|
+
|
|
295
|
+
expectChildren(container, [childC, childB]);
|
|
296
|
+
});
|
|
269
297
|
});
|
|
270
298
|
|
|
271
299
|
describe('clearNodeOrderList', () => {
|
|
@@ -433,7 +461,7 @@ describe('removeNodeOrderListEntry', () => {
|
|
|
433
461
|
|
|
434
462
|
// A keeps slot 0 — only its entry was removed, not the child. C was entered before B and stays
|
|
435
463
|
// before it; a swap-with-last removal would have put B first and left the children untouched.
|
|
436
|
-
|
|
464
|
+
expectChildren(container, [childA, childC, childB]);
|
|
437
465
|
});
|
|
438
466
|
});
|
|
439
467
|
|
|
@@ -467,7 +495,7 @@ describe('setNodeOrderListEntryAbove', () => {
|
|
|
467
495
|
setNodeOrderListEntryAbove(list, childA, childC);
|
|
468
496
|
applyNodeOrderList(container, list);
|
|
469
497
|
|
|
470
|
-
|
|
498
|
+
expectChildren(container, [childB, childC, childA]);
|
|
471
499
|
});
|
|
472
500
|
|
|
473
501
|
it('places between neighbours whose keys leave no gap', () => {
|
|
@@ -477,11 +505,20 @@ describe('setNodeOrderListEntryAbove', () => {
|
|
|
477
505
|
addNodeOrderListEntry(list, childC, 6);
|
|
478
506
|
|
|
479
507
|
setNodeOrderListEntryAbove(list, childA, childB);
|
|
508
|
+
|
|
509
|
+
expect(list.entryCount).toBe(3);
|
|
510
|
+
expect(list.nodes).toHaveLength(3);
|
|
511
|
+
expect(list.sortKeys).toHaveLength(3);
|
|
512
|
+
expect(list.nodes[0]).toBe(childB);
|
|
513
|
+
expect(list.nodes[1]).toBe(childA);
|
|
514
|
+
expect(list.nodes[2]).toBe(childC);
|
|
515
|
+
expect(list.sortKeys.slice(0, 3)).toEqual([5, 5, 6]);
|
|
516
|
+
|
|
480
517
|
applyNodeOrderList(container, list);
|
|
481
518
|
|
|
482
519
|
// No midpoint exists between 5 and 6, so bisection could not express this; the equal-key plus
|
|
483
520
|
// entry-position rule can.
|
|
484
|
-
|
|
521
|
+
expectChildren(container, [childB, childA, childC]);
|
|
485
522
|
});
|
|
486
523
|
|
|
487
524
|
it('moves a node that is already entered rather than duplicating it', () => {
|
|
@@ -503,6 +540,16 @@ describe('setNodeOrderListEntryAbove', () => {
|
|
|
503
540
|
expect(list.entryCount).toBe(0);
|
|
504
541
|
});
|
|
505
542
|
|
|
543
|
+
it('keeps an existing node entry when the target has no entry', () => {
|
|
544
|
+
const list = createNodeOrderList();
|
|
545
|
+
addNodeOrderListEntry(list, childA, 4);
|
|
546
|
+
|
|
547
|
+
setNodeOrderListEntryAbove(list, childA, childC);
|
|
548
|
+
|
|
549
|
+
expect(list.entryCount).toBe(1);
|
|
550
|
+
expect(getNodeOrderListEntrySortKey(list, childA)).toBe(4);
|
|
551
|
+
});
|
|
552
|
+
|
|
506
553
|
it('does nothing when the node is its own target', () => {
|
|
507
554
|
const list = createNodeOrderList();
|
|
508
555
|
addNodeOrderListEntry(list, childA, 4);
|
|
@@ -523,7 +570,7 @@ describe('setNodeOrderListEntryBelow', () => {
|
|
|
523
570
|
setNodeOrderListEntryBelow(list, childA, childC);
|
|
524
571
|
applyNodeOrderList(container, list);
|
|
525
572
|
|
|
526
|
-
|
|
573
|
+
expectChildren(container, [childB, childA, childC]);
|
|
527
574
|
});
|
|
528
575
|
|
|
529
576
|
it('places between neighbours whose keys leave no gap', () => {
|
|
@@ -535,7 +582,7 @@ describe('setNodeOrderListEntryBelow', () => {
|
|
|
535
582
|
setNodeOrderListEntryBelow(list, childA, childC);
|
|
536
583
|
applyNodeOrderList(container, list);
|
|
537
584
|
|
|
538
|
-
|
|
585
|
+
expectChildren(container, [childB, childA, childC]);
|
|
539
586
|
});
|
|
540
587
|
|
|
541
588
|
it('does nothing when the target has no entry', () => {
|
|
@@ -545,6 +592,16 @@ describe('setNodeOrderListEntryBelow', () => {
|
|
|
545
592
|
|
|
546
593
|
expect(list.entryCount).toBe(0);
|
|
547
594
|
});
|
|
595
|
+
|
|
596
|
+
it('keeps an existing node entry when the target has no entry', () => {
|
|
597
|
+
const list = createNodeOrderList();
|
|
598
|
+
addNodeOrderListEntry(list, childA, 4);
|
|
599
|
+
|
|
600
|
+
setNodeOrderListEntryBelow(list, childA, childC);
|
|
601
|
+
|
|
602
|
+
expect(list.entryCount).toBe(1);
|
|
603
|
+
expect(getNodeOrderListEntrySortKey(list, childA)).toBe(4);
|
|
604
|
+
});
|
|
548
605
|
});
|
|
549
606
|
|
|
550
607
|
describe('setNodeOrderListFromNodeChildren', () => {
|
|
@@ -568,7 +625,7 @@ describe('setNodeOrderListFromNodeChildren', () => {
|
|
|
568
625
|
|
|
569
626
|
applyNodeOrderList(container, list);
|
|
570
627
|
|
|
571
|
-
|
|
628
|
+
expectChildren(container, [childA, childB, childC]);
|
|
572
629
|
expect(getNodeChildrenRevision(container)).toBe(revision);
|
|
573
630
|
});
|
|
574
631
|
|
|
@@ -603,7 +660,7 @@ describe('swapNodeOrderListEntries', () => {
|
|
|
603
660
|
swapNodeOrderListEntries(list, childA, childC);
|
|
604
661
|
applyNodeOrderList(container, list);
|
|
605
662
|
|
|
606
|
-
|
|
663
|
+
expectChildren(container, [childC, childB, childA]);
|
|
607
664
|
});
|
|
608
665
|
|
|
609
666
|
it('does nothing unless both nodes are entered', () => {
|
|
@@ -304,6 +304,29 @@ describe('setNodeLocalMatrix', () => {
|
|
|
304
304
|
expect(m.ty).toBeCloseTo(20);
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
+
// Pins the losslessness this function's contract promises for a reflected (negative determinant)
|
|
308
|
+
// matrix. Positive-determinant cases round-tripped even while reflected ones silently lost the
|
|
309
|
+
// reflection, so a fixture that is not reflected cannot speak to this.
|
|
310
|
+
it('round-trips a reflected matrix, keeping the mirror', () => {
|
|
311
|
+
setNodeLocalMatrix(node, createMatrix(1, 0, 0, -1, 0, 0));
|
|
312
|
+
const m = getNodeLocalMatrix(node);
|
|
313
|
+
expect(m.a).toBeCloseTo(1);
|
|
314
|
+
expect(m.b).toBeCloseTo(0);
|
|
315
|
+
expect(m.c).toBeCloseTo(0);
|
|
316
|
+
expect(m.d).toBeCloseTo(-1);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('round-trips a rotated reflection through all four matrix cells', () => {
|
|
320
|
+
setNodeLocalMatrix(node, createMatrix(0.5, 0.8, 0.8, -0.5, 3, 4));
|
|
321
|
+
const m = getNodeLocalMatrix(node);
|
|
322
|
+
expect(m.a).toBeCloseTo(0.5);
|
|
323
|
+
expect(m.b).toBeCloseTo(0.8);
|
|
324
|
+
expect(m.c).toBeCloseTo(0.8);
|
|
325
|
+
expect(m.d).toBeCloseTo(-0.5);
|
|
326
|
+
expect(m.tx).toBeCloseTo(3);
|
|
327
|
+
expect(m.ty).toBeCloseTo(4);
|
|
328
|
+
});
|
|
329
|
+
|
|
307
330
|
it('resets pivot and absorbs its offset into translation', () => {
|
|
308
331
|
node.pivotX = 5;
|
|
309
332
|
node.pivotY = 5;
|
package/src/revision.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createNode, getNodeRuntime } from '@flighthq/node/contract';
|
|
2
2
|
import type { Node, NodeRuntime } from '@flighthq/types/contract';
|
|
3
3
|
|
|
4
|
+
import { addNodeChild } from './hierarchy';
|
|
4
5
|
import {
|
|
5
6
|
computeNodeWorldTransformRevision,
|
|
6
7
|
getNodeAppearanceRevision,
|
|
@@ -41,6 +42,7 @@ describe('computeNodeWorldTransformRevision', () => {
|
|
|
41
42
|
computeNodeWorldTransformRevision(runtime);
|
|
42
43
|
expect(runtime.worldTransformUsingLocalTransformId).toBe(3);
|
|
43
44
|
expect(runtime.worldTransformUsingParentTransformId).toBe(0);
|
|
45
|
+
expect(runtime.worldTransformId).not.toBe(0);
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
it('incorporates parent worldTransformId when provided', () => {
|
|
@@ -51,6 +53,17 @@ describe('computeNodeWorldTransformRevision', () => {
|
|
|
51
53
|
computeNodeWorldTransformRevision(runtime, parentRuntime);
|
|
52
54
|
expect(runtime.worldTransformUsingParentTransformId).toBe(7);
|
|
53
55
|
});
|
|
56
|
+
|
|
57
|
+
it('assigns the next monotonic world revision on every recompute', () => {
|
|
58
|
+
const runtime = getEntityRuntime(node);
|
|
59
|
+
computeNodeWorldTransformRevision(runtime);
|
|
60
|
+
const first = runtime.worldTransformId;
|
|
61
|
+
|
|
62
|
+
computeNodeWorldTransformRevision(runtime);
|
|
63
|
+
|
|
64
|
+
const next = (first + 1) >>> 0;
|
|
65
|
+
expect(runtime.worldTransformId).toBe(next === 0 ? 1 : next);
|
|
66
|
+
});
|
|
54
67
|
});
|
|
55
68
|
|
|
56
69
|
describe('getNodeAppearanceRevision', () => {
|
|
@@ -246,6 +259,19 @@ describe('invalidateNodeWorldBounds', () => {
|
|
|
246
259
|
expect(runtime.worldBoundsUsingWorldTransformId).toBe(-1);
|
|
247
260
|
expect(runtime.worldBoundsUsingLocalBoundsId).toBe(-1);
|
|
248
261
|
});
|
|
262
|
+
|
|
263
|
+
it('invalidates every ancestor whose aggregate includes the target', () => {
|
|
264
|
+
const parent = createTestNode();
|
|
265
|
+
addNodeChild(parent, node);
|
|
266
|
+
const parentRuntime = getEntityRuntime(parent);
|
|
267
|
+
parentRuntime.worldBoundsUsingWorldTransformId = 1;
|
|
268
|
+
parentRuntime.worldBoundsUsingLocalBoundsId = 1;
|
|
269
|
+
|
|
270
|
+
invalidateNodeWorldBounds(node);
|
|
271
|
+
|
|
272
|
+
expect(parentRuntime.worldBoundsUsingWorldTransformId).toBe(-1);
|
|
273
|
+
expect(parentRuntime.worldBoundsUsingLocalBoundsId).toBe(-1);
|
|
274
|
+
});
|
|
249
275
|
});
|
|
250
276
|
|
|
251
277
|
type TestNode = Node;
|
package/src/stageFit.test.ts
CHANGED
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
|
|
19
19
|
const TestNodeKind = 'TestNode';
|
|
20
20
|
|
|
21
|
-
function makeNodeWithBounds(width: number, height: number) {
|
|
21
|
+
function makeNodeWithBounds(width: number, height: number, x = 0, y = 0) {
|
|
22
22
|
const node = createNode(TestNodeKind);
|
|
23
23
|
const runtime = getNodeRuntime(node) as unknown as HasBoundsRectangleRuntime;
|
|
24
24
|
runtime.computeLocalBoundsRectangle = (out) => {
|
|
25
|
+
out.x = x;
|
|
26
|
+
out.y = y;
|
|
25
27
|
out.width = width;
|
|
26
28
|
out.height = height;
|
|
27
29
|
};
|
|
@@ -137,6 +139,18 @@ describe('computeScene2DFitTransform', () => {
|
|
|
137
139
|
expect(m.d).toBe(1);
|
|
138
140
|
});
|
|
139
141
|
|
|
142
|
+
it('sets identity when only the content width is zero', () => {
|
|
143
|
+
const m = createMatrix(2, 3, 4, 5, 6, 7);
|
|
144
|
+
computeScene2DFitTransform(m, fit({ root: makeNodeWithBounds(0, 50), scaleMode: 'exactfit' }), 800, 600);
|
|
145
|
+
expect(m).toMatchObject({ a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('sets identity when only the content height is zero', () => {
|
|
149
|
+
const m = createMatrix(2, 3, 4, 5, 6, 7);
|
|
150
|
+
computeScene2DFitTransform(m, fit({ root: makeNodeWithBounds(100, 0), scaleMode: 'exactfit' }), 800, 600);
|
|
151
|
+
expect(m).toMatchObject({ a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 });
|
|
152
|
+
});
|
|
153
|
+
|
|
140
154
|
it('noscale with topleft: identity scale at origin', () => {
|
|
141
155
|
const m = createMatrix();
|
|
142
156
|
computeScene2DFitTransform(
|
|
@@ -226,4 +240,45 @@ describe('computeScene2DFitTransform', () => {
|
|
|
226
240
|
expect(m.b).toBe(0);
|
|
227
241
|
expect(m.c).toBe(0);
|
|
228
242
|
});
|
|
243
|
+
|
|
244
|
+
it('maps a nonzero content-bounds origin to the selected alignment', () => {
|
|
245
|
+
const m = createMatrix();
|
|
246
|
+
computeScene2DFitTransform(
|
|
247
|
+
m,
|
|
248
|
+
fit({ align: 'topleft', root: makeNodeWithBounds(100, 50, 10, -20), scaleMode: 'exactfit' }),
|
|
249
|
+
200,
|
|
250
|
+
100,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(m.a).toBe(2);
|
|
254
|
+
expect(m.d).toBe(2);
|
|
255
|
+
expect(m.tx).toBe(-20);
|
|
256
|
+
expect(m.ty).toBe(40);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('uses scaled width before subtracting a nonzero origin on a centered horizontal axis', () => {
|
|
260
|
+
const m = createMatrix();
|
|
261
|
+
computeScene2DFitTransform(
|
|
262
|
+
m,
|
|
263
|
+
fit({ align: 'top', root: makeNodeWithBounds(100, 50, 10, -20), scaleMode: 'exactfit' }),
|
|
264
|
+
200,
|
|
265
|
+
100,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
expect(m.tx).toBe(-20);
|
|
269
|
+
expect(m.ty).toBe(40);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('uses scaled height before subtracting a nonzero origin on a centered vertical axis', () => {
|
|
273
|
+
const m = createMatrix();
|
|
274
|
+
computeScene2DFitTransform(
|
|
275
|
+
m,
|
|
276
|
+
fit({ align: 'left', root: makeNodeWithBounds(100, 50, 10, -20), scaleMode: 'exactfit' }),
|
|
277
|
+
200,
|
|
278
|
+
100,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(m.tx).toBe(-20);
|
|
282
|
+
expect(m.ty).toBe(40);
|
|
283
|
+
});
|
|
229
284
|
});
|
package/src/traversal.test.ts
CHANGED
|
@@ -196,6 +196,12 @@ describe('getNodeNextSibling', () => {
|
|
|
196
196
|
|
|
197
197
|
expect(getNodeNextSibling(childA)).toBeNull();
|
|
198
198
|
});
|
|
199
|
+
|
|
200
|
+
it('returns null for a stale parent whose nonempty child list omits the source', () => {
|
|
201
|
+
(getNodeRuntime(root) as NodeRuntime).children = [childB];
|
|
202
|
+
|
|
203
|
+
expect(getNodeNextSibling(childA)).toBeNull();
|
|
204
|
+
});
|
|
199
205
|
});
|
|
200
206
|
|
|
201
207
|
describe('getNodePreviousSibling', () => {
|