1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
// @ts-nocheck
import { describe, expect, it, vi } from 'vitest';
import { mount, unmount } from 'svelte';
import resultsRaw from '../pages/ProjectResultsTab.svelte?raw';
import backtestRaw from '../pages/ProjectBacktestTab.svelte?raw';
// 用户拍板方案 1:执行假设长文不再混入用户面 warnings。
// worker 已把 EXECUTION_ASSUMPTIONS 移到 result.assumptions(tests/worker/test_backtest.py
// test_assumptions_out_of_warnings 守门 worker 侧);本文件守门前端渲染侧:
// 技术口径只在 assumptions 字段驱动下渲染,且默认折叠(Collapsible)。
const PROJECT_ID = '7630e296-0b2f-44c3-85f1-fe695e7a7e4b';
const RUN_ID = '569b23d0-1111-4222-8333-4444444444442';
const ASSUMPTIONS = { slippage: '0.1%', 'next_bar_open_fill': true };
function resultBody(withAssumptions) {
return {
id: RUN_ID, project_id: PROJECT_ID, version_id: 'v', dataset_id: 'd',
status: 'succeeded',
config: { capital: 100000, commission: 0.0003, slippage: 0.001 },
created_at: '2026-01-01T00:00:00Z',
result: {
engine: { name: 'backtrader', version: '1.9' },
metrics: { total_return: 0.0123, annual_return: 0.05, max_drawdown: -0.033, sharpe: 0.9, trade_count: 1, final_equity: 101234.5 },
equity: [{ date: '2024-01-02', equity: 100000, cash: 100000 }],
orders: [], trades: [], closed_trades: [],
logs: [], warnings: ['真实警告:数据源公告休市'],
assumptions: withAssumptions ? ASSUMPTIONS : undefined,
elapsed_ms: 1,
},
};
}
const fetchMock = (body) => (url) => {
if (/\/api\/runs\/[0-9a-f-]+$/.test(String(url))) return Promise.resolve(new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } }));
if (/\/api\/projects\/[0-9a-f-]+$/.test(String(url)))
return Promise.resolve(new Response(JSON.stringify({ id: PROJECT_ID, name: '端到端验收项目', draft_code: 'x=1', draft_generation: 2 }), { status: 200, headers: { 'content-type': 'application/json' } }));
return Promise.resolve(new Response('{"items":[]}', { status: 200, headers: { 'content-type': 'application/json' } }));
};
async function mountResultsTab(body) {
vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} });
vi.stubGlobal('fetch', fetchMock(body));
const ProjectResultsTab = (await import('../pages/ProjectResultsTab.svelte')).default;
const host = document.createElement('div');
document.body.appendChild(host);
const inst = mount(ProjectResultsTab, { target: host, props: { projectId: PROJECT_ID, runId: RUN_ID } });
await new Promise((r) => setTimeout(r, 80));
return { inst, host };
}
describe('技术口径:assumptions 独立折叠区,不混入 warnings', () => {
it('assumptions 渲染在折叠区;warnings 列表只含真实警告', { timeout: 20000 }, async () => {
const { inst, host } = await mountResultsTab(resultBody(true));
try {
const text = host.textContent.replace(/\s+/g, ' ');
// warnings Alert 渲染真实警告
expect(text).toContain('真实警告:数据源公告休市');
// assumptions keys/values 不出现在 warnings <li>
const warnLis = [...host.querySelectorAll('li')].map((n) => n.textContent);
for (const k of Object.keys(ASSUMPTIONS)) {
expect(warnLis.some((w) => w.includes(k))).toBe(false);
}
// 折叠区标题存在(默认收起,仅结构存在不展开内容)
expect(host.querySelector('[data-testid="assumptions-toggle"]')?.textContent).toContain('技术口径');
expect(host.querySelector('[data-testid="assumptions-list"]')).toBeTruthy();
} finally { unmount(inst); host.remove(); vi.unstubAllGlobals(); }
});
it('没有 assumptions 字段则不渲染技术口径区', async () => {
const { inst, host } = await mountResultsTab(resultBody(false));
try {
expect(host.querySelector('[data-testid="assumptions-toggle"]')).toBeNull();
expect(host.querySelector('[data-testid="assumptions-list"]')).toBeNull();
} finally { unmount(inst); host.remove(); vi.unstubAllGlobals(); }
});
it('渲染路径源码不含 "execution assumptions" 旧拼串文案', () => {
for (const src of [resultsRaw, backtestRaw]) {
expect(src).not.toContain('execution assumptions');
// assumptions 数据只进 技术口径 折叠区(一个 dl 列表锚点)
expect(src).toContain('assumptions-list');
}
});
});
|