@@ -12,14 +12,24 @@ import Image from '../';
12
12
import ImageLoader , { ImageUriCache } from '../../../modules/ImageLoader' ;
13
13
import PixelRatio from '../../PixelRatio' ;
14
14
import React from 'react' ;
15
- import { act , render } from '@testing-library/react' ;
15
+ import { act , render , waitFor } from '@testing-library/react' ;
16
16
17
17
const originalImage = window . Image ;
18
18
19
19
describe ( 'components/Image' , ( ) => {
20
20
beforeEach ( ( ) => {
21
21
ImageUriCache . _entries = { } ;
22
22
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
+ } ) ) ;
23
33
} ) ;
24
34
25
35
afterEach ( ( ) => {
@@ -102,10 +112,6 @@ describe('components/Image', () => {
102
112
103
113
describe ( 'prop "onLoad"' , ( ) => {
104
114
test ( 'is called after image is loaded from network' , ( ) => {
105
- jest . useFakeTimers ( ) ;
106
- ImageLoader . load = jest . fn ( ) . mockImplementation ( ( _ , onLoad , onError ) => {
107
- onLoad ( ) ;
108
- } ) ;
109
115
const onLoadStartStub = jest . fn ( ) ;
110
116
const onLoadStub = jest . fn ( ) ;
111
117
const onLoadEndStub = jest . fn ( ) ;
@@ -117,15 +123,10 @@ describe('components/Image', () => {
117
123
source = "https://test.com/img.jpg"
118
124
/>
119
125
) ;
120
- jest . runOnlyPendingTimers ( ) ;
121
126
expect ( onLoadStub ) . toBeCalled ( ) ;
122
127
} ) ;
123
128
124
129
test ( 'is called after image is loaded from cache' , ( ) => {
125
- jest . useFakeTimers ( ) ;
126
- ImageLoader . load = jest . fn ( ) . mockImplementation ( ( _ , onLoad , onError ) => {
127
- onLoad ( ) ;
128
- } ) ;
129
130
const onLoadStartStub = jest . fn ( ) ;
130
131
const onLoadStub = jest . fn ( ) ;
131
132
const onLoadEndStub = jest . fn ( ) ;
@@ -139,7 +140,6 @@ describe('components/Image', () => {
139
140
source = { uri }
140
141
/>
141
142
) ;
142
- jest . runOnlyPendingTimers ( ) ;
143
143
expect ( onLoadStub ) . toBeCalled ( ) ;
144
144
ImageUriCache . remove ( uri ) ;
145
145
} ) ;
@@ -223,6 +223,34 @@ describe('components/Image', () => {
223
223
} ) ;
224
224
} ) ;
225
225
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
+
226
254
describe ( 'prop "resizeMode"' , ( ) => {
227
255
[ 'contain' , 'cover' , 'none' , 'repeat' , 'stretch' , undefined ] . forEach (
228
256
( resizeMode ) => {
@@ -241,7 +269,8 @@ describe('components/Image', () => {
241
269
'' ,
242
270
{ } ,
243
271
{ uri : '' } ,
244
- { uri : 'https://google.com' }
272
+ { uri : 'https://google.com' } ,
273
+ { uri : 'https://google.com' , headers : { 'x-custom-header' : 'abc123' } }
245
274
] ;
246
275
sources . forEach ( ( source ) => {
247
276
expect ( ( ) => render ( < Image source = { source } /> ) ) . not . toThrow ( ) ;
@@ -257,11 +286,6 @@ describe('components/Image', () => {
257
286
258
287
test ( 'is set immediately if the image was preloaded' , ( ) => {
259
288
const uri = 'https://yahoo.com/favicon.ico' ;
260
- ImageLoader . load = jest
261
- . fn ( )
262
- . mockImplementationOnce ( ( _ , onLoad , onError ) => {
263
- onLoad ( ) ;
264
- } ) ;
265
289
return Image . prefetch ( uri ) . then ( ( ) => {
266
290
const source = { uri } ;
267
291
const { container } = render ( < Image source = { source } /> , {
@@ -342,6 +366,51 @@ describe('components/Image', () => {
342
366
'http://localhost/static/[email protected] '
343
367
) ;
344
368
} ) ;
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
+ } ) ;
345
414
} ) ;
346
415
347
416
describe ( 'prop "style"' , ( ) => {
0 commit comments