Skip to content

Commit aca3af2

Browse files
authored
fix(worker-image): Add attribute functions for worker-image (#681)
1 parent 2f58d37 commit aca3af2

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

.changeset/shiny-things-wave.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@qwik.dev/partytown": patch
3+
---
4+
5+
FEAT: Implement full attribute methods for HTMLImageElement
6+
7+
Implemented complete attribute handling for HTMLImageElement class including getAttribute(), setAttribute(), hasAttribute(), removeAttribute(), and toggleAttribute() methods. Added attributes Map to store element attributes and enhanced setAttribute() to properly handle src attribute. Includes comprehensive unit tests covering all attribute methods.

src/lib/web-worker/worker-image.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ export const createImageConstructor = (env: WebWorkerEnvironment) =>
1212
l: EventHandler[];
1313
e: EventHandler[];
1414
style: Record<string, string>;
15+
attributes: Map<string, string>;
1516

1617
constructor() {
1718
this.s = '';
1819
this.l = [];
1920
this.e = [];
2021
this.style = {};
22+
this.attributes = new Map();
2123
}
2224

2325
get src() {
@@ -46,6 +48,51 @@ export const createImageConstructor = (env: WebWorkerEnvironment) =>
4648
);
4749
}
4850

51+
getAttribute(name: string): string | null {
52+
const value = this.attributes.get(name.toLowerCase());
53+
return value !== undefined ? value : null;
54+
}
55+
56+
setAttribute(name: string, value: string): void {
57+
this.attributes.set(name.toLowerCase(), String(value));
58+
if (name.toLowerCase() === 'src') {
59+
this.src = value;
60+
}
61+
}
62+
63+
hasAttribute(name: string): boolean {
64+
return this.attributes.has(name.toLowerCase());
65+
}
66+
67+
removeAttribute(name: string): void {
68+
this.attributes.delete(name.toLowerCase());
69+
}
70+
71+
toggleAttribute(name: string, force?: boolean): boolean {
72+
const normalizedName = name.toLowerCase();
73+
const hasAttr = this.attributes.has(normalizedName);
74+
75+
if (force !== undefined) {
76+
if (force) {
77+
if (!hasAttr) {
78+
this.attributes.set(normalizedName, '');
79+
}
80+
return true;
81+
} else {
82+
this.attributes.delete(normalizedName);
83+
return false;
84+
}
85+
}
86+
87+
if (hasAttr) {
88+
this.attributes.delete(normalizedName);
89+
return false;
90+
} else {
91+
this.attributes.set(normalizedName, '');
92+
return true;
93+
}
94+
}
95+
4996
addEventListener(eventName: HTMLImageElementEvents, cb: EventHandler) {
5097
if (eventName === 'load') {
5198
this.l.push(cb);

tests/unit/worker-image.spec.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import * as assert from 'uvu/assert';
2+
import { createImageConstructor } from '../../src/lib/web-worker/worker-image';
3+
import { suite } from './utils';
4+
5+
const test = suite();
6+
7+
test('HTMLImageElement constructor', ({ env }) => {
8+
const HTMLImageElement = createImageConstructor(env);
9+
const img = new HTMLImageElement();
10+
11+
assert.is(img.src, '');
12+
assert.is(img.style instanceof Object, true);
13+
assert.is(img.attributes instanceof Map, true);
14+
assert.is(img.attributes.size, 0);
15+
});
16+
17+
test('getAttribute/setAttribute', ({ env }) => {
18+
const HTMLImageElement = createImageConstructor(env);
19+
const img = new HTMLImageElement();
20+
21+
assert.is(img.getAttribute('alt'), null);
22+
23+
img.setAttribute('alt', 'test image');
24+
assert.is(img.getAttribute('alt'), 'test image');
25+
26+
img.setAttribute('data-test', '123');
27+
assert.is(img.getAttribute('data-test'), '123');
28+
29+
// Case insensitive
30+
img.setAttribute('ALT', 'uppercase');
31+
assert.is(img.getAttribute('alt'), 'uppercase');
32+
assert.is(img.getAttribute('ALT'), 'uppercase');
33+
});
34+
35+
test('setAttribute with src attribute', ({ env }) => {
36+
const HTMLImageElement = createImageConstructor(env);
37+
const img = new HTMLImageElement();
38+
39+
img.setAttribute('src', 'http://example.com/test.jpg');
40+
assert.is(img.getAttribute('src'), 'http://example.com/test.jpg');
41+
assert.is(img.src, 'http://example.com/test.jpg');
42+
});
43+
44+
test('hasAttribute', ({ env }) => {
45+
const HTMLImageElement = createImageConstructor(env);
46+
const img = new HTMLImageElement();
47+
48+
assert.is(img.hasAttribute('alt'), false);
49+
50+
img.setAttribute('alt', 'test');
51+
assert.is(img.hasAttribute('alt'), true);
52+
assert.is(img.hasAttribute('ALT'), true); // Case insensitive
53+
});
54+
55+
test('removeAttribute', ({ env }) => {
56+
const HTMLImageElement = createImageConstructor(env);
57+
const img = new HTMLImageElement();
58+
59+
img.setAttribute('alt', 'test');
60+
img.setAttribute('data-test', '123');
61+
62+
assert.is(img.hasAttribute('alt'), true);
63+
img.removeAttribute('alt');
64+
assert.is(img.hasAttribute('alt'), false);
65+
assert.is(img.getAttribute('alt'), null);
66+
67+
// Other attributes unaffected
68+
assert.is(img.hasAttribute('data-test'), true);
69+
});
70+
71+
test('toggleAttribute without force', ({ env }) => {
72+
const HTMLImageElement = createImageConstructor(env);
73+
const img = new HTMLImageElement();
74+
75+
// Toggle on
76+
const result1 = img.toggleAttribute('hidden');
77+
assert.is(result1, true);
78+
assert.is(img.hasAttribute('hidden'), true);
79+
assert.is(img.getAttribute('hidden'), '');
80+
81+
// Toggle off
82+
const result2 = img.toggleAttribute('hidden');
83+
assert.is(result2, false);
84+
assert.is(img.hasAttribute('hidden'), false);
85+
});
86+
87+
test('toggleAttribute with force', ({ env }) => {
88+
const HTMLImageElement = createImageConstructor(env);
89+
const img = new HTMLImageElement();
90+
91+
// Force on
92+
const result1 = img.toggleAttribute('hidden', true);
93+
assert.is(result1, true);
94+
assert.is(img.hasAttribute('hidden'), true);
95+
96+
// Force on again (should stay on)
97+
const result2 = img.toggleAttribute('hidden', true);
98+
assert.is(result2, true);
99+
assert.is(img.hasAttribute('hidden'), true);
100+
101+
// Force off
102+
const result3 = img.toggleAttribute('hidden', false);
103+
assert.is(result3, false);
104+
assert.is(img.hasAttribute('hidden'), false);
105+
106+
// Force off again (should stay off)
107+
const result4 = img.toggleAttribute('hidden', false);
108+
assert.is(result4, false);
109+
assert.is(img.hasAttribute('hidden'), false);
110+
});
111+
112+
test('addEventListener/removeEventListener for load', ({ env }) => {
113+
const HTMLImageElement = createImageConstructor(env);
114+
const img = new HTMLImageElement();
115+
116+
const loadHandler = () => {};
117+
118+
img.addEventListener('load', loadHandler);
119+
assert.is(img.l.length, 1);
120+
assert.is(img.l[0], loadHandler);
121+
122+
img.removeEventListener('load', loadHandler);
123+
assert.is(img.l.length, 0);
124+
});
125+
126+
test('addEventListener/removeEventListener for error', ({ env }) => {
127+
const HTMLImageElement = createImageConstructor(env);
128+
const img = new HTMLImageElement();
129+
130+
const errorHandler = () => {};
131+
132+
img.addEventListener('error', errorHandler);
133+
assert.is(img.e.length, 1);
134+
assert.is(img.e[0], errorHandler);
135+
136+
img.removeEventListener('error', errorHandler);
137+
assert.is(img.e.length, 0);
138+
});
139+
140+
test('onload getter/setter', ({ env }) => {
141+
const HTMLImageElement = createImageConstructor(env);
142+
const img = new HTMLImageElement();
143+
144+
const handler = () => {};
145+
img.onload = handler;
146+
147+
assert.is(img.onload, handler);
148+
assert.is(img.l.length, 1);
149+
assert.is(img.l[0], handler);
150+
});
151+
152+
test('onerror getter/setter', ({ env }) => {
153+
const HTMLImageElement = createImageConstructor(env);
154+
const img = new HTMLImageElement();
155+
156+
const handler = () => {};
157+
img.onerror = handler;
158+
159+
assert.is(img.onerror, handler);
160+
assert.is(img.e.length, 1);
161+
assert.is(img.e[0], handler);
162+
});
163+
164+
test.run();

0 commit comments

Comments
 (0)