From 3869c969820f4024e5ec927158c9c2759f119f95 Mon Sep 17 00:00:00 2001 From: Avan Date: Sun, 10 Nov 2024 21:46:03 +0800 Subject: [PATCH 1/8] refactor: use Promise.allSettled --- .../image-uploader/image-uploader.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/image-uploader/image-uploader.tsx b/src/components/image-uploader/image-uploader.tsx index 109bc0814e..386fdafc40 100644 --- a/src/components/image-uploader/image-uploader.tsx +++ b/src/components/image-uploader/image-uploader.tsx @@ -1,22 +1,22 @@ -import React, { forwardRef, useRef, useState, useImperativeHandle } from 'react' +import { useIsomorphicLayoutEffect, useSize, useUnmount } from 'ahooks' +import { AddOutline, CloseOutline } from 'antd-mobile-icons' import type { - ReactNode, - InputHTMLAttributes, CSSProperties, + InputHTMLAttributes, ReactElement, + ReactNode, } from 'react' -import { AddOutline, CloseOutline } from 'antd-mobile-icons' -import { mergeProps } from '../../utils/with-default-props' -import ImageViewer, { ImageViewerShowHandler } from '../image-viewer' -import PreviewItem from './preview-item' -import { usePropsValue } from '../../utils/use-props-value' -import { useIsomorphicLayoutEffect, useUnmount, useSize } from 'ahooks' -import Space from '../space' -import { NativeProps, withNativeProps } from '../../utils/native-props' +import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react' import { measureCSSLength } from '../../utils/measure-css-length' +import { NativeProps, withNativeProps } from '../../utils/native-props' +import { usePropsValue } from '../../utils/use-props-value' +import { mergeProps } from '../../utils/with-default-props' import { useConfig } from '../config-provider' -import type { ImageProps } from '../image' import Grid, { GridProps } from '../grid' +import type { ImageProps } from '../image' +import ImageViewer, { ImageViewerShowHandler } from '../image-viewer' +import Space from '../space' +import PreviewItem from './preview-item' export type TaskStatus = 'pending' | 'fail' | 'success' @@ -196,7 +196,7 @@ export const ImageUploader = forwardRef( setTasks(prev => [...getFinalTasks(prev), ...newTasks]) const newVal: ImageUploadItem[] = [] - await Promise.all( + await Promise.allSettled( newTasks.map(async (currentTask, index) => { try { const result = await props.upload(currentTask.file) From c14473c020c8d906450f2ad53b1210c6f161db11 Mon Sep 17 00:00:00 2001 From: Avan Date: Sun, 10 Nov 2024 21:59:56 +0800 Subject: [PATCH 2/8] fix: console.error test --- src/components/image-uploader/image-uploader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image-uploader/image-uploader.tsx b/src/components/image-uploader/image-uploader.tsx index 386fdafc40..4498eaf0d8 100644 --- a/src/components/image-uploader/image-uploader.tsx +++ b/src/components/image-uploader/image-uploader.tsx @@ -225,10 +225,10 @@ export const ImageUploader = forwardRef( return task }) }) - throw e + console.error(e) } }) - ).catch(error => console.error(error)) + ) setValue(prev => prev.concat(newVal)) } From 07a2cdd34372597dbf195464ff2290f6f969e17c Mon Sep 17 00:00:00 2001 From: Avan Date: Mon, 11 Nov 2024 11:38:59 +0800 Subject: [PATCH 3/8] refactor: use Promise.all --- src/components/image-uploader/image-uploader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/image-uploader/image-uploader.tsx b/src/components/image-uploader/image-uploader.tsx index 4498eaf0d8..6593cc9aa9 100644 --- a/src/components/image-uploader/image-uploader.tsx +++ b/src/components/image-uploader/image-uploader.tsx @@ -196,7 +196,7 @@ export const ImageUploader = forwardRef( setTasks(prev => [...getFinalTasks(prev), ...newTasks]) const newVal: ImageUploadItem[] = [] - await Promise.allSettled( + await Promise.all( newTasks.map(async (currentTask, index) => { try { const result = await props.upload(currentTask.file) From b48998ad94c9b3cdff73bda7df65cefbe63feb79 Mon Sep 17 00:00:00 2001 From: Avan Date: Mon, 11 Nov 2024 13:03:48 +0800 Subject: [PATCH 4/8] test: add test case --- .../tests/image-uploader.test.tsx | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/components/image-uploader/tests/image-uploader.test.tsx b/src/components/image-uploader/tests/image-uploader.test.tsx index 0d9c23322a..92484d8d7c 100644 --- a/src/components/image-uploader/tests/image-uploader.test.tsx +++ b/src/components/image-uploader/tests/image-uploader.test.tsx @@ -1,14 +1,14 @@ import React, { createRef, forwardRef, useState } from 'react' import { + act, + cleanup, + fireEvent, render, + screen, + sleep, testA11y, - fireEvent, - waitFor, userEvent, - sleep, - screen, - cleanup, - act, + waitFor, waitForElementToBeRemoved, } from 'testing' import ImageUploader, { ImageUploadItem, ImageUploaderRef } from '..' @@ -35,6 +35,19 @@ export async function mockUploadFail() { throw new Error('Fail to upload') } +export function mockUploadWithFailure(failOnCount: number) { + let count = 0 + return async () => { + count++ + if (count === failOnCount) { + throw new Error('Fail to upload') + } + return { + url: 'count: ' + count, + } + } +} + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }) function mockInputFile(file: File | File[] = mockImg) { @@ -65,19 +78,24 @@ describe('ImageUploader', () => { }) const App = forwardRef((props, ref) => { + const { onChange: propsOnChange, ...restProps } = props const [fileList, setFileList] = useState([ { url: demoSrc, }, ]) + const onChange = (newFileList: ImageUploadItem[]) => { + setFileList(newFileList) + propsOnChange?.(newFileList) + } return ( ) }) @@ -389,4 +407,23 @@ describe('ImageUploader', () => { expect(ref.current).toBeDefined() expect(ref.current?.nativeElement).toBeDefined() }) + + test('get all upload url', async () => { + const fn = jest.fn() + const mockUpload = mockUploadWithFailure(2) + + render() + + mockInputFile([ + new File(['one'], 'one.png', { type: 'image/png' }), + new File(['two'], 'two.png', { type: 'image/png' }), + new File(['three'], 'three.png', { type: 'image/png' }), + ]) + + await act(async () => { + jest.runAllTimers() + }) + + expect(fn.mock.lastCall[0].length).toBe(4) + }) }) From 9175ac273c730ba2b7eaad0078f8d292f5092655 Mon Sep 17 00:00:00 2001 From: Avan Date: Mon, 11 Nov 2024 15:22:16 +0800 Subject: [PATCH 5/8] chore: adjust code order --- .../tests/image-uploader.test.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/image-uploader/tests/image-uploader.test.tsx b/src/components/image-uploader/tests/image-uploader.test.tsx index 92484d8d7c..6689de4d84 100644 --- a/src/components/image-uploader/tests/image-uploader.test.tsx +++ b/src/components/image-uploader/tests/image-uploader.test.tsx @@ -35,19 +35,6 @@ export async function mockUploadFail() { throw new Error('Fail to upload') } -export function mockUploadWithFailure(failOnCount: number) { - let count = 0 - return async () => { - count++ - if (count === failOnCount) { - throw new Error('Fail to upload') - } - return { - url: 'count: ' + count, - } - } -} - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }) function mockInputFile(file: File | File[] = mockImg) { @@ -425,5 +412,18 @@ describe('ImageUploader', () => { }) expect(fn.mock.lastCall[0].length).toBe(4) + + function mockUploadWithFailure(failOnCount: number) { + let count = 0 + return async () => { + count++ + if (count === failOnCount) { + throw new Error('Fail to upload') + } + return { + url: 'count: ' + count, + } + } + } }) }) From 3a07d5422b0a5e2ce3a7e8ad39ee8ec68c1ba178 Mon Sep 17 00:00:00 2001 From: Avan Date: Mon, 11 Nov 2024 15:32:11 +0800 Subject: [PATCH 6/8] chore: adjust code order --- .../tests/image-uploader.test.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/image-uploader/tests/image-uploader.test.tsx b/src/components/image-uploader/tests/image-uploader.test.tsx index 6689de4d84..aa76a8b961 100644 --- a/src/components/image-uploader/tests/image-uploader.test.tsx +++ b/src/components/image-uploader/tests/image-uploader.test.tsx @@ -396,6 +396,19 @@ describe('ImageUploader', () => { }) test('get all upload url', async () => { + function mockUploadWithFailure(failOnCount: number) { + let count = 0 + return async () => { + count++ + if (count === failOnCount) { + throw new Error('Fail to upload') + } + return { + url: 'count: ' + count, + } + } + } + const fn = jest.fn() const mockUpload = mockUploadWithFailure(2) @@ -412,18 +425,5 @@ describe('ImageUploader', () => { }) expect(fn.mock.lastCall[0].length).toBe(4) - - function mockUploadWithFailure(failOnCount: number) { - let count = 0 - return async () => { - count++ - if (count === failOnCount) { - throw new Error('Fail to upload') - } - return { - url: 'count: ' + count, - } - } - } }) }) From 3d157eb6f5562c81e8de036b3300a1436b1112f5 Mon Sep 17 00:00:00 2001 From: Avan Date: Mon, 11 Nov 2024 15:43:50 +0800 Subject: [PATCH 7/8] fix: only return successful data --- src/components/image-uploader/image-uploader.tsx | 2 +- src/components/image-uploader/tests/image-uploader.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image-uploader/image-uploader.tsx b/src/components/image-uploader/image-uploader.tsx index 6593cc9aa9..5dada6834d 100644 --- a/src/components/image-uploader/image-uploader.tsx +++ b/src/components/image-uploader/image-uploader.tsx @@ -229,7 +229,7 @@ export const ImageUploader = forwardRef( } }) ) - setValue(prev => prev.concat(newVal)) + setValue(prev => prev.concat(newVal).filter(Boolean)) } const imageViewerHandlerRef = useRef(null) diff --git a/src/components/image-uploader/tests/image-uploader.test.tsx b/src/components/image-uploader/tests/image-uploader.test.tsx index aa76a8b961..960be5dddd 100644 --- a/src/components/image-uploader/tests/image-uploader.test.tsx +++ b/src/components/image-uploader/tests/image-uploader.test.tsx @@ -424,6 +424,6 @@ describe('ImageUploader', () => { jest.runAllTimers() }) - expect(fn.mock.lastCall[0].length).toBe(4) + expect(fn.mock.lastCall[0].length).toBe(3) }) }) From ff3fec038d7945012acb8e6775f88553e64b6065 Mon Sep 17 00:00:00 2001 From: Avan Date: Tue, 12 Nov 2024 20:25:34 +0800 Subject: [PATCH 8/8] test: verify the successfully uploaded file --- .../tests/image-uploader.test.tsx | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/components/image-uploader/tests/image-uploader.test.tsx b/src/components/image-uploader/tests/image-uploader.test.tsx index 960be5dddd..a0690a6825 100644 --- a/src/components/image-uploader/tests/image-uploader.test.tsx +++ b/src/components/image-uploader/tests/image-uploader.test.tsx @@ -65,12 +65,14 @@ describe('ImageUploader', () => { }) const App = forwardRef((props, ref) => { - const { onChange: propsOnChange, ...restProps } = props - const [fileList, setFileList] = useState([ - { - url: demoSrc, - }, - ]) + const { onChange: propsOnChange, defaultFileList, ...restProps } = props + const [fileList, setFileList] = useState( + defaultFileList || [ + { + url: demoSrc, + }, + ] + ) const onChange = (newFileList: ImageUploadItem[]) => { setFileList(newFileList) propsOnChange?.(newFileList) @@ -398,32 +400,44 @@ describe('ImageUploader', () => { test('get all upload url', async () => { function mockUploadWithFailure(failOnCount: number) { let count = 0 - return async () => { + return async (file: File) => { count++ - if (count === failOnCount) { + if (count === failOnCount + 1) { throw new Error('Fail to upload') } return { - url: 'count: ' + count, + url: URL.createObjectURL(file), + extra: { + fileName: file.name, + }, } } } const fn = jest.fn() - const mockUpload = mockUploadWithFailure(2) + const FAIL_INDEX = 1 + const mockUpload = mockUploadWithFailure(FAIL_INDEX) - render() + render( + + ) - mockInputFile([ - new File(['one'], 'one.png', { type: 'image/png' }), - new File(['two'], 'two.png', { type: 'image/png' }), - new File(['three'], 'three.png', { type: 'image/png' }), - ]) + const fileNameList = ['one.png', 'two.png', 'three.png'] + + mockInputFile( + fileNameList.map(name => new File([name], name, { type: 'image/png' })) + ) await act(async () => { jest.runAllTimers() }) - expect(fn.mock.lastCall[0].length).toBe(3) + expect(fn.mock.lastCall[0].length).toBe(2) + + const successFileNames = fileNameList.filter((_, i) => i !== FAIL_INDEX) + const mockInputSuccessFileNames = fn.mock.lastCall[0].map( + (item: ImageUploadItem) => item.extra.fileName + ) + expect(successFileNames).toEqual(mockInputSuccessFileNames) }) })