Files
one_divine_lot/scripts/test-r011-tabs.mjs
T
kyugao 4ac8a29805 feat(Tab设置+UI主题): 迭代09 Tab设置统一管理 + 迭代10 UI适配DSH主题
迭代09 (R-011): Tab 设置统一管理所有会话 tab
- settings.tabs 布尔对象 → 统一有序数组 (kind: builtin|strategy)
- 旧配置 schema union 兼容 + 读取归一化无感迁移
- strategies 收窄为 {id,name},策略行 name join 自动跟随改名
- 策略增删联动 tabs 条目 (add 追加末尾 / remove 联动删除)
- 客户端统一注册读 tabs 数组,内置与策略可任意混排
- 设置页 Tab 设置子 tab:混排表格 + 拖动手柄 + 显隐开关 + 落点立即持久化
- 策略分组瘦身:仅 新增/重命名/删除 + 指引提示
- 回归测试: test-r011-tabs (23) + test-r011-api (12)

迭代10 (R-012): UI 适配 DSH 浅色/深色/跟随系统主题
- 10 个视图文件 141 处硬编码色 → var(--dsw-alias-*, fallback)
- 语义映射: bg-layer/label/border/state/button-contrast 等
- 淡底 color-mix 自适应;遮罩 bg-mask-1;阴影 shadow-lv3
2026-09-02 14:00:56 +08:00

