@flighthq/scene2d-dom 0.2.1-next.840.857425c

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 (135) hide show
  1. package/dist/domBackground.d.ts +3 -0
  2. package/dist/domBackground.d.ts.map +1 -0
  3. package/dist/domBackground.js +9 -0
  4. package/dist/domBackground.js.map +1 -0
  5. package/dist/domBitmap.d.ts +4 -0
  6. package/dist/domBitmap.d.ts.map +1 -0
  7. package/dist/domBitmap.js +81 -0
  8. package/dist/domBitmap.js.map +1 -0
  9. package/dist/domCSSFilterBinding.d.ts +19 -0
  10. package/dist/domCSSFilterBinding.d.ts.map +1 -0
  11. package/dist/domCSSFilterBinding.js +36 -0
  12. package/dist/domCSSFilterBinding.js.map +1 -0
  13. package/dist/domCache.d.ts +14 -0
  14. package/dist/domCache.d.ts.map +1 -0
  15. package/dist/domCache.js +64 -0
  16. package/dist/domCache.js.map +1 -0
  17. package/dist/domClip.d.ts +3 -0
  18. package/dist/domClip.d.ts.map +1 -0
  19. package/dist/domClip.js +38 -0
  20. package/dist/domClip.js.map +1 -0
  21. package/dist/domClipContours.d.ts +6 -0
  22. package/dist/domClipContours.d.ts.map +1 -0
  23. package/dist/domClipContours.js +48 -0
  24. package/dist/domClipContours.js.map +1 -0
  25. package/dist/domClipRectangle.d.ts +6 -0
  26. package/dist/domClipRectangle.d.ts.map +1 -0
  27. package/dist/domClipRectangle.js +148 -0
  28. package/dist/domClipRectangle.js.map +1 -0
  29. package/dist/domFontSource.d.ts +5 -0
  30. package/dist/domFontSource.d.ts.map +1 -0
  31. package/dist/domFontSource.js +18 -0
  32. package/dist/domFontSource.js.map +1 -0
  33. package/dist/domHtmlView.d.ts +4 -0
  34. package/dist/domHtmlView.d.ts.map +1 -0
  35. package/dist/domHtmlView.js +28 -0
  36. package/dist/domHtmlView.js.map +1 -0
  37. package/dist/domImageSource.d.ts +4 -0
  38. package/dist/domImageSource.d.ts.map +1 -0
  39. package/dist/domImageSource.js +36 -0
  40. package/dist/domImageSource.js.map +1 -0
  41. package/dist/domMaterials.d.ts +15 -0
  42. package/dist/domMaterials.d.ts.map +1 -0
  43. package/dist/domMaterials.js +68 -0
  44. package/dist/domMaterials.js.map +1 -0
  45. package/dist/domNativeText.d.ts +5 -0
  46. package/dist/domNativeText.d.ts.map +1 -0
  47. package/dist/domNativeText.js +88 -0
  48. package/dist/domNativeText.js.map +1 -0
  49. package/dist/domNode2D.d.ts +5 -0
  50. package/dist/domNode2D.d.ts.map +1 -0
  51. package/dist/domNode2D.js +60 -0
  52. package/dist/domNode2D.js.map +1 -0
  53. package/dist/domReconcile.d.ts +9 -0
  54. package/dist/domReconcile.d.ts.map +1 -0
  55. package/dist/domReconcile.js +69 -0
  56. package/dist/domReconcile.js.map +1 -0
  57. package/dist/domRenderState.d.ts +5 -0
  58. package/dist/domRenderState.d.ts.map +1 -0
  59. package/dist/domRenderState.js +43 -0
  60. package/dist/domRenderState.js.map +1 -0
  61. package/dist/domRenderView.d.ts +4 -0
  62. package/dist/domRenderView.d.ts.map +1 -0
  63. package/dist/domRenderView.js +24 -0
  64. package/dist/domRenderView.js.map +1 -0
  65. package/dist/domRichText.d.ts +6 -0
  66. package/dist/domRichText.d.ts.map +1 -0
  67. package/dist/domRichText.js +184 -0
  68. package/dist/domRichText.js.map +1 -0
  69. package/dist/domScale9Mapper.d.ts +3 -0
  70. package/dist/domScale9Mapper.d.ts.map +1 -0
  71. package/dist/domScale9Mapper.js +37 -0
  72. package/dist/domScale9Mapper.js.map +1 -0
  73. package/dist/domScale9Shape.d.ts +10 -0
  74. package/dist/domScale9Shape.d.ts.map +1 -0
  75. package/dist/domScale9Shape.js +59 -0
  76. package/dist/domScale9Shape.js.map +1 -0
  77. package/dist/domShape.d.ts +4 -0
  78. package/dist/domShape.d.ts.map +1 -0
  79. package/dist/domShape.js +45 -0
  80. package/dist/domShape.js.map +1 -0
  81. package/dist/domStyle.d.ts +9 -0
  82. package/dist/domStyle.d.ts.map +1 -0
  83. package/dist/domStyle.js +25 -0
  84. package/dist/domStyle.js.map +1 -0
  85. package/dist/domTextHelpers.d.ts +6 -0
  86. package/dist/domTextHelpers.d.ts.map +1 -0
  87. package/dist/domTextHelpers.js +8 -0
  88. package/dist/domTextHelpers.js.map +1 -0
  89. package/dist/domTextInput.d.ts +4 -0
  90. package/dist/domTextInput.d.ts.map +1 -0
  91. package/dist/domTextInput.js +60 -0
  92. package/dist/domTextInput.js.map +1 -0
  93. package/dist/domTextLabel.d.ts +4 -0
  94. package/dist/domTextLabel.d.ts.map +1 -0
  95. package/dist/domTextLabel.js +75 -0
  96. package/dist/domTextLabel.js.map +1 -0
  97. package/dist/domTransform.d.ts +4 -0
  98. package/dist/domTransform.d.ts.map +1 -0
  99. package/dist/domTransform.js +16 -0
  100. package/dist/domTransform.js.map +1 -0
  101. package/dist/domVideo.d.ts +4 -0
  102. package/dist/domVideo.d.ts.map +1 -0
  103. package/dist/domVideo.js +27 -0
  104. package/dist/domVideo.js.map +1 -0
  105. package/dist/index.d.ts +24 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.js +24 -0
  108. package/dist/index.js.map +1 -0
  109. package/package.json +55 -0
  110. package/src/domBackground.test.ts +35 -0
  111. package/src/domBitmap.test.ts +117 -0
  112. package/src/domCSSFilterBinding.test.ts +62 -0
  113. package/src/domCache.test.ts +93 -0
  114. package/src/domClip.test.ts +12 -0
  115. package/src/domClipContours.test.ts +62 -0
  116. package/src/domClipRectangle.test.ts +81 -0
  117. package/src/domFontSource.test.ts +30 -0
  118. package/src/domHtmlView.test.ts +117 -0
  119. package/src/domImageSource.test.ts +47 -0
  120. package/src/domMaterials.test.ts +100 -0
  121. package/src/domNativeText.test.ts +102 -0
  122. package/src/domNode2D.test.ts +236 -0
  123. package/src/domReconcile.test.ts +158 -0
  124. package/src/domRenderState.test.ts +87 -0
  125. package/src/domRenderView.test.ts +69 -0
  126. package/src/domRichText.test.ts +169 -0
  127. package/src/domScale9Mapper.test.ts +20 -0
  128. package/src/domScale9Shape.test.ts +76 -0
  129. package/src/domShape.test.ts +85 -0
  130. package/src/domStyle.test.ts +130 -0
  131. package/src/domTextHelpers.test.ts +27 -0
  132. package/src/domTextInput.test.ts +61 -0
  133. package/src/domTextLabel.test.ts +92 -0
  134. package/src/domTransform.test.ts +68 -0
  135. package/src/domVideo.test.ts +30 -0
