@@ -13,14 +13,24 @@ import Image from '../';
13
13
import ImageLoader , { ImageUriCache } from '../../../modules/ImageLoader' ;
14
14
import PixelRatio from '../../PixelRatio' ;
15
15
import React from 'react' ;
16
- import { render } from '@testing-library/react' ;
16
+ import { render , waitFor } from '@testing-library/react' ;
17
17
18
18
const originalImage = window . Image ;
19
19
20
20
describe ( 'components/Image' , ( ) => {
21
21
beforeEach ( ( ) => {
22
22
ImageUriCache . _entries = { } ;
23
23
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
+ } ) ) ;
24
34
} ) ;
25
35
26
36
afterEach ( ( ) => {
@@ -106,10 +116,6 @@ describe('components/Image', () => {
106
116
107
117
describe ( 'prop "onLoad"' , ( ) => {
108
118
test ( 'is called after image is loaded from network' , ( ) => {
109
- jest . useFakeTimers ( ) ;
110
- ImageLoader . load = jest . fn ( ) . mockImplementation ( ( _ , onLoad , onError ) => {
111
- onLoad ( ) ;
112
- } ) ;
113
119
const onLoadStartStub = jest . fn ( ) ;
114
120
const onLoadStub = jest . fn ( ) ;
115
121
const onLoadEndStub = jest . fn ( ) ;
@@ -121,15 +127,10 @@ describe('components/Image', () => {
121
127
source = "https://test.com/img.jpg"
122
128
/>
123
129
) ;
124
- jest . runOnlyPendingTimers ( ) ;
125
130
expect ( onLoadStub ) . toBeCalled ( ) ;
126
131
} ) ;
127
132
128
133
test ( 'is called after image is loaded from cache' , ( ) => {
129
- jest . useFakeTimers ( ) ;
130
- ImageLoader . load = jest . fn ( ) . mockImplementation ( ( _ , onLoad , onError ) => {
131
- onLoad ( ) ;
132
- } ) ;
133
134
const onLoadStartStub = jest . fn ( ) ;
134
135
const onLoadStub = jest . fn ( ) ;
135
136
const onLoadEndStub = jest . fn ( ) ;
@@ -143,7 +144,6 @@ describe('components/Image', () => {
143
144
source = { uri }
144
145
/>
145
146
) ;
146
- jest . runOnlyPendingTimers ( ) ;
147
147
expect ( onLoadStub ) . toBeCalled ( ) ;
148
148
ImageUriCache . remove ( uri ) ;
149
149
} ) ;
@@ -227,6 +227,34 @@ describe('components/Image', () => {
227
227
} ) ;
228
228
} ) ;
229
229
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
+
230
258
describe ( 'prop "resizeMode"' , ( ) => {
231
259
[ 'contain' , 'cover' , 'none' , 'repeat' , 'stretch' , undefined ] . forEach (
232
260
( resizeMode ) => {
@@ -245,7 +273,8 @@ describe('components/Image', () => {
245
273
'' ,
246
274
{ } ,
247
275
{ uri : '' } ,
248
- { uri : 'https://google.com' }
276
+ { uri : 'https://google.com' } ,
277
+ { uri : 'https://google.com' , headers : { 'x-custom-header' : 'abc123' } }
249
278
] ;
250
279
sources . forEach ( ( source ) => {
251
280
expect ( ( ) => render ( < Image source = { source } /> ) ) . not . toThrow ( ) ;
@@ -261,11 +290,6 @@ describe('components/Image', () => {
261
290
262
291
test ( 'is set immediately if the image was preloaded' , ( ) => {
263
292
const uri = 'https://yahoo.com/favicon.ico' ;
264
- ImageLoader . load = jest
265
- . fn ( )
266
- . mockImplementationOnce ( ( _ , onLoad , onError ) => {
267
- onLoad ( ) ;
268
- } ) ;
269
293
return Image . prefetch ( uri ) . then ( ( ) => {
270
294
const source = { uri } ;
271
295
const { container } = render ( < Image source = { source } /> , {
@@ -346,6 +370,51 @@ describe('components/Image', () => {
346
370
'http://localhost/static/[email protected] '
347
371
) ;
348
372
} ) ;
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
+ } ) ;
349
418
} ) ;
350
419
351
420
describe ( 'prop "style"' , ( ) => {
0 commit comments