test(web): 添加 vitest 单元测试基础设施和初始测试用例
- 安装 vitest + @testing-library/react + @testing-library/jest-dom + jsdom - 创建 vitest.config.ts (jsdom 环境, 全局 API, e2e 目录排除) - 创建 test/setup.ts (@testing-library/jest-dom 匹配器) - 添加 29 个测试用例: health 常量 (14), useThemeMode hook (2), StatusTag 组件 (13)
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui"
|
||||
},
|
||||
@@ -29,6 +30,8 @@
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
@@ -37,9 +40,11 @@
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.4.0",
|
||||
"jsdom": "^29.0.2",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^8.0.4"
|
||||
"vite": "^8.0.4",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
773
apps/web/pnpm-lock.yaml
generated
773
apps/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
85
apps/web/src/constants/health.test.ts
Normal file
85
apps/web/src/constants/health.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
GENDER_OPTIONS,
|
||||
BLOOD_TYPE_OPTIONS,
|
||||
STATUS_OPTIONS,
|
||||
} from './health'
|
||||
|
||||
describe('GENDER_OPTIONS', () => {
|
||||
it('should be an array with 3 items', () => {
|
||||
expect(GENDER_OPTIONS).toBeInstanceOf(Array)
|
||||
expect(GENDER_OPTIONS).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('each item should have value and label string properties', () => {
|
||||
for (const opt of GENDER_OPTIONS) {
|
||||
expect(opt).toHaveProperty('value')
|
||||
expect(opt).toHaveProperty('label')
|
||||
expect(typeof opt.value).toBe('string')
|
||||
expect(typeof opt.label).toBe('string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should contain expected gender values', () => {
|
||||
const values = GENDER_OPTIONS.map((o) => o.value)
|
||||
expect(values).toEqual(['male', 'female', 'other'])
|
||||
})
|
||||
|
||||
it('should contain expected Chinese labels', () => {
|
||||
const labels = GENDER_OPTIONS.map((o) => o.label)
|
||||
expect(labels).toEqual(['男', '女', '其他'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('BLOOD_TYPE_OPTIONS', () => {
|
||||
it('should be an array with 4 items', () => {
|
||||
expect(BLOOD_TYPE_OPTIONS).toBeInstanceOf(Array)
|
||||
expect(BLOOD_TYPE_OPTIONS).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('each item should have value and label string properties', () => {
|
||||
for (const opt of BLOOD_TYPE_OPTIONS) {
|
||||
expect(opt).toHaveProperty('value')
|
||||
expect(opt).toHaveProperty('label')
|
||||
expect(typeof opt.value).toBe('string')
|
||||
expect(typeof opt.label).toBe('string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should contain expected blood type values', () => {
|
||||
const values = BLOOD_TYPE_OPTIONS.map((o) => o.value)
|
||||
expect(values).toEqual(['A', 'B', 'AB', 'O'])
|
||||
})
|
||||
|
||||
it('should contain expected Chinese labels', () => {
|
||||
const labels = BLOOD_TYPE_OPTIONS.map((o) => o.label)
|
||||
expect(labels).toEqual(['A 型', 'B 型', 'AB 型', 'O 型'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('STATUS_OPTIONS', () => {
|
||||
it('should be an array with 4 items', () => {
|
||||
expect(STATUS_OPTIONS).toBeInstanceOf(Array)
|
||||
expect(STATUS_OPTIONS).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('each item should have value and label string properties', () => {
|
||||
for (const opt of STATUS_OPTIONS) {
|
||||
expect(opt).toHaveProperty('value')
|
||||
expect(opt).toHaveProperty('label')
|
||||
expect(typeof opt.value).toBe('string')
|
||||
expect(typeof opt.label).toBe('string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should include an empty-string value for "all statuses" filter', () => {
|
||||
const allOption = STATUS_OPTIONS.find((o) => o.value === '')
|
||||
expect(allOption).toBeDefined()
|
||||
expect(allOption!.label).toBe('全部状态')
|
||||
})
|
||||
|
||||
it('should contain expected status values', () => {
|
||||
const values = STATUS_OPTIONS.map((o) => o.value)
|
||||
expect(values).toEqual(['', 'active', 'inactive', 'deceased'])
|
||||
})
|
||||
})
|
||||
15
apps/web/src/hooks/useThemeMode.test.ts
Normal file
15
apps/web/src/hooks/useThemeMode.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useThemeMode } from './useThemeMode'
|
||||
|
||||
describe('useThemeMode', () => {
|
||||
it('should return false when no ConfigProvider is present (light default)', () => {
|
||||
const { result } = renderHook(() => useThemeMode())
|
||||
expect(result.current).toBe(false)
|
||||
})
|
||||
|
||||
it('should return a boolean value', () => {
|
||||
const { result } = renderHook(() => useThemeMode())
|
||||
expect(typeof result.current).toBe('boolean')
|
||||
})
|
||||
})
|
||||
120
apps/web/src/pages/health/components/StatusTag.test.tsx
Normal file
120
apps/web/src/pages/health/components/StatusTag.test.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { StatusTag } from './StatusTag'
|
||||
|
||||
describe('StatusTag', () => {
|
||||
describe('appointment statuses', () => {
|
||||
it('renders pending status with gold color', () => {
|
||||
const { container } = render(<StatusTag status="pending" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
expect(tag).toHaveClass('ant-tag-gold')
|
||||
expect(screen.getByText('待确认')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders confirmed status with blue color', () => {
|
||||
const { container } = render(<StatusTag status="confirmed" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-blue')
|
||||
expect(screen.getByText('已确认')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders completed status with green color', () => {
|
||||
const { container } = render(<StatusTag status="completed" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-green')
|
||||
expect(screen.getByText('已完成')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders cancelled status with default color', () => {
|
||||
const { container } = render(<StatusTag status="cancelled" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
expect(screen.getByText('已取消')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders no_show status with red color', () => {
|
||||
const { container } = render(<StatusTag status="no_show" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-red')
|
||||
expect(screen.getByText('未到诊')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('follow-up statuses', () => {
|
||||
it('renders overdue status with red color', () => {
|
||||
const { container } = render(<StatusTag status="overdue" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-red')
|
||||
expect(screen.getByText('逾期')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders in_progress status with processing color', () => {
|
||||
const { container } = render(<StatusTag status="in_progress" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-processing')
|
||||
expect(screen.getByText('进行中')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('consultation statuses', () => {
|
||||
it('renders waiting status with gold color', () => {
|
||||
const { container } = render(<StatusTag status="waiting" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-gold')
|
||||
expect(screen.getByText('等待中')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders active consultation status with green color', () => {
|
||||
const { container } = render(<StatusTag status="active" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-green')
|
||||
expect(screen.getByText('进行中')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders closed status with default color', () => {
|
||||
const { container } = render(<StatusTag status="closed" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
expect(screen.getByText('已关闭')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('patient statuses', () => {
|
||||
it('renders inactive status with default color', () => {
|
||||
const { container } = render(<StatusTag status="inactive" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
expect(screen.getByText('停用')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders deceased status with default color', () => {
|
||||
const { container } = render(<StatusTag status="deceased" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
expect(screen.getByText('已故')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders verified status with green color', () => {
|
||||
const { container } = render(<StatusTag status="verified" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toHaveClass('ant-tag-green')
|
||||
expect(screen.getByText('已认证')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown status fallback', () => {
|
||||
it('renders unknown status text with default color', () => {
|
||||
const { container } = render(<StatusTag status="unknown_status" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
expect(screen.getByText('unknown_status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders empty string status with default color', () => {
|
||||
const { container } = render(<StatusTag status="" />)
|
||||
const tag = container.querySelector('.ant-tag')
|
||||
expect(tag).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
1
apps/web/src/test/setup.ts
Normal file
1
apps/web/src/test/setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom'
|
||||
18
apps/web/vitest.config.ts
Normal file
18
apps/web/vitest.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
exclude: ['e2e/**', 'node_modules/**'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user