@flighthq/displayobject 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 (44) hide show
  1. package/dist/bitmap.d.ts +8 -0
  2. package/dist/bitmap.d.ts.map +1 -0
  3. package/dist/bitmap.js +41 -0
  4. package/dist/bitmap.js.map +1 -0
  5. package/dist/displayContainer.d.ts +5 -0
  6. package/dist/displayContainer.d.ts.map +1 -0
  7. package/dist/displayContainer.js +12 -0
  8. package/dist/displayContainer.js.map +1 -0
  9. package/dist/displayObject.d.ts +8 -0
  10. package/dist/displayObject.d.ts.map +1 -0
  11. package/dist/displayObject.js +32 -0
  12. package/dist/displayObject.js.map +1 -0
  13. package/dist/htmlView.d.ts +8 -0
  14. package/dist/htmlView.d.ts.map +1 -0
  15. package/dist/htmlView.js +35 -0
  16. package/dist/htmlView.js.map +1 -0
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +8 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/internal.d.ts +7 -0
  22. package/dist/internal.d.ts.map +1 -0
  23. package/dist/internal.js +2 -0
  24. package/dist/internal.js.map +1 -0
  25. package/dist/renderView.d.ts +8 -0
  26. package/dist/renderView.d.ts.map +1 -0
  27. package/dist/renderView.js +35 -0
  28. package/dist/renderView.js.map +1 -0
  29. package/dist/stage.d.ts +12 -0
  30. package/dist/stage.d.ts.map +1 -0
  31. package/dist/stage.js +59 -0
  32. package/dist/stage.js.map +1 -0
  33. package/dist/video.d.ts +9 -0
  34. package/dist/video.d.ts.map +1 -0
  35. package/dist/video.js +41 -0
  36. package/dist/video.js.map +1 -0
  37. package/package.json +41 -0
  38. package/src/bitmap.test.ts +130 -0
  39. package/src/displayContainer.test.ts +37 -0
  40. package/src/displayObject.test.ts +174 -0
  41. package/src/htmlView.test.ts +111 -0
  42. package/src/renderView.test.ts +104 -0
  43. package/src/stage.test.ts +211 -0
  44. package/src/video.test.ts +112 -0
