Skip to content

Commit

Permalink
feat(components): [upload] support async data (#14015)
Browse files Browse the repository at this point in the history
* feat(components): [upload] support async data

* feat(components): [upload] support async data

* fix(components): [upload] unit test

* fix(components): [upload] unit test

* feat(components): [upload] update data docs

* fix(components): [upload] unit test

* chore: add version

---------

Co-authored-by: Hefty <yeyuqiudeng@gmail.com>
  • Loading branch information
jianjunyuu and HeftyKoo committed Sep 8, 2023
1 parent 82e9d07 commit d96555f
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/en-US/component/upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ upload/manual
| `headers` | request headers. | `Headers \| Record<string, any>` || No |
| `method` | set upload request method. | `string` | `'post'` | No |
| `multiple` | whether uploading multiple files is permitted. | `boolean` | `false` | No |
| `data` | additions options of request. | `Record<string, any>` || No |
| `data` | additions options of request. support `Awaitable` data and `Function` since v3.3.13 | `Record<string, any> \| Awaitable<Record<string, any>> \| ((rawFile: UploadRawFile) => Awaitable<Record<string, any>>)` || No |

This comment has been minimized.

Copy link
@HADB

HADB Sep 8, 2023

Contributor

It seems should be v2.3.13 instead of v3.3.13.

| `name` | key name for uploaded file. | `string` | `'file'` | No |
| `with-credentials` | whether cookies are sent. | `boolean` | `false` | No |
| `show-file-list` | whether to show the uploaded file list. | `boolean` | `true` | No |
Expand Down
112 changes: 112 additions & 0 deletions packages/components/upload/__tests__/upload.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest'
import { EVENT_CODE } from '@element-plus/constants'
import Upload from '../src/upload.vue'
import UploadContent from '../src/upload-content.vue'
import type { UploadRawFile } from '../src/upload'

const AXIOM = 'Rem is the best girl'

Expand Down Expand Up @@ -203,6 +204,117 @@ describe('<upload />', () => {
expect(keyList).toEqual(['test-file.txt', 'test-file2.txt'])
})

test('data support receive promise', async () => {
const onSuccess = vi.fn()
const onError = vi.fn()
const onRemove = vi.fn()
let requestData: any = {}

const httpRequest = vi.fn((val) => {
requestData = val?.data
return Promise.resolve()
})

const data = ref(Promise.resolve({ type: 'promise' }))

const wrapper = mount(() => (
<UploadContent
data={data.value}
multiple={true}
httpRequest={httpRequest}
onSuccess={onSuccess}
onError={onError}
onRemove={onRemove}
/>
))

const fileList = [new File(['content'], 'test-file.txt')]
mockGetFile(wrapper.find('input').element, fileList)

await wrapper.find('input').trigger('change')

await flushPromises()

expect(requestData).toEqual(await data.value)
expect(onSuccess).toHaveBeenCalled()
expect(onError).not.toHaveBeenCalled()

vi.clearAllMocks()

data.value = Promise.reject({ type: 'error promise' })
await expect(data.value).rejects.toThrowError(undefined)
await nextTick()
await nextTick()
await wrapper.find('input').trigger('change')

await flushPromises()
expect(onSuccess).not.toHaveBeenCalled()
expect(onError).not.toHaveBeenCalled()
expect(onRemove).toHaveBeenCalled()
})

test('data support receive function', async () => {
const keyList: string[] = []
const httpRequest = vi.fn((val) => {
keyList.push(val?.data?.key)
return Promise.resolve()
})

const data = vi.fn((file: UploadRawFile) => ({ key: file.name }))

const wrapper = mount(() => (
<UploadContent data={data} multiple={true} httpRequest={httpRequest} />
))

const fileList = [
new File(['content'], 'test-file.txt'),
new File(['content2'], 'test-file2.txt'),
]
mockGetFile(wrapper.find('input').element, fileList)

await wrapper.find('input').trigger('change')

await flushPromises()
await flushPromises()
await nextTick()

expect(keyList).toEqual(['test-file.txt', 'test-file2.txt'])
})

test('data support receive async function', async () => {
const keyList: string[] = []
const httpRequest = vi.fn(() => Promise.resolve())

const dataFN = vi.fn(async (file: UploadRawFile) => {
return new Promise((resolve) => {
keyList.push(file.name)
resolve({ key: file.name })
})
})

const wrapper = mount(() => (
<UploadContent
data={dataFN}
multiple={true}
httpRequest={httpRequest}
/>
))

const fileList = [
new File(['content'], 'test-file.txt'),
new File(['content2'], 'test-file2.txt'),
]
mockGetFile(wrapper.find('input').element, fileList)

await wrapper.find('input').trigger('change')

await flushPromises()

expect(dataFN).toHaveBeenCalledTimes(2)

expect(keyList).toEqual(['test-file.txt', 'test-file2.txt'])
})

test('upload files and save keyList', async () => {
const keyList: string[] = []
const beforeUpload = vi.fn((file: File) => {
Expand Down
32 changes: 25 additions & 7 deletions packages/components/upload/src/upload-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@

<script lang="ts" setup>
import { shallowRef } from 'vue'
import { isObject } from '@vue/shared'
import { isPlainObject } from '@vue/shared'
import { cloneDeep, isEqual } from 'lodash-unified'
import { useNamespace } from '@element-plus/hooks'
import { entriesOf } from '@element-plus/utils'
import { entriesOf, isFunction } from '@element-plus/utils'
import { useFormDisabled } from '@element-plus/components/form'
import UploadDragger from './upload-dragger.vue'
import { uploadContentProps } from './upload-content'
Expand Down Expand Up @@ -81,7 +81,7 @@ const uploadFiles = (files: File[]) => {
}
}
const upload = async (rawFile: UploadRawFile) => {
const upload = async (rawFile: UploadRawFile): Promise<void> => {
inputRef.value!.value = ''
if (!props.beforeUpload) {
Expand All @@ -95,9 +95,9 @@ const upload = async (rawFile: UploadRawFile) => {
// origin data: Handle data changes after synchronization tasks are executed
const originData = props.data
const beforeUploadPromise = props.beforeUpload(rawFile)
beforeData = isObject(props.data) ? cloneDeep(props.data) : props.data
beforeData = isPlainObject(props.data) ? cloneDeep(props.data) : props.data
hookResult = await beforeUploadPromise
if (isObject(props.data) && isEqual(originData, beforeData)) {
if (isPlainObject(props.data) && isEqual(originData, beforeData)) {
beforeData = cloneDeep(props.data)
}
} catch {
Expand Down Expand Up @@ -128,7 +128,18 @@ const upload = async (rawFile: UploadRawFile) => {
)
}
const doUpload = (
const resolveData = async (
data: UploadContentProps['data'],
rawFile: UploadRawFile
): Promise<Record<string, any>> => {
if (isFunction(data)) {
return data(rawFile)
}
return data
}
const doUpload = async (
rawFile: UploadRawFile,
beforeData?: UploadContentProps['data']
) => {
Expand All @@ -145,12 +156,19 @@ const doUpload = (
httpRequest,
} = props
try {
beforeData = await resolveData(beforeData ?? data, rawFile)
} catch {
props.onRemove(rawFile)
return
}
const { uid } = rawFile
const options: UploadRequestOptions = {
headers: headers || {},
withCredentials,
file: rawFile,
data: beforeData ?? data,
data: beforeData,
method,
filename,
action,
Expand Down
9 changes: 7 additions & 2 deletions packages/components/upload/src/upload.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NOOP } from '@vue/shared'
import { buildProps, definePropType, mutable } from '@element-plus/utils'
import { ajaxUpload } from './ajax'
import type { Awaitable, Mutable } from '@element-plus/utils'

import type { UploadAjaxError } from './ajax'
import type { Awaitable } from '@element-plus/utils'
import type { ExtractPropTypes } from 'vue'
import type Upload from './upload.vue'

Expand Down Expand Up @@ -78,6 +78,8 @@ export interface UploadHooks {
onExceed: (files: File[], uploadFiles: UploadUserFile[]) => void
}

export type UploadData = Mutable<Record<string, any>>

export const uploadBaseProps = buildProps({
action: {
type: String,
Expand All @@ -91,7 +93,10 @@ export const uploadBaseProps = buildProps({
default: 'post',
},
data: {
type: Object,
type: definePropType<
| Awaitable<UploadData>
| ((rawFile: UploadRawFile) => Awaitable<UploadData>)
>([Object, Function, Promise]),
default: () => mutable({} as const),
},
multiple: {
Expand Down

0 comments on commit d96555f

Please sign in to comment.