From 8533f2df7d8945b1c66e3c985906d0f9be21bca9 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Fri, 3 Feb 2023 15:26:33 +0200 Subject: [PATCH] [test] Image: test headers related functionality --- .../__snapshots__/index-test.js.snap | 8 +- .../src/exports/Image/__tests__/index-test.js | 103 +++++++++++++++--- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap b/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap index 04b41e3efa..e0fe801e7e 100644 --- a/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap +++ b/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap @@ -329,14 +329,14 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`] >
{ beforeEach(() => { ImageUriCache._entries = {}; window.Image = jest.fn(() => ({})); + ImageLoader.load = jest + .fn() + .mockImplementation((source, onLoad, onError) => { + act(() => onLoad({ source })); + }); + ImageLoader.loadWithHeaders = jest.fn().mockImplementation((source) => ({ + source, + promise: Promise.resolve(`blob:${Math.random()}`), + cancel: jest.fn() + })); }); afterEach(() => { @@ -106,10 +116,6 @@ describe('components/Image', () => { describe('prop "onLoad"', () => { test('is called after image is loaded from network', () => { - jest.useFakeTimers(); - ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => { - onLoad(); - }); const onLoadStartStub = jest.fn(); const onLoadStub = jest.fn(); const onLoadEndStub = jest.fn(); @@ -121,15 +127,10 @@ describe('components/Image', () => { source="https://test.com/img.jpg" /> ); - jest.runOnlyPendingTimers(); expect(onLoadStub).toBeCalled(); }); test('is called after image is loaded from cache', () => { - jest.useFakeTimers(); - ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => { - onLoad(); - }); const onLoadStartStub = jest.fn(); const onLoadStub = jest.fn(); const onLoadEndStub = jest.fn(); @@ -143,7 +144,6 @@ describe('components/Image', () => { source={uri} /> ); - jest.runOnlyPendingTimers(); expect(onLoadStub).toBeCalled(); ImageUriCache.remove(uri); }); @@ -227,6 +227,34 @@ describe('components/Image', () => { }); }); + describe('prop "onLoadStart"', () => { + test('is called on update if "headers" are modified', () => { + const onLoadStartStub = jest.fn(); + const { rerender } = render( + + ); + act(() => { + rerender( + + ); + }); + + expect(onLoadStartStub.mock.calls.length).toBe(2); + }); + }); + describe('prop "resizeMode"', () => { ['contain', 'cover', 'none', 'repeat', 'stretch', undefined].forEach( (resizeMode) => { @@ -245,7 +273,8 @@ describe('components/Image', () => { '', {}, { uri: '' }, - { uri: 'https://google.com' } + { uri: 'https://google.com' }, + { uri: 'https://google.com', headers: { 'x-custom-header': 'abc123' } } ]; sources.forEach((source) => { expect(() => render()).not.toThrow(); @@ -261,11 +290,6 @@ describe('components/Image', () => { test('is set immediately if the image was preloaded', () => { const uri = 'https://yahoo.com/favicon.ico'; - ImageLoader.load = jest - .fn() - .mockImplementationOnce((_, onLoad, onError) => { - onLoad(); - }); return Image.prefetch(uri).then(() => { const source = { uri }; const { container } = render(, { @@ -346,6 +370,51 @@ describe('components/Image', () => { 'http://localhost/static/img@2x.png' ); }); + + test('it works with headers in 2 stages', async () => { + const uri = 'https://google.com/favicon.ico'; + const headers = { 'x-custom-header': 'abc123' }; + const source = { uri, headers }; + + // Stage 1 + const loadRequest = { + promise: Promise.resolve('blob:123'), + cancel: jest.fn(), + source + }; + + ImageLoader.loadWithHeaders.mockReturnValue(loadRequest); + + render(); + + expect(ImageLoader.loadWithHeaders).toHaveBeenCalledWith( + expect.objectContaining(source) + ); + + // Stage 2 + return waitFor(() => { + expect(ImageLoader.load).toHaveBeenCalledWith( + 'blob:123', + expect.any(Function), + expect.any(Function) + ); + }); + }); + + // A common case is `source` declared as an inline object, which cause is to be a + // new object (with the same content) each time parent component renders + test('it still loads the image if source object is changed', () => { + const uri = 'https://google.com/favicon.ico'; + const headers = { 'x-custom-header': 'abc123' }; + const { rerender } = render(); + rerender(); + + // when the underlying source didn't change we don't expect more than 1 load calls + return waitFor(() => { + expect(ImageLoader.loadWithHeaders).toHaveBeenCalledTimes(1); + expect(ImageLoader.load).toHaveBeenCalledTimes(1); + }); + }); }); describe('prop "style"', () => {