@canvas-commons/2d 0.2.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/LICENSE +22 -0
- package/editor/index.css +40 -0
- package/editor/index.js +525 -0
- package/editor/index.js.map +1 -0
- package/lib/index-OvpZ-GsA.d.ts +5673 -0
- package/lib/index-OvpZ-GsA.d.ts.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +10326 -0
- package/lib/index.js.map +1 -0
- package/lib/jsx-dev-runtime.d.ts +2 -0
- package/lib/jsx-dev-runtime.js +2 -0
- package/lib/jsx-runtime.d.ts +2 -0
- package/lib/jsx-runtime.js +28 -0
- package/lib/jsx-runtime.js.map +1 -0
- package/package.json +79 -0
- package/src/editor/NodeInspectorConfig.tsx +76 -0
- package/src/editor/PreviewOverlayConfig.tsx +65 -0
- package/src/editor/Provider.tsx +109 -0
- package/src/editor/SceneGraphTabConfig.tsx +87 -0
- package/src/editor/icons/CircleIcon.tsx +7 -0
- package/src/editor/icons/CodeIcon.tsx +8 -0
- package/src/editor/icons/CurveIcon.tsx +7 -0
- package/src/editor/icons/GridIcon.tsx +7 -0
- package/src/editor/icons/IconMap.ts +35 -0
- package/src/editor/icons/ImgIcon.tsx +8 -0
- package/src/editor/icons/LayoutIcon.tsx +9 -0
- package/src/editor/icons/LineIcon.tsx +7 -0
- package/src/editor/icons/NodeIcon.tsx +7 -0
- package/src/editor/icons/RayIcon.tsx +7 -0
- package/src/editor/icons/RectIcon.tsx +7 -0
- package/src/editor/icons/ShapeIcon.tsx +7 -0
- package/src/editor/icons/TxtIcon.tsx +8 -0
- package/src/editor/icons/VideoIcon.tsx +7 -0
- package/src/editor/icons/View2DIcon.tsx +10 -0
- package/src/editor/index.css +0 -0
- package/src/editor/index.ts +19 -0
- package/src/editor/shortcuts.ts +27 -0
- package/src/editor/tree/DetachedRoot.tsx +27 -0
- package/src/editor/tree/NodeElement.tsx +72 -0
- package/src/editor/tree/TreeElement.tsx +70 -0
- package/src/editor/tree/TreeRoot.tsx +10 -0
- package/src/editor/tree/ViewRoot.tsx +20 -0
- package/src/editor/tree/index.module.scss +45 -0
- package/src/editor/tree/index.ts +4 -0
- package/src/editor/tree/navigation.ts +145 -0
- package/src/editor/tsconfig.build.json +5 -0
- package/src/editor/tsconfig.json +12 -0
- package/src/editor/tsdoc.json +4 -0
- package/src/editor/utils/SignalSet.ts +37 -0
- package/src/editor/utils/index.ts +1 -0
- package/src/editor/vite-env.d.ts +1 -0
- package/src/lib/code/CodeCursor.ts +468 -0
- package/src/lib/code/CodeDiffer.ts +77 -0
- package/src/lib/code/CodeFragment.ts +96 -0
- package/src/lib/code/CodeHighlighter.ts +73 -0
- package/src/lib/code/CodeMetrics.ts +47 -0
- package/src/lib/code/CodeRange.test.ts +113 -0
- package/src/lib/code/CodeRange.ts +222 -0
- package/src/lib/code/CodeScope.ts +100 -0
- package/src/lib/code/CodeSelection.ts +28 -0
- package/src/lib/code/CodeSignal.ts +348 -0
- package/src/lib/code/CodeTokenizer.ts +54 -0
- package/src/lib/code/DefaultHighlightStyle.ts +98 -0
- package/src/lib/code/LezerHighlighter.ts +113 -0
- package/src/lib/code/diff.test.ts +311 -0
- package/src/lib/code/diff.ts +319 -0
- package/src/lib/code/extractRange.ts +125 -0
- package/src/lib/code/index.ts +13 -0
- package/src/lib/components/Bezier.ts +103 -0
- package/src/lib/components/Camera.ts +401 -0
- package/src/lib/components/Circle.ts +310 -0
- package/src/lib/components/Code.ts +532 -0
- package/src/lib/components/CubicBezier.ts +115 -0
- package/src/lib/components/Curve.ts +460 -0
- package/src/lib/components/Grid.ts +134 -0
- package/src/lib/components/Icon.ts +153 -0
- package/src/lib/components/Img.ts +328 -0
- package/src/lib/components/Knot.ts +156 -0
- package/src/lib/components/Latex.ts +537 -0
- package/src/lib/components/Layout.ts +1101 -0
- package/src/lib/components/Line.ts +394 -0
- package/src/lib/components/Node.ts +1941 -0
- package/src/lib/components/Path.ts +132 -0
- package/src/lib/components/Polygon.ts +266 -0
- package/src/lib/components/QuadBezier.ts +103 -0
- package/src/lib/components/Ray.ts +126 -0
- package/src/lib/components/Rect.ts +219 -0
- package/src/lib/components/SVG.ts +853 -0
- package/src/lib/components/Shape.ts +322 -0
- package/src/lib/components/Spline.ts +318 -0
- package/src/lib/components/Txt.test.tsx +81 -0
- package/src/lib/components/Txt.ts +204 -0
- package/src/lib/components/TxtLeaf.ts +210 -0
- package/src/lib/components/Video.ts +368 -0
- package/src/lib/components/View2D.ts +85 -0
- package/src/lib/components/__logs__/image-without-source.ts +18 -0
- package/src/lib/components/__logs__/line-without-points.ts +31 -0
- package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
- package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
- package/src/lib/components/__tests__/Camera.test.tsx +73 -0
- package/src/lib/components/__tests__/children.test.tsx +142 -0
- package/src/lib/components/__tests__/clone.test.tsx +126 -0
- package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
- package/src/lib/components/__tests__/generatorTest.ts +27 -0
- package/src/lib/components/__tests__/mockScene2D.ts +50 -0
- package/src/lib/components/__tests__/query.test.tsx +122 -0
- package/src/lib/components/__tests__/state.test.tsx +60 -0
- package/src/lib/components/index.ts +26 -0
- package/src/lib/components/types.ts +35 -0
- package/src/lib/curves/ArcSegment.ts +169 -0
- package/src/lib/curves/CircleSegment.ts +99 -0
- package/src/lib/curves/CubicBezierSegment.ts +80 -0
- package/src/lib/curves/CurveDrawingInfo.ts +11 -0
- package/src/lib/curves/CurvePoint.ts +15 -0
- package/src/lib/curves/CurveProfile.ts +28 -0
- package/src/lib/curves/KnotInfo.ts +10 -0
- package/src/lib/curves/LineSegment.ts +75 -0
- package/src/lib/curves/Polynomial.ts +355 -0
- package/src/lib/curves/Polynomial2D.ts +62 -0
- package/src/lib/curves/PolynomialSegment.ts +151 -0
- package/src/lib/curves/QuadBezierSegment.ts +66 -0
- package/src/lib/curves/Segment.ts +31 -0
- package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
- package/src/lib/curves/createCurveProfileLerp.ts +471 -0
- package/src/lib/curves/getBezierSplineProfile.ts +227 -0
- package/src/lib/curves/getCircleProfile.ts +86 -0
- package/src/lib/curves/getPathProfile.ts +177 -0
- package/src/lib/curves/getPointAtDistance.ts +21 -0
- package/src/lib/curves/getPolylineProfile.test.ts +21 -0
- package/src/lib/curves/getPolylineProfile.ts +88 -0
- package/src/lib/curves/getRectProfile.ts +138 -0
- package/src/lib/curves/index.ts +16 -0
- package/src/lib/decorators/canvasStyleSignal.ts +15 -0
- package/src/lib/decorators/colorSignal.ts +9 -0
- package/src/lib/decorators/compound.ts +85 -0
- package/src/lib/decorators/computed.ts +18 -0
- package/src/lib/decorators/defaultStyle.ts +15 -0
- package/src/lib/decorators/filtersSignal.ts +133 -0
- package/src/lib/decorators/index.ts +10 -0
- package/src/lib/decorators/initializers.ts +32 -0
- package/src/lib/decorators/nodeName.ts +13 -0
- package/src/lib/decorators/signal.test.ts +89 -0
- package/src/lib/decorators/signal.ts +348 -0
- package/src/lib/decorators/spacingSignal.ts +15 -0
- package/src/lib/decorators/transformSignals.test.ts +858 -0
- package/src/lib/decorators/transformSignals.ts +1633 -0
- package/src/lib/decorators/vector2Signal.ts +35 -0
- package/src/lib/globals.d.ts +2 -0
- package/src/lib/index.ts +9 -0
- package/src/lib/jsx-dev-runtime.ts +2 -0
- package/src/lib/jsx-runtime.ts +45 -0
- package/src/lib/morphers/PathMorpher.ts +8 -0
- package/src/lib/morphers/defaultMorpher.ts +43 -0
- package/src/lib/morphers/index.ts +4 -0
- package/src/lib/morphers/manimMorpher.ts +658 -0
- package/src/lib/parse-svg-path.d.ts +14 -0
- package/src/lib/partials/Filter.ts +185 -0
- package/src/lib/partials/Gradient.ts +103 -0
- package/src/lib/partials/Pattern.ts +35 -0
- package/src/lib/partials/RoughConfig.ts +180 -0
- package/src/lib/partials/ShaderConfig.ts +122 -0
- package/src/lib/partials/index.ts +5 -0
- package/src/lib/partials/types.ts +58 -0
- package/src/lib/scenes/Scene2D.ts +167 -0
- package/src/lib/scenes/index.ts +3 -0
- package/src/lib/scenes/makeScene2D.ts +19 -0
- package/src/lib/scenes/useScene2D.ts +6 -0
- package/src/lib/tsconfig.build.json +5 -0
- package/src/lib/tsconfig.json +11 -0
- package/src/lib/tsdoc.json +4 -0
- package/src/lib/utils/CanvasUtils.ts +304 -0
- package/src/lib/utils/PathDataBuilder.ts +320 -0
- package/src/lib/utils/diff.test.ts +453 -0
- package/src/lib/utils/diff.ts +148 -0
- package/src/lib/utils/index.ts +5 -0
- package/src/lib/utils/is.ts +11 -0
- package/src/lib/utils/makeSignalExtensions.ts +29 -0
- package/src/lib/utils/rough.ts +513 -0
- package/src/lib/utils/withDefaults.tsx +26 -0
- package/src/tsconfig.base.json +18 -0
- package/src/tsconfig.build.json +8 -0
- package/src/tsconfig.json +5 -0
- package/tsconfig.project.json +7 -0
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
import {Vector2} from '@canvas-commons/core';
|
|
2
|
+
import 'geometry-polyfill';
|
|
3
|
+
import {beforeEach, describe, expect, test} from 'vitest';
|
|
4
|
+
import {Layout} from '../components/Layout';
|
|
5
|
+
import {Rect} from '../components/Rect';
|
|
6
|
+
import {mockScene2D} from '../components/__tests__/mockScene2D';
|
|
7
|
+
import {useScene2D} from '../scenes';
|
|
8
|
+
|
|
9
|
+
describe('Curried Transform Signals', () => {
|
|
10
|
+
mockScene2D();
|
|
11
|
+
|
|
12
|
+
let parent: Rect;
|
|
13
|
+
let child: Rect;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
parent = new Rect({
|
|
17
|
+
position: [100, 100],
|
|
18
|
+
scale: [1, 1],
|
|
19
|
+
rotation: 0,
|
|
20
|
+
size: [200, 200],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child = new Rect({
|
|
24
|
+
position: [50, 50],
|
|
25
|
+
scale: [1, 1],
|
|
26
|
+
rotation: 0,
|
|
27
|
+
size: [100, 100],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
parent.add(child);
|
|
31
|
+
|
|
32
|
+
useScene2D().getView().add(parent);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('Position relativeTo curried API', () => {
|
|
36
|
+
test('should return curried signal when called with only node', () => {
|
|
37
|
+
const curried = child.position.relativeTo(parent);
|
|
38
|
+
|
|
39
|
+
// Should be callable
|
|
40
|
+
expect(typeof curried).toBe('function');
|
|
41
|
+
|
|
42
|
+
// Should have component accessors
|
|
43
|
+
expect(curried.x).toBeDefined();
|
|
44
|
+
expect(curried.y).toBeDefined();
|
|
45
|
+
expect(typeof curried.x).toBe('function');
|
|
46
|
+
expect(typeof curried.y).toBe('function');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('should get relative position when calling curried signal', () => {
|
|
50
|
+
const curried = child.position.relativeTo(parent);
|
|
51
|
+
const relativePos = curried();
|
|
52
|
+
|
|
53
|
+
expect(relativePos).toBeInstanceOf(Vector2);
|
|
54
|
+
// With simple transforms, this should work
|
|
55
|
+
expect(typeof relativePos.x).toBe('number');
|
|
56
|
+
expect(typeof relativePos.y).toBe('number');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should set relative position when calling curried signal with value', () => {
|
|
60
|
+
const curried = child.position.relativeTo(parent);
|
|
61
|
+
const originalPos = child.position();
|
|
62
|
+
|
|
63
|
+
// This should work without errors
|
|
64
|
+
curried([100, 200]);
|
|
65
|
+
|
|
66
|
+
// Position should have changed
|
|
67
|
+
expect(child.position()).not.toEqual(originalPos);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should access x component of relative position', () => {
|
|
71
|
+
const curried = child.position.relativeTo(parent);
|
|
72
|
+
|
|
73
|
+
// Get x component
|
|
74
|
+
const xValue = curried.x();
|
|
75
|
+
expect(typeof xValue).toBe('number');
|
|
76
|
+
|
|
77
|
+
// Set x component
|
|
78
|
+
const originalPos = child.position();
|
|
79
|
+
curried.x(150);
|
|
80
|
+
|
|
81
|
+
// Position should have changed
|
|
82
|
+
expect(child.position()).not.toEqual(originalPos);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should access y component of relative position', () => {
|
|
86
|
+
const curried = child.position.relativeTo(parent);
|
|
87
|
+
|
|
88
|
+
// Get y component
|
|
89
|
+
const yValue = curried.y();
|
|
90
|
+
expect(typeof yValue).toBe('number');
|
|
91
|
+
|
|
92
|
+
// Set y component
|
|
93
|
+
const originalPos = child.position();
|
|
94
|
+
curried.y(250);
|
|
95
|
+
|
|
96
|
+
// Position should have changed
|
|
97
|
+
expect(child.position()).not.toEqual(originalPos);
|
|
98
|
+
|
|
99
|
+
// Y component should be updated
|
|
100
|
+
expect(curried.y()).toBeCloseTo(250, 0.01);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Scale relativeTo curried API', () => {
|
|
105
|
+
test('should return curried signal when called with only node', () => {
|
|
106
|
+
const curried = child.scale.relativeTo(parent);
|
|
107
|
+
|
|
108
|
+
expect(typeof curried).toBe('function');
|
|
109
|
+
expect(curried.x).toBeDefined();
|
|
110
|
+
expect(curried.y).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('Rotation relativeTo curried API', () => {
|
|
115
|
+
test('should return curried signal when called with only node', () => {
|
|
116
|
+
const curried = child.rotation.relativeTo(parent);
|
|
117
|
+
|
|
118
|
+
expect(typeof curried).toBe('function');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('Origin signals with transform methods', () => {
|
|
123
|
+
test('should support transform methods on origin signals', () => {
|
|
124
|
+
const layout = new Layout({size: [200, 100]});
|
|
125
|
+
|
|
126
|
+
// Origin signals should have transform methods
|
|
127
|
+
expect(layout.top.abs).toBeDefined();
|
|
128
|
+
expect(layout.top.view).toBeDefined();
|
|
129
|
+
expect(layout.top.local).toBeDefined();
|
|
130
|
+
expect(layout.top.relativeTo).toBeDefined();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('Component Tweening', () => {
|
|
135
|
+
test('should support component tweening for enhanced transform methods', () => {
|
|
136
|
+
// Test abs component tweening
|
|
137
|
+
expect(() => {
|
|
138
|
+
child.position.abs.x(100, 1);
|
|
139
|
+
}).not.toThrow();
|
|
140
|
+
|
|
141
|
+
expect(() => {
|
|
142
|
+
child.position.abs.y(200, 1);
|
|
143
|
+
}).not.toThrow();
|
|
144
|
+
|
|
145
|
+
// Test view component tweening
|
|
146
|
+
expect(() => {
|
|
147
|
+
child.position.view.x(300, 1);
|
|
148
|
+
}).not.toThrow();
|
|
149
|
+
|
|
150
|
+
expect(() => {
|
|
151
|
+
child.position.view.y(400, 1);
|
|
152
|
+
}).not.toThrow();
|
|
153
|
+
|
|
154
|
+
// Test local component tweening
|
|
155
|
+
expect(() => {
|
|
156
|
+
child.position.local.x(50, 1);
|
|
157
|
+
}).not.toThrow();
|
|
158
|
+
|
|
159
|
+
expect(() => {
|
|
160
|
+
child.position.local.y(75, 1);
|
|
161
|
+
}).not.toThrow();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('should support component tweening for curried relativeTo', () => {
|
|
165
|
+
const curried = child.position.relativeTo(parent);
|
|
166
|
+
|
|
167
|
+
// Component tweening should now work
|
|
168
|
+
expect(() => {
|
|
169
|
+
curried.x(100, 1); // Duration parameter should work
|
|
170
|
+
}).not.toThrow();
|
|
171
|
+
|
|
172
|
+
expect(() => {
|
|
173
|
+
curried.y(100, 1);
|
|
174
|
+
}).not.toThrow();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should support component tweening for scale signals', () => {
|
|
178
|
+
const curried = child.scale.relativeTo(parent);
|
|
179
|
+
|
|
180
|
+
// Scale component tweening
|
|
181
|
+
expect(() => {
|
|
182
|
+
curried.x(2, 1);
|
|
183
|
+
}).not.toThrow();
|
|
184
|
+
|
|
185
|
+
expect(() => {
|
|
186
|
+
curried.y(3, 1);
|
|
187
|
+
}).not.toThrow();
|
|
188
|
+
|
|
189
|
+
// Enhanced scale methods
|
|
190
|
+
expect(() => {
|
|
191
|
+
child.scale.abs.x(1.5, 1);
|
|
192
|
+
}).not.toThrow();
|
|
193
|
+
|
|
194
|
+
expect(() => {
|
|
195
|
+
child.scale.view.y(2.5, 1);
|
|
196
|
+
}).not.toThrow();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('Type safety and error handling', () => {
|
|
201
|
+
test('should maintain type safety with proper return types', () => {
|
|
202
|
+
const curried = child.position.relativeTo(parent);
|
|
203
|
+
|
|
204
|
+
// Getter should return Vector2
|
|
205
|
+
const pos = curried();
|
|
206
|
+
expect(pos).toBeInstanceOf(Vector2);
|
|
207
|
+
|
|
208
|
+
// Component getters should return numbers
|
|
209
|
+
const x = curried.x();
|
|
210
|
+
const y = curried.y();
|
|
211
|
+
expect(typeof x).toBe('number');
|
|
212
|
+
expect(typeof y).toBe('number');
|
|
213
|
+
|
|
214
|
+
// Setters should return the owner (child)
|
|
215
|
+
const result1 = curried([100, 100]);
|
|
216
|
+
expect(result1).toBe(child);
|
|
217
|
+
|
|
218
|
+
const result2 = curried.x(150);
|
|
219
|
+
expect(result2).toBe(child);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('Coordinate Space Verification', () => {
|
|
224
|
+
test('should correctly handle absolute space transformations', () => {
|
|
225
|
+
// Set up known initial state
|
|
226
|
+
parent.position([100, 100]);
|
|
227
|
+
parent.scale([2, 2]);
|
|
228
|
+
parent.rotation(45);
|
|
229
|
+
child.position([50, 50]);
|
|
230
|
+
|
|
231
|
+
// Test absolute position getter
|
|
232
|
+
const absPos = child.position.abs();
|
|
233
|
+
expect(absPos).toBeInstanceOf(Vector2);
|
|
234
|
+
|
|
235
|
+
// Test absolute position setter
|
|
236
|
+
child.position.abs([200, 300]);
|
|
237
|
+
const newAbsPos = child.position.abs();
|
|
238
|
+
expect(newAbsPos.x).toBeCloseTo(200, 0.01);
|
|
239
|
+
expect(newAbsPos.y).toBeCloseTo(300, 0.01);
|
|
240
|
+
|
|
241
|
+
// Test absolute component setters
|
|
242
|
+
child.position.abs.x(250);
|
|
243
|
+
expect(child.position.abs().x).toBeCloseTo(250, 0.01);
|
|
244
|
+
|
|
245
|
+
child.position.abs.y(350);
|
|
246
|
+
expect(child.position.abs().y).toBeCloseTo(350, 0.01);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('should correctly handle relative space transformations', () => {
|
|
250
|
+
// Set up known state
|
|
251
|
+
parent.position([100, 100]);
|
|
252
|
+
child.position([50, 50]);
|
|
253
|
+
|
|
254
|
+
const curried = child.position.relativeTo(parent);
|
|
255
|
+
|
|
256
|
+
// Test relative position getter
|
|
257
|
+
const relPos = curried();
|
|
258
|
+
expect(relPos).toBeInstanceOf(Vector2);
|
|
259
|
+
|
|
260
|
+
// Test relative position setter
|
|
261
|
+
curried([75, 25]);
|
|
262
|
+
const newRelPos = curried();
|
|
263
|
+
expect(newRelPos.x).toBeCloseTo(75, 0.01);
|
|
264
|
+
expect(newRelPos.y).toBeCloseTo(25, 0.01);
|
|
265
|
+
|
|
266
|
+
// Test relative component setters
|
|
267
|
+
curried.x(100);
|
|
268
|
+
expect(curried.x()).toBeCloseTo(100, 0.01);
|
|
269
|
+
|
|
270
|
+
curried.y(50);
|
|
271
|
+
expect(curried.y()).toBeCloseTo(50, 0.01);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('should correctly handle view space transformations', () => {
|
|
275
|
+
// Test view space getter
|
|
276
|
+
const viewPos = child.position.view();
|
|
277
|
+
expect(viewPos).toBeInstanceOf(Vector2);
|
|
278
|
+
|
|
279
|
+
// Test view space setter
|
|
280
|
+
child.position.view([400, 300]);
|
|
281
|
+
const newViewPos = child.position.view();
|
|
282
|
+
expect(newViewPos.x).toBeCloseTo(400, 0.01);
|
|
283
|
+
expect(newViewPos.y).toBeCloseTo(300, 0.01);
|
|
284
|
+
|
|
285
|
+
// Test view component setters
|
|
286
|
+
child.position.view.x(450);
|
|
287
|
+
expect(child.position.view().x).toBeCloseTo(450, 0.01);
|
|
288
|
+
|
|
289
|
+
child.position.view.y(350);
|
|
290
|
+
expect(child.position.view().y).toBeCloseTo(350, 0.01);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('should correctly handle local space transformations', () => {
|
|
294
|
+
// Local space should be the raw position values
|
|
295
|
+
child.position([60, 80]);
|
|
296
|
+
|
|
297
|
+
// Test local space getter
|
|
298
|
+
const localPos = child.position.local();
|
|
299
|
+
expect(localPos.x).toBeCloseTo(60, 0.01);
|
|
300
|
+
expect(localPos.y).toBeCloseTo(80, 0.01);
|
|
301
|
+
|
|
302
|
+
// Test local space setter
|
|
303
|
+
child.position.local([120, 140]);
|
|
304
|
+
const newLocalPos = child.position.local();
|
|
305
|
+
expect(newLocalPos.x).toBeCloseTo(120, 0.01);
|
|
306
|
+
expect(newLocalPos.y).toBeCloseTo(140, 0.01);
|
|
307
|
+
|
|
308
|
+
// Test local component setters
|
|
309
|
+
child.position.local.x(150);
|
|
310
|
+
expect(child.position.local().x).toBeCloseTo(150, 0.01);
|
|
311
|
+
|
|
312
|
+
child.position.local.y(170);
|
|
313
|
+
expect(child.position.local().y).toBeCloseTo(170, 0.01);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('should maintain consistency between coordinate spaces', () => {
|
|
317
|
+
// Set a known absolute position
|
|
318
|
+
child.position.abs([300, 200]);
|
|
319
|
+
const absPos = child.position.abs();
|
|
320
|
+
|
|
321
|
+
// The absolute position should be consistent
|
|
322
|
+
expect(absPos.x).toBeCloseTo(300, 0.01);
|
|
323
|
+
expect(absPos.y).toBeCloseTo(200, 0.01);
|
|
324
|
+
|
|
325
|
+
// Relative position should reflect the difference from parent
|
|
326
|
+
const relPos = child.position.relativeTo(parent)();
|
|
327
|
+
const parentAbsPos = parent.position.abs();
|
|
328
|
+
expect(relPos.x).toBeCloseTo(absPos.x - parentAbsPos.x, 0.01);
|
|
329
|
+
expect(relPos.y).toBeCloseTo(absPos.y - parentAbsPos.y, 0.01);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('Consistency across signal types', () => {
|
|
334
|
+
test('should provide consistent API across position, scale, and rotation', () => {
|
|
335
|
+
// All should support curried relativeTo
|
|
336
|
+
const positionCurried = child.position.relativeTo(parent);
|
|
337
|
+
const scaleCurried = child.scale.relativeTo(parent);
|
|
338
|
+
const rotationCurried = child.rotation.relativeTo(parent);
|
|
339
|
+
|
|
340
|
+
// All should be callable functions
|
|
341
|
+
expect(typeof positionCurried).toBe('function');
|
|
342
|
+
expect(typeof scaleCurried).toBe('function');
|
|
343
|
+
expect(typeof rotationCurried).toBe('function');
|
|
344
|
+
|
|
345
|
+
// Vector signals should have component access
|
|
346
|
+
expect(positionCurried.x).toBeDefined();
|
|
347
|
+
expect(positionCurried.y).toBeDefined();
|
|
348
|
+
expect(scaleCurried.x).toBeDefined();
|
|
349
|
+
expect(scaleCurried.y).toBeDefined();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('should work with method chaining', () => {
|
|
353
|
+
// Should be able to chain operations naturally
|
|
354
|
+
const curried = child.position.relativeTo(parent);
|
|
355
|
+
|
|
356
|
+
// Chain component setters
|
|
357
|
+
const result = curried.x(100).position.relativeTo(parent).y(200);
|
|
358
|
+
expect(result).toBe(child);
|
|
359
|
+
|
|
360
|
+
// Verify values were set
|
|
361
|
+
expect(curried.x()).toBeCloseTo(100, 0.01);
|
|
362
|
+
expect(curried.y()).toBeCloseTo(200, 0.01);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('Layout Origin Signals', () => {
|
|
367
|
+
let layout: Layout;
|
|
368
|
+
|
|
369
|
+
beforeEach(() => {
|
|
370
|
+
layout = new Layout({
|
|
371
|
+
size: [300, 200],
|
|
372
|
+
position: [50, 50],
|
|
373
|
+
});
|
|
374
|
+
useScene2D().getView().add(layout);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('should provide transform methods for layout origin signals', () => {
|
|
378
|
+
// All origin signals should have transform methods
|
|
379
|
+
expect(layout.left.abs).toBeDefined();
|
|
380
|
+
expect(layout.left.view).toBeDefined();
|
|
381
|
+
expect(layout.left.local).toBeDefined();
|
|
382
|
+
expect(layout.left.relativeTo).toBeDefined();
|
|
383
|
+
|
|
384
|
+
expect(layout.top.abs).toBeDefined();
|
|
385
|
+
expect(layout.top.view).toBeDefined();
|
|
386
|
+
expect(layout.top.local).toBeDefined();
|
|
387
|
+
expect(layout.top.relativeTo).toBeDefined();
|
|
388
|
+
|
|
389
|
+
expect(layout.right.abs).toBeDefined();
|
|
390
|
+
expect(layout.right.view).toBeDefined();
|
|
391
|
+
expect(layout.right.local).toBeDefined();
|
|
392
|
+
expect(layout.right.relativeTo).toBeDefined();
|
|
393
|
+
|
|
394
|
+
expect(layout.bottom.abs).toBeDefined();
|
|
395
|
+
expect(layout.bottom.view).toBeDefined();
|
|
396
|
+
expect(layout.bottom.local).toBeDefined();
|
|
397
|
+
expect(layout.bottom.relativeTo).toBeDefined();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('should support component access for layout origin signals', () => {
|
|
401
|
+
// Component access should be available for all transform methods
|
|
402
|
+
expect(layout.left.abs.x).toBeDefined();
|
|
403
|
+
expect(layout.left.abs.y).toBeDefined();
|
|
404
|
+
expect(layout.left.view.x).toBeDefined();
|
|
405
|
+
expect(layout.left.view.y).toBeDefined();
|
|
406
|
+
expect(layout.left.local.x).toBeDefined();
|
|
407
|
+
expect(layout.left.local.y).toBeDefined();
|
|
408
|
+
|
|
409
|
+
expect(layout.top.abs.x).toBeDefined();
|
|
410
|
+
expect(layout.top.abs.y).toBeDefined();
|
|
411
|
+
expect(layout.top.view.x).toBeDefined();
|
|
412
|
+
expect(layout.top.view.y).toBeDefined();
|
|
413
|
+
expect(layout.top.local.x).toBeDefined();
|
|
414
|
+
expect(layout.top.local.y).toBeDefined();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('should allow getting layout origin positions', () => {
|
|
418
|
+
// Test absolute coordinate access
|
|
419
|
+
const leftAbs = layout.left.abs();
|
|
420
|
+
const topAbs = layout.top.abs();
|
|
421
|
+
const rightAbs = layout.right.abs();
|
|
422
|
+
const bottomAbs = layout.bottom.abs();
|
|
423
|
+
|
|
424
|
+
expect(leftAbs).toBeInstanceOf(Vector2);
|
|
425
|
+
expect(topAbs).toBeInstanceOf(Vector2);
|
|
426
|
+
expect(rightAbs).toBeInstanceOf(Vector2);
|
|
427
|
+
expect(bottomAbs).toBeInstanceOf(Vector2);
|
|
428
|
+
|
|
429
|
+
// Test component access
|
|
430
|
+
expect(typeof layout.left.abs.x()).toBe('number');
|
|
431
|
+
expect(typeof layout.left.abs.y()).toBe('number');
|
|
432
|
+
expect(typeof layout.top.view.x()).toBe('number');
|
|
433
|
+
expect(typeof layout.top.view.y()).toBe('number');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('should allow setting layout origin positions', () => {
|
|
437
|
+
// Test setting absolute position
|
|
438
|
+
const originalPosition = layout.position();
|
|
439
|
+
layout.left.abs([100, 150]);
|
|
440
|
+
|
|
441
|
+
// Position should have changed
|
|
442
|
+
expect(layout.position()).not.toEqual(originalPosition);
|
|
443
|
+
|
|
444
|
+
// Test component setters
|
|
445
|
+
layout.top.abs.y(200);
|
|
446
|
+
layout.right.view.x(300);
|
|
447
|
+
|
|
448
|
+
// Should not throw errors
|
|
449
|
+
expect(() => {
|
|
450
|
+
layout.bottom.local([250, 180]);
|
|
451
|
+
}).not.toThrow();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('should support component tweening for layout origin signals', () => {
|
|
455
|
+
// Test component tweening for different coordinate spaces
|
|
456
|
+
expect(() => {
|
|
457
|
+
layout.left.abs.x(100, 1);
|
|
458
|
+
}).not.toThrow();
|
|
459
|
+
|
|
460
|
+
expect(() => {
|
|
461
|
+
layout.top.view.y(150, 1);
|
|
462
|
+
}).not.toThrow();
|
|
463
|
+
|
|
464
|
+
expect(() => {
|
|
465
|
+
layout.right.local.x(200, 1);
|
|
466
|
+
}).not.toThrow();
|
|
467
|
+
|
|
468
|
+
expect(() => {
|
|
469
|
+
layout.bottom.abs.y(250, 1);
|
|
470
|
+
}).not.toThrow();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('should support relativeTo for layout origin signals', () => {
|
|
474
|
+
// Create another layout to use as reference
|
|
475
|
+
const reference = new Layout({
|
|
476
|
+
size: [200, 150],
|
|
477
|
+
position: [0, 0],
|
|
478
|
+
});
|
|
479
|
+
useScene2D().getView().add(reference);
|
|
480
|
+
|
|
481
|
+
// Test getting relative position
|
|
482
|
+
const relativePos = layout.left.relativeTo(reference);
|
|
483
|
+
expect(relativePos).toBeInstanceOf(Vector2);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('should maintain consistency between origin signal coordinate spaces', () => {
|
|
487
|
+
// Set a known position
|
|
488
|
+
layout.position([100, 75]);
|
|
489
|
+
|
|
490
|
+
// Get position through different origin signals
|
|
491
|
+
const leftLocal = layout.left.local();
|
|
492
|
+
const topLocal = layout.top.local();
|
|
493
|
+
|
|
494
|
+
// Local positions should be relative to layout's local coordinate system
|
|
495
|
+
expect(leftLocal).toBeInstanceOf(Vector2);
|
|
496
|
+
expect(topLocal).toBeInstanceOf(Vector2);
|
|
497
|
+
|
|
498
|
+
// Setting position through origin signals should update the layout
|
|
499
|
+
const originalPos = layout.position();
|
|
500
|
+
layout.left.local([150, 100]);
|
|
501
|
+
expect(layout.position()).not.toEqual(originalPos);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('should work with different layout configurations', () => {
|
|
505
|
+
const centerLayout = new Layout({
|
|
506
|
+
size: [200, 100],
|
|
507
|
+
position: [0, 0],
|
|
508
|
+
});
|
|
509
|
+
useScene2D().getView().add(centerLayout);
|
|
510
|
+
|
|
511
|
+
expect(() => {
|
|
512
|
+
centerLayout.left.abs.x(50);
|
|
513
|
+
}).not.toThrow();
|
|
514
|
+
|
|
515
|
+
expect(() => {
|
|
516
|
+
centerLayout.top.view.y(25);
|
|
517
|
+
}).not.toThrow();
|
|
518
|
+
|
|
519
|
+
const topLeftLayout = new Layout({
|
|
520
|
+
size: [200, 100],
|
|
521
|
+
position: [0, 0],
|
|
522
|
+
});
|
|
523
|
+
useScene2D().getView().add(topLeftLayout);
|
|
524
|
+
|
|
525
|
+
expect(() => {
|
|
526
|
+
topLeftLayout.right.abs([100, 50]);
|
|
527
|
+
}).not.toThrow();
|
|
528
|
+
|
|
529
|
+
expect(() => {
|
|
530
|
+
topLeftLayout.bottom.local.y(75);
|
|
531
|
+
}).not.toThrow();
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe('Edge Cases and Error Handling', () => {
|
|
536
|
+
test('should handle null or undefined references gracefully', () => {
|
|
537
|
+
// Test relativeTo with null/undefined nodes
|
|
538
|
+
expect(() => {
|
|
539
|
+
child.position.relativeTo(null as any);
|
|
540
|
+
}).not.toThrow();
|
|
541
|
+
|
|
542
|
+
expect(() => {
|
|
543
|
+
child.position.relativeTo(undefined as any);
|
|
544
|
+
}).not.toThrow();
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('should handle nodes not in the scene tree', () => {
|
|
548
|
+
const orphanNode = new Rect({
|
|
549
|
+
position: [100, 100],
|
|
550
|
+
size: [50, 50],
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Should not throw when working with orphaned nodes
|
|
554
|
+
expect(() => {
|
|
555
|
+
child.position.relativeTo(orphanNode);
|
|
556
|
+
}).not.toThrow();
|
|
557
|
+
|
|
558
|
+
expect(() => {
|
|
559
|
+
orphanNode.position.abs([200, 200]);
|
|
560
|
+
}).not.toThrow();
|
|
561
|
+
|
|
562
|
+
expect(() => {
|
|
563
|
+
orphanNode.position.view.x(150);
|
|
564
|
+
}).not.toThrow();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('should handle invalid vector inputs', () => {
|
|
568
|
+
// Test with various invalid inputs
|
|
569
|
+
expect(() => {
|
|
570
|
+
child.position.abs([NaN, 100]);
|
|
571
|
+
}).not.toThrow();
|
|
572
|
+
|
|
573
|
+
expect(() => {
|
|
574
|
+
child.position.view([100, Infinity]);
|
|
575
|
+
}).not.toThrow();
|
|
576
|
+
|
|
577
|
+
expect(() => {
|
|
578
|
+
child.position.local([-Infinity, -100]);
|
|
579
|
+
}).not.toThrow();
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('should handle deeply nested node hierarchies', () => {
|
|
583
|
+
// Create a deep hierarchy
|
|
584
|
+
let currentParent = parent;
|
|
585
|
+
const nodes = [parent];
|
|
586
|
+
|
|
587
|
+
for (let i = 0; i < 10; i++) {
|
|
588
|
+
const newNode = new Rect({
|
|
589
|
+
position: [10, 10],
|
|
590
|
+
size: [50, 50],
|
|
591
|
+
});
|
|
592
|
+
currentParent.add(newNode);
|
|
593
|
+
nodes.push(newNode);
|
|
594
|
+
currentParent = newNode;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const deepChild = nodes[nodes.length - 1];
|
|
598
|
+
const rootParent = nodes[0];
|
|
599
|
+
|
|
600
|
+
// Should handle deep transformations
|
|
601
|
+
expect(() => {
|
|
602
|
+
deepChild.position.relativeTo(rootParent);
|
|
603
|
+
}).not.toThrow();
|
|
604
|
+
|
|
605
|
+
expect(() => {
|
|
606
|
+
deepChild.position.abs([500, 500]);
|
|
607
|
+
}).not.toThrow();
|
|
608
|
+
|
|
609
|
+
const relative = deepChild.position.relativeTo(rootParent);
|
|
610
|
+
expect(() => {
|
|
611
|
+
relative.x(250);
|
|
612
|
+
}).not.toThrow();
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test('should handle circular references in relativeTo', () => {
|
|
616
|
+
// Test circular references
|
|
617
|
+
const nodeA = new Rect({position: [0, 0], size: [100, 100]});
|
|
618
|
+
const nodeB = new Rect({position: [50, 50], size: [100, 100]});
|
|
619
|
+
|
|
620
|
+
nodeA.add(nodeB);
|
|
621
|
+
useScene2D().getView().add(nodeA);
|
|
622
|
+
|
|
623
|
+
// This creates a logical circular reference but should not crash
|
|
624
|
+
expect(() => {
|
|
625
|
+
const relativeA = nodeA.position.relativeTo(nodeB);
|
|
626
|
+
const relativeB = nodeB.position.relativeTo(nodeA);
|
|
627
|
+
|
|
628
|
+
relativeA([100, 100]);
|
|
629
|
+
relativeB([50, 50]);
|
|
630
|
+
}).not.toThrow();
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test('should handle components with zero or negative size', () => {
|
|
634
|
+
const zeroLayout = new Layout({
|
|
635
|
+
size: [0, 0],
|
|
636
|
+
position: [100, 100],
|
|
637
|
+
});
|
|
638
|
+
useScene2D().getView().add(zeroLayout);
|
|
639
|
+
|
|
640
|
+
expect(() => {
|
|
641
|
+
zeroLayout.left.abs.x(50);
|
|
642
|
+
}).not.toThrow();
|
|
643
|
+
|
|
644
|
+
expect(() => {
|
|
645
|
+
zeroLayout.top.view.y(75);
|
|
646
|
+
}).not.toThrow();
|
|
647
|
+
|
|
648
|
+
const negativeLayout = new Layout({
|
|
649
|
+
size: [-100, -50],
|
|
650
|
+
position: [100, 100],
|
|
651
|
+
});
|
|
652
|
+
useScene2D().getView().add(negativeLayout);
|
|
653
|
+
|
|
654
|
+
expect(() => {
|
|
655
|
+
negativeLayout.right.local([200, 150]);
|
|
656
|
+
}).not.toThrow();
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test('should handle extreme coordinate values', () => {
|
|
660
|
+
const extremeValues = [
|
|
661
|
+
Number.MAX_SAFE_INTEGER,
|
|
662
|
+
Number.MIN_SAFE_INTEGER,
|
|
663
|
+
1e10,
|
|
664
|
+
-1e10,
|
|
665
|
+
0.00001,
|
|
666
|
+
-0.00001,
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
extremeValues.forEach(value => {
|
|
670
|
+
expect(() => {
|
|
671
|
+
child.position.abs([value, value]);
|
|
672
|
+
}).not.toThrow();
|
|
673
|
+
|
|
674
|
+
expect(() => {
|
|
675
|
+
child.position.view.x(value);
|
|
676
|
+
}).not.toThrow();
|
|
677
|
+
|
|
678
|
+
expect(() => {
|
|
679
|
+
child.position.local.y(value);
|
|
680
|
+
}).not.toThrow();
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test('should handle transform signals on nodes with complex transformations', () => {
|
|
685
|
+
// Set up complex transformations
|
|
686
|
+
parent.position([200, 150]);
|
|
687
|
+
parent.scale([3, 2]);
|
|
688
|
+
parent.rotation(90);
|
|
689
|
+
parent.skew([30, -15]);
|
|
690
|
+
|
|
691
|
+
child.position([75, 25]);
|
|
692
|
+
child.scale([0.5, 1.5]);
|
|
693
|
+
child.rotation(-45);
|
|
694
|
+
|
|
695
|
+
// Should handle complex transform chains
|
|
696
|
+
expect(() => {
|
|
697
|
+
const relative = child.position.relativeTo(parent);
|
|
698
|
+
relative([100, 200]);
|
|
699
|
+
}).not.toThrow();
|
|
700
|
+
|
|
701
|
+
expect(() => {
|
|
702
|
+
child.position.abs([400, 300]);
|
|
703
|
+
}).not.toThrow();
|
|
704
|
+
|
|
705
|
+
expect(() => {
|
|
706
|
+
child.position.view.x(500);
|
|
707
|
+
}).not.toThrow();
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test('should handle concurrent modifications', () => {
|
|
711
|
+
// Simulate concurrent modifications
|
|
712
|
+
const curried = child.position.relativeTo(parent);
|
|
713
|
+
|
|
714
|
+
expect(() => {
|
|
715
|
+
// Modify through different interfaces simultaneously
|
|
716
|
+
child.position([100, 100]);
|
|
717
|
+
curried([150, 150]);
|
|
718
|
+
child.position.abs([200, 200]);
|
|
719
|
+
curried.x(250);
|
|
720
|
+
child.position.view.y(300);
|
|
721
|
+
}).not.toThrow();
|
|
722
|
+
|
|
723
|
+
// Values should be consistent after all modifications
|
|
724
|
+
expect(typeof curried.x()).toBe('number');
|
|
725
|
+
expect(typeof curried.y()).toBe('number');
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
describe('Advanced Component Access and Chaining', () => {
|
|
730
|
+
test('should maintain component state across coordinate space changes', () => {
|
|
731
|
+
// Set initial position through one coordinate space
|
|
732
|
+
child.position.abs([100, 150]);
|
|
733
|
+
const absPos = child.position.abs();
|
|
734
|
+
|
|
735
|
+
// Get the same position through different coordinate spaces
|
|
736
|
+
const viewPos = child.position.view();
|
|
737
|
+
const localPos = child.position.local();
|
|
738
|
+
|
|
739
|
+
// All should be valid Vector2 instances
|
|
740
|
+
expect(absPos).toBeInstanceOf(Vector2);
|
|
741
|
+
expect(viewPos).toBeInstanceOf(Vector2);
|
|
742
|
+
expect(localPos).toBeInstanceOf(Vector2);
|
|
743
|
+
|
|
744
|
+
// Modify through one space and verify in another
|
|
745
|
+
child.position.local.x(250);
|
|
746
|
+
const newAbsPos = child.position.abs();
|
|
747
|
+
expect(newAbsPos.x).not.toBe(absPos.x);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test('should support layout origin signal component chaining', () => {
|
|
751
|
+
const layout = new Layout({
|
|
752
|
+
size: [200, 100],
|
|
753
|
+
position: [50, 25],
|
|
754
|
+
});
|
|
755
|
+
useScene2D().getView().add(layout);
|
|
756
|
+
|
|
757
|
+
// Test chaining layout origin signals
|
|
758
|
+
const result = layout.left.abs
|
|
759
|
+
.x(100)
|
|
760
|
+
.top.view.y(75)
|
|
761
|
+
.right.local([150, 50]);
|
|
762
|
+
|
|
763
|
+
expect(result).toBe(layout);
|
|
764
|
+
|
|
765
|
+
// Test component access for layout origin signals
|
|
766
|
+
const leftX = layout.left.abs.x;
|
|
767
|
+
const topY = layout.top.view.y;
|
|
768
|
+
const rightLocal = layout.right.local;
|
|
769
|
+
|
|
770
|
+
expect(typeof leftX).toBe('function');
|
|
771
|
+
expect(typeof topY).toBe('function');
|
|
772
|
+
expect(typeof rightLocal).toBe('function');
|
|
773
|
+
|
|
774
|
+
// Should be able to chain layout operations
|
|
775
|
+
expect(() => {
|
|
776
|
+
layout.left.view.x(200).bottom.abs.y(300).top.local([175, 125]);
|
|
777
|
+
}).not.toThrow();
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test('should support mixed signal type chaining', () => {
|
|
781
|
+
// Test chaining between different signal types (position, scale, rotation)
|
|
782
|
+
const result = child
|
|
783
|
+
.position([100, 100])
|
|
784
|
+
.scale([2, 1.5])
|
|
785
|
+
.rotation(30)
|
|
786
|
+
.position.abs.x(150)
|
|
787
|
+
.scale.view.y(3)
|
|
788
|
+
.rotation(60);
|
|
789
|
+
|
|
790
|
+
expect(result).toBe(child);
|
|
791
|
+
|
|
792
|
+
// Verify final values
|
|
793
|
+
expect(child.position.abs().x).toBeCloseTo(150, 0.01);
|
|
794
|
+
expect(child.rotation()).toBeCloseTo(60, 0.01);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test('should support component access with relativeTo chaining', () => {
|
|
798
|
+
// Test component access through relativeTo chains
|
|
799
|
+
const relativePosition = child.position.relativeTo(parent);
|
|
800
|
+
const relativeScale = child.scale.relativeTo(parent);
|
|
801
|
+
|
|
802
|
+
// Should support chaining after relativeTo
|
|
803
|
+
const result = relativePosition
|
|
804
|
+
.x(100)
|
|
805
|
+
.scale.relativeTo(parent)
|
|
806
|
+
.y(2)
|
|
807
|
+
.position.relativeTo(parent)
|
|
808
|
+
.y(150);
|
|
809
|
+
|
|
810
|
+
expect(result).toBe(child);
|
|
811
|
+
expect(relativePosition.x()).toBeCloseTo(100, 0.01);
|
|
812
|
+
expect(relativeScale.y()).toBeCloseTo(2, 0.01);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test('should handle rapid successive component modifications', () => {
|
|
816
|
+
// Test rapid modifications through component access
|
|
817
|
+
const iterations = 100;
|
|
818
|
+
|
|
819
|
+
expect(() => {
|
|
820
|
+
for (let i = 0; i < iterations; i++) {
|
|
821
|
+
child.position.abs.x(i);
|
|
822
|
+
child.position.view.y(i * 2);
|
|
823
|
+
child.scale.local.x(1 + i * 0.01);
|
|
824
|
+
}
|
|
825
|
+
}).not.toThrow();
|
|
826
|
+
|
|
827
|
+
// Final values should be from last iteration
|
|
828
|
+
expect(child.position.abs().x).toBeCloseTo(iterations - 1, 0.01);
|
|
829
|
+
expect(child.scale.local().x).toBeCloseTo(
|
|
830
|
+
1 + (iterations - 1) * 0.01,
|
|
831
|
+
0.01,
|
|
832
|
+
);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
test('should support component getter/setter symmetry', () => {
|
|
836
|
+
// Test that getting and setting through components is symmetric
|
|
837
|
+
const testValue = 123.456;
|
|
838
|
+
|
|
839
|
+
// Set through component
|
|
840
|
+
child.position.abs.x(testValue);
|
|
841
|
+
const retrievedValue = child.position.abs.x();
|
|
842
|
+
|
|
843
|
+
expect(retrievedValue).toBeCloseTo(testValue, 0.01);
|
|
844
|
+
|
|
845
|
+
// Test with view coordinates
|
|
846
|
+
child.position.view.y(testValue);
|
|
847
|
+
const retrievedViewValue = child.position.view.y();
|
|
848
|
+
|
|
849
|
+
expect(retrievedViewValue).toBeCloseTo(testValue, 0.01);
|
|
850
|
+
|
|
851
|
+
// Test with local coordinates
|
|
852
|
+
child.position.local.x(testValue);
|
|
853
|
+
const retrievedLocalValue = child.position.local.x();
|
|
854
|
+
|
|
855
|
+
expect(retrievedLocalValue).toBeCloseTo(testValue, 0.01);
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
});
|