@@ -0,0 +1,81 @@
1
+ import { createMatrix, createRectangle } from '@flighthq/geometry';
2
+ import { getOrCreateRenderProxy2D } from '@flighthq/render';
3
+ import { createDisplayObject } from '@flighthq/scene2d';
4
+ import type { DomRenderState, DomScene2DRectangle } from '@flighthq/types';
5
+
6
+ import {
7
+ applyDomClipRectangles,
8
+ createDomScene2DRectangle,
9
+ pushDomClipRectangle,
10
+ setDomClipHooks,
11
+ } from './domClipRectangle';
12
+ import { createDomRenderState, getDomRenderStateRuntime } from './domRenderState';
13
+
14
+ function makeState(): DomRenderState {
15
+ return createDomRenderState(document.createElement('div'));
16
+ }
17
+
18
+ describe('applyDomClipRectangles', () => {
19
+ it('clears clip styles when no rectangles are active', () => {
20
+ const state = makeState();
21
+ const source = createDisplayObject();
22
+ const data = getOrCreateRenderProxy2D(state, source);
23
+ const element = document.createElement('div');
24
+ element.style.clipPath = 'inset(0)';
25
+ getDomRenderStateRuntime(state).domElementMap.set(data, element);
26
+
27
+ applyDomClipRectangles(state, data, []);
28
+
29
+ expect(element.style.clipPath).toBe('');
30
+ });
31
+
32
+ it('sets a local polygon clip path from intersected scene2d rectangles', () => {
33
+ const state = makeState();
34
+ const source = createDisplayObject();
35
+ const data = getOrCreateRenderProxy2D(state, source);
36
+ const element = document.createElement('div');
37
+ element.style.transform = 'matrix(1,0,0,1,10,20)';
38
+ getDomRenderStateRuntime(state).domElementMap.set(data, element);
39
+
40
+ applyDomClipRectangles(state, data, [
41
+ { bottom: 100, left: 0, right: 100, top: 0 },
42
+ { bottom: 90, left: 20, right: 80, top: 30 },
43
+ ]);
44
+
45
+ expect(element.style.clipPath).toBe('polygon(10px 10px, 70px 10px, 70px 70px, 10px 70px)');
46
+ });
47
+ });
48
+
49
+ describe('createDomScene2DRectangle', () => {
50
+ it('transforms a local rectangle into a scene2d-space AABB', () => {
51
+ const rect = createDomScene2DRectangle(createRectangle(10, 20, 30, 40), createMatrix(1, 0, 0, 1, 5, 6));
52
+
53
+ expect(rect).toEqual({ bottom: 66, left: 15, right: 45, top: 26 });
54
+ });
55
+ });
56
+
57
+ describe('pushDomClipRectangle', () => {
58
+ it('pushes a transformed scene2d rectangle', () => {
59
+ const rectangles: DomScene2DRectangle[] = [];
60
+
61
+ pushDomClipRectangle(rectangles, createRectangle(0, 0, 10, 20), createMatrix(1, 0, 0, 1, 2, 3));
62
+
63
+ expect(rectangles).toEqual([{ bottom: 23, left: 2, right: 12, top: 3 }]);
64
+ });
65
+ });
66
+
67
+ describe('setDomClipHooks', () => {
68
+ it('sets DOM clip hooks on the render state', () => {
69
+ const state = makeState();
70
+ setDomClipHooks(state);
71
+ expect(getDomRenderStateRuntime(state).domClipHooks).not.toBeNull();
72
+ });
73
+
74
+ it('is idempotent', () => {
75
+ const state = makeState();
76
+ setDomClipHooks(state);
77
+ const first = getDomRenderStateRuntime(state).domClipHooks;
78
+ setDomClipHooks(state);
79
+ expect(getDomRenderStateRuntime(state).domClipHooks).toBe(first);
80
+ });
81
+ });
@@ -0,0 +1,30 @@
1
+ import { getDomFontAscentCached, invalidateDomFontResource, setDomFontAscentCached } from './domFontSource';
2
+
3
+ describe('getDomFontAscentCached', () => {
4
+ it('returns undefined for an unknown font key', () => {
5
+ expect(getDomFontAscentCached('12px "UnknownFont-x9z"')).toBeUndefined();
6
+ });
7
+ });
8
+
9
+ describe('invalidateDomFontResource', () => {
10
+ it('removes cache entries that match the font family', () => {
11
+ setDomFontAscentCached("16px 'MyFont'", 14);
12
+ setDomFontAscentCached('16px "MyFont"', 14);
13
+ invalidateDomFontResource({ family: 'MyFont', face: null });
14
+ expect(getDomFontAscentCached("16px 'MyFont'")).toBeUndefined();
15
+ expect(getDomFontAscentCached('16px "MyFont"')).toBeUndefined();
16
+ });
17
+
18
+ it('does not remove cache entries for other families', () => {
19
+ setDomFontAscentCached("16px 'OtherFont'", 13);
20
+ invalidateDomFontResource({ family: 'MyFont', face: null });
21
+ expect(getDomFontAscentCached("16px 'OtherFont'")).toBe(13);
22
+ });
23
+ });
24
+
25
+ describe('setDomFontAscentCached', () => {
26
+ it('stores and retrieves an ascent value', () => {
27
+ setDomFontAscentCached('12px "CacheTestFont"', 10);
28
+ expect(getDomFontAscentCached('12px "CacheTestFont"')).toBe(10);
29
+ });
30
+ });
@@ -0,0 +1,117 @@
1
+ import { registerRenderer } from '@flighthq/render';
2
+ import { getOrCreateRenderProxy2D } from '@flighthq/render';
3
+ import { createHtmlView } from '@flighthq/scene2d';
4
+ import { HtmlViewKind } from '@flighthq/types';
5
+
6
+ import { defaultHtmlViewRenderer, drawDomHtmlView } from './domHtmlView';
7
+ import { createDomRenderState, getDomRenderStateRuntime } from './domRenderState';
8
+
9
+ function makeState() {
10
+ const container = document.createElement('div');
11
+ const state = createDomRenderState(container);
12
+ registerRenderer(state, HtmlViewKind, defaultHtmlViewRenderer);
13
+ return state;
14
+ }
15
+
16
+ function drawGetEl(state: ReturnType<typeof makeState>, drawFn: () => void): HTMLElement | null {
17
+ getDomRenderStateRuntime(state).domCurrentElement = null;
18
+ drawFn();
19
+ return getDomRenderStateRuntime(state).domCurrentElement;
20
+ }
21
+
22
+ describe('defaultHtmlViewRenderer', () => {
23
+ it('has submit, and createData', () => {
24
+ expect(typeof defaultHtmlViewRenderer.submit).toBe('function');
25
+ expect(typeof defaultHtmlViewRenderer.createData).toBe('function');
26
+ });
27
+ });
28
+
29
+ describe('drawDomHtmlView', () => {
30
+ it('produces no element when source element is null', () => {
31
+ const state = makeState();
32
+ const node = createHtmlView();
33
+ node.data.element = null;
34
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
35
+
36
+ const el = drawGetEl(state, () => drawDomHtmlView(state, renderProxy));
37
+
38
+ expect(el).toBeNull();
39
+ });
40
+
41
+ it('produces the source element', () => {
42
+ const state = makeState();
43
+ const inner = document.createElement('span');
44
+ const node = createHtmlView({ data: { element: inner } });
45
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
46
+
47
+ const el = drawGetEl(state, () => drawDomHtmlView(state, renderProxy));
48
+
49
+ expect(el).toBe(inner);
50
+ });
51
+
52
+ it('initializes position and overflow styles on first draw', () => {
53
+ const state = makeState();
54
+ const inner = document.createElement('div');
55
+ const node = createHtmlView({ data: { element: inner } });
56
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
57
+
58
+ drawDomHtmlView(state, renderProxy);
59
+
60
+ expect(inner.style.position).toBe('absolute');
61
+ expect(inner.style.left).toBe('0px');
62
+ expect(inner.style.top).toBe('0px');
63
+ expect(inner.style.transformOrigin).toBe('0 0');
64
+ expect(inner.style.overflow).toBe('hidden');
65
+ });
66
+
67
+ it('does not reset position styles on subsequent draws when already absolute', () => {
68
+ const state = makeState();
69
+ const inner = document.createElement('div');
70
+ inner.style.position = 'absolute';
71
+ inner.style.left = '50px';
72
+ const node = createHtmlView({ data: { element: inner } });
73
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
74
+
75
+ drawDomHtmlView(state, renderProxy);
76
+
77
+ expect(inner.style.left).toBe('50px');
78
+ });
79
+
80
+ it('applies width and height from data', () => {
81
+ const state = makeState();
82
+ const inner = document.createElement('div');
83
+ const node = createHtmlView({ data: { element: inner, width: 320, height: 240 } });
84
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
85
+
86
+ drawDomHtmlView(state, renderProxy);
87
+
88
+ expect(inner.style.width).toBe('320px');
89
+ expect(inner.style.height).toBe('240px');
90
+ });
91
+
92
+ it('updates width and height when data changes between draws', () => {
93
+ const state = makeState();
94
+ const inner = document.createElement('div');
95
+ const node = createHtmlView({ data: { element: inner, width: 100, height: 100 } });
96
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
97
+
98
+ drawDomHtmlView(state, renderProxy);
99
+ node.data.width = 640;
100
+ node.data.height = 480;
101
+ drawDomHtmlView(state, renderProxy);
102
+
103
+ expect(inner.style.width).toBe('640px');
104
+ expect(inner.style.height).toBe('480px');
105
+ });
106
+
107
+ it('applies transform from the render node', () => {
108
+ const state = makeState();
109
+ const inner = document.createElement('div');
110
+ const node = createHtmlView({ data: { element: inner } });
111
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
112
+
113
+ drawDomHtmlView(state, renderProxy);
114
+
115
+ expect(inner.style.transform).toMatch(/^matrix\(/);
116
+ });
117
+ });
@@ -0,0 +1,47 @@
1
+ import { createImageResource, invalidateImageResource } from '@flighthq/image';
2
+ import type { ImageResource } from '@flighthq/types';
3
+
4
+ import { explainDomImageSource, resolveDomImageSource } from './domImageSource';
5
+ import { createDomRenderState } from './domRenderState';
6
+
7
+ function makeState() {
8
+ return createDomRenderState(document.createElement('div'));
9
+ }
10
+
11
+ function makeDataOnlyResource(width = 4, height = 4): ImageResource {
12
+ const resource = createImageResource();
13
+ resource.width = width;
14
+ resource.height = height;
15
+ resource.data = new Uint8ClampedArray(width * height * 4).fill(255);
16
+ return resource;
17
+ }
18
+
19
+ describe('explainDomImageSource', () => {
20
+ it('reports element, data, and none for the three representations', () => {
21
+ expect(explainDomImageSource(createImageResource(document.createElement('img')))).toBe('element');
22
+ expect(explainDomImageSource(makeDataOnlyResource())).toBe('data');
23
+ expect(explainDomImageSource(createImageResource())).toBe('none');
24
+ });
25
+ });
26
+
27
+ describe('resolveDomImageSource', () => {
28
+ it('returns the host source element directly', () => {
29
+ const state = makeState();
30
+ const img = document.createElement('img');
31
+ expect(resolveDomImageSource(state, createImageResource(img))).toBe(img);
32
+ });
33
+
34
+ it('materializes and caches a canvas for a data-only resource, rebuilding on version bump', () => {
35
+ const state = makeState();
36
+ const resource = makeDataOnlyResource();
37
+ const first = resolveDomImageSource(state, resource);
38
+ expect(first).toBeInstanceOf(HTMLCanvasElement);
39
+ expect(resolveDomImageSource(state, resource)).toBe(first);
40
+ invalidateImageResource(resource);
41
+ expect(resolveDomImageSource(state, resource)).not.toBe(first);
42
+ });
43
+
44
+ it('returns null when the resource carries neither pixels form', () => {
45
+ expect(resolveDomImageSource(makeState(), createImageResource())).toBeNull();
46
+ });
47
+ });
@@ -0,0 +1,100 @@
1
+ import { AdvancedBlendMode, BlendMode } from '@flighthq/types';
2
+
3
+ import { applyDomBlendMode, enableDomBlendModeSupport, getDomBlendModeFidelity } from './domMaterials';
4
+ import { createDomRenderState } from './domRenderState';
5
+
6
+ describe('applyDomBlendMode', () => {
7
+ let el: HTMLElement;
8
+
9
+ beforeEach(() => {
10
+ el = document.createElement('div');
11
+ });
12
+
13
+ it('sets mixBlendMode to "screen" for BlendMode.Add', () => {
14
+ applyDomBlendMode(el, BlendMode.Add);
15
+ expect(el.style.mixBlendMode).toBe('screen');
16
+ });
17
+
18
+ it('sets mixBlendMode to "darken" for BlendMode.Darken', () => {
19
+ applyDomBlendMode(el, BlendMode.Darken);
20
+ expect(el.style.mixBlendMode).toBe('darken');
21
+ });
22
+
23
+ it('sets mixBlendMode to "difference" for AdvancedBlendMode.Difference', () => {
24
+ applyDomBlendMode(el, AdvancedBlendMode.Difference);
25
+ expect(el.style.mixBlendMode).toBe('difference');
26
+ });
27
+
28
+ it('sets mixBlendMode to "hard-light" for AdvancedBlendMode.HardLight', () => {
29
+ applyDomBlendMode(el, AdvancedBlendMode.HardLight);
30
+ expect(el.style.mixBlendMode).toBe('hard-light');
31
+ });
32
+
33
+ it('sets mixBlendMode to "lighten" for BlendMode.Lighten', () => {
34
+ applyDomBlendMode(el, BlendMode.Lighten);
35
+ expect(el.style.mixBlendMode).toBe('lighten');
36
+ });
37
+
38
+ it('sets mixBlendMode to "multiply" for BlendMode.Multiply', () => {
39
+ applyDomBlendMode(el, BlendMode.Multiply);
40
+ expect(el.style.mixBlendMode).toBe('multiply');
41
+ });
42
+
43
+ it('sets mixBlendMode to "overlay" for AdvancedBlendMode.Overlay', () => {
44
+ applyDomBlendMode(el, AdvancedBlendMode.Overlay);
45
+ expect(el.style.mixBlendMode).toBe('overlay');
46
+ });
47
+
48
+ it('sets mixBlendMode to "screen" for BlendMode.Screen', () => {
49
+ applyDomBlendMode(el, BlendMode.Screen);
50
+ expect(el.style.mixBlendMode).toBe('screen');
51
+ });
52
+
53
+ it('clears mixBlendMode for null (default)', () => {
54
+ el.style.mixBlendMode = 'multiply';
55
+ applyDomBlendMode(el, null);
56
+ expect(el.style.mixBlendMode).toBe('');
57
+ });
58
+
59
+ it('clears mixBlendMode for BlendMode.Normal', () => {
60
+ el.style.mixBlendMode = 'multiply';
61
+ applyDomBlendMode(el, BlendMode.Normal);
62
+ expect(el.style.mixBlendMode).toBe('');
63
+ });
64
+ });
65
+
66
+ describe('enableDomBlendModeSupport', () => {
67
+ it('wires applyBlendMode onto the state', () => {
68
+ const state = createDomRenderState(document.createElement('div'));
69
+ expect(state.applyBlendMode).toBeNull();
70
+ enableDomBlendModeSupport(state);
71
+ expect(state.applyBlendMode).not.toBeNull();
72
+ });
73
+
74
+ it('causes blend modes to be applied to elements', () => {
75
+ const state = createDomRenderState(document.createElement('div'));
76
+ enableDomBlendModeSupport(state);
77
+ const el = document.createElement('div');
78
+ state.applyBlendMode!(el, BlendMode.Multiply);
79
+ expect(el.style.mixBlendMode).toBe('multiply');
80
+ });
81
+ });
82
+
83
+ describe('getDomBlendModeFidelity', () => {
84
+ it('returns "approximate" for BlendMode.Add', () => {
85
+ expect(getDomBlendModeFidelity(BlendMode.Add)).toBe('approximate');
86
+ });
87
+
88
+ it('returns "exact" for BlendMode.Multiply', () => {
89
+ expect(getDomBlendModeFidelity(BlendMode.Multiply)).toBe('exact');
90
+ });
91
+
92
+ it('returns "exact" for BlendMode.Normal', () => {
93
+ expect(getDomBlendModeFidelity(BlendMode.Normal)).toBe('exact');
94
+ });
95
+
96
+ it('returns "exact" for the advanced modes CSS realizes natively', () => {
97
+ expect(getDomBlendModeFidelity(AdvancedBlendMode.Overlay)).toBe('exact');
98
+ expect(getDomBlendModeFidelity(AdvancedBlendMode.SoftLight)).toBe('exact');
99
+ });
100
+ });
@@ -0,0 +1,102 @@
1
+ import { getOrCreateRenderProxy2D, registerRenderer } from '@flighthq/render';
2
+ import { createNativeText } from '@flighthq/text';
3
+ import { NativeTextKind } from '@flighthq/types';
4
+
5
+ import { defaultDomNativeTextRenderer, drawDomNativeText, drawDomNativeTextMask } from './domNativeText';
6
+ import { createDomRenderState, getDomRenderStateRuntime } from './domRenderState';
7
+
8
+ function makeState() {
9
+ const container = document.createElement('div');
10
+ const state = createDomRenderState(container);
11
+ registerRenderer(state, NativeTextKind, defaultDomNativeTextRenderer);
12
+ return state;
13
+ }
14
+
15
+ function drawGetEl(state: ReturnType<typeof makeState>, drawFn: () => void): HTMLElement | null {
16
+ getDomRenderStateRuntime(state).domCurrentElement = null;
17
+ drawFn();
18
+ return getDomRenderStateRuntime(state).domCurrentElement;
19
+ }
20
+
21
+ describe('defaultDomNativeTextRenderer', () => {
22
+ it('has submit and createData', () => {
23
+ expect(typeof defaultDomNativeTextRenderer.submit).toBe('function');
24
+ expect(typeof defaultDomNativeTextRenderer.createData).toBe('function');
25
+ });
26
+ });
27
+
28
+ describe('drawDomNativeText', () => {
29
+ it('produces a div carrying the text content', () => {
30
+ const state = makeState();
31
+ const node = createNativeText({ data: { text: 'hello' } });
32
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
33
+
34
+ const el = drawGetEl(state, () => drawDomNativeText(state, renderProxy));
35
+
36
+ expect(el).not.toBeNull();
37
+ expect(el!.tagName).toBe('DIV');
38
+ expect(el!.textContent).toBe('hello');
39
+ });
40
+
41
+ it('reuses the same element across multiple draws', () => {
42
+ const state = makeState();
43
+ const node = createNativeText({ data: { text: 'hello' } });
44
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
45
+
46
+ const first = drawGetEl(state, () => drawDomNativeText(state, renderProxy));
47
+ const second = drawGetEl(state, () => drawDomNativeText(state, renderProxy));
48
+
49
+ expect(first).toBe(second);
50
+ });
51
+
52
+ it('sets a fixed box under autoSize none', () => {
53
+ const state = makeState();
54
+ const node = createNativeText({ data: { text: 'hi', width: 120, height: 40 } });
55
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
56
+
57
+ const div = drawGetEl(state, () => drawDomNativeText(state, renderProxy))!;
58
+ expect(div.style.width).toBe('120px');
59
+ expect(div.style.height).toBe('40px');
60
+ expect(div.style.overflow).toBe('hidden');
61
+ });
62
+
63
+ it('positions the block vertically via the flexbox on a fixed box', () => {
64
+ const state = makeState();
65
+ const node = createNativeText({ data: { text: 'hi', verticalAlign: 'middle' } });
66
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
67
+
68
+ const div = drawGetEl(state, () => drawDomNativeText(state, renderProxy))!;
69
+ expect(div.style.display).toBe('flex');
70
+ expect(div.style.flexDirection).toBe('column');
71
+ expect(div.style.justifyContent).toBe('center');
72
+ });
73
+
74
+ it('maps top and bottom to the box ends', () => {
75
+ const state = makeState();
76
+ const top = createNativeText({ data: { text: 'hi', verticalAlign: 'top' } });
77
+ const bottom = createNativeText({ data: { text: 'hi', verticalAlign: 'bottom' } });
78
+ const topDiv = drawGetEl(state, () => drawDomNativeText(state, getOrCreateRenderProxy2D(state, top)))!;
79
+ const bottomDiv = drawGetEl(state, () => drawDomNativeText(state, getOrCreateRenderProxy2D(state, bottom)))!;
80
+ expect(topDiv.style.justifyContent).toBe('flex-start');
81
+ expect(bottomDiv.style.justifyContent).toBe('flex-end');
82
+ });
83
+
84
+ it('drops the flexbox framing under autoSize (no slack to align within)', () => {
85
+ const state = makeState();
86
+ const node = createNativeText({ data: { text: 'hi', autoSize: 'left', verticalAlign: 'middle' } });
87
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
88
+
89
+ const div = drawGetEl(state, () => drawDomNativeText(state, renderProxy))!;
90
+ expect(div.style.display).toBe('');
91
+ expect(div.style.justifyContent).toBe('');
92
+ });
93
+ });
94
+
95
+ describe('drawDomNativeTextMask', () => {
96
+ it('does not throw', () => {
97
+ const state = makeState();
98
+ const node = createNativeText({ data: { text: 'hi' } });
99
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
100
+ expect(() => drawDomNativeTextMask(state, renderProxy)).not.toThrow();
101
+ });
102
+ });