@mkhuda/dom-screenshot 0.0.1 → 1.0.1
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/.gitattributes +1 -0
- package/EXAMPLES_QUICKSTART.md +240 -0
- package/README.md +542 -25
- package/TESTING.md +269 -0
- package/TESTING_STATUS.md +215 -0
- package/TEST_SETUP_SUMMARY.md +335 -0
- package/dist/dom-screenshot.d.ts +44 -272
- package/dist/dom-screenshot.d.ts.map +1 -0
- package/dist/dom-screenshot.esm.js +753 -0
- package/dist/dom-screenshot.esm.js.map +1 -0
- package/dist/dom-screenshot.min.js +2 -1
- package/dist/dom-screenshot.min.js.map +1 -0
- package/examples/README.md +211 -0
- package/examples/react-app/README.md +161 -0
- package/examples/react-app/index.html +12 -0
- package/examples/react-app/node_modules/.vite/deps/_metadata.json +46 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-FK77NBP6.js +1895 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-FK77NBP6.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-VSODSHUF.js +21647 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-VSODSHUF.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/package.json +3 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom.js +5 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom_client.js +38 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react.js +4 -0
- package/examples/react-app/node_modules/.vite/deps/react.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-dev-runtime.js +898 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-runtime.js +910 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/examples/react-app/package.json +21 -0
- package/examples/react-app/tsconfig.json +25 -0
- package/examples/react-app/tsconfig.node.json +10 -0
- package/examples/react-app/vite.config.ts +10 -0
- package/package.json +75 -44
- package/rollup.config.mjs +35 -0
- package/tests/README.md +394 -0
- package/tests/fixtures/html.ts +192 -0
- package/tests/fixtures/images.ts +86 -0
- package/tests/fixtures/styles.ts +288 -0
- package/tests/helpers/dom-helpers.ts +242 -0
- package/tests/mocks/canvas-mock.ts +94 -0
- package/tests/mocks/image-mock.ts +147 -0
- package/tests/mocks/xhr-mock.ts +202 -0
- package/tests/setup.ts +103 -0
- package/tests/unit/basic.test.ts +263 -0
- package/tests/unit/simple.test.ts +172 -0
- package/tsconfig.json +44 -20
- package/vitest.config.mts +35 -0
- package/rollup.config.js +0 -20
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS and style test fixtures
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Basic CSS fixtures
|
|
7
|
+
*/
|
|
8
|
+
export const CSS_FIXTURES = {
|
|
9
|
+
/**
|
|
10
|
+
* Basic div with color
|
|
11
|
+
*/
|
|
12
|
+
coloredDiv: `
|
|
13
|
+
<div style="
|
|
14
|
+
width: 100px;
|
|
15
|
+
height: 100px;
|
|
16
|
+
background-color: blue;
|
|
17
|
+
color: white;
|
|
18
|
+
">Colored</div>
|
|
19
|
+
`,
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gradient background
|
|
23
|
+
*/
|
|
24
|
+
gradientDiv: `
|
|
25
|
+
<div style="
|
|
26
|
+
width: 100px;
|
|
27
|
+
height: 100px;
|
|
28
|
+
background: linear-gradient(to right, red, blue);
|
|
29
|
+
">Gradient</div>
|
|
30
|
+
`,
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Border and shadow
|
|
34
|
+
*/
|
|
35
|
+
borderShadowDiv: `
|
|
36
|
+
<div style="
|
|
37
|
+
width: 100px;
|
|
38
|
+
height: 100px;
|
|
39
|
+
border: 2px solid black;
|
|
40
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
41
|
+
background-color: white;
|
|
42
|
+
">Border & Shadow</div>
|
|
43
|
+
`,
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Border radius
|
|
47
|
+
*/
|
|
48
|
+
borderRadiusDiv: `
|
|
49
|
+
<div style="
|
|
50
|
+
width: 100px;
|
|
51
|
+
height: 100px;
|
|
52
|
+
background-color: green;
|
|
53
|
+
border-radius: 50%;
|
|
54
|
+
">Circle</div>
|
|
55
|
+
`,
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Transform and rotation
|
|
59
|
+
*/
|
|
60
|
+
transformDiv: `
|
|
61
|
+
<div style="
|
|
62
|
+
width: 100px;
|
|
63
|
+
height: 100px;
|
|
64
|
+
background-color: orange;
|
|
65
|
+
transform: rotate(45deg);
|
|
66
|
+
">Rotated</div>
|
|
67
|
+
`,
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Opacity
|
|
71
|
+
*/
|
|
72
|
+
opacityDiv: `
|
|
73
|
+
<div style="
|
|
74
|
+
width: 100px;
|
|
75
|
+
height: 100px;
|
|
76
|
+
background-color: purple;
|
|
77
|
+
opacity: 0.5;
|
|
78
|
+
">Transparent</div>
|
|
79
|
+
`,
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Font styling
|
|
83
|
+
*/
|
|
84
|
+
fontStyledDiv: `
|
|
85
|
+
<div style="
|
|
86
|
+
font-family: Arial, sans-serif;
|
|
87
|
+
font-size: 16px;
|
|
88
|
+
font-weight: bold;
|
|
89
|
+
color: #333;
|
|
90
|
+
line-height: 1.5;
|
|
91
|
+
">
|
|
92
|
+
<p>Styled text</p>
|
|
93
|
+
</div>
|
|
94
|
+
`,
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Flexbox layout
|
|
98
|
+
*/
|
|
99
|
+
flexboxDiv: `
|
|
100
|
+
<div style="
|
|
101
|
+
display: flex;
|
|
102
|
+
gap: 10px;
|
|
103
|
+
background-color: #f0f0f0;
|
|
104
|
+
padding: 10px;
|
|
105
|
+
">
|
|
106
|
+
<div style="
|
|
107
|
+
width: 50px;
|
|
108
|
+
height: 50px;
|
|
109
|
+
background-color: red;
|
|
110
|
+
"></div>
|
|
111
|
+
<div style="
|
|
112
|
+
width: 50px;
|
|
113
|
+
height: 50px;
|
|
114
|
+
background-color: green;
|
|
115
|
+
"></div>
|
|
116
|
+
<div style="
|
|
117
|
+
width: 50px;
|
|
118
|
+
height: 50px;
|
|
119
|
+
background-color: blue;
|
|
120
|
+
"></div>
|
|
121
|
+
</div>
|
|
122
|
+
`,
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Grid layout
|
|
126
|
+
*/
|
|
127
|
+
gridDiv: `
|
|
128
|
+
<div style="
|
|
129
|
+
display: grid;
|
|
130
|
+
grid-template-columns: repeat(3, 1fr);
|
|
131
|
+
gap: 10px;
|
|
132
|
+
background-color: #f0f0f0;
|
|
133
|
+
padding: 10px;
|
|
134
|
+
">
|
|
135
|
+
<div style="background-color: red; height: 50px;"></div>
|
|
136
|
+
<div style="background-color: green; height: 50px;"></div>
|
|
137
|
+
<div style="background-color: blue; height: 50px;"></div>
|
|
138
|
+
</div>
|
|
139
|
+
`,
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Padding and margin
|
|
143
|
+
*/
|
|
144
|
+
spacingDiv: `
|
|
145
|
+
<div style="
|
|
146
|
+
background-color: #ddd;
|
|
147
|
+
padding: 20px;
|
|
148
|
+
margin: 10px;
|
|
149
|
+
border: 1px solid #999;
|
|
150
|
+
">
|
|
151
|
+
<div style="
|
|
152
|
+
background-color: white;
|
|
153
|
+
padding: 10px;
|
|
154
|
+
margin: 10px;
|
|
155
|
+
border: 1px solid #ccc;
|
|
156
|
+
">Spaced content</div>
|
|
157
|
+
</div>
|
|
158
|
+
`,
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Text alignment
|
|
162
|
+
*/
|
|
163
|
+
textAlignDiv: `
|
|
164
|
+
<div style="width: 200px;">
|
|
165
|
+
<p style="text-align: left;">Left aligned</p>
|
|
166
|
+
<p style="text-align: center;">Center aligned</p>
|
|
167
|
+
<p style="text-align: right;">Right aligned</p>
|
|
168
|
+
</div>
|
|
169
|
+
`,
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Overflow handling
|
|
173
|
+
*/
|
|
174
|
+
overflowDiv: `
|
|
175
|
+
<div style="
|
|
176
|
+
width: 100px;
|
|
177
|
+
height: 100px;
|
|
178
|
+
background-color: #f0f0f0;
|
|
179
|
+
overflow: hidden;
|
|
180
|
+
border: 1px solid #ccc;
|
|
181
|
+
">
|
|
182
|
+
This text will be cut off because it exceeds the container size.
|
|
183
|
+
</div>
|
|
184
|
+
`,
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* List styling
|
|
188
|
+
*/
|
|
189
|
+
listDiv: `
|
|
190
|
+
<ul style="
|
|
191
|
+
list-style: none;
|
|
192
|
+
padding: 0;
|
|
193
|
+
">
|
|
194
|
+
<li style="
|
|
195
|
+
padding: 5px;
|
|
196
|
+
background-color: #eee;
|
|
197
|
+
margin: 2px;
|
|
198
|
+
border-left: 3px solid blue;
|
|
199
|
+
padding-left: 10px;
|
|
200
|
+
">Item 1</li>
|
|
201
|
+
<li style="
|
|
202
|
+
padding: 5px;
|
|
203
|
+
background-color: #eee;
|
|
204
|
+
margin: 2px;
|
|
205
|
+
border-left: 3px solid blue;
|
|
206
|
+
padding-left: 10px;
|
|
207
|
+
">Item 2</li>
|
|
208
|
+
</ul>
|
|
209
|
+
`,
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Table styling
|
|
213
|
+
*/
|
|
214
|
+
tableDiv: `
|
|
215
|
+
<table style="
|
|
216
|
+
border-collapse: collapse;
|
|
217
|
+
width: 200px;
|
|
218
|
+
">
|
|
219
|
+
<tr>
|
|
220
|
+
<th style="
|
|
221
|
+
background-color: #333;
|
|
222
|
+
color: white;
|
|
223
|
+
padding: 8px;
|
|
224
|
+
border: 1px solid #333;
|
|
225
|
+
">Header 1</th>
|
|
226
|
+
<th style="
|
|
227
|
+
background-color: #333;
|
|
228
|
+
color: white;
|
|
229
|
+
padding: 8px;
|
|
230
|
+
border: 1px solid #333;
|
|
231
|
+
">Header 2</th>
|
|
232
|
+
</tr>
|
|
233
|
+
<tr>
|
|
234
|
+
<td style="padding: 8px; border: 1px solid #ddd;">Data 1</td>
|
|
235
|
+
<td style="padding: 8px; border: 1px solid #ddd;">Data 2</td>
|
|
236
|
+
</tr>
|
|
237
|
+
</table>
|
|
238
|
+
`,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Create a CSS style element
|
|
243
|
+
*/
|
|
244
|
+
export function createStyleElement(css: string): HTMLStyleElement {
|
|
245
|
+
const style = document.createElement('style');
|
|
246
|
+
style.textContent = css;
|
|
247
|
+
document.head.appendChild(style);
|
|
248
|
+
return style;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create pseudo-element CSS
|
|
253
|
+
*/
|
|
254
|
+
export function createPseudoElementCSS(): string {
|
|
255
|
+
return `
|
|
256
|
+
.with-before::before {
|
|
257
|
+
content: '→ ';
|
|
258
|
+
color: red;
|
|
259
|
+
font-weight: bold;
|
|
260
|
+
}
|
|
261
|
+
.with-after::after {
|
|
262
|
+
content: ' ←';
|
|
263
|
+
color: blue;
|
|
264
|
+
font-weight: bold;
|
|
265
|
+
}
|
|
266
|
+
.with-both::before {
|
|
267
|
+
content: '[';
|
|
268
|
+
color: green;
|
|
269
|
+
}
|
|
270
|
+
.with-both::after {
|
|
271
|
+
content: ']';
|
|
272
|
+
color: green;
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Create a div with pseudo-elements
|
|
279
|
+
*/
|
|
280
|
+
export function createDivWithPseudoElements(): HTMLDivElement {
|
|
281
|
+
const style = createStyleElement(createPseudoElementCSS());
|
|
282
|
+
|
|
283
|
+
const div = document.createElement('div');
|
|
284
|
+
div.className = 'with-both';
|
|
285
|
+
div.textContent = 'Text with pseudo-elements';
|
|
286
|
+
|
|
287
|
+
return div;
|
|
288
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM testing helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a simple div with text content
|
|
7
|
+
*/
|
|
8
|
+
export function createSimpleDiv(text: string = 'Test'): HTMLDivElement {
|
|
9
|
+
const div = document.createElement('div');
|
|
10
|
+
div.textContent = text;
|
|
11
|
+
return div;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a styled div with custom CSS
|
|
16
|
+
*/
|
|
17
|
+
export function createStyledDiv(
|
|
18
|
+
text: string,
|
|
19
|
+
styles: Record<string, string>
|
|
20
|
+
): HTMLDivElement {
|
|
21
|
+
const div = createSimpleDiv(text);
|
|
22
|
+
Object.assign(div.style, styles);
|
|
23
|
+
return div;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a div with background color
|
|
28
|
+
*/
|
|
29
|
+
export function createColoredDiv(
|
|
30
|
+
text: string,
|
|
31
|
+
color: string,
|
|
32
|
+
bgColor: string
|
|
33
|
+
): HTMLDivElement {
|
|
34
|
+
return createStyledDiv(text, { color, backgroundColor: bgColor });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a simple image element
|
|
39
|
+
*/
|
|
40
|
+
export function createImageElement(
|
|
41
|
+
src: string = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
42
|
+
alt: string = 'test'
|
|
43
|
+
): HTMLImageElement {
|
|
44
|
+
const img = document.createElement('img');
|
|
45
|
+
img.src = src;
|
|
46
|
+
img.alt = alt;
|
|
47
|
+
return img;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a canvas element with some drawing
|
|
52
|
+
*/
|
|
53
|
+
export function createCanvasElement(
|
|
54
|
+
width: number = 100,
|
|
55
|
+
height: number = 100
|
|
56
|
+
): HTMLCanvasElement {
|
|
57
|
+
const canvas = document.createElement('canvas');
|
|
58
|
+
canvas.width = width;
|
|
59
|
+
canvas.height = height;
|
|
60
|
+
|
|
61
|
+
const ctx = canvas.getContext('2d');
|
|
62
|
+
if (ctx) {
|
|
63
|
+
ctx.fillStyle = 'blue';
|
|
64
|
+
ctx.fillRect(0, 0, width, height);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return canvas;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a video element
|
|
72
|
+
*/
|
|
73
|
+
export function createVideoElement(
|
|
74
|
+
src: string = 'data:video/mp4;base64,',
|
|
75
|
+
width: number = 100,
|
|
76
|
+
height: number = 100
|
|
77
|
+
): HTMLVideoElement {
|
|
78
|
+
const video = document.createElement('video');
|
|
79
|
+
video.src = src;
|
|
80
|
+
video.width = width;
|
|
81
|
+
video.height = height;
|
|
82
|
+
return video;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a textarea with value
|
|
87
|
+
*/
|
|
88
|
+
export function createTextarea(value: string = 'test value'): HTMLTextAreaElement {
|
|
89
|
+
const textarea = document.createElement('textarea');
|
|
90
|
+
textarea.value = value;
|
|
91
|
+
return textarea;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create an input element with value
|
|
96
|
+
*/
|
|
97
|
+
export function createInput(
|
|
98
|
+
value: string = 'test input',
|
|
99
|
+
type: string = 'text'
|
|
100
|
+
): HTMLInputElement {
|
|
101
|
+
const input = document.createElement('input');
|
|
102
|
+
input.type = type;
|
|
103
|
+
input.value = value;
|
|
104
|
+
return input;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a complex DOM tree with various elements
|
|
109
|
+
*/
|
|
110
|
+
export function createComplexDOM(): HTMLDivElement {
|
|
111
|
+
const container = document.createElement('div');
|
|
112
|
+
container.style.width = '200px';
|
|
113
|
+
container.style.height = '200px';
|
|
114
|
+
container.style.backgroundColor = '#f0f0f0';
|
|
115
|
+
|
|
116
|
+
// Add text
|
|
117
|
+
const heading = document.createElement('h1');
|
|
118
|
+
heading.textContent = 'Test Heading';
|
|
119
|
+
heading.style.color = '#333';
|
|
120
|
+
container.appendChild(heading);
|
|
121
|
+
|
|
122
|
+
// Add paragraph
|
|
123
|
+
const para = document.createElement('p');
|
|
124
|
+
para.textContent = 'This is a test paragraph.';
|
|
125
|
+
para.style.fontSize = '14px';
|
|
126
|
+
container.appendChild(para);
|
|
127
|
+
|
|
128
|
+
// Add image
|
|
129
|
+
const img = createImageElement();
|
|
130
|
+
img.style.width = '50px';
|
|
131
|
+
img.style.height = '50px';
|
|
132
|
+
container.appendChild(img);
|
|
133
|
+
|
|
134
|
+
// Add nested div
|
|
135
|
+
const nested = document.createElement('div');
|
|
136
|
+
nested.style.border = '1px solid #ccc';
|
|
137
|
+
nested.style.padding = '10px';
|
|
138
|
+
nested.textContent = 'Nested content';
|
|
139
|
+
container.appendChild(nested);
|
|
140
|
+
|
|
141
|
+
return container;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a DOM with inline styles
|
|
146
|
+
*/
|
|
147
|
+
export function createDOMWithStyles(): HTMLDivElement {
|
|
148
|
+
const container = document.createElement('div');
|
|
149
|
+
container.style.background = 'linear-gradient(to right, #ff0000, #00ff00)';
|
|
150
|
+
container.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
|
|
151
|
+
container.style.borderRadius = '8px';
|
|
152
|
+
container.style.padding = '20px';
|
|
153
|
+
|
|
154
|
+
const text = document.createElement('span');
|
|
155
|
+
text.textContent = 'Styled content';
|
|
156
|
+
text.style.fontWeight = 'bold';
|
|
157
|
+
text.style.color = 'white';
|
|
158
|
+
container.appendChild(text);
|
|
159
|
+
|
|
160
|
+
return container;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create a DOM with pseudo-elements (via CSS class)
|
|
165
|
+
*/
|
|
166
|
+
export function createDOMWithPseudoElements(): HTMLDivElement {
|
|
167
|
+
const style = document.createElement('style');
|
|
168
|
+
style.textContent = `
|
|
169
|
+
.pseudo-element {
|
|
170
|
+
position: relative;
|
|
171
|
+
}
|
|
172
|
+
.pseudo-element::before {
|
|
173
|
+
content: '→ ';
|
|
174
|
+
color: red;
|
|
175
|
+
}
|
|
176
|
+
.pseudo-element::after {
|
|
177
|
+
content: ' ←';
|
|
178
|
+
color: blue;
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
181
|
+
document.head.appendChild(style);
|
|
182
|
+
|
|
183
|
+
const div = document.createElement('div');
|
|
184
|
+
div.className = 'pseudo-element';
|
|
185
|
+
div.textContent = 'Text with pseudo-elements';
|
|
186
|
+
|
|
187
|
+
return div;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create a SVG element
|
|
192
|
+
*/
|
|
193
|
+
export function createSVGElement(): SVGSVGElement {
|
|
194
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
195
|
+
svg.setAttribute('width', '100');
|
|
196
|
+
svg.setAttribute('height', '100');
|
|
197
|
+
svg.setAttribute('viewBox', '0 0 100 100');
|
|
198
|
+
|
|
199
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
200
|
+
circle.setAttribute('cx', '50');
|
|
201
|
+
circle.setAttribute('cy', '50');
|
|
202
|
+
circle.setAttribute('r', '40');
|
|
203
|
+
circle.setAttribute('fill', 'blue');
|
|
204
|
+
|
|
205
|
+
svg.appendChild(circle);
|
|
206
|
+
return svg;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Wait for a specified time
|
|
211
|
+
*/
|
|
212
|
+
export function wait(ms: number): Promise<void> {
|
|
213
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get image data from a data URL
|
|
218
|
+
*/
|
|
219
|
+
export function parseDataUrl(dataUrl: string): { type: string; data: string } {
|
|
220
|
+
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
221
|
+
if (!match) {
|
|
222
|
+
throw new Error(`Invalid data URL: ${dataUrl}`);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
type: match[1],
|
|
226
|
+
data: match[2],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if a data URL is valid
|
|
232
|
+
*/
|
|
233
|
+
export function isValidDataUrl(str: string): boolean {
|
|
234
|
+
return typeof str === 'string' && str.startsWith('data:');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check if a data URL is an image
|
|
239
|
+
*/
|
|
240
|
+
export function isImageDataUrl(str: string): boolean {
|
|
241
|
+
return typeof str === 'string' && str.startsWith('data:image/');
|
|
242
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas mocking utilities for testing
|
|
3
|
+
*/
|
|
4
|
+
import { vi, SinonStub } from 'vitest';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mock HTMLCanvasElement.prototype.toDataURL
|
|
8
|
+
*/
|
|
9
|
+
export function mockCanvasToDataUrl(
|
|
10
|
+
returnValue: string = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
|
|
11
|
+
): SinonStub {
|
|
12
|
+
return vi.spyOn(HTMLCanvasElement.prototype, 'toDataURL').mockReturnValue(returnValue);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Mock HTMLCanvasElement.prototype.toBlob
|
|
17
|
+
*/
|
|
18
|
+
export function mockCanvasToBlob(
|
|
19
|
+
returnBlob: Blob = new Blob(['test'], { type: 'image/png' })
|
|
20
|
+
): SinonStub {
|
|
21
|
+
return vi
|
|
22
|
+
.spyOn(HTMLCanvasElement.prototype, 'toBlob')
|
|
23
|
+
.mockImplementation((callback: BlobCallback) => {
|
|
24
|
+
// Use setTimeout to simulate async behavior
|
|
25
|
+
setTimeout(() => callback(returnBlob), 0);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mock canvas context operations
|
|
31
|
+
*/
|
|
32
|
+
export function mockCanvasContext(): {
|
|
33
|
+
fillRect: SinonStub;
|
|
34
|
+
drawImage: SinonStub;
|
|
35
|
+
getImageData: SinonStub;
|
|
36
|
+
} {
|
|
37
|
+
const mockContext = {
|
|
38
|
+
fillRect: vi.fn(),
|
|
39
|
+
drawImage: vi.fn(),
|
|
40
|
+
getImageData: vi.fn().mockReturnValue({
|
|
41
|
+
data: new Uint8ClampedArray(4), // RGBA for 1x1 pixel
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(
|
|
46
|
+
mockContext as any
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
fillRect: mockContext.fillRect,
|
|
51
|
+
drawImage: mockContext.drawImage,
|
|
52
|
+
getImageData: mockContext.getImageData,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a valid PNG data URL
|
|
58
|
+
*/
|
|
59
|
+
export function createValidPngDataUrl(): string {
|
|
60
|
+
// Minimal valid PNG: 1x1 transparent pixel
|
|
61
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create a valid JPEG data URL
|
|
66
|
+
*/
|
|
67
|
+
export function createValidJpegDataUrl(): string {
|
|
68
|
+
// Minimal valid JPEG: 1x1 red pixel
|
|
69
|
+
return 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8VAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCwAA8A/9k=';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a valid SVG data URL
|
|
74
|
+
*/
|
|
75
|
+
export function createValidSvgDataUrl(): string {
|
|
76
|
+
const svg =
|
|
77
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><circle cx="50" cy="50" r="40" fill="blue"/></svg>';
|
|
78
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Mock multiple data URLs for different formats
|
|
83
|
+
*/
|
|
84
|
+
export function mockCanvasDataUrls(): {
|
|
85
|
+
png: string;
|
|
86
|
+
jpeg: string;
|
|
87
|
+
svg: string;
|
|
88
|
+
} {
|
|
89
|
+
return {
|
|
90
|
+
png: createValidPngDataUrl(),
|
|
91
|
+
jpeg: createValidJpegDataUrl(),
|
|
92
|
+
svg: createValidSvgDataUrl(),
|
|
93
|
+
};
|
|
94
|
+
}
|