140 lines
6.9 KiB
JavaScript
Raw 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 回归测试:tabs 统一管理数据层
* 独立内存 mock scopesettings 读写),验证:
* 1. 归一化迁移:旧布尔对象 tabs → 有序数组(内置保留原显隐,策略接续追加)
* 2. 迁移:旧隐藏策略 → visible=trueQ3
* 3. getTabs:策略行 name join strategiesQ4 自动跟随改名)
* 4. updateTabs:整表更新(顺序 + 显隐)
* 5. addStrategy:联动追加 tab 条目(末尾,Q2)
* 6. removeStrategy:联动删除对应 tab 条目(D3)
* 纯内存,不触真实数据(技术约束-011)
*/
import {
getTabs, updateTabs, normalizeTabs,
addStrategy, removeStrategy, renameStrategy, getStrategies,
} from '../src/settings.js';
let pass = 0, fail = 0;
function assert(cond, msg) {
if (cond) { pass++; console.log(" ✅ " + msg); }
else { fail++; console.log(" ❌ " + msg); }
}
/** 内存 mock scopeget/update 读改写对象 */
function makeScope(initial) {
const store = structuredClone(initial ?? {});
return {
get: () => structuredClone(store),
update: async (patch) => { Object.assign(store, structuredClone(patch)); },
_store: store,
};
}
// ---------- 1. 旧格式归一化(布尔对象 tabs + 策略带 visible/order ----------
console.log("\n[1] 旧格式归一化迁移");
const oldScope = makeScope({
strategies: [
{ id: 'grid-supermarket', name: '网格超市', visible: true, order: 5 },
{ id: 'manual-t', name: '手动做T', visible: false, order: 9 },
],
tabs: { allPositions: true, tradeRecords: false, watchlist: true },
});
const oldTabs = normalizeTabs(oldScope);
assert(oldTabs.length === 5, "归一化后共 5 个条目(3 内置 + 2 策略)");
assert(oldTabs[0].kind === 'builtin' && oldTabs[0].refKey === 'allPositions', '内置 allPositions 在前');
assert(oldTabs[1].refKey === 'tradeRecords' && oldTabs[1].visible === false, '内置 tradeRecords 保留旧显隐=false');
assert(oldTabs[2].refKey === 'watchlist' && oldTabs[2].visible === true, '内置 watchlist 保留旧显隐=true');
const manualTab = oldTabs.find((t) => t.kind === 'strategy' && t.refId === 'manual-t');
assert(manualTab && manualTab.visible === true, "旧隐藏策略 manual-t 迁移后 visible=trueQ3");
assert(manualTab && manualTab.name === '手动做T', '策略行 name 来自 strategies');
assert(oldTabs[3].order === 3 && oldTabs[4].order === 4, '策略按 order 接续(order 连续)');
// ---------- 2. 已归一化数组直接返回 ----------
console.log("\n[2] 已归一化数组直接返回");
const arrScope = makeScope({ tabs: [
{ id: 'tab-a', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
{ id: 'tab-b', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 0 },
] });
const arrTabs = normalizeTabs(arrScope);
assert(arrTabs[0].id === 'tab-b' && arrTabs[1].id === 'tab-a', '数组按 order 排序');
// ---------- 3. 策略行 name join(改名自动跟随,Q4 ----------
console.log("\n[3] getTabs 策略名 join");
const joinScope = makeScope({
strategies: [{ id: 'manual-t', name: '手动做T新名' }],
tabs: [
{ id: 'tab-all', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-s', kind: 'strategy', refId: 'manual-t', name: '旧名', visible: true, order: 1 },
],
});
const joined = getTabs(joinScope);
assert(joined.find((t) => t.id === 'tab-s').name === '手动做T新名', '策略改名后 tabs 行 name 自动跟随(Q4');
// ---------- 4. updateTabs 整表更新 ----------
console.log("\n[4] updateTabs 整表更新");
const updScope = makeScope({ tabs: [
{ id: 'tab-a', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-b', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 1 },
] });
await updateTabs(updScope, [
{ id: 'tab-b', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: false, order: 0 },
{ id: 'tab-a', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
]);
const updTabs = getTabs(updScope);
assert(updTabs[0].id === 'tab-b' && updTabs[0].visible === false, '整表更新:顺序与显隐生效');
assert(updTabs.length === 2, '整表更新后条目数不变');
// ---------- 5. addStrategy 联动追加 tab(末尾,Q2 ----------
console.log("\n[5] addStrategy 联动追加 tab");
const addScope = makeScope({
strategies: [{ id: 'grid-supermarket', name: '网格超市' }],
tabs: [
{ id: 'tab-all', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-s', kind: 'strategy', refId: 'grid-supermarket', name: '网格超市', visible: true, order: 1 },
],
});
const added = await addStrategy(addScope, '长线持有');
assert(getStrategies(addScope).length === 2, '策略定义表新增');
const addTabs = getTabs(addScope);
assert(addTabs.length === 3, 'tabs 联动新增一条');
assert(addTabs[2].kind === 'strategy' && addTabs[2].refId === added.id, '新策略 tab 追加到末尾(Q2');
assert(addTabs[2].name === '长线持有', '新策略 tab name 正确');
assert(addTabs[2].visible === true, '新策略 tab 默认显示');
assert(addTabs[2].order === 2, 'order 连续');
// ---------- 6. removeStrategy 联动删除 tabD3 ----------
console.log("\n[6] removeStrategy 联动删除 tab");
const rmScope = makeScope({
strategies: [
{ id: 'grid-supermarket', name: '网格超市' },
{ id: 'manual-t', name: '手动做T' },
],
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 },
{ id: 'tab-m', kind: 'strategy', refId: 'manual-t', name: '手动做T', visible: true, order: 2 },
],
});
await removeStrategy(rmScope, 'grid-supermarket');
const rmTabs = getTabs(rmScope);
assert(rmTabs.length === 2, '删除策略后 tabs 联动删除对应条目');
assert(!rmTabs.some((t) => t.refId === 'grid-supermarket'), 'grid-supermarket tab 条目已删除');
assert(rmTabs.some((t) => t.refId === 'manual-t'), 'manual-t tab 条目保留');
assert(getStrategies(rmScope).length === 1, '策略定义表删除');
// ---------- 7. renameStrategy 只改名(tabs 行 join 跟随,不影响定义) ----------
console.log("\n[7] renameStrategy");
const rnScope = makeScope({
strategies: [{ id: 'manual-t', name: '手动做T' }],
tabs: [
{ id: 'tab-m', kind: 'strategy', refId: 'manual-t', name: '手动做T', visible: true, order: 0 },
],
});
await renameStrategy(rnScope, 'manual-t', '手动T+');
assert(getStrategies(rnScope)[0].name === '手动T+', '策略定义名更新');
assert(getTabs(rnScope)[0].name === '手动T+', 'tabs 行 name join 跟随(Q4');
// ---------- 汇总 ----------
console.log("\n结果: " + pass + " 通过 / " + fail + " 失败");
if (fail > 0) process.exit(1);