From b428f0f9c88b294dc123e4b2b95a249fb07ae98a Mon Sep 17 00:00:00 2001 From: lkinasiewicz Date: Mon, 30 Dec 2024 14:14:59 +0100 Subject: [PATCH 1/3] [ImageLoader] Add unit tests for getSize --- .../ImageLoader/__tests__/index-test.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js diff --git a/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js b/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js new file mode 100644 index 000000000..5a94e4575 --- /dev/null +++ b/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js @@ -0,0 +1,69 @@ +import ImageLoader from '../index'; + +const testImage = + 'data:image/webp;base64,UklGRkYAAABXRUJQVlA4IDoAAADwAgCdASoXABMAPi0QhkKhoQ36AAwBYllAHYAAajokAAD+/SFF//G83mta3//9QZ/5Bn/kGfp4AAAA'; +const testImageWidth = 23; +const testImageHeight = 19; + +const DefaultImage = window.Image; + +describe('ImageLoader', () => { + afterEach(() => { + window.Image = DefaultImage; + }); + + test('Success callback is called when image loads', async () => { + window.Image = MockImage; + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + ImageLoader.getSize(testImage, successCallback, failureCallback); + await jest.runAllTimers(); + expect(failureCallback).toHaveBeenCalledTimes(0); + expect(successCallback).toHaveBeenCalledTimes(1); + expect(successCallback).toHaveBeenCalledWith( + testImageWidth, + testImageHeight + ); + }); + + test('Failure callback is called when image fails to load', async () => { + window.Image = NotLoadingMockImage; + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + ImageLoader.getSize(testImage, successCallback, failureCallback); + await jest.runAllTimers(); + expect(failureCallback).toHaveBeenCalledTimes(1); + expect(successCallback).toHaveBeenCalledTimes(0); + }); +}); + +class MockImage { + constructor(width = 0, height = 0) { + this.width = width; + this.height = height; + this.naturalWidth = 0; + this.naturalHeight = 0; + this._src = ''; + } + get src() { + return this._src; + } + set src(uri) { + this._src = uri; + window.setTimeout(this.onload, 0); + } + decode() { + this.naturalWidth = testImageWidth; + this.naturalHeight = testImageHeight; + return Promise.resolve(); + } + onerror() {} + onload() {} +} + +class NotLoadingMockImage extends MockImage { + set src(uri) { + this._src = uri; + window.setTimeout(this.onerror, 0); + } +} From a46e08593c4044e275be1c834980eba6c378ee08 Mon Sep 17 00:00:00 2001 From: lkinasiewicz Date: Tue, 14 Jan 2025 10:13:32 +0100 Subject: [PATCH 2/3] [ImageLoader] Simplify getSize implementation Fixes possible infinite intervals --- .../react-native-web/src/modules/ImageLoader/index.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/react-native-web/src/modules/ImageLoader/index.js b/packages/react-native-web/src/modules/ImageLoader/index.js index 892db9929..0a25fc1a4 100644 --- a/packages/react-native-web/src/modules/ImageLoader/index.js +++ b/packages/react-native-web/src/modules/ImageLoader/index.js @@ -88,31 +88,21 @@ const ImageLoader = { success: (width: number, height: number) => void, failure: () => void ) { - let complete = false; - const interval = setInterval(callback, 16); const requestId = ImageLoader.load(uri, callback, errorCallback); - function callback() { const image = requests[`${requestId}`]; if (image) { const { naturalHeight, naturalWidth } = image; if (naturalHeight && naturalWidth) { success(naturalWidth, naturalHeight); - complete = true; } } - if (complete) { - ImageLoader.abort(requestId); - clearInterval(interval); - } } - function errorCallback() { if (typeof failure === 'function') { failure(); } ImageLoader.abort(requestId); - clearInterval(interval); } }, has(uri: string): boolean { From f8ddd30302f04fc740b12492ab87369514be9b26 Mon Sep 17 00:00:00 2001 From: lkinasiewicz Date: Tue, 14 Jan 2025 10:15:43 +0100 Subject: [PATCH 3/3] [ImageLoader] Call failure callback from getSize when image decoding fails --- .../modules/ImageLoader/__tests__/index-test.js | 16 ++++++++++++++++ .../src/modules/ImageLoader/index.js | 2 ++ 2 files changed, 18 insertions(+) diff --git a/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js b/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js index 5a94e4575..4f8daae15 100644 --- a/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/ImageLoader/__tests__/index-test.js @@ -35,6 +35,16 @@ describe('ImageLoader', () => { expect(failureCallback).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledTimes(0); }); + + test('Failure callback is called when image fails to decode', async () => { + window.Image = NotDecodingMockImage; + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + ImageLoader.getSize(testImage, successCallback, failureCallback); + await jest.runAllTimers(); + expect(failureCallback).toHaveBeenCalledTimes(1); + expect(successCallback).toHaveBeenCalledTimes(0); + }); }); class MockImage { @@ -67,3 +77,9 @@ class NotLoadingMockImage extends MockImage { window.setTimeout(this.onerror, 0); } } + +class NotDecodingMockImage extends MockImage { + decode() { + return Promise.reject(); + } +} diff --git a/packages/react-native-web/src/modules/ImageLoader/index.js b/packages/react-native-web/src/modules/ImageLoader/index.js index 0a25fc1a4..bf1dfb5a8 100644 --- a/packages/react-native-web/src/modules/ImageLoader/index.js +++ b/packages/react-native-web/src/modules/ImageLoader/index.js @@ -95,6 +95,8 @@ const ImageLoader = { const { naturalHeight, naturalWidth } = image; if (naturalHeight && naturalWidth) { success(naturalWidth, naturalHeight); + } else { + errorCallback(); } } }