Files
kyugao ec02a791ee 迭代22+23: 策略tab静态化 + 计算字段(数字/判定型)+ 网格超市信号字段
- R-027 静态化:策略恒为内置两项(不可增删改名,保留显隐/排序);设置页移除「策略分组」;
  字段配置入口迁到策略 tab 内「列设置」旁(FieldConfigDialog + schema-update 单策略写入)
- R-026 计算字段:自写公式引擎(中文变量、四则/括号/round·abs·min·max、缺值短路→—)+
  变量目录(行情/合约/持仓/自定义字段)+ strategy-positions 逐行现算(只读内存缓存,不落库、列只读)
- R-028 判定型:比较运算 + and/or + inferResultKind + 结果类型一致性校验 + 判定列 ✓/— 渲染;
  网格超市落地 可下空单=涨停价>基准值+网格大小、可下多单=跌停价<基准值-网格大小
- 变量选择改标签平铺(老师反馈);公式手册 docs/99-其他材料/计算字段公式说明.md
- 约束同步:产品约束-002/003/004/009/013/014、技术约束-014/015/022/023/024、UI约束-002/003/005/008
- 回归:新增 test-r026/test-r027,更新 r011/r013/r017,17 个脚本全绿;typecheck/build 通过
2026-09-10 14:05:06 +08:00

107 lines
5.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* R-011 API 层集成测试:handleStrategy tabs 域(R-027 策略静态化更新)
* 独立内存 mock settings scope + mock manager,验证:
* 1. tabs 端点:返回归一化统一数组(恒 5 条:3 内置 + 2 策略)
* 2. tabs/update:整表更新(顺序 + 显隐)
* 3. strategies/add:已退役(方法表不含 + 调用抛 not-found
* 4. strategies/remove:已退役(方法表不含 + 调用抛 not-found
* 5. strategies/update:已退役(方法表不含 + 调用抛 not-found
* 6. strategies/schema-update:单策略字段定义写入(读回一致)
* 7. 端点表不含 strategies/move(已废弃)
* 纯内存,不触真实数据(技术约束-011)
*/
import { handleStrategy, STRATEGY_METHODS } from '../src/api/strategies.js';
let pass = 0, fail = 0;
function assert(cond, msg) {
if (cond) { pass++; console.log(" ✅ " + msg); }
else { fail++; console.log(" ❌ " + msg); }
}
/** 内存 mock scope */
function makeScope(initial) {
const store = structuredClone(initial ?? {});
return {
get: () => structuredClone(store),
update: async (patch) => { Object.assign(store, structuredClone(patch)); },
};
}
/** 断言某端点已退役:STRATEGY_METHODS 不含 + 调用抛 code=not-found */
async function assertRetired(method, args, runtime) {
assert(!STRATEGY_METHODS.has(method), "STRATEGY_METHODS 不含 " + method);
try {
await handleStrategy(method, args, runtime);
assert(false, method + " 应抛 not-found(未抛错)");
} catch (e) {
assert(e.code === "not-found", method + " 抛 code=not-found");
}
}
const scope = makeScope({
strategies: [
{ id: "grid-supermarket", name: "网格超市" },
{ id: "manual-t", name: "手动做T" },
],
tabs: [
{ id: "tab-all-positions", kind: "builtin", refKey: "allPositions", name: "全部持仓", visible: true, order: 0 },
{ id: "tab-trade-records", kind: "builtin", refKey: "tradeRecords", name: "交易记录", visible: true, order: 1 },
{ id: "tab-watchlist", kind: "builtin", refKey: "watchlist", name: "关注列表", visible: true, order: 2 },
{ id: "tab-strategy-grid-supermarket", kind: "strategy", refId: "grid-supermarket", name: "网格超市", visible: true, order: 3 },
{ id: "tab-strategy-manual-t", kind: "strategy", refId: "manual-t", name: "手动做T", visible: true, order: 4 },
],
});
const runtime = { manager: {}, settings: scope };
// 1. tabs 端点
console.log("\n[1] tabs 端点");
const strat1 = await handleStrategy("strategies", {}, runtime);
assert(Array.isArray(strat1) && strat1.length === 2, "strategies 端点恒 2 项(内置)");
const tabs1 = await handleStrategy("tabs", {}, runtime);
assert(Array.isArray(tabs1) && tabs1.length === 5, "tabs 恒 5 条(3 内置 + 2 策略)");
assert(tabs1.filter((t) => t.kind === "builtin").length === 3 && tabs1.filter((t) => t.kind === "strategy").length === 2, "内置 3 + 策略 2 混排");
assert(tabs1.some((t) => t.refId === "grid-supermarket") && tabs1.some((t) => t.refId === "manual-t"), "策略 tab 恒在(不可增删)");
// 2. tabs/update 整表更新
console.log("\n[2] tabs/update 整表更新");
const afterUpdate = await handleStrategy("tabs/update", { tabs: [
{ id: "tab-strategy-manual-t", kind: "strategy", refId: "manual-t", name: "手动做T", visible: false, order: 0 },
{ id: "tab-all-positions", kind: "builtin", refKey: "allPositions", name: "全部持仓", visible: true, order: 1 },
{ id: "tab-watchlist", kind: "builtin", refKey: "watchlist", name: "关注列表", visible: true, order: 2 },
{ id: "tab-strategy-grid-supermarket", kind: "strategy", refId: "grid-supermarket", name: "网格超市", visible: true, order: 3 },
{ id: "tab-trade-records", kind: "builtin", refKey: "tradeRecords", name: "交易记录", visible: true, order: 4 },
] }, runtime);
assert(afterUpdate[0].id === "tab-strategy-manual-t" && afterUpdate[0].visible === false, "手动做T 排最前且隐藏(顺序+显隐生效)");
assert(afterUpdate[1].id === "tab-all-positions", "内置 tab 排第二位");
assert(afterUpdate.length === 5, "整表更新后恒 5 条");
// 3. strategies/add 已退役
console.log("\n[3] strategies/add 退役");
await assertRetired("strategies/add", { name: "长线持有" }, runtime);
// 4. strategies/remove 已退役
console.log("\n[4] strategies/remove 退役");
await assertRetired("strategies/remove", { strategyId: "grid-supermarket" }, runtime);
// 5. strategies/update 已退役
console.log("\n[5] strategies/update 退役");
await assertRetired("strategies/update", { strategies: [] }, runtime);
// 6. strategies/schema-update 单策略写入(R-027 唯一写路径)
console.log("\n[6] strategies/schema-update");
const saved = await handleStrategy("strategies/schema-update", {
strategyId: "grid-supermarket",
configSchema: [{ key: "gridGap", label: "网格间距", type: "text", def: "3%" }],
}, runtime);
assert(saved.configSchema[0].label === "网格间距", "schema-update 返回更新后的策略对象");
const stratAfter = await handleStrategy("strategies", {}, runtime);
assert(stratAfter.find((s) => s.id === "grid-supermarket").configSchema[0].label === "网格间距", "读回一致(写入 strategyFields");
assert(stratAfter.find((s) => s.id === "manual-t").configSchema.length === 0, "只影响目标策略(manual-t 无定义)");
// 7. 端点表不含 strategies/move
console.log("\n[7] strategies/move 已废弃");
assert(!STRATEGY_METHODS.has("strategies/move"), "STRATEGY_METHODS 不含 strategies/move");
console.log("\n结果: " + pass + " 通过 / " + fail + " 失败");
if (fail > 0) process.exit(1);