@@ -13,14 +13,24 @@ import Image from '../';
1313import ImageLoader , { ImageUriCache } from '../../../modules/ImageLoader' ;
1414import PixelRatio from '../../PixelRatio' ;
1515import React from 'react' ;
16- import { render } from '@testing-library/react' ;
16+ import { render , waitFor } from '@testing-library/react' ;
1717
1818const originalImage = window . Image ;
1919
2020describe ( '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