Files
gym-manage/gym-manage-web/src/__tests__/components/BannerManagement.test.ts
T

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)
})
})