feat(web): 迁移前端源代码(任务 T4.1)

- 删除 novalon 前端 src/ 下所有文件
- 从 gym-manage 复制前端 src/ 完整目录树
- 替换 gym-manage-api → novalon-manage-api
- 替换 gym_system → manage_system
- 无 gym 残留引用
This commit is contained in:
张翔
2026-04-27 14:57:45 +08:00
parent f18e904e65
commit f0746d06db
31 changed files with 105 additions and 48 deletions
@@ -0,0 +1,267 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import ConfigManagement from '@/views/config/ConfigManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/utils/request', () => {
const mockRequest = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
}
mockRequest.get.mockResolvedValue([])
mockRequest.post.mockResolvedValue({})
mockRequest.put.mockResolvedValue({})
mockRequest.delete.mockResolvedValue({})
return {
default: mockRequest,
}
})
describe('ConfigManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render config management container', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(wrapper.find('.config-management').exists()).toBe(true)
})
it('should initialize with empty data source', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.dataSource).toBeDefined()
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
})
it('should initialize with loading state false', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.loading).toBeDefined()
expect(typeof wrapper.vm.loading).toBe('boolean')
})
it('should initialize with modal visible false', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.modalVisible).toBe(false)
})
it('should initialize with empty form state', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.formState.configName).toBe('')
expect(wrapper.vm.formState.configKey).toBe('')
expect(wrapper.vm.formState.configValue).toBe('')
})
})
describe('add config functionality', () => {
it('should have handleAdd method', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleAdd).toBe('function')
})
})
describe('edit config functionality', () => {
it('should have handleEdit method', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleEdit).toBe('function')
})
})
describe('delete config functionality', () => {
it('should have handleDelete method', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleDelete).toBe('function')
})
})
describe('form submission', () => {
it('should have handleModalOk method', () => {
wrapper = mount(ConfigManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleModalOk).toBe('function')
})
})
})
@@ -0,0 +1,261 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import Dashboard from '@/views/system/Dashboard.vue'
vi.mock('vue-router')
vi.mock('@/api/user.api.ts', () => ({
getUserStats: vi.fn(),
getRecentLogins: vi.fn(),
}))
describe('Dashboard Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Dashboard</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render dashboard container', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.find('.dashboard').exists()).toBe(true)
})
it('should initialize with loading state', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.loading).toBe(true)
})
it('should initialize with empty stats', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.stats).toEqual({
userCount: 0,
roleCount: 0,
todayLogin: 0,
operationLog: 0,
})
})
})
describe('statistics cards', () => {
it('should render user count card', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.stats.userCount).toBeDefined()
})
it('should render role count card', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.stats.roleCount).toBeDefined()
})
it('should render today login card', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.stats.todayLogin).toBeDefined()
})
it('should render operation log card', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.stats.operationLog).toBeDefined()
})
})
describe('recent logins', () => {
it('should initialize with empty recent logins', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.recentLogins).toEqual([])
})
it('should display empty state when no recent logins', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.recentLogins.length).toBe(0)
})
})
describe('data loading', () => {
it('should set loading to false after data loaded', async () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.vm.loading).toBe(true)
wrapper.vm.loading = false
await wrapper.vm.$nextTick()
expect(wrapper.vm.loading).toBe(false)
})
})
describe('document title', () => {
it('should have dashboard component mounted', () => {
wrapper = mount(Dashboard, {
global: {
plugins: [router],
stubs: {
'el-row': true,
'el-col': true,
'el-card': true,
'el-statistic': true,
'el-icon': true,
'el-timeline': true,
'el-timeline-item': true,
},
},
})
expect(wrapper.exists()).toBe(true)
})
})
})
@@ -0,0 +1,286 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import DictManagement from '@/views/config/DictManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/utils/request', () => {
const mockRequest = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
}
mockRequest.get.mockResolvedValue([])
mockRequest.post.mockResolvedValue({})
mockRequest.put.mockResolvedValue({})
mockRequest.delete.mockResolvedValue({})
return {
default: mockRequest,
}
})
describe('DictManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render dict management container', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.find('.dict-management').exists()).toBe(true)
})
it('should initialize with empty data source', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.dataSource).toBeDefined()
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
})
it('should initialize with loading state false', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.loading).toBeDefined()
expect(typeof wrapper.vm.loading).toBe('boolean')
})
it('should initialize with modal visible false', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.modalVisible).toBe(false)
})
it('should initialize with empty form state', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.formState.dictName).toBe('')
expect(wrapper.vm.formState.dictType).toBe('')
expect(wrapper.vm.formState.status).toBe('0')
expect(wrapper.vm.formState.remark).toBe('')
})
})
describe('add dict functionality', () => {
it('should have handleAdd method', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleAdd).toBe('function')
})
})
describe('edit dict functionality', () => {
it('should have handleEdit method', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleEdit).toBe('function')
})
})
describe('delete dict functionality', () => {
it('should have handleDelete method', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleDelete).toBe('function')
})
})
describe('form submission', () => {
it('should have handleModalOk method', () => {
wrapper = mount(DictManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleModalOk).toBe('function')
})
})
})
@@ -0,0 +1,257 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import ExceptionLog from '@/views/audit/ExceptionLog.vue'
vi.mock('vue-router')
vi.mock('@/api/exceptionLog', () => ({
exceptionLogApi: {
getPage: vi.fn().mockResolvedValue({
content: [
{ id: 1, username: 'admin', operation: '用户登录', method: 'POST /api/auth/login', errorMsg: 'NullPointerException', ip: '192.168.1.1', createTime: '2026-01-01T10:00:00' },
{ id: 2, username: 'user', operation: '文件上传', method: 'POST /api/files/upload', errorMsg: 'FileSizeLimitExceededException', ip: '192.168.1.2', createTime: '2026-01-02T11:00:00' },
],
totalElements: 2,
}),
},
}))
describe('ExceptionLog Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render exception log container', () => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
expect(wrapper.find('.exception-log').exists()).toBe(true)
})
it('should initialize with empty search keyword', () => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
expect(wrapper.vm.searchKeyword).toBe('')
})
it('should initialize with correct pagination defaults', () => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
expect(wrapper.vm.pagination.current).toBe(1)
expect(wrapper.vm.pagination.pageSize).toBe(10)
expect(wrapper.vm.pagination.total).toBe(0)
})
it('should initialize with hidden detail dialog', () => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
expect(wrapper.vm.detailVisible).toBe(false)
})
})
describe('detail view handling', () => {
beforeEach(() => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
})
it('should show detail dialog when viewing exception', () => {
const exception = {
id: 1,
username: 'admin',
operation: '用户登录',
method: 'POST /api/auth/login',
errorMsg: 'NullPointerException',
ip: '192.168.1.1',
createTime: '2026-01-01T10:00:00',
}
wrapper.vm.handleViewDetail(exception)
expect(wrapper.vm.detailVisible).toBe(true)
expect(wrapper.vm.currentDetail).toEqual(exception)
})
it('should create a copy of exception data for detail view', () => {
const exception = {
id: 1,
username: 'admin',
}
wrapper.vm.handleViewDetail(exception)
wrapper.vm.currentDetail.username = 'modified'
expect(exception.username).toBe('admin')
})
})
describe('sort handling', () => {
beforeEach(() => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
})
it('should update sort info on ascending order', () => {
wrapper.vm.handleSortChange({ prop: 'username', order: 'ascending' })
expect(wrapper.vm.sortInfo.sort).toBe('username')
expect(wrapper.vm.sortInfo.order).toBe('asc')
})
it('should update sort info on descending order', () => {
wrapper.vm.handleSortChange({ prop: 'createTime', order: 'descending' })
expect(wrapper.vm.sortInfo.sort).toBe('createTime')
expect(wrapper.vm.sortInfo.order).toBe('desc')
})
})
describe('pagination handling', () => {
beforeEach(() => {
wrapper = mount(ExceptionLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
'el-dialog': true,
'el-descriptions': true,
'el-descriptions-item': true,
},
},
})
})
it('should reset to first page on size change', () => {
wrapper.vm.pagination.current = 5
wrapper.vm.handleSizeChange()
expect(wrapper.vm.pagination.current).toBe(1)
})
it('should reset to first page on search', () => {
wrapper.vm.pagination.current = 5
wrapper.vm.handleSearch()
expect(wrapper.vm.pagination.current).toBe(1)
})
})
})
@@ -0,0 +1,247 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import FileManagement from '@/views/file/FileManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/utils/request', () => {
const mockRequest = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
}
mockRequest.get.mockResolvedValue([
{ id: 1, fileName: 'test.pdf', fileSize: 1024, fileType: 'application/pdf', storageType: 'local', createdAt: '2026-01-01', createBy: 'admin' },
{ id: 2, fileName: 'image.png', fileSize: 2048, fileType: 'image/png', storageType: 'local', createdAt: '2026-01-02', createBy: 'user' },
])
mockRequest.post.mockResolvedValue({})
mockRequest.put.mockResolvedValue({})
mockRequest.delete.mockResolvedValue({})
return {
default: mockRequest,
}
})
describe('FileManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render file management container', () => {
wrapper = mount(FileManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-upload': true,
'el-tag': true,
'el-icon': true,
},
},
})
expect(wrapper.find('.file-management').exists()).toBe(true)
})
it('should initialize with empty search keyword', () => {
wrapper = mount(FileManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-upload': true,
'el-tag': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.searchKeyword).toBe('')
})
it('should initialize with loading state false before data fetch', async () => {
wrapper = mount(FileManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-upload': true,
'el-tag': true,
'el-icon': true,
},
},
})
await wrapper.vm.$nextTick()
expect([true, false]).toContain(wrapper.vm.loading)
})
})
describe('file type utilities', () => {
beforeEach(() => {
wrapper = mount(FileManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-upload': true,
'el-tag': true,
'el-icon': true,
},
},
})
})
it('should return correct file type name for images', () => {
expect(wrapper.vm.getFileTypeName('image/png')).toBe('图片')
expect(wrapper.vm.getFileTypeName('image/jpeg')).toBe('图片')
})
it('should return correct file type name for videos', () => {
expect(wrapper.vm.getFileTypeName('video/mp4')).toBe('视频')
})
it('should return correct file type name for audio', () => {
expect(wrapper.vm.getFileTypeName('audio/mp3')).toBe('音频')
})
it('should return correct file type name for PDF', () => {
expect(wrapper.vm.getFileTypeName('application/pdf')).toBe('PDF')
})
it('should return correct file type name for Word', () => {
expect(wrapper.vm.getFileTypeName('application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toBe('Word')
})
it('should return correct file type name for Excel', () => {
expect(wrapper.vm.getFileTypeName('application/vnd.ms-excel')).toBe('Excel')
})
it('should return unknown for unknown file types', () => {
expect(wrapper.vm.getFileTypeName('')).toBe('未知')
expect(wrapper.vm.getFileTypeName('unknown/type')).toBe('其他')
})
it('should return correct tag type for images', () => {
expect(wrapper.vm.getFileTypeTag('image/png')).toBe('success')
})
it('should return correct tag type for videos', () => {
expect(wrapper.vm.getFileTypeTag('video/mp4')).toBe('danger')
})
it('should return correct tag type for audio', () => {
expect(wrapper.vm.getFileTypeTag('audio/mp3')).toBe('warning')
})
it('should return correct tag type for PDF', () => {
expect(wrapper.vm.getFileTypeTag('application/pdf')).toBe('danger')
})
it('should return correct tag type for unknown', () => {
expect(wrapper.vm.getFileTypeTag('')).toBe('info')
})
})
describe('search functionality', () => {
beforeEach(() => {
wrapper = mount(FileManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-upload': true,
'el-tag': true,
'el-icon': true,
},
},
})
})
it('should filter files by search keyword', async () => {
wrapper.vm.dataSource = [
{ id: 1, fileName: 'test.pdf' },
{ id: 2, fileName: 'image.png' },
{ id: 3, fileName: 'document.doc' },
]
wrapper.vm.searchKeyword = 'test'
await wrapper.vm.$nextTick()
expect(wrapper.vm.filteredDataSource.length).toBe(1)
expect(wrapper.vm.filteredDataSource[0].fileName).toBe('test.pdf')
})
it('should return all files when search keyword is empty', () => {
wrapper.vm.dataSource = [
{ id: 1, fileName: 'test.pdf' },
{ id: 2, fileName: 'image.png' },
]
wrapper.vm.searchKeyword = ''
expect(wrapper.vm.filteredDataSource.length).toBe(2)
})
it('should be case insensitive when searching', () => {
wrapper.vm.dataSource = [
{ id: 1, fileName: 'TEST.pdf' },
{ id: 2, fileName: 'image.png' },
]
wrapper.vm.searchKeyword = 'test'
expect(wrapper.vm.filteredDataSource.length).toBe(1)
})
})
})
@@ -0,0 +1,186 @@
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 Login from '@/views/system/Login.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
}))
vi.mock('@/utils/request', () => ({
default: {
post: vi.fn(),
},
}))
describe('Login Component', () => {
let router: any
let wrapper: any
let pinia: any
beforeEach(() => {
pinia = createPinia()
setActivePinia(pinia)
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Dashboard</div>' } },
{ path: '/login', component: { template: '<div>Login</div>' } },
],
})
vi.clearAllMocks()
localStorage.clear()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component rendering', () => {
it('should render login form', () => {
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
expect(wrapper.find('.login-container').exists()).toBe(true)
expect(wrapper.find('.login-card').exists()).toBe(true)
})
it('should initialize with empty form state', () => {
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
expect(wrapper.vm.formState.username).toBe('')
expect(wrapper.vm.formState.password).toBe('')
})
it('should initialize loading as false', () => {
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
expect(wrapper.vm.loading).toBe(false)
})
})
describe('form state management', () => {
it('should update username when input changes', async () => {
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
wrapper.vm.formState.username = 'testuser'
await wrapper.vm.$nextTick()
expect(wrapper.vm.formState.username).toBe('testuser')
})
it('should update password when input changes', async () => {
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
wrapper.vm.formState.password = 'password123'
await wrapper.vm.$nextTick()
expect(wrapper.vm.formState.password).toBe('password123')
})
})
describe('form submission', () => {
it('should have onFinish method', () => {
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
expect(typeof wrapper.vm.onFinish).toBe('function')
})
})
describe('document title', () => {
it('should set document title on mount', () => {
const originalTitle = document.title
wrapper = mount(Login, {
global: {
plugins: [router, pinia],
stubs: {
'el-card': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-button': true,
},
},
})
expect(document.title).toBe('登录 - Novalon 管理系统')
document.title = originalTitle
})
})
})
@@ -0,0 +1,195 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import LoginLog from '@/views/audit/LoginLog.vue'
vi.mock('vue-router')
vi.mock('@/utils/request', () => {
const mockRequest = {
get: vi.fn().mockResolvedValue({
content: [
{ id: 1, username: 'admin', ip: '192.168.1.1', location: '北京', browser: 'Chrome', os: 'Windows', status: '0', loginTime: '2026-01-01T10:00:00' },
{ id: 2, username: 'user', ip: '192.168.1.2', location: '上海', browser: 'Firefox', os: 'MacOS', status: '1', loginTime: '2026-01-02T11:00:00' },
],
totalElements: 2,
}),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
}
return {
default: mockRequest,
}
})
describe('LoginLog Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render login log container', () => {
wrapper = mount(LoginLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
},
},
})
expect(wrapper.find('.login-log').exists()).toBe(true)
})
it('should initialize with empty search keyword', () => {
wrapper = mount(LoginLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
},
},
})
expect(wrapper.vm.searchKeyword).toBe('')
})
it('should initialize with correct pagination defaults', () => {
wrapper = mount(LoginLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
},
},
})
expect(wrapper.vm.pagination.current).toBe(1)
expect(wrapper.vm.pagination.pageSize).toBe(10)
expect(wrapper.vm.pagination.total).toBe(0)
})
it('should initialize with correct sort defaults', () => {
wrapper = mount(LoginLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
},
},
})
expect(wrapper.vm.sortInfo.sort).toBe('id')
expect(wrapper.vm.sortInfo.order).toBe('asc')
})
})
describe('sort handling', () => {
beforeEach(() => {
wrapper = mount(LoginLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
},
},
})
})
it('should update sort info on ascending order', () => {
wrapper.vm.handleSortChange({ prop: 'username', order: 'ascending' })
expect(wrapper.vm.sortInfo.sort).toBe('username')
expect(wrapper.vm.sortInfo.order).toBe('asc')
})
it('should update sort info on descending order', () => {
wrapper.vm.handleSortChange({ prop: 'loginTime', order: 'descending' })
expect(wrapper.vm.sortInfo.sort).toBe('loginTime')
expect(wrapper.vm.sortInfo.order).toBe('desc')
})
})
describe('pagination handling', () => {
beforeEach(() => {
wrapper = mount(LoginLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-pagination': true,
},
},
})
})
it('should reset to first page on size change', () => {
wrapper.vm.pagination.current = 5
wrapper.vm.handleSizeChange()
expect(wrapper.vm.pagination.current).toBe(1)
})
it('should reset to first page on search', () => {
wrapper.vm.pagination.current = 5
wrapper.vm.handleSearch()
expect(wrapper.vm.pagination.current).toBe(1)
})
})
})
@@ -0,0 +1,279 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import MenuManagement from '@/views/system/MenuManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/api/menu.api', () => ({
menuApi: {
getAll: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
}))
vi.mock('@/utils/request', () => {
const mockRequest = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
}
mockRequest.get.mockResolvedValue([])
mockRequest.post.mockResolvedValue({})
mockRequest.put.mockResolvedValue({})
mockRequest.delete.mockResolvedValue({})
return {
default: mockRequest,
}
})
describe('MenuManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render menu management container', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.find('.menu-management').exists()).toBe(true)
})
it('should initialize with empty data source', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.dataSource).toBeDefined()
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
})
it('should initialize with loading state false', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.loading).toBeDefined()
expect(typeof wrapper.vm.loading).toBe('boolean')
})
it('should initialize with modal visible false', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.modalVisible).toBe(false)
})
it('should initialize with empty form state', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.formState.menuName).toBe('')
expect(wrapper.vm.formState.menuType).toBe('C')
expect(wrapper.vm.formState.perms).toBe('')
expect(wrapper.vm.formState.component).toBe('')
expect(wrapper.vm.formState.orderNum).toBe(0)
expect(wrapper.vm.formState.status).toBe('0')
})
})
describe('add menu functionality', () => {
it('should have handleAdd method', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleAdd).toBe('function')
})
})
describe('edit menu functionality', () => {
it('should have handleEdit method', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleEdit).toBe('function')
})
})
describe('delete menu functionality', () => {
it('should have handleDelete method', () => {
wrapper = mount(MenuManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-input': true,
'el-input-number': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleDelete).toBe('function')
})
})
})
@@ -0,0 +1,231 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import NoticeManagement from '@/views/notify/NoticeManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/utils/request', () => {
const mockRequest = {
get: vi.fn().mockResolvedValue([
{ id: 1, noticeTitle: '系统维护通知', noticeType: '1', noticeContent: '系统将于今晚维护', status: '0', createdAt: '2026-01-01T10:00:00' },
{ id: 2, noticeTitle: '新功能上线', noticeType: '2', noticeContent: '新功能已上线', status: '0', createdAt: '2026-01-02T11:00:00' },
]),
post: vi.fn().mockResolvedValue({}),
put: vi.fn().mockResolvedValue({}),
delete: vi.fn().mockResolvedValue({}),
}
return {
default: mockRequest,
}
})
describe('NoticeManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render notice management container', () => {
wrapper = mount(NoticeManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
},
},
})
expect(wrapper.find('.notice-management').exists()).toBe(true)
})
it('should initialize with hidden modal', () => {
wrapper = mount(NoticeManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
},
},
})
expect(wrapper.vm.modalVisible).toBe(false)
})
it('should initialize with empty form state', () => {
wrapper = mount(NoticeManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
},
},
})
expect(wrapper.vm.formState.noticeTitle).toBe('')
expect(wrapper.vm.formState.noticeType).toBe('1')
expect(wrapper.vm.formState.status).toBe('0')
})
})
describe('add notice', () => {
beforeEach(() => {
wrapper = mount(NoticeManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
},
},
})
})
it('should show modal with add title', () => {
wrapper.vm.handleAdd()
expect(wrapper.vm.modalTitle).toBe('新增公告')
expect(wrapper.vm.modalVisible).toBe(true)
})
it('should reset form state when adding', () => {
wrapper.vm.formState.noticeTitle = 'existing title'
wrapper.vm.handleAdd()
expect(wrapper.vm.formState.noticeTitle).toBe('')
expect(wrapper.vm.formState.id).toBe(null)
})
})
describe('edit notice', () => {
beforeEach(() => {
wrapper = mount(NoticeManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
},
},
})
})
it('should show modal with edit title', () => {
const notice = { id: 1, noticeTitle: 'Test', noticeType: '1', noticeContent: 'Content', status: '0' }
wrapper.vm.handleEdit(notice)
expect(wrapper.vm.modalTitle).toBe('编辑公告')
expect(wrapper.vm.modalVisible).toBe(true)
})
it('should populate form with notice data', () => {
const notice = { id: 1, noticeTitle: 'Test Notice', noticeType: '2', noticeContent: 'Test Content', status: '1' }
wrapper.vm.handleEdit(notice)
expect(wrapper.vm.formState.id).toBe(1)
expect(wrapper.vm.formState.noticeTitle).toBe('Test Notice')
expect(wrapper.vm.formState.noticeType).toBe('2')
})
})
describe('form state', () => {
beforeEach(() => {
wrapper = mount(NoticeManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
},
},
})
})
it('should have default notice type as notification', () => {
expect(wrapper.vm.formState.noticeType).toBe('1')
})
it('should have default status as normal', () => {
expect(wrapper.vm.formState.status).toBe('0')
})
})
})
@@ -0,0 +1,216 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import OperationLog from '@/views/audit/OperationLog.vue'
vi.mock('vue-router')
vi.mock('@/api/operationLog', () => ({
operationLogApi: {
getPage: vi.fn().mockResolvedValue({
content: [
{ id: 1, username: 'admin', operation: '用户登录', method: 'POST', params: '{}', status: '0', duration: 100, createdAt: '2026-01-01T10:00:00' },
{ id: 2, username: 'user', operation: '查看用户', method: 'GET', params: '{"id":1}', status: '0', duration: 50, createdAt: '2026-01-02T11:00:00' },
],
totalElements: 2,
}),
},
}))
describe('OperationLog Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render operation log container', () => {
wrapper = mount(OperationLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-popover': true,
'el-pagination': true,
},
},
})
expect(wrapper.find('.operation-log').exists()).toBe(true)
})
it('should initialize with empty search keyword', () => {
wrapper = mount(OperationLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-popover': true,
'el-pagination': true,
},
},
})
expect(wrapper.vm.searchKeyword).toBe('')
})
it('should initialize with correct pagination defaults', () => {
wrapper = mount(OperationLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-popover': true,
'el-pagination': true,
},
},
})
expect(wrapper.vm.pagination.current).toBe(1)
expect(wrapper.vm.pagination.pageSize).toBe(10)
expect(wrapper.vm.pagination.total).toBe(0)
})
})
describe('operation icon mapping', () => {
beforeEach(() => {
wrapper = mount(OperationLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-popover': true,
'el-pagination': true,
},
},
})
})
it('should return User icon for login operations', () => {
const icon = wrapper.vm.getOperationIcon('用户登录')
expect(icon.name).toBe('User')
})
it('should return Delete icon for delete operations', () => {
const icon = wrapper.vm.getOperationIcon('删除用户')
expect(icon.name).toBe('Delete')
})
it('should return Edit icon for update operations', () => {
const icon = wrapper.vm.getOperationIcon('编辑用户')
expect(icon.name).toBe('Edit')
})
it('should return View icon for view operations', () => {
const icon = wrapper.vm.getOperationIcon('查看用户')
expect(icon.name).toBe('View')
})
it('should return Plus icon for create operations', () => {
const icon = wrapper.vm.getOperationIcon('新增用户')
expect(icon.name).toBe('Plus')
})
it('should return Download icon for download operations', () => {
const icon = wrapper.vm.getOperationIcon('下载文件')
expect(icon.name).toBe('Download')
})
it('should return Setting icon for config operations', () => {
const icon = wrapper.vm.getOperationIcon('系统设置')
expect(icon.name).toBe('Setting')
})
it('should return Lock icon for password operations', () => {
const icon = wrapper.vm.getOperationIcon('重置密码')
expect(icon.name).toBe('Lock')
})
it('should return Document icon for unknown operations', () => {
const icon = wrapper.vm.getOperationIcon('未知操作')
expect(icon.name).toBe('Document')
})
})
describe('params formatting', () => {
beforeEach(() => {
wrapper = mount(OperationLog, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-input': true,
'el-tag': true,
'el-icon': true,
'el-popover': true,
'el-pagination': true,
},
},
})
})
it('should format valid JSON params', () => {
const params = '{"name":"test","id":1}'
const formatted = wrapper.vm.formatParams(params)
expect(formatted).toContain('name')
expect(formatted).toContain('test')
})
it('should return empty string for null params', () => {
const formatted = wrapper.vm.formatParams(null)
expect(formatted).toBe('')
})
it('should return empty string for undefined params', () => {
const formatted = wrapper.vm.formatParams(undefined)
expect(formatted).toBe('')
})
it('should return original string for invalid JSON', () => {
const params = 'not a json'
const formatted = wrapper.vm.formatParams(params)
expect(formatted).toBe('not a json')
})
})
})
@@ -0,0 +1,383 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import RoleManagement from '@/views/system/RoleManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/api/role.api', () => ({
roleApi: {
getPage: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
getAll: vi.fn(),
},
}))
vi.mock('@/api/permission.api', () => ({
permissionApi: {
getAll: vi.fn(),
},
}))
describe('RoleManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render role management container', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.find('.role-management').exists()).toBe(true)
})
it('should initialize with empty search keyword', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.searchKeyword).toBe('')
})
it('should initialize with empty data source', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.dataSource).toEqual([])
})
it('should initialize with pagination on page 1', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.pagination.current).toBe(1)
})
it('should initialize with modal visible false', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.modalVisible).toBe(false)
})
it('should initialize with empty form state', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.formState.roleName).toBe('')
expect(wrapper.vm.formState.roleKey).toBe('')
expect(wrapper.vm.formState.roleSort).toBe(1)
expect(wrapper.vm.formState.status).toBe(1)
expect(wrapper.vm.formState.permissions).toEqual([])
})
})
describe('search functionality', () => {
it('should have handleSearch method', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleSearch).toBe('function')
})
it('should update search keyword when input changes', async () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
wrapper.vm.searchKeyword = 'admin'
await wrapper.vm.$nextTick()
expect(wrapper.vm.searchKeyword).toBe('admin')
})
})
describe('add role functionality', () => {
it('should have handleAdd method', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleAdd).toBe('function')
})
})
describe('pagination functionality', () => {
it('should have handleTableChange method', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleTableChange).toBe('function')
})
it('should have handleSizeChange method', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleSizeChange).toBe('function')
})
})
describe('sort functionality', () => {
it('should have handleSortChange method', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleSortChange).toBe('function')
})
it('should initialize with default sort info', () => {
wrapper = mount(RoleManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-tree': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.sortInfo.sortBy).toBe('id')
expect(wrapper.vm.sortInfo.sortOrder).toBe('asc')
})
})
})
@@ -0,0 +1,423 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import UserManagement from '@/views/system/UserManagement.vue'
vi.mock('vue-router')
vi.mock('element-plus', () => ({
ElMessage: {
success: vi.fn(),
error: vi.fn(),
},
ElMessageBox: {
confirm: vi.fn(),
},
}))
vi.mock('@/api/user.api', () => ({
userApi: {
getPage: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
assignRoles: vi.fn(),
},
}))
vi.mock('@/api/role.api', () => ({
roleApi: {
getAll: vi.fn(),
},
}))
describe('UserManagement Component', () => {
let router: any
let wrapper: any
beforeEach(() => {
router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
],
})
vi.clearAllMocks()
})
afterEach(() => {
if (wrapper) {
wrapper.unmount()
}
})
describe('component initialization', () => {
it('should render user management container', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.find('.user-management').exists()).toBe(true)
})
it('should initialize with empty search keyword', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.searchKeyword).toBe('')
})
it('should initialize with loading state false before data fetch', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.loading).toBeDefined()
expect(typeof wrapper.vm.loading).toBe('boolean')
})
it('should initialize with empty data source', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.dataSource).toEqual([])
})
it('should initialize with pagination on page 1', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.pagination.current).toBe(1)
})
it('should initialize with modal visible false', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.modalVisible).toBe(false)
})
it('should initialize with empty form state', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.formState.username).toBe('')
expect(wrapper.vm.formState.password).toBe('')
expect(wrapper.vm.formState.nickname).toBe('')
expect(wrapper.vm.formState.email).toBe('')
expect(wrapper.vm.formState.phone).toBe('')
expect(wrapper.vm.formState.roles).toEqual([])
})
})
describe('search functionality', () => {
it('should have handleSearch method', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleSearch).toBe('function')
})
it('should update search keyword when input changes', async () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
wrapper.vm.searchKeyword = 'testuser'
await wrapper.vm.$nextTick()
expect(wrapper.vm.searchKeyword).toBe('testuser')
})
})
describe('add user functionality', () => {
it('should have handleAdd method', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleAdd).toBe('function')
})
})
describe('pagination functionality', () => {
it('should have handleTableChange method', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleTableChange).toBe('function')
})
it('should have handleSizeChange method', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleSizeChange).toBe('function')
})
})
describe('sort functionality', () => {
it('should have handleSortChange method', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(typeof wrapper.vm.handleSortChange).toBe('function')
})
it('should initialize with default sort info', () => {
wrapper = mount(UserManagement, {
global: {
plugins: [router],
stubs: {
'el-card': true,
'el-input': true,
'el-button': true,
'el-table': true,
'el-table-column': true,
'el-tag': true,
'el-pagination': true,
'el-dialog': true,
'el-form': true,
'el-form-item': true,
'el-select': true,
'el-option': true,
'el-icon': true,
},
},
})
expect(wrapper.vm.sortInfo.sortBy).toBe('id')
expect(wrapper.vm.sortInfo.sortOrder).toBe('asc')
})
})
})
@@ -0,0 +1,12 @@
import { describe, it, expect } from 'vitest'
describe('Vitest Configuration Test', () => {
it('should run a simple test', () => {
expect(1 + 1).toBe(2)
})
it('should handle async operations', async () => {
const result = await Promise.resolve(42)
expect(result).toBe(42)
})
})
@@ -0,0 +1,88 @@
export const mockUser = {
id: 1,
username: 'testuser',
nickname: 'Test User',
email: 'test@example.com',
phone: '13800138000',
avatar: 'https://example.com/avatar.jpg',
roles: ['admin'],
permissions: ['user:view', 'user:create', 'user:edit', 'user:delete'],
}
export const mockRole = {
id: 1,
roleName: '测试角色',
roleKey: 'test_role',
roleSort: 1,
status: '1',
remark: '测试角色备注',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}
export const mockMenu = {
id: 1,
menuName: '系统管理',
parentId: 0,
orderNum: 1,
menuType: 'M',
component: 'system',
perms: 'system:view',
status: '1',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}
export const mockDict = {
id: 1,
dictName: '用户状态',
dictType: 'user_status',
status: '1',
remark: '用户状态字典',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}
export const mockConfig = {
id: 1,
configName: '系统名称',
configKey: 'sys.name',
configValue: 'Novalon管理系统',
configType: 'Y',
status: '1',
remark: '系统名称配置',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}
export const mockNotice = {
id: 1,
noticeTitle: '系统通知',
noticeType: '1',
noticeContent: '这是一条测试通知',
status: '0',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}
export const mockLoginRequest = {
username: 'admin',
password: 'admin123',
}
export const mockLoginResponse = {
token: 'mock-jwt-token',
user: mockUser,
}
export const mockApiResponse = <T>(data: T, code = 200, message = 'success') => ({
code,
message,
data,
})
export const mockErrorResponse = (code = 500, message = 'Internal Server Error') => ({
code,
message,
data: null,
})
+61
View File
@@ -0,0 +1,61 @@
import { vi } from 'vitest'
import { config } from '@vue/test-utils'
config.global.stubs = {
transition: false,
'transition-group': false,
}
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
const localStorageMock = (() => {
let store: Record<string, string> = {}
return {
getItem: vi.fn((key: string) => store[key] || null),
setItem: vi.fn((key: string, value: string) => {
store[key] = value
}),
removeItem: vi.fn((key: string) => {
delete store[key]
}),
clear: vi.fn(() => {
store = {}
}),
}
})()
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
})
const sessionStorageMock = (() => {
let store: Record<string, string> = {}
return {
getItem: vi.fn((key: string) => store[key] || null),
setItem: vi.fn((key: string, value: string) => {
store[key] = value
}),
removeItem: vi.fn((key: string) => {
delete store[key]
}),
clear: vi.fn(() => {
store = {}
}),
}
})()
Object.defineProperty(window, 'sessionStorage', {
value: sessionStorageMock,
})
+61
View File
@@ -0,0 +1,61 @@
import { VueWrapper } from '@vue/test-utils'
import { ComponentPublicInstance } from 'vue'
export interface TestHelpers {
findByText: (text: string) => HTMLElement | null
findByTestId: (testId: string) => HTMLElement | null
clickByText: (text: string) => Promise<void>
clickByTestId: (testId: string) => Promise<void>
fillByTestId: (testId: string, value: string) => Promise<void>
}
export function createTestHelpers(wrapper: VueWrapper<ComponentPublicInstance>): TestHelpers {
return {
findByText: (text: string) => {
return wrapper.element.textContent?.includes(text) ? wrapper.element : null
},
findByTestId: (testId: string) => {
return wrapper.element.querySelector(`[data-testid="${testId}"]`)
},
clickByText: async (text: string) => {
const element = wrapper.element.textContent?.includes(text) ? wrapper.element : null
if (element) {
element.click()
await wrapper.vm.$nextTick()
}
},
clickByTestId: async (testId: string) => {
const element = wrapper.element.querySelector(`[data-testid="${testId}"]`)
if (element) {
element.click()
await wrapper.vm.$nextTick()
}
},
fillByTestId: async (testId: string, value: string) => {
const element = wrapper.element.querySelector(`[data-testid="${testId}"]`) as HTMLInputElement
if (element) {
element.value = value
element.dispatchEvent(new Event('input', { bubbles: true }))
await wrapper.vm.$nextTick()
}
},
}
}
export function waitFor(condition: () => boolean, timeout = 5000): Promise<void> {
return new Promise((resolve, reject) => {
const startTime = Date.now()
const check = () => {
if (condition()) {
resolve()
} else if (Date.now() - startTime > timeout) {
reject(new Error(`Timeout waiting for condition`))
} else {
setTimeout(check, 100)
}
}
check()
})
}
@@ -0,0 +1,233 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { ElMessage } from 'element-plus'
import { handleApiError, ApiErrorHandler } from '@/utils/errorHandler'
vi.mock('element-plus', () => ({
ElMessage: {
error: vi.fn(),
success: vi.fn(),
},
}))
describe('errorHandler', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.stubGlobal('localStorage', {
removeItem: vi.fn(),
})
vi.stubGlobal('window', {
location: { href: '' },
})
})
describe('handleApiError', () => {
it('should call ApiErrorHandler.handle', () => {
const mockError = { response: { status: 500, data: {} } }
const handleSpy = vi.spyOn(ApiErrorHandler, 'handle')
handleApiError(mockError)
expect(handleSpy).toHaveBeenCalledWith(mockError)
})
})
describe('ApiErrorHandler.handle', () => {
it('should handle network error', () => {
const mockError = new Error('Network Error')
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('网络连接失败,请检查网络设置')
expect(consoleSpy).toHaveBeenCalledWith('Network Error:', mockError)
})
it('should handle 400 Bad Request', () => {
const mockError = {
response: {
status: 400,
data: { message: 'Invalid parameters' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('Invalid parameters')
expect(consoleSpy).toHaveBeenCalledWith('Bad Request:', mockError.response.data)
})
it('should handle 401 Unauthorized', () => {
const mockError = {
response: {
status: 401,
data: { message: 'Unauthorized' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('登录已过期,请重新登录')
expect(localStorage.removeItem).toHaveBeenCalledWith('token')
expect(window.location.href).toBe('/login')
expect(consoleSpy).toHaveBeenCalledWith('Unauthorized:', mockError.response.data)
})
it('should handle 403 Forbidden', () => {
const mockError = {
response: {
status: 403,
data: { message: 'Access denied' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('没有权限访问该资源')
expect(consoleSpy).toHaveBeenCalledWith('Forbidden:', mockError.response.data)
})
it('should handle 404 Not Found', () => {
const mockError = {
response: {
status: 404,
data: { message: 'Resource not found' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('Resource not found')
expect(consoleSpy).toHaveBeenCalledWith('Not Found:', mockError.response.data)
})
it('should handle 409 Conflict', () => {
const mockError = {
response: {
status: 409,
data: { message: 'Resource conflict' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('Resource conflict')
expect(consoleSpy).toHaveBeenCalledWith('Conflict:', mockError.response.data)
})
it('should handle 422 Validation Error with details', () => {
const mockError = {
response: {
status: 422,
data: {
message: 'Validation failed',
details: {
username: 'Username is required',
password: 'Password is too short',
},
},
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('Username is required、Password is too short')
expect(consoleSpy).toHaveBeenCalledWith('Validation Error:', mockError.response.data)
})
it('should handle 422 Validation Error without details', () => {
const mockError = {
response: {
status: 422,
data: { message: 'Validation failed' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('Validation failed')
expect(consoleSpy).toHaveBeenCalledWith('Validation Error:', mockError.response.data)
})
it('should handle 500 Internal Server Error', () => {
const mockError = {
response: {
status: 500,
data: { message: 'Server error' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('服务器内部错误,请稍后重试')
expect(consoleSpy).toHaveBeenCalledWith('Internal Server Error:', mockError.response.data)
})
it('should handle 502 Service Unavailable', () => {
const mockError = {
response: {
status: 502,
data: { message: 'Service unavailable' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试')
expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data)
})
it('should handle 503 Service Unavailable', () => {
const mockError = {
response: {
status: 503,
data: { message: 'Service unavailable' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试')
expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data)
})
it('should handle 504 Gateway Timeout', () => {
const mockError = {
response: {
status: 504,
data: { message: 'Gateway timeout' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试')
expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data)
})
it('should handle unknown status code', () => {
const mockError = {
response: {
status: 418,
data: { message: 'I am a teapot' },
},
}
const consoleSpy = vi.spyOn(console, 'error')
ApiErrorHandler.handle(mockError)
expect(ElMessage.error).toHaveBeenCalledWith('I am a teapot')
expect(consoleSpy).toHaveBeenCalledWith('Unknown Error:', mockError.response.data)
})
})
})