Skip to content

Commit 081216d

Browse files
committed
[test] Image: test headers related functionality
1 parent b433435 commit 081216d

File tree

2 files changed

+90
-21
lines changed

2 files changed

+90
-21
lines changed

packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,14 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
329329
>
330330
<div
331331
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
332-
style="filter: url(#tint-57);"
332+
style="filter: url(#tint-72);"
333333
/>
334334
<svg
335335
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
336336
>
337337
<defs>
338338
<filter
339-
id="tint-57"
339+
id="tint-72"
340340
>
341341
<feflood
342342
flood-color="blue"
@@ -378,7 +378,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
378378
>
379379
<div
380380
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
381-
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
381+
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-71);"
382382
/>
383383
<img
384384
alt=""
@@ -391,7 +391,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
391391
>
392392
<defs>
393393
<filter
394-
id="tint-56"
394+
id="tint-71"
395395
>
396396
<feflood
397397
flood-color="red"

packages/react-native-web/src/exports/Image/__tests__/index-test.js

+86-17
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,24 @@ import Image from '../';
1313
import ImageLoader, { ImageUriCache } from '../../../modules/ImageLoader';
1414
import PixelRatio from '../../PixelRatio';
1515
import React from 'react';
16-
import { render } from '@testing-library/react';
16+
import { render, waitFor } from '@testing-library/react';
1717

1818
const originalImage = window.Image;
1919

2020
describe('components/Image', () => {
2121
beforeEach(() => {
2222
ImageUriCache._entries = {};
2323
window.Image = jest.fn(() => ({}));
24+
ImageLoader.load = jest
25+
.fn()
26+
.mockImplementation((source, onLoad, onError) => {
27+
act(() => onLoad({ source }));
28+
});
29+
ImageLoader.loadWithHeaders = jest.fn().mockImplementation((source) => ({
30+
source,
31+
promise: Promise.resolve(`blob:${Math.random()}`),
32+
cancel: jest.fn()
33+
}));
2434
});
2535

2636
afterEach(() => {
@@ -106,10 +116,6 @@ describe('components/Image', () => {
106116

107117
describe('prop "onLoad"', () => {
108118
test('is called after image is loaded from network', () => {
109-
jest.useFakeTimers();
110-
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
111-
onLoad();
112-
});
113119
const onLoadStartStub = jest.fn();
114120
const onLoadStub = jest.fn();
115121
const onLoadEndStub = jest.fn();
@@ -121,15 +127,10 @@ describe('components/Image', () => {
121127
source="https://test.com/img.jpg"
122128
/>
123129
);
124-
jest.runOnlyPendingTimers();
125130
expect(onLoadStub).toBeCalled();
126131
});
127132

128133
test('is called after image is loaded from cache', () => {
129-
jest.useFakeTimers();
130-
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
131-
onLoad();
132-
});
133134
const onLoadStartStub = jest.fn();
134135
const onLoadStub = jest.fn();
135136
const onLoadEndStub = jest.fn();
@@ -143,7 +144,6 @@ describe('components/Image', () => {
143144
source={uri}
144145
/>
145146
);
146-
jest.runOnlyPendingTimers();
147147
expect(onLoadStub).toBeCalled();
148148
ImageUriCache.remove(uri);
149149
});
@@ -227,6 +227,34 @@ describe('components/Image', () => {
227227
});
228228
});
229229

230+
describe('prop "onLoadStart"', () => {
231+
test('is called on update if "headers" are modified', () => {
232+
const onLoadStartStub = jest.fn();
233+
const { rerender } = render(
234+
<Image
235+
onLoadStart={onLoadStartStub}
236+
source={{
237+
uri: 'https://test.com/img.jpg',
238+
headers: { 'x-custom-header': 'abc123' }
239+
}}
240+
/>
241+
);
242+
act(() => {
243+
rerender(
244+
<Image
245+
onLoadStart={onLoadStartStub}
246+
source={{
247+
uri: 'https://test.com/img.jpg',
248+
headers: { 'x-custom-header': '123abc' }
249+
}}
250+
/>
251+
);
252+
});
253+
254+
expect(onLoadStartStub.mock.calls.length).toBe(2);
255+
});
256+
});
257+
230258
describe('prop "resizeMode"', () => {
231259
['contain', 'cover', 'none', 'repeat', 'stretch', undefined].forEach(
232260
(resizeMode) => {
@@ -245,7 +273,8 @@ describe('components/Image', () => {
245273
'',
246274
{},
247275
{ uri: '' },
248-
{ uri: 'https://google.com' }
276+
{ uri: 'https://google.com' },
277+
{ uri: 'https://google.com', headers: { 'x-custom-header': 'abc123' } }
249278
];
250279
sources.forEach((source) => {
251280
expect(() => render(<Image source={source} />)).not.toThrow();
@@ -261,11 +290,6 @@ describe('components/Image', () => {
261290

262291
test('is set immediately if the image was preloaded', () => {
263292
const uri = 'https://yahoo.com/favicon.ico';
264-
ImageLoader.load = jest
265-
.fn()
266-
.mockImplementationOnce((_, onLoad, onError) => {
267-
onLoad();
268-
});
269293
return Image.prefetch(uri).then(() => {
270294
const source = { uri };
271295
const { container } = render(<Image source={source} />, {
@@ -346,6 +370,51 @@ describe('components/Image', () => {
346370
'http://localhost/static/[email protected]'
347371
);
348372
});
373+
374+
test('it works with headers in 2 stages', async () => {
375+
const uri = 'https://google.com/favicon.ico';
376+
const headers = { 'x-custom-header': 'abc123' };
377+
const source = { uri, headers };
378+
379+
// Stage 1
380+
const loadRequest = {
381+
promise: Promise.resolve('blob:123'),
382+
cancel: jest.fn(),
383+
source
384+
};
385+
386+
ImageLoader.loadWithHeaders.mockReturnValue(loadRequest);
387+
388+
render(<Image source={source} />);
389+
390+
expect(ImageLoader.loadWithHeaders).toHaveBeenCalledWith(
391+
expect.objectContaining(source)
392+
);
393+
394+
// Stage 2
395+
return waitFor(() => {
396+
expect(ImageLoader.load).toHaveBeenCalledWith(
397+
'blob:123',
398+
expect.any(Function),
399+
expect.any(Function)
400+
);
401+
});
402+
});
403+
404+
// A common case is `source` declared as an inline object, which cause is to be a
405+
// new object (with the same content) each time parent component renders
406+
test('it still loads the image if source object is changed', () => {
407+
const uri = 'https://google.com/favicon.ico';
408+
const headers = { 'x-custom-header': 'abc123' };
409+
const { rerender } = render(<Image source={{ uri, headers }} />);
410+
rerender(<Image source={{ uri, headers }} />);
411+
412+
// when the underlying source didn't change we don't expect more than 1 load calls
413+
return waitFor(() => {
414+
expect(ImageLoader.loadWithHeaders).toHaveBeenCalledTimes(1);
415+
expect(ImageLoader.load).toHaveBeenCalledTimes(1);
416+
});
417+
});
349418
});
350419

351420
describe('prop "style"', () => {

0 commit comments

Comments
 (0)