Skip to content

Commit 12a214d

Browse files
authored
Merge pull request #3 from DustinJSilk/fix-reactivity
fix: image reactivity
2 parents 580cce8 + e121f8d commit 12a214d

File tree

4 files changed

+415
-36
lines changed

4 files changed

+415
-36
lines changed
Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
import { QwikCityMockProvider } from '@builder.io/qwik-city';
2+
import { createDOM } from '@builder.io/qwik/testing';
3+
import { expect, test } from 'vitest';
4+
import {
5+
Image,
6+
ImageProps,
7+
ImageTransformerProps,
8+
useImageProvider,
9+
} from 'qwik-image';
10+
import { $, Slot, component$, useSignal } from '@builder.io/qwik';
11+
import { providers, selectedProvider } from '../providers';
12+
13+
const TransformerProvider = component$(() => {
14+
const imageTransformer$ = $((props: ImageTransformerProps): string => {
15+
return providers[selectedProvider].transformer(props);
16+
});
17+
18+
useImageProvider({
19+
imageTransformer$,
20+
});
21+
22+
return <Slot />;
23+
});
24+
25+
const SRC =
26+
'image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F869bfbaec9c64415ae68235d9b7b1425';
27+
28+
test(`should render a constrained img`, async () => {
29+
const { screen, render } = await createDOM();
30+
await render(
31+
<QwikCityMockProvider>
32+
<TransformerProvider>
33+
<Image width={400} height={400} layout="constrained" src={SRC} />
34+
</TransformerProvider>
35+
</QwikCityMockProvider>
36+
);
37+
38+
validateImg(screen.querySelector('img'), {
39+
src: SRC,
40+
layout: 'constrained',
41+
width: 400,
42+
height: 400,
43+
srcSizes: [
44+
{
45+
height: 400,
46+
width: 400,
47+
breakpoint: 400,
48+
},
49+
{
50+
height: 400,
51+
width: 640,
52+
breakpoint: 640,
53+
},
54+
{
55+
height: 400,
56+
width: 800,
57+
breakpoint: 800,
58+
},
59+
],
60+
});
61+
});
62+
63+
test(`should render a constrained rectangular img`, async () => {
64+
const { screen, render } = await createDOM();
65+
await render(
66+
<QwikCityMockProvider>
67+
<TransformerProvider>
68+
<Image width={300} height={200} layout="constrained" src={SRC} />
69+
</TransformerProvider>
70+
</QwikCityMockProvider>
71+
);
72+
73+
validateImg(screen.querySelector('img'), {
74+
width: 300,
75+
height: 200,
76+
src: SRC,
77+
layout: 'constrained',
78+
srcSizes: [
79+
{
80+
height: 200,
81+
width: 300,
82+
breakpoint: 300,
83+
},
84+
{
85+
height: 200,
86+
width: 600,
87+
breakpoint: 600,
88+
},
89+
],
90+
});
91+
});
92+
93+
test(`should render a fullWidth img`, async () => {
94+
const { screen, render } = await createDOM();
95+
await render(
96+
<QwikCityMockProvider>
97+
<TransformerProvider>
98+
<Image width={400} height={400} layout="fullWidth" src={SRC} />
99+
</TransformerProvider>
100+
</QwikCityMockProvider>
101+
);
102+
103+
validateImg(screen.querySelector('img'), {
104+
src: SRC,
105+
layout: 'fullWidth',
106+
width: 400,
107+
height: 400,
108+
srcSizes: [
109+
{
110+
height: 400,
111+
width: 1280,
112+
breakpoint: 1280,
113+
},
114+
{
115+
height: 400,
116+
width: 1920,
117+
breakpoint: 1920,
118+
},
119+
{
120+
height: 400,
121+
width: 3840,
122+
breakpoint: 3840,
123+
},
124+
{
125+
height: 400,
126+
width: 640,
127+
breakpoint: 640,
128+
},
129+
{
130+
height: 400,
131+
width: 960,
132+
breakpoint: 960,
133+
},
134+
],
135+
});
136+
});
137+
138+
test(`should render a fullWidth rectangular img`, async () => {
139+
const { screen, render } = await createDOM();
140+
await render(
141+
<QwikCityMockProvider>
142+
<TransformerProvider>
143+
<Image width={300} height={200} layout="fullWidth" src={SRC} />
144+
</TransformerProvider>
145+
</QwikCityMockProvider>
146+
);
147+
148+
validateImg(screen.querySelector('img'), {
149+
width: 300,
150+
height: 200,
151+
src: SRC,
152+
layout: 'fullWidth',
153+
srcSizes: [
154+
{
155+
height: 200,
156+
width: 1280,
157+
breakpoint: 1280,
158+
},
159+
{
160+
height: 200,
161+
width: 1920,
162+
breakpoint: 1920,
163+
},
164+
{
165+
height: 200,
166+
width: 3840,
167+
breakpoint: 3840,
168+
},
169+
{
170+
height: 200,
171+
width: 640,
172+
breakpoint: 640,
173+
},
174+
{
175+
height: 200,
176+
width: 960,
177+
breakpoint: 960,
178+
},
179+
],
180+
});
181+
});
182+
183+
test(`should render a fixed img`, async () => {
184+
const { screen, render } = await createDOM();
185+
await render(
186+
<QwikCityMockProvider>
187+
<TransformerProvider>
188+
<Image width={600} height={500} layout="fixed" src={SRC} />
189+
</TransformerProvider>
190+
</QwikCityMockProvider>
191+
);
192+
193+
validateImg(screen.querySelector('img'), {
194+
src: SRC,
195+
layout: 'fixed',
196+
width: 600,
197+
height: 500,
198+
srcSizes: [
199+
{
200+
height: 500,
201+
width: 1200,
202+
breakpoint: 1200,
203+
},
204+
{
205+
height: 500,
206+
width: 600,
207+
breakpoint: 600,
208+
},
209+
],
210+
});
211+
});
212+
213+
const DynamicImage = component$(
214+
(props: { before: ImageProps; after: ImageProps }) => {
215+
const width = useSignal(props.before.width);
216+
const height = useSignal(props.before.height);
217+
const layout = useSignal(props.before.layout);
218+
const src = useSignal(props.before.src);
219+
220+
return (
221+
<>
222+
<Image
223+
width={width.value}
224+
height={height.value}
225+
layout={layout.value}
226+
src={src.value}
227+
/>
228+
<button
229+
onClick$={() => {
230+
width.value = props.after.width;
231+
height.value = props.after.height;
232+
layout.value = props.after.layout;
233+
src.value = props.after.src;
234+
}}
235+
></button>
236+
</>
237+
);
238+
}
239+
);
240+
241+
test(`should update img when props change`, async () => {
242+
const { screen, render, userEvent } = await createDOM();
243+
await render(
244+
<QwikCityMockProvider>
245+
<TransformerProvider>
246+
<DynamicImage
247+
before={{
248+
width: 400,
249+
height: 400,
250+
layout: 'constrained',
251+
src: SRC,
252+
}}
253+
after={{
254+
width: 300,
255+
height: 200,
256+
layout: 'fullWidth',
257+
src: SRC + 'something',
258+
}}
259+
/>
260+
</TransformerProvider>
261+
</QwikCityMockProvider>
262+
);
263+
264+
validateImg(screen.querySelector('img'), {
265+
src: SRC,
266+
layout: 'constrained',
267+
width: 400,
268+
height: 400,
269+
srcSizes: [
270+
{
271+
height: 400,
272+
width: 400,
273+
breakpoint: 400,
274+
},
275+
{
276+
height: 400,
277+
width: 640,
278+
breakpoint: 640,
279+
},
280+
{
281+
height: 400,
282+
width: 800,
283+
breakpoint: 800,
284+
},
285+
],
286+
});
287+
288+
await userEvent('button', 'click');
289+
290+
validateImg(screen.querySelector('img'), {
291+
src: SRC + 'something',
292+
layout: 'fullWidth',
293+
width: 300,
294+
height: 200,
295+
srcSizes: [
296+
{
297+
height: 200,
298+
width: 1280,
299+
breakpoint: 1280,
300+
},
301+
{
302+
height: 200,
303+
width: 1920,
304+
breakpoint: 1920,
305+
},
306+
{
307+
height: 200,
308+
width: 3840,
309+
breakpoint: 3840,
310+
},
311+
{
312+
height: 200,
313+
width: 640,
314+
breakpoint: 640,
315+
},
316+
{
317+
height: 200,
318+
width: 960,
319+
breakpoint: 960,
320+
},
321+
],
322+
});
323+
});
324+
325+
function validateImg(
326+
img: HTMLImageElement | null,
327+
props: {
328+
width: number;
329+
height: number;
330+
srcSizes: {
331+
height: number;
332+
width: number;
333+
breakpoint: number;
334+
}[];
335+
src: string;
336+
layout: 'constrained' | 'fixed' | 'fullWidth';
337+
}
338+
) {
339+
expect(img).toBeTruthy();
340+
expect(img?.outerHTML).toContain(`src="${props.src}"`);
341+
342+
const expectedSizes = props.srcSizes
343+
.map(
344+
({ width, height, breakpoint }) =>
345+
`https://cdn.builder.io/api/v1/${props.src}?height=${height}&width=${width}&format=webp&fit=fill ${breakpoint}w`
346+
)
347+
.join(',\n');
348+
expect(img?.srcset).toEqual(expectedSizes);
349+
350+
if (props.layout === 'constrained') {
351+
expect(img?.outerHTML).toContain(
352+
`sizes="(min-width: ${props.width}px) ${props.width}px, 100vw"`
353+
);
354+
} else if (props.layout === 'fixed') {
355+
expect(img?.outerHTML).toContain(`width="${props.width}"`);
356+
expect(img?.outerHTML).toContain(`height="${props.height}"`);
357+
expect(img?.outerHTML).toContain(`sizes="${props.width}px"`);
358+
} else {
359+
expect(img?.outerHTML).toContain(`sizes="100vw"`);
360+
}
361+
}

0 commit comments

Comments
 (0)