课程名称:web前端架构师
课程章节:第11周 第五章
主讲老师:张轩
课程内容:TDD方式给上传组件编写自定义模板
上传模板
之前测试 mock 组件使,使用的是。global.components
let wrapper: VueWrapper<any>
beforeAll(() => {
wrapper = mount(UserProfile, {
global: {
components: globalComponents
}
})
})
如果要 mock 在组件中注册的组件,需要设置 global.components.stubs。 参考文档地址 https://test-utils.vuejs.org/api/#global
const mockComponents = {
'DeleteOutlined': mockComponent,
'LoadingOutlined': mockComponent,
'FileOutlined': mockComponent
}
let wrapper: VueWrapper<any>
beforeAll(() => {
wrapper = mount(UserProfile, {
global: {
// 组件中注册的组件
stubs: mockComponents
}
})
})
接下来编写自定义模板的测试
- 支持用户传入的自定义上传状态显示
- 上传中slot 值为loading
- 上传完成为uploaded
- 默认状态为 default
要实现自定义模板的测试代码,我们需要对 vue.js 的插槽功能有所了解,文档地址[https://cn.vuejs.org/guide/components/slots.html#named-slots]
下面定义自定义模板上传插槽的名称
- 上传。loading ,默认为一个上传按钮
- 上传成功。uploaded,默认为一个上传按钮
- 上传中 loading。默认为一个正在上传按钮
下面编写测试代码
t.only('should show the correct interface when using custom slot', async () => {
const url = 'test.url'
request.mockResolvedValueOnce({ data: { url } })
wrapper = mount(UploadFile, {
slots: {
default: '<button>Custom button</button>',
loading: '<div class="loading">custom loading</div>',
uploaded: `<template #uploaded="{uploadedData}">
<div class="custom-loaded">{{uploadedData.status}}</div>
</template>`
}
})
// 默认状态为 slot default 的内容
expect(wrapper.get('button').text()).toBe('Custom button')
const fileInput = wrapper.get('input').element as HTMLInputElement
// 点击上传文件,为 loading 状态
setInputVal(fileInput)
await wrapper.get('input').trigger('change')
expect(wrapper.get('.loading').text()).toBe('custom loading')
await flushPromises()
// 上传成功
expect(wrapper.get('.custom-loaded').text()).toBe('success')
})
然后根据测试编写代码,跑通每一项测试
<script setup lang="ts">
import { reactive, ref, computed } from 'vue'
import request from '@/utils/request'
type UploadStatus = 'ready' | 'loading' | 'success' | 'fail'
interface FileItem {
id: string
name: string
size: number
status:UploadStatus
raw: File
res?: any
}
const fileRef = ref<HTMLInputElement | null>(null)
// const uploadStatus = ref<UploadStatus>('ready')
const uploadFiles = reactive<FileItem[]>([])
const isUploading = computed(() => uploadFiles.some(file => file.status === 'loading'))
const loadStatus = ref<UploadStatus>('ready')
async function uploadFile (e: Event) {
const target = e.target as HTMLInputElement
const files = target.files
if (files) {
const uploadFile = files[0]
const formData = new FormData()
console.log(formData, target.name)
formData.append(target.name, uploadFile)
const fileObj = reactive<FileItem>({
id: '' + Date.now(),
name: uploadFile.name,
size: uploadFile.size,
status: 'loading',
raw: uploadFile
})
uploadFiles.push(fileObj)
try {
fileObj.status = 'loading'
loadStatus.value = 'loading'
const res = await request(formData)
fileObj.status = 'success'
loadStatus.value = 'success'
// fileObj.res = res.data
console.log(res)
} catch (e) {
fileObj.status = 'fail'
loadStatus.value = 'fail'
} finally {
(fileRef.value as HTMLInputElement).value = ''
}
}
}
function triggerUpload () {
fileRef.value?.click()
}
function delFile (index: number) {
uploadFiles.splice(index, 1)
}
</script>
<template>
<input
type="file"
name="file"
ref="fileRef"
:style="{display: 'none'}"
@change="uploadFile"
>
<div
@click="triggerUpload"
>
<slot
v-if="isUploading"
name="loading"
>
<button>正在上传</button>
</slot>
<slot
v-else-if="loadStatus==='success'"
name="uploaded"
:uploaded-data="{status: loadStatus}"
>
<button>点击1上传</button>
</slot>
<slot v-else>
<button>点击上传</button>
</slot>
</div>
<ul>
<li
v-for="(file,index) in uploadFiles"
:key="file.id"
:class="'upload-'+ file.status"
>
<span class="filename">{{ file.name }}</span>
<button
class="delete-icon"
@click="delFile(index)"
>
del
</button>
</li>
</ul>
</template>
今天再次通过 TDD 的方式来编写组件,再次体会到了 TDD 开发所带来的方便。编写完测试跑通后,基本上就可以就可以确定实现的功能已经没问题了,非常方便。过去到浏览器一步一步操作进行测试,每次修改完都需要跑完整个流程,而TDD开发每次修改完,都会自动测试之前的模块有没有问题,只要跑通测试就基本上可以确定没问题,还不容易出错。