Skip to content

Commit 616975e

Browse files
committed
[test] Image: test headers related functionality
1 parent be12f9f commit 616975e

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-55);"
332+
style="filter: url(#tint-66);"
333333
/>
334334
<svg
335335
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
336336
>
337337
<defs>
338338
<filter
339-
id="tint-55"
339+
id="tint-66"
340340
>
341341
<feflood
342342
flood-color="blue"
@@ -379,7 +379,7 @@ exports[`components/Image prop "tintColor" convert to filter 1`] = `
379379
>
380380
<div
381381
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"
382-
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
382+
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-67);"
383383
/>
384384
<img
385385
alt=""
@@ -392,7 +392,7 @@ exports[`components/Image prop "tintColor" convert to filter 1`] = `
392392
>
393393
<defs>
394394
<filter
395-
id="tint-56"
395+
id="tint-67"
396396
>
397397
<feflood
398398
flood-color="red"

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

+86-17
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,24 @@ import Image from '../';
1212
import ImageLoader, { ImageUriCache } from '../../../modules/ImageLoader';
1313
import PixelRatio from '../../PixelRatio';
1414
import React from 'react';
15-
import { act, render } from '@testing-library/react';
15+
import { act, render, waitFor } from '@testing-library/react';
1616

1717
const originalImage = window.Image;
1818

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

2535
afterEach(() => {
@@ -102,10 +112,6 @@ describe('components/Image', () => {
102112

103113
describe('prop "onLoad"', () => {
104114
test('is called after image is loaded from network', () => {
105-
jest.useFakeTimers();
106-
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
107-
onLoad();
108-
});
109115
const onLoadStartStub = jest.fn();
110116
const onLoadStub = jest.fn();
111117
const onLoadEndStub = jest.fn();
@@ -117,15 +123,10 @@ describe('components/Image', () => {
117123
source="https://test.com/img.jpg"
118124
/>
119125
);
120-
jest.runOnlyPendingTimers();
121126
expect(onLoadStub).toBeCalled();
122127
});
123128

124129
test('is called after image is loaded from cache', () => {
125-
jest.useFakeTimers();
126-
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
127-
onLoad();
128-
});
129130
const onLoadStartStub = jest.fn();
130131
const onLoadStub = jest.fn();
131132
const onLoadEndStub = jest.fn();
@@ -139,7 +140,6 @@ describe('components/Image', () => {
139140
source={uri}
140141
/>
141142
);
142-
jest.runOnlyPendingTimers();
143143
expect(onLoadStub).toBeCalled();
144144
ImageUriCache.remove(uri);
145145
});
@@ -223,6 +223,34 @@ describe('components/Image', () => {
223223
});
224224
});
225225

226+
describe('prop "onLoadStart"', () => {
227+
test('is called on update if "headers" are modified', () => {
228+
const onLoadStartStub = jest.fn();
229+
const { rerender } = render(
230+
<Image
231+
onLoadStart={onLoadStartStub}
232+
source={{
233+
uri: 'https://test.com/img.jpg',
234+
headers: { 'x-custom-header': 'abc123' }
235+
}}
236+
/>
237+
);
238+
act(() => {
239+
rerender(
240+
<Image
241+
onLoadStart={onLoadStartStub}
242+
source={{
243+
uri: 'https://test.com/img.jpg',
244+
headers: { 'x-custom-header': '123abc' }
245+
}}
246+
/>
247+
);
248+
});
249+
250+
expect(onLoadStartStub.mock.calls.length).toBe(2);
251+
});
252+
});
253+
226254
describe('prop "resizeMode"', () => {
227255
['contain', 'cover', 'none', 'repeat', 'stretch', undefined].forEach(
228256
(resizeMode) => {
@@ -241,7 +269,8 @@ describe('components/Image', () => {
241269
'',
242270
{},
243271
{ uri: '' },
244-
{ uri: 'https://google.com' }
272+
{ uri: 'https://google.com' },
273+
{ uri: 'https://google.com', headers: { 'x-custom-header': 'abc123' } }
245274
];
246275
sources.forEach((source) => {
247276
expect(() => render(<Image source={source} />)).not.toThrow();
@@ -257,11 +286,6 @@ describe('components/Image', () => {
257286

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

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

0 commit comments

Comments
 (0)