/** * R-011 API 层集成测试:handleStrategy tabs 域(联动链路) * 独立内存 mock settings scope + mock manager,验证: * 1. tabs 端点:返回归一化后的统一数组 * 2. tabs/update:整表更新(顺序 + 显隐) * 3. strategies/add:联动追加 tab 条目(末尾) * 4. strategies/remove:联动删除 tab 条目 + 清份额(manager) * 5. strategies/update:重命名(tabs 行 name join 跟随) * 纯内存,不触真实数据(技术约束-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)); }, }; } const scope = makeScope({ strategies: [{ id: "grid-supermarket", name: "网格超市" }], tabs: [ { id: "tab-all", kind: "builtin", refKey: "allPositions", name: "全部持仓", visible: true, order: 0 }, { id: "tab-g", kind: "strategy", refId: "grid-supermarket", name: "网格超市", visible: true, order: 1 }, ], }); const cleared = []; const manager = { async clearStrategyShares(id) { cleared.push(id); }, }; const runtime = { manager, settings: scope }; // 1. tabs 端点 console.log("\n[1] tabs 端点"); const tabs1 = await handleStrategy("tabs", {}, runtime); assert(Array.isArray(tabs1) && tabs1.length === 2, "tabs 返回统一数组"); assert(tabs1[0].kind === "builtin" && tabs1[1].kind === "strategy", "内置 + 策略混排"); // 2. tabs/update 整表更新 console.log("\n[2] tabs/update 整表更新"); const afterUpdate = await handleStrategy("tabs/update", { tabs: [ { id: "tab-g", kind: "strategy", refId: "grid-supermarket", name: "网格超市", visible: false, order: 0 }, { id: "tab-all", kind: "builtin", refKey: "allPositions", name: "全部持仓", visible: true, order: 1 }, ] }, runtime); assert(afterUpdate[0].id === "tab-g" && afterUpdate[0].visible === false, "策略 tab 排到前面且隐藏"); assert(afterUpdate[1].id === "tab-all", "内置 tab 排后"); // 3. strategies/add 联动追加 tab console.log("\n[3] strategies/add 联动追加 tab"); const added = await handleStrategy("strategies/add", { name: "长线持有" }, runtime); const tabsAfterAdd = await handleStrategy("tabs", {}, runtime); assert(tabsAfterAdd.length === 3, "add 后 tabs 联动新增一条"); assert(tabsAfterAdd[2].kind === "strategy" && tabsAfterAdd[2].refId === added.id, "新策略 tab 追加末尾"); assert(tabsAfterAdd[2].name === "长线持有", "新策略 tab name 正确"); // 4. strategies/remove 联动删除 tab + 清份额 console.log("\n[4] strategies/remove 联动删除 tab"); await handleStrategy("strategies/remove", { strategyId: "grid-supermarket" }, runtime); const tabsAfterRm = await handleStrategy("tabs", {}, runtime); assert(!tabsAfterRm.some((t) => t.refId === "grid-supermarket"), "删除策略后对应 tab 条目消失"); assert(tabsAfterRm.some((t) => t.refId === added.id), "其余策略 tab 保留"); assert(cleared.includes("grid-supermarket"), "manager.clearStrategyShares 被调用(份额回未分配)"); // 5. strategies/update 重命名(tabs 行 join 跟随) console.log("\n[5] strategies/update 重命名跟随"); await handleStrategy("strategies/update", { strategies: [ { id: added.id, name: "长线持有v2" }, ] }, runtime); const tabsAfterRename = await handleStrategy("tabs", {}, runtime); assert(tabsAfterRename.find((t) => t.refId === added.id).name === "长线持有v2", "重命名后 tabs 行 name join 跟随(Q4)"); // 6. 端点表不含 strategies/move console.log("\n[6] strategies/move 已废弃"); assert(!STRATEGY_METHODS.has("strategies/move"), "STRATEGY_METHODS 不含 strategies/move"); console.log("\n结果: " + pass + " 通过 / " + fail + " 失败"); if (fail > 0) process.exit(1);