feat(ProgramLogs): add event status summary parsing and corresponding labels

This commit is contained in:
cita
2026-03-05 13:08:59 +08:00
parent 11fc61f4fa
commit 27501b5a85
3 changed files with 147 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, create, type ReactTestInstance } from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
import { ToastProvider } from '../components/Toast.js';
import ProgramLogs from './ProgramLogs.js';
const { apiMock } = vi.hoisted(() => ({
apiMock: {
getEvents: vi.fn(),
markEventRead: vi.fn(),
markAllEventsRead: vi.fn(),
clearEvents: vi.fn(),
},
}));
vi.mock('../api.js', () => ({
api: apiMock,
}));
function collectText(node: ReactTestInstance): string {
const children = node.children || [];
return children.map((child) => {
if (typeof child === 'string') return child;
return collectText(child);
}).join('');
}
async function flushMicrotasks() {
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
}
describe('ProgramLogs status label', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
it('treats summary with failed=0 as success', async () => {
apiMock.getEvents.mockResolvedValue([
{
id: 1,
type: 'status',
title: '同步全部账号令牌已完成(成功31/跳过0/失败0)',
message: '全部账号令牌同步完成:成功 31,跳过 0,失败 0',
level: 'info',
read: false,
createdAt: '2026-03-04T06:43:03.000Z',
},
]);
let root: ReturnType<typeof create> | null = null;
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/events']}>
<ToastProvider>
<ProgramLogs />
</ToastProvider>
</MemoryRouter>,
);
});
await flushMicrotasks();
const rows = root!.root.findAll((node) => node.type === 'tr');
const targetRow = rows.find((row) => collectText(row).includes('同步全部账号令牌已完成'));
expect(targetRow).toBeTruthy();
const tds = targetRow!.findAll((node) => node.type === 'td');
const statusCell = tds[5];
expect(collectText(statusCell).trim()).toBe('成功');
const statusBadge = statusCell.find((node) => node.type === 'span');
expect(String(statusBadge.props.className || '')).toContain('badge-success');
});
it('treats parenthesized counts with failed=0 as success', async () => {
apiMock.getEvents.mockResolvedValue([
{
id: 2,
type: 'status',
title: '同步全部账号令牌已完成',
message: '成功(15): a, b\n跳过(1): c\n失败(0): -',
level: 'info',
read: false,
createdAt: '2026-03-04T06:43:03.000Z',
},
]);
let root: ReturnType<typeof create> | null = null;
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/events']}>
<ToastProvider>
<ProgramLogs />
</ToastProvider>
</MemoryRouter>,
);
});
await flushMicrotasks();
const rows = root!.root.findAll((node) => node.type === 'tr');
const targetRow = rows.find((row) => collectText(row).includes('同步全部账号令牌已完成'));
expect(targetRow).toBeTruthy();
const tds = targetRow!.findAll((node) => node.type === 'td');
const statusCell = tds[5];
expect(collectText(statusCell).trim()).toBe('成功');
const statusBadge = statusCell.find((node) => node.type === 'span');
expect(String(statusBadge.props.className || '')).toContain('badge-success');
});
});
+26
View File
@@ -37,6 +37,32 @@ function levelLabel(level: string) {
function eventStatusLabel(row: ProgramEvent) {
const text = `${row.title || ''} ${row.message || ''}`.toLowerCase();
const parseCount = (pattern: RegExp): number | undefined => {
const match = text.match(pattern);
if (!match?.[1]) return undefined;
const value = Number.parseInt(match[1], 10);
return Number.isFinite(value) ? value : undefined;
};
const summary = {
success: parseCount(/成功[^\d]{0,6}(\d+)/i) ?? parseCount(/success(?:ful)?[^\d]{0,6}(\d+)/i),
skipped: parseCount(/跳过[^\d]{0,6}(\d+)/i) ?? parseCount(/skipped?[^\d]{0,6}(\d+)/i),
failed: parseCount(/失败[^\d]{0,6}(\d+)/i) ?? parseCount(/failed[^\d]{0,6}(\d+)/i),
};
if (summary.failed !== undefined || summary.success !== undefined || summary.skipped !== undefined) {
if ((summary.failed ?? 0) > 0) {
return { label: '失败', cls: 'badge-error' };
}
if ((summary.success ?? 0) > 0) {
return { label: '成功', cls: 'badge-success' };
}
if ((summary.skipped ?? 0) > 0) {
return { label: '跳过', cls: 'badge-warning' };
}
return { label: '成功', cls: 'badge-success' };
}
if (text.includes('失败') || text.includes('failed') || text.includes('error')) {
return { label: '失败', cls: 'badge-error' };
}