372 lines
11 KiB
TypeScript
372 lines
11 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import { createRouter, createMemoryHistory } from 'vue-router'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import BannerManagement from '@/views/banner/BannerManagement.vue'
|
|
|
|
// ===== Mock Element Plus =====
|
|
vi.mock('element-plus', () => ({
|
|
ElMessage: { success: vi.fn(), error: vi.fn(), warning: vi.fn() },
|
|
ElMessageBox: { confirm: vi.fn() },
|
|
}))
|
|
|
|
// ===== Mock @element-plus/icons-vue =====
|
|
vi.mock('@element-plus/icons-vue', () => ({
|
|
Plus: { template: '<svg/>' },
|
|
}))
|
|
|
|
// ===== Mock banner.api =====
|
|
vi.mock('@/api/banner.api', () => ({
|
|
bannerApi: {
|
|
getAll: vi.fn().mockResolvedValue([
|
|
{ id: 1, imageUrl: '/api/files/1/preview', title: '轮播图1', subtitle: '副标题1', sortOrder: 1, isActive: true, createdAt: '2025-01-01T00:00:00' },
|
|
{ id: 2, imageUrl: '/api/files/2/preview', title: '轮播图2', subtitle: '副标题2', sortOrder: 2, isActive: false, createdAt: '2025-01-02T00:00:00' },
|
|
]),
|
|
create: vi.fn().mockResolvedValue({}),
|
|
update: vi.fn().mockResolvedValue({}),
|
|
delete: vi.fn().mockResolvedValue({}),
|
|
},
|
|
}))
|
|
|
|
// ===== Mock request =====
|
|
vi.mock('@/utils/request', () => {
|
|
const mockRequest = {
|
|
get: vi.fn().mockResolvedValue({}),
|
|
post: vi.fn().mockResolvedValue({}),
|
|
put: vi.fn().mockResolvedValue({}),
|
|
delete: vi.fn().mockResolvedValue({}),
|
|
}
|
|
return { default: mockRequest }
|
|
})
|
|
|
|
// ===== Mock dateFormat =====
|
|
vi.mock('@/utils/dateFormat', () => ({
|
|
formatDateTime: vi.fn((val: string) => val || '-'),
|
|
}))
|
|
|
|
// ===== Mock URL.createObjectURL =====
|
|
if (!globalThis.URL.createObjectURL) {
|
|
// @ts-ignore
|
|
globalThis.URL.createObjectURL = vi.fn(() => `blob:mock-${Math.random()}`)
|
|
// @ts-ignore
|
|
globalThis.URL.revokeObjectURL = vi.fn()
|
|
}
|
|
|
|
describe('BannerManagement Component', () => {
|
|
let router: any
|
|
let wrapper: any
|
|
|
|
const defaultStubs = {
|
|
'el-button': { template: '<button @click="$emit(\'click\')"><slot/></button>' },
|
|
'el-card': { template: '<div class="el-card"><slot/><slot name="header"/></div>' },
|
|
'el-input': { template: '<input/>', props: ['modelValue'], emits: ['update:modelValue'] },
|
|
'el-icon': { template: '<svg/>' },
|
|
'el-table': { template: '<div class="el-table"><slot/></div>', props: ['data', 'loading'] },
|
|
'el-table-column': true,
|
|
'el-tag': { template: '<span class="el-tag"><slot/></span>', props: ['type', 'effect', 'size'] },
|
|
'el-dialog': { template: '<div v-if="modelValue" class="el-dialog"><slot/><slot name="footer"/></div>', props: ['modelValue', 'title', 'width'] },
|
|
'el-form': { template: '<form><slot/></form>', props: ['model', 'rules', 'label-width', 'ref'] },
|
|
'el-form-item': { template: '<div class="el-form-item"><slot/></div>', props: ['label', 'prop'] },
|
|
'el-upload': { template: '<div class="el-upload-stub"><slot/></div>', props: ['http-request', 'show-file-list', 'before-upload', 'accept'] },
|
|
'el-input-number': true,
|
|
'el-switch': true,
|
|
'el-image': true,
|
|
}
|
|
|
|
beforeEach(() => {
|
|
const pinia = createPinia()
|
|
setActivePinia(pinia)
|
|
|
|
router = createRouter({
|
|
history: createMemoryHistory(),
|
|
routes: [{ path: '/', component: { template: '<div/>' } }],
|
|
})
|
|
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
afterEach(() => {
|
|
if (wrapper) wrapper.unmount()
|
|
})
|
|
|
|
// ==================== 组件初始化 ====================
|
|
|
|
it('应该渲染轮播图管理容器', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.find('.banner-management').exists()).toBe(true)
|
|
})
|
|
|
|
it('应该渲染页面标题', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.html()).toContain('轮播图管理')
|
|
})
|
|
|
|
it('应该渲染新增轮播图按钮', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.html()).toContain('新增轮播图')
|
|
})
|
|
|
|
// ==================== 初始状态 ====================
|
|
|
|
it('初始 loading 状态为 false', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.loading).toBe(false)
|
|
})
|
|
|
|
it('初始 modalVisible 为 false', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.modalVisible).toBe(false)
|
|
})
|
|
|
|
it('初始 dataSource 为空数组', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.dataSource).toBeDefined()
|
|
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
|
|
})
|
|
|
|
// ==================== 方法存在性 ====================
|
|
|
|
it('应该暴露 handleAdd 方法', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(typeof wrapper.vm.handleAdd).toBe('function')
|
|
})
|
|
|
|
it('应该暴露 handleEdit 方法', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(typeof wrapper.vm.handleEdit).toBe('function')
|
|
})
|
|
|
|
it('应该暴露 handleDelete 方法', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(typeof wrapper.vm.handleDelete).toBe('function')
|
|
})
|
|
|
|
it('应该暴露 handleUpload 方法', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(typeof wrapper.vm.handleUpload).toBe('function')
|
|
})
|
|
|
|
it('应该暴露 beforeUpload 方法', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(typeof wrapper.vm.beforeUpload).toBe('function')
|
|
})
|
|
|
|
// ==================== 新增/编辑表单 ====================
|
|
|
|
it('点击新增后 modalVisible 应为 true', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
wrapper.vm.handleAdd()
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.vm.modalVisible).toBe(true)
|
|
expect(wrapper.vm.modalTitle).toBe('新增轮播图')
|
|
})
|
|
|
|
it('新增时表单 title 应为空', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
wrapper.vm.handleAdd()
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.vm.formState.title).toBe('')
|
|
})
|
|
|
|
it('新增时 sortOrder 应为 0', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
wrapper.vm.handleAdd()
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.vm.formState.sortOrder).toBe(0)
|
|
})
|
|
|
|
it('新增时 isActive 应为 true', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
wrapper.vm.handleAdd()
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.vm.formState.isActive).toBe(true)
|
|
})
|
|
|
|
// ==================== 表单规则 ====================
|
|
|
|
it('应该定义 imageUrl 必填规则', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.formRules.imageUrl).toBeDefined()
|
|
expect(wrapper.vm.formRules.imageUrl[0].required).toBe(true)
|
|
})
|
|
|
|
it('应该定义 title 必填规则', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.formRules.title).toBeDefined()
|
|
expect(wrapper.vm.formRules.title[0].required).toBe(true)
|
|
})
|
|
|
|
it('应该定义 sortOrder 必填规则', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.formRules.sortOrder).toBeDefined()
|
|
})
|
|
|
|
// ==================== extractFileId ====================
|
|
|
|
it('extractFileId 应能提取 /api/files/{id}/preview 格式的 ID', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.extractFileId('/api/files/123/preview')).toBe('123')
|
|
})
|
|
|
|
it('extractFileId 应对纯数字字符串返回该数字', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.extractFileId('456')).toBe('456')
|
|
})
|
|
|
|
it('extractFileId 应对 null/undefined 返回 null', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.extractFileId(null)).toBe(null)
|
|
expect(wrapper.vm.extractFileId(undefined)).toBe(null)
|
|
})
|
|
|
|
// ==================== beforeUpload ====================
|
|
|
|
it('beforeUpload 应拒绝非图片文件', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
const { ElMessage } = await import('element-plus')
|
|
|
|
const result = wrapper.vm.beforeUpload(new File([''], 'test.txt', { type: 'text/plain' }))
|
|
expect(result).toBe(false)
|
|
expect(ElMessage.error).toHaveBeenCalled()
|
|
})
|
|
|
|
it('beforeUpload 应拒绝超过 5MB 的图片', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
const { ElMessage } = await import('element-plus')
|
|
|
|
const largeFile = new File([''], 'large.jpg', { type: 'image/jpeg' })
|
|
Object.defineProperty(largeFile, 'size', { value: 6 * 1024 * 1024 })
|
|
const result = wrapper.vm.beforeUpload(largeFile)
|
|
expect(result).toBe(false)
|
|
expect(ElMessage.error).toHaveBeenCalled()
|
|
})
|
|
|
|
it('beforeUpload 应接受合法图片', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
|
|
const validFile = new File([''], 'small.jpg', { type: 'image/jpeg' })
|
|
const result = wrapper.vm.beforeUpload(validFile)
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
// ==================== 预览 ====================
|
|
|
|
it('应该暴露 previewImage 方法', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(typeof wrapper.vm.previewImage).toBe('function')
|
|
})
|
|
|
|
it('初始 previewVisible 应为 false', async () => {
|
|
wrapper = mount(BannerManagement, {
|
|
global: { plugins: [router], stubs: defaultStubs },
|
|
})
|
|
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.previewVisible).toBe(false)
|
|
})
|
|
})
|