@flighthq/displayobject-canvas 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.
- package/dist/canvasBackground.d.ts +3 -0
- package/dist/canvasBackground.d.ts.map +1 -0
- package/dist/canvasBackground.js +19 -0
- package/dist/canvasBackground.js.map +1 -0
- package/dist/canvasBitmap.d.ts +4 -0
- package/dist/canvasBitmap.d.ts.map +1 -0
- package/dist/canvasBitmap.js +32 -0
- package/dist/canvasBitmap.js.map +1 -0
- package/dist/canvasCSSFilterBinding.d.ts +24 -0
- package/dist/canvasCSSFilterBinding.d.ts.map +1 -0
- package/dist/canvasCSSFilterBinding.js +43 -0
- package/dist/canvasCSSFilterBinding.js.map +1 -0
- package/dist/canvasCache.d.ts +33 -0
- package/dist/canvasCache.d.ts.map +1 -0
- package/dist/canvasCache.js +141 -0
- package/dist/canvasCache.js.map +1 -0
- package/dist/canvasClip.d.ts +3 -0
- package/dist/canvasClip.d.ts.map +1 -0
- package/dist/canvasClip.js +38 -0
- package/dist/canvasClip.js.map +1 -0
- package/dist/canvasClipRectangle.d.ts +5 -0
- package/dist/canvasClipRectangle.d.ts.map +1 -0
- package/dist/canvasClipRectangle.js +31 -0
- package/dist/canvasClipRectangle.js.map +1 -0
- package/dist/canvasDisplayObject.d.ts +5 -0
- package/dist/canvasDisplayObject.d.ts.map +1 -0
- package/dist/canvasDisplayObject.js +46 -0
- package/dist/canvasDisplayObject.js.map +1 -0
- package/dist/canvasElement.d.ts +2 -0
- package/dist/canvasElement.d.ts.map +1 -0
- package/dist/canvasElement.js +9 -0
- package/dist/canvasElement.js.map +1 -0
- package/dist/canvasFillPattern.d.ts +4 -0
- package/dist/canvasFillPattern.d.ts.map +1 -0
- package/dist/canvasFillPattern.js +108 -0
- package/dist/canvasFillPattern.js.map +1 -0
- package/dist/canvasMaterialRegistry.d.ts +6 -0
- package/dist/canvasMaterialRegistry.d.ts.map +1 -0
- package/dist/canvasMaterialRegistry.js +42 -0
- package/dist/canvasMaterialRegistry.js.map +1 -0
- package/dist/canvasMaterials.d.ts +5 -0
- package/dist/canvasMaterials.d.ts.map +1 -0
- package/dist/canvasMaterials.js +33 -0
- package/dist/canvasMaterials.js.map +1 -0
- package/dist/canvasParticleEmitter.d.ts +4 -0
- package/dist/canvasParticleEmitter.d.ts.map +1 -0
- package/dist/canvasParticleEmitter.js +64 -0
- package/dist/canvasParticleEmitter.js.map +1 -0
- package/dist/canvasQuadBatch.d.ts +4 -0
- package/dist/canvasQuadBatch.d.ts.map +1 -0
- package/dist/canvasQuadBatch.js +64 -0
- package/dist/canvasQuadBatch.js.map +1 -0
- package/dist/canvasRegistration.d.ts +4 -0
- package/dist/canvasRegistration.d.ts.map +1 -0
- package/dist/canvasRegistration.js +39 -0
- package/dist/canvasRegistration.js.map +1 -0
- package/dist/canvasRenderState.d.ts +5 -0
- package/dist/canvasRenderState.d.ts.map +1 -0
- package/dist/canvasRenderState.js +44 -0
- package/dist/canvasRenderState.js.map +1 -0
- package/dist/canvasRenderTarget.d.ts +16 -0
- package/dist/canvasRenderTarget.d.ts.map +1 -0
- package/dist/canvasRenderTarget.js +66 -0
- package/dist/canvasRenderTarget.js.map +1 -0
- package/dist/canvasRichText.d.ts +7 -0
- package/dist/canvasRichText.d.ts.map +1 -0
- package/dist/canvasRichText.js +127 -0
- package/dist/canvasRichText.js.map +1 -0
- package/dist/canvasScale9Mapper.d.ts +10 -0
- package/dist/canvasScale9Mapper.d.ts.map +1 -0
- package/dist/canvasScale9Mapper.js +148 -0
- package/dist/canvasScale9Mapper.js.map +1 -0
- package/dist/canvasScale9Shape.d.ts +5 -0
- package/dist/canvasScale9Shape.d.ts.map +1 -0
- package/dist/canvasScale9Shape.js +156 -0
- package/dist/canvasScale9Shape.js.map +1 -0
- package/dist/canvasShape.d.ts +5 -0
- package/dist/canvasShape.d.ts.map +1 -0
- package/dist/canvasShape.js +79 -0
- package/dist/canvasShape.js.map +1 -0
- package/dist/canvasShapeCommands.d.ts +19 -0
- package/dist/canvasShapeCommands.d.ts.map +1 -0
- package/dist/canvasShapeCommands.js +352 -0
- package/dist/canvasShapeCommands.js.map +1 -0
- package/dist/canvasShapeRegistry.d.ts +5 -0
- package/dist/canvasShapeRegistry.d.ts.map +1 -0
- package/dist/canvasShapeRegistry.js +17 -0
- package/dist/canvasShapeRegistry.js.map +1 -0
- package/dist/canvasSprite.d.ts +5 -0
- package/dist/canvasSprite.d.ts.map +1 -0
- package/dist/canvasSprite.js +59 -0
- package/dist/canvasSprite.js.map +1 -0
- package/dist/canvasTextInput.d.ts +4 -0
- package/dist/canvasTextInput.d.ts.map +1 -0
- package/dist/canvasTextInput.js +67 -0
- package/dist/canvasTextInput.js.map +1 -0
- package/dist/canvasTextLabel.d.ts +4 -0
- package/dist/canvasTextLabel.d.ts.map +1 -0
- package/dist/canvasTextLabel.js +55 -0
- package/dist/canvasTextLabel.js.map +1 -0
- package/dist/canvasTextMeasure.d.ts +3 -0
- package/dist/canvasTextMeasure.d.ts.map +1 -0
- package/dist/canvasTextMeasure.js +15 -0
- package/dist/canvasTextMeasure.js.map +1 -0
- package/dist/canvasTilemap.d.ts +4 -0
- package/dist/canvasTilemap.d.ts.map +1 -0
- package/dist/canvasTilemap.js +50 -0
- package/dist/canvasTilemap.js.map +1 -0
- package/dist/canvasTransform.d.ts +3 -0
- package/dist/canvasTransform.d.ts.map +1 -0
- package/dist/canvasTransform.js +9 -0
- package/dist/canvasTransform.js.map +1 -0
- package/dist/canvasVideo.d.ts +4 -0
- package/dist/canvasVideo.d.ts.map +1 -0
- package/dist/canvasVideo.js +26 -0
- package/dist/canvasVideo.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/src/canvasBackground.test.ts +65 -0
- package/src/canvasBitmap.test.ts +92 -0
- package/src/canvasCSSFilterBinding.test.ts +86 -0
- package/src/canvasCache.test.ts +189 -0
- package/src/canvasClip.test.ts +12 -0
- package/src/canvasClipRectangle.test.ts +101 -0
- package/src/canvasDisplayObject.test.ts +117 -0
- package/src/canvasElement.test.ts +21 -0
- package/src/canvasFillPattern.test.ts +96 -0
- package/src/canvasMaterialRegistry.test.ts +69 -0
- package/src/canvasMaterials.test.ts +85 -0
- package/src/canvasParticleEmitter.test.ts +105 -0
- package/src/canvasQuadBatch.test.ts +119 -0
- package/src/canvasRegistration.test.ts +57 -0
- package/src/canvasRenderState.test.ts +186 -0
- package/src/canvasRenderTarget.test.ts +154 -0
- package/src/canvasRichText.test.ts +89 -0
- package/src/canvasScale9Mapper.test.ts +53 -0
- package/src/canvasScale9Shape.test.ts +89 -0
- package/src/canvasShape.test.ts +183 -0
- package/src/canvasShapeCommands.test.ts +448 -0
- package/src/canvasShapeRegistry.test.ts +42 -0
- package/src/canvasSprite.test.ts +90 -0
- package/src/canvasTextInput.test.ts +67 -0
- package/src/canvasTextLabel.test.ts +32 -0
- package/src/canvasTextMeasure.test.ts +9 -0
- package/src/canvasTilemap.test.ts +69 -0
- package/src/canvasTransform.test.ts +42 -0
- package/src/canvasVideo.test.ts +43 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendShapeBeginBitmapFill,
|
|
3
|
+
appendShapeBeginFill,
|
|
4
|
+
appendShapeBeginGradientFill,
|
|
5
|
+
appendShapeCircle,
|
|
6
|
+
appendShapeCubicCurveTo,
|
|
7
|
+
appendShapeCurveTo,
|
|
8
|
+
appendShapeEllipse,
|
|
9
|
+
appendShapeEndFill,
|
|
10
|
+
appendShapeLineBitmapStyle,
|
|
11
|
+
appendShapeLineGradientStyle,
|
|
12
|
+
appendShapeLineStyle,
|
|
13
|
+
appendShapeLineTo,
|
|
14
|
+
appendShapeMoveTo,
|
|
15
|
+
appendShapePath,
|
|
16
|
+
appendShapeRectangle,
|
|
17
|
+
appendShapeRoundRectangle,
|
|
18
|
+
createShape,
|
|
19
|
+
PathCommand,
|
|
20
|
+
} from '@flighthq/shape';
|
|
21
|
+
|
|
22
|
+
import { renderCanvasShapeCommands } from './canvasShape';
|
|
23
|
+
import { defaultCanvasShapeCommands } from './canvasShapeCommands';
|
|
24
|
+
import { registerCanvasShapeCommands } from './canvasShapeRegistry';
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
registerCanvasShapeCommands(defaultCanvasShapeCommands);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function makeContext(): CanvasRenderingContext2D {
|
|
31
|
+
const canvas = document.createElement('canvas');
|
|
32
|
+
canvas.width = 200;
|
|
33
|
+
canvas.height = 200;
|
|
34
|
+
const context = canvas.getContext('2d') as CanvasRenderingContext2D;
|
|
35
|
+
if (typeof context.roundRect !== 'function') {
|
|
36
|
+
context.roundRect = vi.fn();
|
|
37
|
+
}
|
|
38
|
+
return context;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeBitmapSource(w: number, h: number) {
|
|
42
|
+
const canvas = document.createElement('canvas');
|
|
43
|
+
canvas.width = w;
|
|
44
|
+
canvas.height = h;
|
|
45
|
+
return { source: canvas, width: w, height: h } as never;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('defaultCanvasBeginBitmapFill', () => {
|
|
49
|
+
it('uses drawImage when drawRectangle fits within bitmap bounds', () => {
|
|
50
|
+
const context = makeContext();
|
|
51
|
+
const drawImageSpy = vi.spyOn(context, 'drawImage');
|
|
52
|
+
const fillSpy = vi.spyOn(context, 'fill');
|
|
53
|
+
const shape = createShape();
|
|
54
|
+
appendShapeBeginBitmapFill(shape, makeBitmapSource(200, 200));
|
|
55
|
+
appendShapeRectangle(shape, 0, 0, 100, 100);
|
|
56
|
+
appendShapeEndFill(shape);
|
|
57
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
58
|
+
expect(drawImageSpy).toHaveBeenCalledOnce();
|
|
59
|
+
expect(fillSpy).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('falls back to pattern fill when drawRectangle exceeds bitmap bounds', () => {
|
|
63
|
+
const context = makeContext();
|
|
64
|
+
const drawImageSpy = vi.spyOn(context, 'drawImage');
|
|
65
|
+
const fillSpy = vi.spyOn(context, 'fill');
|
|
66
|
+
const shape = createShape();
|
|
67
|
+
appendShapeBeginBitmapFill(shape, makeBitmapSource(50, 50));
|
|
68
|
+
appendShapeRectangle(shape, 0, 0, 100, 100);
|
|
69
|
+
appendShapeEndFill(shape);
|
|
70
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
71
|
+
expect(drawImageSpy).not.toHaveBeenCalled();
|
|
72
|
+
expect(fillSpy).toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('sets imageSmoothingEnabled when smooth is true', () => {
|
|
76
|
+
const context = makeContext();
|
|
77
|
+
context.imageSmoothingEnabled = false;
|
|
78
|
+
const shape = createShape();
|
|
79
|
+
appendShapeBeginBitmapFill(shape, makeBitmapSource(200, 200), null, true, true);
|
|
80
|
+
appendShapeRectangle(shape, 0, 0, 100, 100);
|
|
81
|
+
appendShapeEndFill(shape);
|
|
82
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
83
|
+
expect(context.imageSmoothingEnabled).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('defaultCanvasBeginFill', () => {
|
|
88
|
+
it('calls fill when alpha is above threshold', () => {
|
|
89
|
+
const context = makeContext();
|
|
90
|
+
const spy = vi.spyOn(context, 'fill');
|
|
91
|
+
const shape = createShape();
|
|
92
|
+
appendShapeBeginFill(shape, 0xff0000, 1);
|
|
93
|
+
appendShapeRectangle(shape, 0, 0, 10, 10);
|
|
94
|
+
appendShapeEndFill(shape);
|
|
95
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
96
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('does not call fill when alpha is below threshold', () => {
|
|
100
|
+
const context = makeContext();
|
|
101
|
+
const spy = vi.spyOn(context, 'fill');
|
|
102
|
+
const shape = createShape();
|
|
103
|
+
appendShapeBeginFill(shape, 0xff0000, 0);
|
|
104
|
+
appendShapeRectangle(shape, 0, 0, 10, 10);
|
|
105
|
+
appendShapeEndFill(shape);
|
|
106
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
107
|
+
expect(spy).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('defaultCanvasBeginGradientFill', () => {
|
|
112
|
+
it('calls createLinearGradient for linear type', () => {
|
|
113
|
+
const context = makeContext();
|
|
114
|
+
const spy = vi.spyOn(context, 'createLinearGradient');
|
|
115
|
+
const shape = createShape();
|
|
116
|
+
appendShapeBeginGradientFill(shape, 'linear', [0xff0000, 0x0000ff], [1, 1], [0, 255]);
|
|
117
|
+
appendShapeRectangle(shape, 0, 0, 100, 100);
|
|
118
|
+
appendShapeEndFill(shape);
|
|
119
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
120
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('calls createRadialGradient for radial type', () => {
|
|
124
|
+
const context = makeContext();
|
|
125
|
+
const spy = vi.spyOn(context, 'createRadialGradient');
|
|
126
|
+
const shape = createShape();
|
|
127
|
+
appendShapeBeginGradientFill(shape, 'radial', [0xff0000, 0x0000ff], [1, 1], [0, 255]);
|
|
128
|
+
appendShapeRectangle(shape, 0, 0, 100, 100);
|
|
129
|
+
appendShapeEndFill(shape);
|
|
130
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
131
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('defaultCanvasCubicCurveTo', () => {
|
|
136
|
+
it('calls bezierCurveTo with correct control and anchor points', () => {
|
|
137
|
+
const context = makeContext();
|
|
138
|
+
const spy = vi.spyOn(context, 'bezierCurveTo');
|
|
139
|
+
const shape = createShape();
|
|
140
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
141
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
142
|
+
appendShapeCubicCurveTo(shape, 25, -50, 75, -50, 100, 0);
|
|
143
|
+
appendShapeEndFill(shape);
|
|
144
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
145
|
+
expect(spy).toHaveBeenCalledWith(25, -50, 75, -50, 100, 0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('moves to origin when there is no current point', () => {
|
|
149
|
+
const context = makeContext();
|
|
150
|
+
const moveSpy = vi.spyOn(context, 'moveTo');
|
|
151
|
+
const shape = createShape();
|
|
152
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
153
|
+
appendShapeCubicCurveTo(shape, 25, -50, 75, -50, 100, 0);
|
|
154
|
+
appendShapeEndFill(shape);
|
|
155
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
156
|
+
expect(moveSpy).toHaveBeenCalledWith(0, 0);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('defaultCanvasCurveTo', () => {
|
|
161
|
+
it('calls quadraticCurveTo with correct control and anchor points', () => {
|
|
162
|
+
const context = makeContext();
|
|
163
|
+
const spy = vi.spyOn(context, 'quadraticCurveTo');
|
|
164
|
+
const shape = createShape();
|
|
165
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
166
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
167
|
+
appendShapeCurveTo(shape, 50, -50, 100, 0);
|
|
168
|
+
appendShapeEndFill(shape);
|
|
169
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
170
|
+
expect(spy).toHaveBeenCalledWith(50, -50, 100, 0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('moves to origin when there is no current point', () => {
|
|
174
|
+
const context = makeContext();
|
|
175
|
+
const moveSpy = vi.spyOn(context, 'moveTo');
|
|
176
|
+
const shape = createShape();
|
|
177
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
178
|
+
appendShapeCurveTo(shape, 50, -50, 100, 0);
|
|
179
|
+
appendShapeEndFill(shape);
|
|
180
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
181
|
+
expect(moveSpy).toHaveBeenCalledWith(0, 0);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('defaultCanvasDrawCircle', () => {
|
|
186
|
+
it('draws using arc', () => {
|
|
187
|
+
const context = makeContext();
|
|
188
|
+
const spy = vi.spyOn(context, 'arc');
|
|
189
|
+
const shape = createShape();
|
|
190
|
+
appendShapeBeginFill(shape, 0xffffff);
|
|
191
|
+
appendShapeCircle(shape, 50, 50, 25);
|
|
192
|
+
appendShapeEndFill(shape);
|
|
193
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
194
|
+
expect(spy).toHaveBeenCalledWith(50, 50, 25, 0, Math.PI * 2, true);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('defaultCanvasDrawEllipse', () => {
|
|
199
|
+
it('draws using ellipse', () => {
|
|
200
|
+
const context = makeContext();
|
|
201
|
+
const spy = vi.spyOn(context, 'ellipse');
|
|
202
|
+
const shape = createShape();
|
|
203
|
+
appendShapeBeginFill(shape, 0xffffff);
|
|
204
|
+
appendShapeEllipse(shape, 0, 0, 100, 50);
|
|
205
|
+
appendShapeEndFill(shape);
|
|
206
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
207
|
+
expect(spy).toHaveBeenCalledWith(50, 25, 50, 25, 0, 0, Math.PI * 2);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('defaultCanvasDrawPath', () => {
|
|
212
|
+
it('executes MOVE_TO and LINE_TO path commands', () => {
|
|
213
|
+
const context = makeContext();
|
|
214
|
+
const moveSpy = vi.spyOn(context, 'moveTo');
|
|
215
|
+
const lineSpy = vi.spyOn(context, 'lineTo');
|
|
216
|
+
const shape = createShape();
|
|
217
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
218
|
+
appendShapePath(shape, [PathCommand.MOVE_TO, PathCommand.LINE_TO, PathCommand.LINE_TO], [10, 20, 100, 20, 100, 80]);
|
|
219
|
+
appendShapeEndFill(shape);
|
|
220
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
221
|
+
expect(moveSpy).toHaveBeenCalledWith(10, 20);
|
|
222
|
+
expect(lineSpy).toHaveBeenCalledWith(100, 20);
|
|
223
|
+
expect(lineSpy).toHaveBeenCalledWith(100, 80);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('executes CURVE_TO as quadraticCurveTo', () => {
|
|
227
|
+
const context = makeContext();
|
|
228
|
+
const spy = vi.spyOn(context, 'quadraticCurveTo');
|
|
229
|
+
const shape = createShape();
|
|
230
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
231
|
+
appendShapePath(shape, [PathCommand.MOVE_TO, PathCommand.CURVE_TO], [0, 0, 50, 0, 100, 50]);
|
|
232
|
+
appendShapeEndFill(shape);
|
|
233
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
234
|
+
expect(spy).toHaveBeenCalledWith(50, 0, 100, 50);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('executes CUBIC_CURVE_TO as bezierCurveTo', () => {
|
|
238
|
+
const context = makeContext();
|
|
239
|
+
const spy = vi.spyOn(context, 'bezierCurveTo');
|
|
240
|
+
const shape = createShape();
|
|
241
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
242
|
+
appendShapePath(shape, [PathCommand.MOVE_TO, PathCommand.CUBIC_CURVE_TO], [0, 0, 25, -50, 75, -50, 100, 0]);
|
|
243
|
+
appendShapeEndFill(shape);
|
|
244
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
245
|
+
expect(spy).toHaveBeenCalledWith(25, -50, 75, -50, 100, 0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('uses nonzero winding rule when drawPath winding is nonZero', () => {
|
|
249
|
+
const context = makeContext();
|
|
250
|
+
const fillSpy = vi.spyOn(context, 'fill');
|
|
251
|
+
const shape = createShape();
|
|
252
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
253
|
+
appendShapePath(shape, [PathCommand.MOVE_TO, PathCommand.LINE_TO], [0, 0, 100, 100], 'nonZero');
|
|
254
|
+
appendShapeEndFill(shape);
|
|
255
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
256
|
+
expect(fillSpy).toHaveBeenCalledWith('nonzero');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('uses evenodd winding rule when drawPath winding is evenOdd', () => {
|
|
260
|
+
const context = makeContext();
|
|
261
|
+
const fillSpy = vi.spyOn(context, 'fill');
|
|
262
|
+
const shape = createShape();
|
|
263
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
264
|
+
appendShapePath(shape, [PathCommand.MOVE_TO, PathCommand.LINE_TO], [0, 0, 100, 100], 'evenOdd');
|
|
265
|
+
appendShapeEndFill(shape);
|
|
266
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
267
|
+
expect(fillSpy).toHaveBeenCalledWith('evenodd');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('defaultCanvasDrawRectangle', () => {
|
|
272
|
+
it('calls context.rect for a plain fill', () => {
|
|
273
|
+
const context = makeContext();
|
|
274
|
+
const spy = vi.spyOn(context, 'rect');
|
|
275
|
+
const shape = createShape();
|
|
276
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
277
|
+
appendShapeRectangle(shape, 10, 20, 50, 30);
|
|
278
|
+
appendShapeEndFill(shape);
|
|
279
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
280
|
+
expect(spy).toHaveBeenCalledWith(10, 20, 50, 30);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('defaultCanvasDrawRoundRectangle', () => {
|
|
285
|
+
it('calls roundRect with the minimum of rx and ry', () => {
|
|
286
|
+
const context = makeContext();
|
|
287
|
+
const spy = vi.spyOn(context, 'roundRect');
|
|
288
|
+
const shape = createShape();
|
|
289
|
+
appendShapeBeginFill(shape, 0xffffff);
|
|
290
|
+
appendShapeRoundRectangle(shape, 0, 0, 100, 50, 10, 10);
|
|
291
|
+
appendShapeEndFill(shape);
|
|
292
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
293
|
+
expect(spy).toHaveBeenCalledWith(0, 0, 100, 50, 5);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('defaultCanvasEndFill', () => {
|
|
298
|
+
it('flushes a pending fill path', () => {
|
|
299
|
+
const context = makeContext();
|
|
300
|
+
const spy = vi.spyOn(context, 'fill');
|
|
301
|
+
const shape = createShape();
|
|
302
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
303
|
+
appendShapeRectangle(shape, 0, 0, 10, 10);
|
|
304
|
+
appendShapeEndFill(shape);
|
|
305
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
306
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('defaultCanvasLineBitmapStyle', () => {
|
|
311
|
+
it('applies a bitmap stroke pattern', () => {
|
|
312
|
+
const context = makeContext();
|
|
313
|
+
const spy = vi.spyOn(context, 'stroke');
|
|
314
|
+
const shape = createShape();
|
|
315
|
+
appendShapeLineBitmapStyle(shape, makeBitmapSource(64, 64));
|
|
316
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
317
|
+
appendShapeLineTo(shape, 100, 0);
|
|
318
|
+
appendShapeEndFill(shape);
|
|
319
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
320
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('defaultCanvasLineGradientStyle', () => {
|
|
325
|
+
it('applies a gradient stroke', () => {
|
|
326
|
+
const context = makeContext();
|
|
327
|
+
const spy = vi.spyOn(context, 'stroke');
|
|
328
|
+
const shape = createShape();
|
|
329
|
+
appendShapeLineGradientStyle(shape, 'linear', [0xff0000, 0x0000ff], [1, 1], [0, 255]);
|
|
330
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
331
|
+
appendShapeLineTo(shape, 100, 0);
|
|
332
|
+
appendShapeEndFill(shape);
|
|
333
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
334
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('defaultCanvasLineStyle', () => {
|
|
339
|
+
it('sets lineCap to butt when caps is none', () => {
|
|
340
|
+
const context = makeContext();
|
|
341
|
+
const shape = createShape();
|
|
342
|
+
appendShapeLineStyle(shape, 2, 0x000000, 1, false, 'normal', 'none', 'round', 3);
|
|
343
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
344
|
+
appendShapeLineTo(shape, 100, 0);
|
|
345
|
+
appendShapeEndFill(shape);
|
|
346
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
347
|
+
expect(context.lineCap).toBe('butt');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('sets lineCap to round when caps is round', () => {
|
|
351
|
+
const context = makeContext();
|
|
352
|
+
const shape = createShape();
|
|
353
|
+
appendShapeLineStyle(shape, 2, 0x000000, 1, false, 'normal', 'round', 'round', 3);
|
|
354
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
355
|
+
appendShapeLineTo(shape, 100, 0);
|
|
356
|
+
appendShapeEndFill(shape);
|
|
357
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
358
|
+
expect(context.lineCap).toBe('round');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('sets lineJoin', () => {
|
|
362
|
+
const context = makeContext();
|
|
363
|
+
const shape = createShape();
|
|
364
|
+
appendShapeLineStyle(shape, 2, 0x000000, 1, false, 'normal', 'none', 'bevel', 3);
|
|
365
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
366
|
+
appendShapeLineTo(shape, 100, 0);
|
|
367
|
+
appendShapeEndFill(shape);
|
|
368
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
369
|
+
expect(context.lineJoin).toBe('bevel');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('sets miterLimit', () => {
|
|
373
|
+
const context = makeContext();
|
|
374
|
+
const shape = createShape();
|
|
375
|
+
appendShapeLineStyle(shape, 2, 0x000000, 1, false, 'normal', 'none', 'miter', 8);
|
|
376
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
377
|
+
appendShapeLineTo(shape, 100, 0);
|
|
378
|
+
appendShapeEndFill(shape);
|
|
379
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
380
|
+
expect(context.miterLimit).toBe(8);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('defaultCanvasLineTo', () => {
|
|
385
|
+
it('calls context.lineTo', () => {
|
|
386
|
+
const context = makeContext();
|
|
387
|
+
const spy = vi.spyOn(context, 'lineTo');
|
|
388
|
+
const shape = createShape();
|
|
389
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
390
|
+
appendShapeMoveTo(shape, 0, 0);
|
|
391
|
+
appendShapeLineTo(shape, 100, 50);
|
|
392
|
+
appendShapeEndFill(shape);
|
|
393
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
394
|
+
expect(spy).toHaveBeenCalledWith(100, 50);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('moves to origin when there is no current point', () => {
|
|
398
|
+
const context = makeContext();
|
|
399
|
+
const moveSpy = vi.spyOn(context, 'moveTo');
|
|
400
|
+
const shape = createShape();
|
|
401
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
402
|
+
appendShapeLineTo(shape, 100, 50);
|
|
403
|
+
appendShapeEndFill(shape);
|
|
404
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
405
|
+
expect(moveSpy).toHaveBeenCalledWith(0, 0);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe('defaultCanvasMoveTo', () => {
|
|
410
|
+
it('calls context.moveTo', () => {
|
|
411
|
+
const context = makeContext();
|
|
412
|
+
const spy = vi.spyOn(context, 'moveTo');
|
|
413
|
+
const shape = createShape();
|
|
414
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
415
|
+
appendShapeMoveTo(shape, 30, 40);
|
|
416
|
+
appendShapeLineTo(shape, 100, 40);
|
|
417
|
+
appendShapeEndFill(shape);
|
|
418
|
+
renderCanvasShapeCommands(context, shape.data.commands);
|
|
419
|
+
expect(spy).toHaveBeenCalledWith(30, 40);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe('defaultCanvasShapeCommands', () => {
|
|
424
|
+
it('contains handlers for all standard shape command keys', () => {
|
|
425
|
+
const keys = [
|
|
426
|
+
'beginBitmapFill',
|
|
427
|
+
'beginFill',
|
|
428
|
+
'beginGradientFill',
|
|
429
|
+
'cubicCurveTo',
|
|
430
|
+
'curveTo',
|
|
431
|
+
'drawCircle',
|
|
432
|
+
'drawEllipse',
|
|
433
|
+
'drawPath',
|
|
434
|
+
'drawRectangle',
|
|
435
|
+
'drawRoundRectangle',
|
|
436
|
+
'endFill',
|
|
437
|
+
'lineBitmapStyle',
|
|
438
|
+
'lineGradientStyle',
|
|
439
|
+
'lineTo',
|
|
440
|
+
'lineStyle',
|
|
441
|
+
'moveTo',
|
|
442
|
+
];
|
|
443
|
+
const registeredKeys = defaultCanvasShapeCommands.map((c) => c.key);
|
|
444
|
+
for (const key of keys) {
|
|
445
|
+
expect(registeredKeys).toContain(key);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getCanvasShapeCommand, registerCanvasShapeCommand, registerCanvasShapeCommands } from './canvasShapeRegistry';
|
|
2
|
+
|
|
3
|
+
describe('getCanvasShapeCommand', () => {
|
|
4
|
+
it('returns undefined for an unregistered key', () => {
|
|
5
|
+
expect(getCanvasShapeCommand('__unregistered__')).toBeUndefined();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('returns the command after registration', () => {
|
|
9
|
+
const fn = vi.fn();
|
|
10
|
+
registerCanvasShapeCommand({ key: '__test_get__' as never, draw: fn });
|
|
11
|
+
expect(getCanvasShapeCommand('__test_get__')?.draw).toBe(fn);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('registerCanvasShapeCommand', () => {
|
|
16
|
+
it('stores and retrieves a command by key', () => {
|
|
17
|
+
const fn = vi.fn();
|
|
18
|
+
registerCanvasShapeCommand({ key: '__test_register__' as never, draw: fn });
|
|
19
|
+
expect(getCanvasShapeCommand('__test_register__')?.draw).toBe(fn);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('replaces an existing command when called again with the same key', () => {
|
|
23
|
+
const first = vi.fn();
|
|
24
|
+
const second = vi.fn();
|
|
25
|
+
registerCanvasShapeCommand({ key: '__test_replace__' as never, draw: first });
|
|
26
|
+
registerCanvasShapeCommand({ key: '__test_replace__' as never, draw: second });
|
|
27
|
+
expect(getCanvasShapeCommand('__test_replace__')?.draw).toBe(second);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('registerCanvasShapeCommands', () => {
|
|
32
|
+
it('registers all commands in the array', () => {
|
|
33
|
+
const a = vi.fn();
|
|
34
|
+
const b = vi.fn();
|
|
35
|
+
registerCanvasShapeCommands([
|
|
36
|
+
{ key: '__test_arr_a__' as never, draw: a },
|
|
37
|
+
{ key: '__test_arr_b__' as never, draw: b },
|
|
38
|
+
]);
|
|
39
|
+
expect(getCanvasShapeCommand('__test_arr_a__')?.draw).toBe(a);
|
|
40
|
+
expect(getCanvasShapeCommand('__test_arr_b__')?.draw).toBe(b);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createImageResource } from '@flighthq/image';
|
|
2
|
+
import { addNodeChild } from '@flighthq/node';
|
|
3
|
+
import { getOrCreateRenderProxy2D, prepareDisplayObjectRender, registerRenderer } from '@flighthq/render';
|
|
4
|
+
import { createSprite } from '@flighthq/sprite';
|
|
5
|
+
import { addTextureAtlasRegion, createTextureAtlas } from '@flighthq/textureatlas';
|
|
6
|
+
import { SpriteKind } from '@flighthq/types';
|
|
7
|
+
|
|
8
|
+
import { createCanvasRenderState } from './canvasRenderState';
|
|
9
|
+
import { defaultCanvasSpriteRenderer, drawCanvasSprite, renderCanvasSprite } from './canvasSprite';
|
|
10
|
+
|
|
11
|
+
function makeAtlas() {
|
|
12
|
+
const img = document.createElement('img') as HTMLImageElement;
|
|
13
|
+
const source = createImageResource(img);
|
|
14
|
+
const atlas = createTextureAtlas({ image: source });
|
|
15
|
+
addTextureAtlasRegion(atlas, 0, 0, 32, 32);
|
|
16
|
+
return atlas;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeState() {
|
|
20
|
+
const canvas = document.createElement('canvas');
|
|
21
|
+
canvas.width = 800;
|
|
22
|
+
canvas.height = 400;
|
|
23
|
+
const state = createCanvasRenderState(canvas, { imageSmoothingEnabled: false });
|
|
24
|
+
registerRenderer(state, SpriteKind, defaultCanvasSpriteRenderer);
|
|
25
|
+
return state;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('drawCanvasSprite', () => {
|
|
29
|
+
it('calls drawImage when sprite has a valid atlas region', () => {
|
|
30
|
+
const atlas = makeAtlas();
|
|
31
|
+
const state = makeState();
|
|
32
|
+
const sprite = createSprite();
|
|
33
|
+
sprite.data.atlas = atlas;
|
|
34
|
+
sprite.data.id = 0;
|
|
35
|
+
prepareDisplayObjectRender(state, sprite);
|
|
36
|
+
const renderProxy = getOrCreateRenderProxy2D(state, sprite);
|
|
37
|
+
const spy = vi.spyOn(state.context, 'drawImage');
|
|
38
|
+
drawCanvasSprite(state, renderProxy);
|
|
39
|
+
expect(spy).toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('skips draw when atlas is null', () => {
|
|
43
|
+
const state = makeState();
|
|
44
|
+
const sprite = createSprite();
|
|
45
|
+
prepareDisplayObjectRender(state, sprite);
|
|
46
|
+
const renderProxy = getOrCreateRenderProxy2D(state, sprite);
|
|
47
|
+
const spy = vi.spyOn(state.context, 'drawImage');
|
|
48
|
+
drawCanvasSprite(state, renderProxy);
|
|
49
|
+
expect(spy).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('renderCanvasSprite', () => {
|
|
54
|
+
it('does not throw for an empty sprite', () => {
|
|
55
|
+
const state = makeState();
|
|
56
|
+
const sprite = createSprite();
|
|
57
|
+
prepareDisplayObjectRender(state, sprite);
|
|
58
|
+
expect(() => renderCanvasSprite(state, sprite)).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('calls renderer.submit for a visible sprite with a renderer', () => {
|
|
62
|
+
const atlas = makeAtlas();
|
|
63
|
+
const state = makeState();
|
|
64
|
+
const sprite = createSprite();
|
|
65
|
+
sprite.data.atlas = atlas;
|
|
66
|
+
sprite.data.id = 0;
|
|
67
|
+
prepareDisplayObjectRender(state, sprite);
|
|
68
|
+
const spy = vi.spyOn(state.context, 'drawImage');
|
|
69
|
+
|
|
70
|
+
renderCanvasSprite(state, sprite);
|
|
71
|
+
|
|
72
|
+
expect(spy).toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('traverses children and draws them', () => {
|
|
76
|
+
const atlas = makeAtlas();
|
|
77
|
+
const state = makeState();
|
|
78
|
+
const parent = createSprite();
|
|
79
|
+
const child = createSprite();
|
|
80
|
+
child.data.atlas = atlas;
|
|
81
|
+
child.data.id = 0;
|
|
82
|
+
addNodeChild(parent, child);
|
|
83
|
+
prepareDisplayObjectRender(state, parent);
|
|
84
|
+
const spy = vi.spyOn(state.context, 'drawImage');
|
|
85
|
+
|
|
86
|
+
renderCanvasSprite(state, parent);
|
|
87
|
+
|
|
88
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getOrCreateRenderProxy2D } from '@flighthq/render';
|
|
2
|
+
import { createRichText } from '@flighthq/text';
|
|
3
|
+
import { enableTextInput, setTextInputSelection } from '@flighthq/textinput';
|
|
4
|
+
import type { RichText } from '@flighthq/types';
|
|
5
|
+
|
|
6
|
+
import { createCanvasRenderState } from './canvasRenderState';
|
|
7
|
+
import { drawCanvasRichText } from './canvasRichText';
|
|
8
|
+
import { drawCanvasTextInputOverlay, enableCanvasTextInput } from './canvasTextInput';
|
|
9
|
+
|
|
10
|
+
function makeState() {
|
|
11
|
+
const canvas = document.createElement('canvas');
|
|
12
|
+
canvas.width = 200;
|
|
13
|
+
canvas.height = 200;
|
|
14
|
+
return createCanvasRenderState(canvas);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeFocusedInput(text: string): RichText {
|
|
18
|
+
const node = createRichText({ data: { text, width: 100, height: 40 } });
|
|
19
|
+
enableTextInput(node).focused = true;
|
|
20
|
+
return node;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('drawCanvasTextInputOverlay', () => {
|
|
24
|
+
it('is installed by enableCanvasTextInput and is the overlay function', () => {
|
|
25
|
+
expect(typeof drawCanvasTextInputOverlay).toBe('function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('draws a caret for the focused collapsed selection', () => {
|
|
29
|
+
enableCanvasTextInput();
|
|
30
|
+
const state = makeState();
|
|
31
|
+
const node = makeFocusedInput('hello');
|
|
32
|
+
setTextInputSelection(node, 2, 2);
|
|
33
|
+
const renderProxy = getOrCreateRenderProxy2D(state, node);
|
|
34
|
+
const spy = vi.spyOn(state.context, 'fillRect');
|
|
35
|
+
|
|
36
|
+
drawCanvasRichText(state, renderProxy);
|
|
37
|
+
|
|
38
|
+
expect(spy).toHaveBeenCalledWith(expect.any(Number), expect.any(Number), 1, expect.any(Number));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('draws selection rectangles for the focused expanded selection', () => {
|
|
42
|
+
enableCanvasTextInput();
|
|
43
|
+
const state = makeState();
|
|
44
|
+
const node = makeFocusedInput('hello');
|
|
45
|
+
setTextInputSelection(node, 1, 4);
|
|
46
|
+
const renderProxy = getOrCreateRenderProxy2D(state, node);
|
|
47
|
+
const spy = vi.spyOn(state.context, 'fillRect');
|
|
48
|
+
|
|
49
|
+
drawCanvasRichText(state, renderProxy);
|
|
50
|
+
|
|
51
|
+
expect(spy).toHaveBeenCalledWith(expect.any(Number), expect.any(Number), expect.any(Number), expect.any(Number));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('enableCanvasTextInput', () => {
|
|
56
|
+
it('installs the overlay without throwing', () => {
|
|
57
|
+
expect(() => enableCanvasTextInput()).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('leaves a static RichText (no input slot) free of overlay drawing', () => {
|
|
61
|
+
enableCanvasTextInput();
|
|
62
|
+
const state = makeState();
|
|
63
|
+
const node = createRichText({ data: { text: 'plain', width: 100, height: 40 } });
|
|
64
|
+
const renderProxy = getOrCreateRenderProxy2D(state, node);
|
|
65
|
+
expect(() => drawCanvasRichText(state, renderProxy)).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getOrCreateRenderProxy2D } from '@flighthq/render';
|
|
2
|
+
import { createTextLabel } from '@flighthq/text';
|
|
3
|
+
|
|
4
|
+
import { createCanvasRenderState } from './canvasRenderState';
|
|
5
|
+
import { drawCanvasTextLabel } from './canvasTextLabel';
|
|
6
|
+
|
|
7
|
+
function makeState() {
|
|
8
|
+
const canvas = document.createElement('canvas');
|
|
9
|
+
canvas.width = 200;
|
|
10
|
+
canvas.height = 200;
|
|
11
|
+
return createCanvasRenderState(canvas);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('drawCanvasTextLabel', () => {
|
|
15
|
+
it('does not throw when text is empty', () => {
|
|
16
|
+
const state = makeState();
|
|
17
|
+
const node = createTextLabel();
|
|
18
|
+
node.data.text = '';
|
|
19
|
+
const renderProxy = getOrCreateRenderProxy2D(state, node);
|
|
20
|
+
expect(() => drawCanvasTextLabel(state, renderProxy)).not.toThrow();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('calls fillText when the text field is non-empty', () => {
|
|
24
|
+
const state = makeState();
|
|
25
|
+
const node = createTextLabel();
|
|
26
|
+
node.data.text = 'hello';
|
|
27
|
+
const renderProxy = getOrCreateRenderProxy2D(state, node);
|
|
28
|
+
const spy = vi.spyOn(state.context, 'fillText');
|
|
29
|
+
drawCanvasTextLabel(state, renderProxy);
|
|
30
|
+
expect(spy).toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createCanvasTextMeasure } from './canvasTextMeasure';
|
|
2
|
+
|
|
3
|
+
describe('createCanvasTextMeasure', () => {
|
|
4
|
+
it('returns a measure function that reports a non-negative width', () => {
|
|
5
|
+
const measure = createCanvasTextMeasure();
|
|
6
|
+
expect(typeof measure).toBe('function');
|
|
7
|
+
expect(measure('hello', {})).toBeGreaterThanOrEqual(0);
|
|
8
|
+
});
|
|
9
|
+
});
|