fix(web): 清零前端 TS 构建错误 — 31 文件类型修复 + 面包屑 + 超时配置
- 修复 verbatimModuleSyntax 要求的 import type 声明 - 修复未使用导入(Badge/EditOutlined/Space/Input/Switch 等) - 修复 mock.calls 类型注解([string,unknown] → any[]) - 修复 vitest 全局超时和 poolTimeout 配置 - 修复 PageContainer 缺少 onBack prop、MenuInfo children 可选 - 修复 CopilotAlert Badge status info→processing、useCopilotRisk 二次解包 - 修复 articles/doctors 测试 delete 调用缺少 version 参数 - 添加排班管理/预约管理面包屑标题 fallback
This commit is contained in:
@@ -44,7 +44,7 @@ describe('articleApi', () => {
|
|||||||
|
|
||||||
it('create 应调用 POST /health/articles', async () => {
|
it('create 应调用 POST /health/articles', async () => {
|
||||||
mockPost.mockResolvedValue(fakeRes)
|
mockPost.mockResolvedValue(fakeRes)
|
||||||
const req = { title: '健康饮食指南', content: '正文内容', content_type: 'markdown' }
|
const req = { title: '健康饮食指南', content: '正文内容', content_type: 'markdown' as const }
|
||||||
await articleApi.create(req)
|
await articleApi.create(req)
|
||||||
|
|
||||||
expect(mockPost).toHaveBeenCalledWith('/health/articles', req)
|
expect(mockPost).toHaveBeenCalledWith('/health/articles', req)
|
||||||
@@ -60,7 +60,7 @@ describe('articleApi', () => {
|
|||||||
|
|
||||||
it('delete 应调用 DELETE /health/articles/:id', async () => {
|
it('delete 应调用 DELETE /health/articles/:id', async () => {
|
||||||
mockDelete.mockResolvedValue({ data: { success: true, data: null } })
|
mockDelete.mockResolvedValue({ data: { success: true, data: null } })
|
||||||
await articleApi.delete('art-001')
|
await articleApi.delete('art-001', 1)
|
||||||
|
|
||||||
expect(mockDelete).toHaveBeenCalledWith('/health/articles/art-001')
|
expect(mockDelete).toHaveBeenCalledWith('/health/articles/art-001')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ describe('doctorApi', () => {
|
|||||||
|
|
||||||
it('delete 应调用 DELETE /health/doctors/:id', async () => {
|
it('delete 应调用 DELETE /health/doctors/:id', async () => {
|
||||||
mockDelete.mockResolvedValue(undefined)
|
mockDelete.mockResolvedValue(undefined)
|
||||||
await doctorApi.delete('d-001')
|
await doctorApi.delete('d-001', 1)
|
||||||
|
|
||||||
expect(mockDelete).toHaveBeenCalledWith('/health/doctors/d-001')
|
expect(mockDelete).toHaveBeenCalledWith('/health/doctors/d-001')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface MenuInfo {
|
|||||||
visible: boolean;
|
visible: boolean;
|
||||||
menu_type: string;
|
menu_type: string;
|
||||||
permission?: string;
|
permission?: string;
|
||||||
children: MenuInfo[];
|
children?: MenuInfo[];
|
||||||
version: number;
|
version: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { CheckOutlined } from '@ant-design/icons';
|
|||||||
import { listAlerts, dismissInsight } from '../../api/copilot';
|
import { listAlerts, dismissInsight } from '../../api/copilot';
|
||||||
import type { CopilotInsight } from '../../api/copilot';
|
import type { CopilotInsight } from '../../api/copilot';
|
||||||
|
|
||||||
const severityConfig: Record<string, { type: 'success' | 'info' | 'warning' | 'error'; label: string }> = {
|
const severityConfig: Record<string, { type: 'success' | 'processing' | 'warning' | 'error'; label: string }> = {
|
||||||
critical: { type: 'error', label: '危急' },
|
critical: { type: 'error', label: '危急' },
|
||||||
warning: { type: 'warning', label: '警告' },
|
warning: { type: 'warning', label: '警告' },
|
||||||
info: { type: 'info', label: '提示' },
|
info: { type: 'processing', label: '提示' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CopilotAlert() {
|
export function CopilotAlert() {
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ export function useCopilotRisk(patientId: string | undefined) {
|
|||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const res = await getPatientRisk(patientId);
|
const res = await getPatientRisk(patientId);
|
||||||
const payload = (res.data as { data?: RiskScore }).data ?? null;
|
setData(res ?? null);
|
||||||
setData(payload);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : '加载风险评分失败');
|
setError(err instanceof Error ? err.message : '加载风险评分失败');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface PageContainerProps {
|
|||||||
batchActions?: React.ReactNode;
|
batchActions?: React.ReactNode;
|
||||||
selectedCount?: number;
|
selectedCount?: number;
|
||||||
onClearSelection?: () => void;
|
onClearSelection?: () => void;
|
||||||
|
onBack?: () => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@ export function PageContainer({
|
|||||||
batchActions,
|
batchActions,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
onClearSelection,
|
onClearSelection,
|
||||||
|
onBack,
|
||||||
children,
|
children,
|
||||||
loading,
|
loading,
|
||||||
}: PageContainerProps) {
|
}: PageContainerProps) {
|
||||||
@@ -36,6 +38,11 @@ export function PageContainer({
|
|||||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||||
|
{onBack && (
|
||||||
|
<Button type="text" size="small" onClick={onBack} style={{ marginRight: 8 }}>
|
||||||
|
←
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{title}
|
{title}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ const routeTitleFallback: Record<string, string> = {
|
|||||||
'/health/follow-up-records': '随访记录',
|
'/health/follow-up-records': '随访记录',
|
||||||
'/health/article-categories': '分类管理',
|
'/health/article-categories': '分类管理',
|
||||||
'/health/article-tags': '标签管理',
|
'/health/article-tags': '标签管理',
|
||||||
|
'/health/schedules': '排班管理',
|
||||||
|
'/health/appointments': '预约管理',
|
||||||
};
|
};
|
||||||
|
|
||||||
function getTitleFromMenus(path: string, menus: MenuInfo[]): string | undefined {
|
function getTitleFromMenus(path: string, menus: MenuInfo[]): string | undefined {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { screen, waitFor } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
import { createListPageTests } from '../../test/factories/listPageTests';
|
import { createListPageTests } from '../../test/factories/listPageTests';
|
||||||
import { createFixtureList, createAlertRuleFixture } from '../../test/fixtures';
|
import { createFixtureList, createAlertRuleFixture } from '../../test/fixtures';
|
||||||
import { renderWithProviders } from '../../test/utils/renderWithProviders';
|
import { renderWithProviders } from '../../test/utils/renderWithProviders';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { screen, waitFor } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
import { createListPageTests } from '../../test/factories/listPageTests';
|
import { createListPageTests } from '../../test/factories/listPageTests';
|
||||||
import { createFixtureList, createArticleFixture } from '../../test/fixtures';
|
import { createFixtureList, createArticleFixture } from '../../test/fixtures';
|
||||||
import { renderWithProviders } from '../../test/utils/renderWithProviders';
|
import { renderWithProviders } from '../../test/utils/renderWithProviders';
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ export default function BannerManage() {
|
|||||||
rowClassName={(record) =>
|
rowClassName={(record) =>
|
||||||
record.status === 'inactive'
|
record.status === 'inactive'
|
||||||
? 'ant-table-row-inactive'
|
? 'ant-table-row-inactive'
|
||||||
: undefined
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Descriptions, Form, Input, message, Modal, Popconfirm, Select, Space, Table, Tag, Tabs,
|
Button, Descriptions, Form, Input, message, Modal, Popconfirm, Select, Table, Tag, Tabs,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Form, Input, InputNumber, message, Modal, Popconfirm, Result, Select, Space, Switch, Table, Tag,
|
Button, Form, Input, InputNumber, message, Modal, Popconfirm, Result, Select, Space, Table, Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Button, Input, message, Popconfirm, Result, Select, Space, Table, Tag, Badge } from 'antd';
|
import { Button, message, Popconfirm, Result, Select, Space, Table, Tag, Badge } from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
|||||||
@@ -191,9 +191,9 @@ export default function DoctorList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ---- 删除 ----
|
// ---- 删除 ----
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string, version: number) => {
|
||||||
try {
|
try {
|
||||||
await doctorApi.delete(id);
|
await doctorApi.delete(id, version);
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
refresh();
|
refresh();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -286,7 +286,7 @@ export default function DoctorList() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定删除该医护?"
|
title="确定删除该医护?"
|
||||||
onConfirm={() => handleDelete(record.id)}
|
onConfirm={() => handleDelete(record.id, record.version)}
|
||||||
okText="确定"
|
okText="确定"
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Card, Descriptions, Form, Input, message, Modal, Popconfirm, Result, Select, Space, Table, Tag,
|
Button, Card, Descriptions, Form, message, Modal, Popconfirm, Result, Select, Space, Table, Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
Popconfirm, InputNumber, Switch, Card, Typography,
|
Popconfirm, InputNumber, Switch, Card, Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined, DeleteOutlined, EditOutlined,
|
PlusOutlined, DeleteOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import {
|
import {
|
||||||
@@ -35,9 +35,9 @@ const FIELD_TYPE_OPTIONS = [
|
|||||||
{ value: 'scale', label: '量表' },
|
{ value: 'scale', label: '量表' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function FieldEditor({ value, onChange }: {
|
function FieldEditor({ value = [], onChange = () => {} }: {
|
||||||
value: TemplateFieldReq[];
|
value?: TemplateFieldReq[];
|
||||||
onChange: (v: TemplateFieldReq[]) => void;
|
onChange?: (v: TemplateFieldReq[]) => void;
|
||||||
}) {
|
}) {
|
||||||
const add = () => {
|
const add = () => {
|
||||||
onChange([...value, {
|
onChange([...value, {
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ export default function MediaLibrary() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, overflow: 'auto', padding: '8px 4px' }}>
|
<div style={{ flex: 1, overflow: 'auto', padding: '8px 4px' }}>
|
||||||
{foldersLoading ? <div style={{ textAlign: 'center', padding: 20 }}><Spin size="small" /></div> : (
|
{foldersLoading ? <div style={{ textAlign: 'center', padding: 20 }}><Spin size="small" /></div> : (
|
||||||
<Tree defaultExpandAll selectedKeys={folderId ? [folderId] : ['__all__']} treeData={treeData}
|
<Tree defaultExpandAll selectedKeys={folderId ? [folderId] : ['__all__']} treeData={treeData as any}
|
||||||
fieldNames={{ title: 'name', key: 'id', children: 'children' }} onSelect={handleFolderSelect}
|
fieldNames={{ title: 'name', key: 'id', children: 'children' }} onSelect={handleFolderSelect}
|
||||||
titleRender={(node: TreeNode) => {
|
titleRender={(node: any) => {
|
||||||
if (node.id === '__all__') return <span>{node.name}</span>;
|
if (node.id === '__all__') return <span>{node.name}</span>;
|
||||||
const matched = folders.find((f) => f.id === node.id);
|
const matched = folders.find((f) => f.id === node.id);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, DatePicker, Form, Input, message, Modal, Popconfirm,
|
Button, DatePicker, Form, Input, message, Modal, Popconfirm,
|
||||||
Result, Select, Space, Switch, Table, Tag,
|
Result, Select, Space, Switch, Table, Tag,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { http, HttpResponse } from 'msw';
|
|
||||||
import { server } from '../../test/mocks/server';
|
|
||||||
import { createListPageTests } from '../../test/factories/listPageTests';
|
import { createListPageTests } from '../../test/factories/listPageTests';
|
||||||
import { createFixtureList, createPatientFixture } from '../../test/fixtures';
|
import { createFixtureList, createPatientFixture } from '../../test/fixtures';
|
||||||
import PatientList from './PatientList';
|
import PatientList from './PatientList';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useVitalSSE } from '../../hooks/useVitalSSE';
|
|||||||
import { usePermission } from '../../hooks/usePermission';
|
import { usePermission } from '../../hooks/usePermission';
|
||||||
import { alertApi, type Alert } from '../../api/health/alerts';
|
import { alertApi, type Alert } from '../../api/health/alerts';
|
||||||
import { PageContainer } from '../../components/PageContainer';
|
import { PageContainer } from '../../components/PageContainer';
|
||||||
import { SEVERITY_COLOR, SEVERITY_LABEL, VITAL_CARD_METRICS } from '../../constants/health';
|
import { VITAL_CARD_METRICS } from '../../constants/health';
|
||||||
|
|
||||||
interface PatientAlertSummary {
|
interface PatientAlertSummary {
|
||||||
patient_id: string;
|
patient_id: string;
|
||||||
@@ -22,7 +22,7 @@ interface PatientAlertSummary {
|
|||||||
*/
|
*/
|
||||||
export default function RealtimeMonitor() {
|
export default function RealtimeMonitor() {
|
||||||
const { hasPermission } = usePermission('health.alerts.list');
|
const { hasPermission } = usePermission('health.alerts.list');
|
||||||
const [alerts, setAlerts] = useState<Alert[]>([]);
|
const [_alerts, setAlerts] = useState<Alert[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selectedPatientId, setSelectedPatientId] = useState<string | null>(null);
|
const [selectedPatientId, setSelectedPatientId] = useState<string | null>(null);
|
||||||
const [alertSummary, setAlertSummary] = useState<PatientAlertSummary[]>([]);
|
const [alertSummary, setAlertSummary] = useState<PatientAlertSummary[]>([]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Badge, Button, DatePicker, Form, Input, message, Modal, Popconfirm,
|
Button, DatePicker, Form, Input, message, Modal, Popconfirm,
|
||||||
Result, Select, Space, Table, Tag,
|
Result, Select, Space, Table, Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface ArticleStyleLibraryProps {
|
|||||||
onThemeChange: (themeId: string) => void;
|
onThemeChange: (themeId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionLabel = (text: string, isDark: boolean) => ({
|
const sectionLabel = (_text: string, isDark: boolean) => ({
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: isDark ? '#64748b' : '#86868b',
|
color: isDark ? '#64748b' : '#86868b',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function parseStyleStr(str: string): Record<string, string> {
|
|||||||
const renderElemConf = {
|
const renderElemConf = {
|
||||||
type: TYPE,
|
type: TYPE,
|
||||||
renderElem(elemNode: SlateElement): VNode {
|
renderElem(elemNode: SlateElement): VNode {
|
||||||
const node = elemNode as Record<string, unknown>;
|
const node = elemNode as unknown as Record<string, unknown>;
|
||||||
const style = (node.style as string) || '';
|
const style = (node.style as string) || '';
|
||||||
const innerHtml = (node.innerHtml as string) || '';
|
const innerHtml = (node.innerHtml as string) || '';
|
||||||
return h(
|
return h(
|
||||||
@@ -44,7 +44,7 @@ const renderElemConf = {
|
|||||||
const elemToHtmlConf = {
|
const elemToHtmlConf = {
|
||||||
type: TYPE,
|
type: TYPE,
|
||||||
elemToHtml(elemNode: SlateElement): string {
|
elemToHtml(elemNode: SlateElement): string {
|
||||||
const node = elemNode as Record<string, unknown>;
|
const node = elemNode as unknown as Record<string, unknown>;
|
||||||
const style = (node.style as string) || '';
|
const style = (node.style as string) || '';
|
||||||
const innerHtml = (node.innerHtml as string) || '';
|
const innerHtml = (node.innerHtml as string) || '';
|
||||||
return `<div data-w-e-type="${TYPE}" style="${style}">${innerHtml}</div>`;
|
return `<div data-w-e-type="${TYPE}" style="${style}">${innerHtml}</div>`;
|
||||||
@@ -53,7 +53,7 @@ const elemToHtmlConf = {
|
|||||||
|
|
||||||
const parseElemHtmlConf = {
|
const parseElemHtmlConf = {
|
||||||
selector: `div[data-w-e-type="${TYPE}"]`,
|
selector: `div[data-w-e-type="${TYPE}"]`,
|
||||||
parseElemHtml($elem: HTMLElement): SlateElement {
|
parseElemHtml($elem: Element): SlateElement {
|
||||||
return {
|
return {
|
||||||
type: TYPE,
|
type: TYPE,
|
||||||
style: $elem.getAttribute('style') || '',
|
style: $elem.getAttribute('style') || '',
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ interface ActionDetailDrawerProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onActionComplete?: () => void;
|
onActionComplete?: () => void;
|
||||||
|
onRefresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ActionDetailDrawer({
|
export default function ActionDetailDrawer({
|
||||||
@@ -45,6 +46,7 @@ export default function ActionDetailDrawer({
|
|||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
onActionComplete,
|
onActionComplete,
|
||||||
|
onRefresh: _onRefresh,
|
||||||
}: ActionDetailDrawerProps) {
|
}: ActionDetailDrawerProps) {
|
||||||
const [thread, setThread] = useState<ThreadResponse | null>(null);
|
const [thread, setThread] = useState<ThreadResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* - 侧边栏折叠状态
|
* - 侧边栏折叠状态
|
||||||
* - 远程主题配置加载
|
* - 远程主题配置加载
|
||||||
*/
|
*/
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
|
||||||
// --- Mock localStorage ---
|
// --- Mock localStorage ---
|
||||||
const localStorageStore: Record<string, string> = {}
|
const localStorageStore: Record<string, string> = {}
|
||||||
@@ -39,7 +39,8 @@ vi.mock('../api/themes', () => ({
|
|||||||
// --- Mock zustand 内部不依赖真实存储 ---
|
// --- Mock zustand 内部不依赖真实存储 ---
|
||||||
// 在 mock 生效后导入被测模块
|
// 在 mock 生效后导入被测模块
|
||||||
import { useAppStore, THEME_OPTIONS } from './app'
|
import { useAppStore, THEME_OPTIONS } from './app'
|
||||||
import type { ThemeName, ThemeConfig } from './app'
|
import type { ThemeName } from './app'
|
||||||
|
import type { ThemeConfig } from '../api/themes'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ describe('connectSSE', () => {
|
|||||||
|
|
||||||
it('有 token 时创建 EventSource 并传递正确 URL', () => {
|
it('有 token 时创建 EventSource 并传递正确 URL', () => {
|
||||||
localStorage.setItem('access_token', 'my-jwt-token')
|
localStorage.setItem('access_token', 'my-jwt-token')
|
||||||
process.env.VITE_API_BASE_URL = undefined
|
vi.stubEnv('VITE_API_BASE_URL', '')
|
||||||
|
|
||||||
const dispose = useMessageStore.getState().connectSSE()
|
const dispose = useMessageStore.getState().connectSSE()
|
||||||
|
|
||||||
@@ -339,7 +339,7 @@ describe('connectSSE', () => {
|
|||||||
const dispose = useMessageStore.getState().connectSSE()
|
const dispose = useMessageStore.getState().connectSSE()
|
||||||
|
|
||||||
const eventTypes = mockEventSourceAddEventListener.mock.calls.map(
|
const eventTypes = mockEventSourceAddEventListener.mock.calls.map(
|
||||||
(call: [string, unknown]) => call[0],
|
(call: any[]) => call[0],
|
||||||
)
|
)
|
||||||
expect(eventTypes).toContain('message')
|
expect(eventTypes).toContain('message')
|
||||||
expect(eventTypes).toContain('alert')
|
expect(eventTypes).toContain('alert')
|
||||||
@@ -355,7 +355,7 @@ describe('connectSSE', () => {
|
|||||||
|
|
||||||
// 找到 message 事件的回调
|
// 找到 message 事件的回调
|
||||||
const messageCall = mockEventSourceAddEventListener.mock.calls.find(
|
const messageCall = mockEventSourceAddEventListener.mock.calls.find(
|
||||||
(call: [string, unknown]) => call[0] === 'message',
|
(call: any[]) => call[0] === 'message',
|
||||||
)
|
)
|
||||||
const messageCallback = messageCall![1] as () => void
|
const messageCallback = messageCall![1] as () => void
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ describe('connectSSE', () => {
|
|||||||
const dispose = useMessageStore.getState().connectSSE()
|
const dispose = useMessageStore.getState().connectSSE()
|
||||||
|
|
||||||
const alertCall = mockEventSourceAddEventListener.mock.calls.find(
|
const alertCall = mockEventSourceAddEventListener.mock.calls.find(
|
||||||
(call: [string, unknown]) => call[0] === 'alert',
|
(call: any[]) => call[0] === 'alert',
|
||||||
)
|
)
|
||||||
const alertCallback = alertCall![1] as () => void
|
const alertCallback = alertCall![1] as () => void
|
||||||
|
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ describe('usePluginStore', () => {
|
|||||||
const schema = createFakeSchema({
|
const schema = createFakeSchema({
|
||||||
ui: {
|
ui: {
|
||||||
pages: [
|
pages: [
|
||||||
{ type: 'tabs' as const, label: '综合视图', icon: 'LayoutOutlined' },
|
{ type: 'tabs' as const, label: '综合视图', icon: 'LayoutOutlined', tabs: [] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Object.defineProperty(window, 'matchMedia', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ResizeObserver mock(Ant Design Table 依赖)
|
// ResizeObserver mock(Ant Design Table 依赖)
|
||||||
global.ResizeObserver = class ResizeObserver {
|
globalThis.ResizeObserver = class ResizeObserver {
|
||||||
observe() {}
|
observe() {}
|
||||||
unobserve() {}
|
unobserve() {}
|
||||||
disconnect() {}
|
disconnect() {}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { render, RenderOptions } from '@testing-library/react';
|
import { render, type RenderOptions } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider } from 'antd';
|
||||||
import zhCN from 'antd/locale/zh_CN';
|
import zhCN from 'antd/locale/zh_CN';
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default defineConfig({
|
|||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: ['./src/test/setup.ts'],
|
setupFiles: ['./src/test/setup.ts'],
|
||||||
exclude: ['e2e/**', 'node_modules/**'],
|
exclude: ['e2e/**', 'node_modules/**'],
|
||||||
|
testTimeout: 15000,
|
||||||
|
poolTimeout: 60000,
|
||||||
coverage: {
|
coverage: {
|
||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
reporter: ['text', 'lcov'],
|
reporter: ['text', 'lcov'],
|
||||||
|
|||||||
Reference in New Issue
Block a user