@@ -12,14 +12,24 @@ import Image from '../';
1212import ImageLoader , { ImageUriCache } from '../../../modules/ImageLoader' ;
1313import PixelRatio from '../../PixelRatio' ;
1414import React from 'react' ;
15- import { act , render } from '@testing-library/react' ;
15+ import { act , render , waitFor } from '@testing-library/react' ;
1616
1717const originalImage = window . Image ;
1818
1919describe ( '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/img@2x.png'
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