@@ -0,0 +1,37 @@
1
+ import type { DisplayContainer } from '@flighthq/types';
2
+ import { DisplayObjectKind } from '@flighthq/types';
3
+
4
+ import { createDisplayContainer, createDisplayContainerRuntime, getDisplayContainerRuntime } from './displayContainer';
5
+
6
+ describe('createDisplayContainer', () => {
7
+ let displayContainer: DisplayContainer;
8
+
9
+ beforeEach(() => {
10
+ displayContainer = createDisplayContainer();
11
+ });
12
+
13
+ it('initializes default values', () => {
14
+ expect(displayContainer.kind).toStrictEqual(DisplayObjectKind);
15
+ });
16
+
17
+ it('returns a new object for better hidden-class performance', () => {
18
+ const base = {};
19
+ const obj = createDisplayContainer(base);
20
+ expect(obj).not.toStrictEqual(base);
21
+ });
22
+ });
23
+
24
+ describe('createDisplayContainerRuntime', () => {
25
+ it('returns a non-null runtime', () => {
26
+ const runtime = createDisplayContainerRuntime();
27
+ expect(runtime).not.toBeNull();
28
+ });
29
+ });
30
+
31
+ describe('getDisplayContainerRuntime', () => {
32
+ it('returns the runtime for a DisplayContainer', () => {
33
+ const container = createDisplayContainer();
34
+ const runtime = getDisplayContainerRuntime(container);
35
+ expect(runtime).not.toBeNull();
36
+ });
37
+ });
@@ -0,0 +1,174 @@
1
+ import { getEntityRuntime } from '@flighthq/entity';
2
+ import type {
3
+ BoundsNode,
4
+ DisplayObject,
5
+ DisplayObjectData,
6
+ DisplayObjectRuntime,
7
+ PartialNode,
8
+ Rectangle,
9
+ } from '@flighthq/types';
10
+ import { BlendMode, DisplayObjectKind, DisplayObjectTraitsKey } from '@flighthq/types';
11
+
12
+ import {
13
+ createDisplayObject,
14
+ createDisplayObjectGeneric,
15
+ createDisplayObjectRuntime,
16
+ getDisplayObjectRuntime,
17
+ isDisplayObject,
18
+ setDisplayObjectClip,
19
+ } from './displayObject';
20
+
21
+ function getRuntime_(obj: DisplayObject): DisplayObjectRuntime {
22
+ return getEntityRuntime(obj) as DisplayObjectRuntime;
23
+ }
24
+
25
+ describe('createDisplayObject', () => {
26
+ let displayObject: DisplayObject;
27
+
28
+ beforeEach(() => {
29
+ displayObject = createDisplayObject();
30
+ });
31
+
32
+ it('initializes default values', () => {
33
+ expect(displayObject.alpha).toBe(1);
34
+ expect(displayObject.blendMode).toBeNull();
35
+ expect(displayObject.name).toBeNull();
36
+ expect(displayObject.visible).toBe(true);
37
+ expect(displayObject.kind).toBe(DisplayObjectKind);
38
+ });
39
+
40
+ it('allows pre-defined values', () => {
41
+ const base = {
42
+ alpha: 2,
43
+ blendMode: BlendMode.Darken,
44
+ name: 'foo',
45
+ rotation: 45,
46
+ scaleX: 2,
47
+ scaleY: 3,
48
+ visible: false,
49
+ x: 100,
50
+ y: 200,
51
+ };
52
+ const obj = createDisplayObject(base);
53
+ expect(obj.alpha).toStrictEqual(base.alpha);
54
+ expect(obj.blendMode).toStrictEqual(base.blendMode);
55
+ expect(obj.name).toStrictEqual(base.name);
56
+ expect(obj.rotation).toStrictEqual(base.rotation);
57
+ expect(obj.scaleX).toStrictEqual(base.scaleX);
58
+ expect(obj.scaleY).toStrictEqual(base.scaleY);
59
+ expect(obj.visible).toStrictEqual(base.visible);
60
+ expect(obj.x).toStrictEqual(base.x);
61
+ expect(obj.y).toStrictEqual(base.y);
62
+ });
63
+
64
+ it('returns a new object for better hidden-class performance', () => {
65
+ const base = {};
66
+ const obj = createDisplayObject(base);
67
+ expect(obj).not.toStrictEqual(base);
68
+ });
69
+ });
70
+
71
+ describe('createDisplayObjectGeneric', () => {
72
+ it('allows creation of a type without a data field', () => {
73
+ const displayObject = createDisplayObjectGeneric(DisplayObjectKind);
74
+ expect(displayObject).not.toBeNull();
75
+ });
76
+
77
+ it('allows a custom type', () => {
78
+ const data: PartialNode<DisplayObjectTest> = {
79
+ x: 100,
80
+ };
81
+ const displayObject = createDisplayObjectGeneric(DisplayObjectKind, data);
82
+ expect(displayObject.x).toBe(data.x);
83
+ });
84
+
85
+ it('returns a new object', () => {
86
+ const data: PartialNode<DisplayObjectTest> = {};
87
+ const displayObject = createDisplayObjectGeneric(DisplayObjectKind, data);
88
+ expect(displayObject).not.toStrictEqual(data);
89
+ });
90
+
91
+ it('allows use of a data initializer', () => {
92
+ const data: PartialNode<DisplayObjectTest> = {};
93
+ const displayObject = createDisplayObjectGeneric(DisplayObjectKind, data, createDisplayObjectTestData);
94
+ expect((displayObject.data as DisplayObjectTestData).foo).toBe('bar');
95
+ });
96
+ });
97
+
98
+ describe('createDisplayObjectRuntime', () => {
99
+ it('returns a graph runtime object', () => {
100
+ const runtime = createDisplayObjectRuntime();
101
+ expect(runtime).not.toBeNull();
102
+ });
103
+
104
+ it('sets traits to DisplayObjectTraitsKey', () => {
105
+ const runtime = createDisplayObjectRuntime();
106
+ expect(runtime.traits).toBe(DisplayObjectTraitsKey);
107
+ });
108
+
109
+ it('allows a custom bounds calculation', () => {
110
+ const func = (_out: Rectangle, _source: Readonly<BoundsNode<any>>) => {};
111
+ const runtime = createDisplayObjectRuntime({ computeLocalBoundsRectangle: func });
112
+ expect(runtime.computeLocalBoundsRectangle).toStrictEqual(func);
113
+ });
114
+ });
115
+
116
+ describe('getDisplayObjectRuntime', () => {
117
+ it('returns the runtime for a DisplayObject', () => {
118
+ const obj = createDisplayObject();
119
+ const runtime = getDisplayObjectRuntime(obj);
120
+ expect(runtime).not.toBeNull();
121
+ });
122
+ });
123
+
124
+ describe('isDisplayObject', () => {
125
+ it('returns true for display objects', () => {
126
+ expect(isDisplayObject(createDisplayObject())).toBe(true);
127
+ });
128
+ });
129
+
130
+ describe('setDisplayObjectClip', () => {
131
+ let obj: DisplayObject;
132
+ beforeEach(() => {
133
+ obj = createDisplayObject();
134
+ });
135
+
136
+ it('sets clip', () => {
137
+ const clip = {
138
+ contours: null,
139
+ rect: { x: 0, y: 0, width: 100, height: 50 } as Rectangle,
140
+ winding: 'nonZero' as const,
141
+ version: 0,
142
+ };
143
+ setDisplayObjectClip(obj, clip);
144
+ expect(obj.clip).toBe(clip);
145
+ });
146
+
147
+ it('accepts null', () => {
148
+ setDisplayObjectClip(obj, null);
149
+ expect(obj.clip).toBeNull();
150
+ });
151
+
152
+ it('invalidates appearance', () => {
153
+ const idBefore = getRuntime_(obj).appearanceId;
154
+ setDisplayObjectClip(obj, {
155
+ contours: null,
156
+ rect: { x: 0, y: 0, width: 10, height: 10 } as Rectangle,
157
+ winding: 'nonZero',
158
+ version: 0,
159
+ });
160
+ expect(getRuntime_(obj).appearanceId).not.toBe(idBefore);
161
+ });
162
+ });
163
+
164
+ interface DisplayObjectTest extends DisplayObject {}
165
+
166
+ interface DisplayObjectTestData extends DisplayObjectData {
167
+ foo: string;
168
+ }
169
+
170
+ function createDisplayObjectTestData(data?: Partial<DisplayObjectTestData>): DisplayObjectTestData {
171
+ return {
172
+ foo: data?.foo ?? 'bar',
173
+ };
174
+ }
@@ -0,0 +1,111 @@
1
+ import { createRectangle } from '@flighthq/geometry';
2
+ import type { HtmlView, Node } from '@flighthq/types';
3
+ import { HtmlViewKind } from '@flighthq/types';
4
+
5
+ import {
6
+ computeHtmlViewLocalBoundsRectangle,
7
+ createHtmlView,
8
+ createHtmlViewData,
9
+ createHtmlViewRuntime,
10
+ getHtmlViewRuntime,
11
+ setHtmlViewSize,
12
+ } from './htmlView';
13
+
14
+ describe('computeHtmlViewLocalBoundsRectangle', () => {
15
+ it('sets out dimensions from data width and height', () => {
16
+ const htmlView = createHtmlView({ data: { width: 320, height: 240 } });
17
+ const out = createRectangle();
18
+ computeHtmlViewLocalBoundsRectangle(out, htmlView as unknown as Node);
19
+ expect(out.width).toBe(320);
20
+ expect(out.height).toBe(240);
21
+ });
22
+ });
23
+
24
+ describe('createHtmlView', () => {
25
+ let htmlView: HtmlView;
26
+
27
+ beforeEach(() => {
28
+ htmlView = createHtmlView();
29
+ });
30
+
31
+ it('initializes default values', () => {
32
+ expect(htmlView.data.element).toBeNull();
33
+ expect(htmlView.data.width).toBe(100);
34
+ expect(htmlView.data.height).toBe(100);
35
+ expect(htmlView.kind).toStrictEqual(HtmlViewKind);
36
+ });
37
+
38
+ it('allows pre-defined values', () => {
39
+ const element = {} as HTMLImageElement;
40
+ const base = {
41
+ data: {
42
+ element: element,
43
+ width: 640,
44
+ height: 480,
45
+ },
46
+ };
47
+ const obj = createHtmlView(base);
48
+ expect(obj.data.element).toStrictEqual(element);
49
+ expect(obj.data.width).toBe(640);
50
+ expect(obj.data.height).toBe(480);
51
+ });
52
+
53
+ it('returns a new object for better hidden-class performance', () => {
54
+ const base = {};
55
+ const obj = createHtmlView(base);
56
+ expect(obj).not.toStrictEqual(base);
57
+ });
58
+ });
59
+
60
+ describe('createHtmlViewData', () => {
61
+ it('returns default values', () => {
62
+ const data = createHtmlViewData();
63
+ expect(data.element).toBeNull();
64
+ expect(data.width).toBe(100);
65
+ expect(data.height).toBe(100);
66
+ });
67
+
68
+ it('allows pre-defined values', () => {
69
+ const element = {} as HTMLImageElement;
70
+ const data = createHtmlViewData({ element, width: 200, height: 150 });
71
+ expect(data.element).toBe(element);
72
+ expect(data.width).toBe(200);
73
+ expect(data.height).toBe(150);
74
+ });
75
+ });
76
+
77
+ describe('createHtmlViewRuntime', () => {
78
+ it('returns a non-null runtime', () => {
79
+ const runtime = createHtmlViewRuntime();
80
+ expect(runtime).not.toBeNull();
81
+ });
82
+
83
+ it('uses computeHtmlViewLocalBoundsRectangle', () => {
84
+ const runtime = createHtmlViewRuntime();
85
+ expect(runtime.computeLocalBoundsRectangle).toStrictEqual(computeHtmlViewLocalBoundsRectangle);
86
+ });
87
+ });
88
+
89
+ describe('getHtmlViewRuntime', () => {
90
+ it('returns the runtime for an HtmlView', () => {
91
+ const htmlView = createHtmlView();
92
+ const runtime = getHtmlViewRuntime(htmlView);
93
+ expect(runtime).not.toBeNull();
94
+ });
95
+ });
96
+
97
+ describe('setHtmlViewSize', () => {
98
+ it('updates width and height', () => {
99
+ const htmlView = createHtmlView();
100
+ setHtmlViewSize(htmlView, 320, 240);
101
+ expect(htmlView.data.width).toBe(320);
102
+ expect(htmlView.data.height).toBe(240);
103
+ });
104
+
105
+ it('is a no-op when dimensions are unchanged', () => {
106
+ const htmlView = createHtmlView({ data: { width: 200, height: 150 } });
107
+ setHtmlViewSize(htmlView, 200, 150);
108
+ expect(htmlView.data.width).toBe(200);
109
+ expect(htmlView.data.height).toBe(150);
110
+ });
111
+ });
@@ -0,0 +1,104 @@
1
+ import { createRectangle } from '@flighthq/geometry';
2
+ import type { Node, RenderView, RenderViewRenderer } from '@flighthq/types';
3
+ import { RenderViewKind } from '@flighthq/types';
4
+
5
+ import {
6
+ computeRenderViewLocalBoundsRectangle,
7
+ createRenderView,
8
+ createRenderViewData,
9
+ createRenderViewRuntime,
10
+ getRenderViewRuntime,
11
+ setRenderViewSize,
12
+ } from './renderView';
13
+
14
+ function makeRenderer(): RenderViewRenderer {
15
+ const canvas = document.createElement('canvas');
16
+ canvas.width = 100;
17
+ canvas.height = 100;
18
+ return { canvas, render: () => {} };
19
+ }
20
+
21
+ describe('computeRenderViewLocalBoundsRectangle', () => {
22
+ it('writes width and height from data', () => {
23
+ const view = createRenderView({ data: { width: 400, height: 300 } });
24
+ const out = createRectangle(0, 0, 0, 0);
25
+ computeRenderViewLocalBoundsRectangle(out, view as unknown as Node);
26
+ expect(out.width).toBe(400);
27
+ expect(out.height).toBe(300);
28
+ });
29
+ });
30
+
31
+ describe('createRenderView', () => {
32
+ let view: RenderView;
33
+
34
+ beforeEach(() => {
35
+ view = createRenderView();
36
+ });
37
+
38
+ it('initializes default values', () => {
39
+ expect(view.data.renderer).toBeNull();
40
+ expect(view.data.width).toBe(0);
41
+ expect(view.data.height).toBe(0);
42
+ expect(view.kind).toStrictEqual(RenderViewKind);
43
+ });
44
+
45
+ it('allows pre-defined values', () => {
46
+ const renderer = makeRenderer();
47
+ const obj = createRenderView({ data: { renderer, width: 320, height: 240 } });
48
+ expect(obj.data.renderer).toStrictEqual(renderer);
49
+ expect(obj.data.width).toBe(320);
50
+ expect(obj.data.height).toBe(240);
51
+ });
52
+
53
+ it('returns a new object for better hidden-class performance', () => {
54
+ const base = {};
55
+ const obj = createRenderView(base);
56
+ expect(obj).not.toStrictEqual(base);
57
+ });
58
+ });
59
+
60
+ describe('createRenderViewData', () => {
61
+ it('returns default values', () => {
62
+ const data = createRenderViewData();
63
+ expect(data.renderer).toBeNull();
64
+ expect(data.width).toBe(0);
65
+ expect(data.height).toBe(0);
66
+ });
67
+
68
+ it('allows pre-defined values', () => {
69
+ const renderer = makeRenderer();
70
+ const data = createRenderViewData({ renderer, width: 800, height: 600 });
71
+ expect(data.renderer).toStrictEqual(renderer);
72
+ expect(data.width).toBe(800);
73
+ expect(data.height).toBe(600);
74
+ });
75
+ });
76
+
77
+ describe('createRenderViewRuntime', () => {
78
+ it('returns a non-null runtime', () => {
79
+ expect(createRenderViewRuntime()).not.toBeNull();
80
+ });
81
+ });
82
+
83
+ describe('getRenderViewRuntime', () => {
84
+ it('returns the runtime for a RenderView', () => {
85
+ const view = createRenderView();
86
+ expect(getRenderViewRuntime(view)).not.toBeNull();
87
+ });
88
+ });
89
+
90
+ describe('setRenderViewSize', () => {
91
+ it('updates width and height', () => {
92
+ const view = createRenderView();
93
+ setRenderViewSize(view, 640, 480);
94
+ expect(view.data.width).toBe(640);
95
+ expect(view.data.height).toBe(480);
96
+ });
97
+
98
+ it('is a no-op when size is unchanged', () => {
99
+ const view = createRenderView({ data: { width: 100, height: 100 } });
100
+ const before = view.data.width;
101
+ setRenderViewSize(view, 100, 100);
102
+ expect(view.data.width).toBe(before);
103
+ });
104
+ });
@@ -0,0 +1,211 @@
1
+ import { createRectangle } from '@flighthq/geometry';
2
+ import { addNodeChild } from '@flighthq/node';
3
+ import { connectSignal } from '@flighthq/signals';
4
+ import type { Node, PartialNode, Stage } from '@flighthq/types';
5
+ import { StageKind } from '@flighthq/types';
6
+
7
+ import { createDisplayObject } from './displayObject';
8
+ import {
9
+ computeStageLocalBoundsRectangle,
10
+ createStage,
11
+ createStageData,
12
+ createStageRuntime,
13
+ createStageSignals,
14
+ enableStageSignals,
15
+ getDisplayObjectStage,
16
+ getStageRuntime,
17
+ getStageSignals,
18
+ setStageSize,
19
+ } from './stage';
20
+
21
+ describe('computeStageLocalBoundsRectangle', () => {
22
+ it('sets out dimensions from stageWidth and stageHeight', () => {
23
+ const stage = createStage({ data: { stageWidth: 800, stageHeight: 600 } });
24
+ const out = createRectangle();
25
+ computeStageLocalBoundsRectangle(out, stage as unknown as Node);
26
+ expect(out.width).toBe(800);
27
+ expect(out.height).toBe(600);
28
+ });
29
+ });
30
+
31
+ describe('createStage', () => {
32
+ let stage: Stage;
33
+
34
+ beforeEach(() => {
35
+ stage = createStage();
36
+ });
37
+
38
+ it('initializes default values', () => {
39
+ expect(stage.data.color).toBe(null);
40
+ expect(stage.data.stageHeight).toBe(550);
41
+ expect(stage.data.stageWidth).toBe(400);
42
+ expect(stage.kind).toStrictEqual(StageKind);
43
+ });
44
+
45
+ it('allows pre-defined values', () => {
46
+ const base: PartialNode<Stage> = {
47
+ data: {
48
+ color: 0xff0000ff,
49
+ stageHeight: 1000,
50
+ stageWidth: 2000,
51
+ },
52
+ };
53
+ const obj = createStage(base);
54
+ const data = base.data!;
55
+ expect(obj.data.color).toStrictEqual(data.color);
56
+ expect(obj.data.stageHeight).toStrictEqual(data.stageHeight);
57
+ expect(obj.data.stageWidth).toStrictEqual(data.stageWidth);
58
+ });
59
+
60
+ it('returns a new object for better hidden-class performance', () => {
61
+ const base = {};
62
+ const obj = createStage(base);
63
+ expect(obj).not.toStrictEqual(base);
64
+ });
65
+ });
66
+
67
+ describe('createStageData', () => {
68
+ it('returns default values', () => {
69
+ const data = createStageData();
70
+ expect(data.stageWidth).toBe(400);
71
+ expect(data.stageHeight).toBe(550);
72
+ expect(data.color).toBe(null);
73
+ });
74
+
75
+ it('allows pre-defined values', () => {
76
+ const data = createStageData({ stageWidth: 1920, stageHeight: 1080 });
77
+ expect(data.stageWidth).toBe(1920);
78
+ expect(data.stageHeight).toBe(1080);
79
+ });
80
+ });
81
+
82
+ describe('createStageRuntime', () => {
83
+ it('returns a non-null runtime', () => {
84
+ const runtime = createStageRuntime();
85
+ expect(runtime).not.toBeNull();
86
+ });
87
+
88
+ it('uses computeStageLocalBoundsRectangle', () => {
89
+ const runtime = createStageRuntime();
90
+ expect(runtime.computeLocalBoundsRectangle).toStrictEqual(computeStageLocalBoundsRectangle);
91
+ });
92
+ });
93
+
94
+ describe('createStageSignals', () => {
95
+ it('returns an object with all signal properties', () => {
96
+ const signals = createStageSignals();
97
+ expect(signals.onResize).toBeDefined();
98
+ expect(signals.onFullscreenChanged).toBeDefined();
99
+ expect(signals.onOrientationChanged).toBeDefined();
100
+ });
101
+ });
102
+
103
+ describe('enableStageSignals', () => {
104
+ it('creates and returns signals on first call', () => {
105
+ const stage = createStage();
106
+ const signals = enableStageSignals(stage);
107
+ expect(signals).toBeDefined();
108
+ expect(signals.onResize).toBeDefined();
109
+ });
110
+
111
+ it('returns the same object on subsequent calls', () => {
112
+ const stage = createStage();
113
+ expect(enableStageSignals(stage)).toBe(enableStageSignals(stage));
114
+ });
115
+
116
+ it('makes getStageSignals return the enabled object', () => {
117
+ const stage = createStage();
118
+ const signals = enableStageSignals(stage);
119
+ expect(getStageSignals(stage)).toBe(signals);
120
+ });
121
+ });
122
+
123
+ describe('getDisplayObjectStage', () => {
124
+ it('returns null when the node has no parent', () => {
125
+ const obj = createDisplayObject();
126
+ expect(getDisplayObjectStage(obj)).toBeNull();
127
+ });
128
+
129
+ it('returns null when the root is not a Stage', () => {
130
+ const root = createDisplayObject();
131
+ const child = createDisplayObject();
132
+ addNodeChild(root, child);
133
+ expect(getDisplayObjectStage(child)).toBeNull();
134
+ });
135
+
136
+ it('returns the Stage when it is the root', () => {
137
+ const stage = createStage();
138
+ const child = createDisplayObject();
139
+ addNodeChild(stage, child);
140
+ expect(getDisplayObjectStage(child)).toBe(stage);
141
+ });
142
+
143
+ it('returns the Stage from a deeply nested node', () => {
144
+ const stage = createStage();
145
+ const mid = createDisplayObject();
146
+ const leaf = createDisplayObject();
147
+ addNodeChild(stage, mid);
148
+ addNodeChild(mid, leaf);
149
+ expect(getDisplayObjectStage(leaf)).toBe(stage);
150
+ });
151
+ });
152
+
153
+ describe('getStageRuntime', () => {
154
+ it('returns the runtime for a Stage', () => {
155
+ const stage = createStage();
156
+ const runtime = getStageRuntime(stage);
157
+ expect(runtime).not.toBeNull();
158
+ });
159
+
160
+ it('initializes stageSignals to null', () => {
161
+ const runtime = createStageRuntime();
162
+ expect(runtime.stageSignals).toBeNull();
163
+ });
164
+ });
165
+
166
+ describe('getStageSignals', () => {
167
+ it('returns null before signals are enabled', () => {
168
+ const stage = createStage();
169
+ expect(getStageSignals(stage)).toBeNull();
170
+ });
171
+
172
+ it('returns the signals after enableStageSignals', () => {
173
+ const stage = createStage();
174
+ const signals = enableStageSignals(stage);
175
+ expect(getStageSignals(stage)).toBe(signals);
176
+ });
177
+ });
178
+
179
+ describe('setStageSize', () => {
180
+ it('updates stageWidth and stageHeight', () => {
181
+ const stage = createStage();
182
+ setStageSize(stage, 1920, 1080);
183
+ expect(stage.data.stageWidth).toBe(1920);
184
+ expect(stage.data.stageHeight).toBe(1080);
185
+ });
186
+
187
+ it('emits onResize when dimensions change', () => {
188
+ const stage = createStage();
189
+ let called = false;
190
+ connectSignal(enableStageSignals(stage).onResize, () => {
191
+ called = true;
192
+ });
193
+ setStageSize(stage, 1280, 720);
194
+ expect(called).toBe(true);
195
+ });
196
+
197
+ it('does not emit onResize when dimensions are unchanged', () => {
198
+ const stage = createStage({ data: { stageWidth: 400, stageHeight: 300 } });
199
+ let called = false;
200
+ connectSignal(enableStageSignals(stage).onResize, () => {
201
+ called = true;
202
+ });
203
+ setStageSize(stage, 400, 300);
204
+ expect(called).toBe(false);
205
+ });
206
+
207
+ it('does not emit onResize when no one has subscribed', () => {
208
+ const stage = createStage();
209
+ expect(() => setStageSize(stage, 1280, 720)).not.toThrow();
210
+ });
211
+ });