Files
one_divine_lot/scripts/test-r013-columns.mjs
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

106 lines
5.4 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-013 列显隐配置回归(迭代 11 扩展,2026-09-02;期望值随 R-015/迭代13 行情列与 R-027/迭代22 真相源更新)
* 覆盖(纯内存 mock scope,技术约束-011):
* 1. normalizeStrategyColumns 默认:基础列全显默认序 + configSchema 字段列全显
* 2. 覆盖生效:visible=false 隐藏、排序调整
* 3. configSchema 增删字段自动跟随(新增字段追加、删除字段移除)
* 4. updateStrategyColumns 整表覆盖 + getStrategyColumns 读回
*/
import {
normalizeStrategyColumns, getStrategyColumns, updateStrategyColumns,
} from '../src/settings.js';
let pass = 0, fail = 0;
function assert(cond, msg) {
if (cond) { pass++; console.log(" ✅ " + msg); }
else { fail++; console.log(" ❌ " + msg); }
}
function section(t) { console.log("\n[" + t + "]"); }
/** 内存 mock scope */
function makeScope(initial) {
const store = structuredClone(initial ?? {});
return {
get: () => structuredClone(store),
update: async (patch) => { Object.assign(store, structuredClone(patch)); },
_store: store,
};
}
const gridSchema = [
{ key: 'gridGap', label: '网格间距', type: 'text', def: '3%' },
{ key: 'riskLevel', label: '风险等级', type: 'enum', enum: ['低','中','高'], def: '中' },
];
// ---------- 1. 默认归一化 ----------
section('1. 默认列配置(无覆盖)');
const scope = makeScope({
strategies: [
{ id: 'grid-supermarket', name: '网格超市', configSchema: gridSchema },
{ id: 'manual-t', name: '手动做T' },
],
});
const gridCols = normalizeStrategyColumns(scope, 'grid-supermarket');
// 基础列目录(COLUMN_META,迭代 13/R-015 扩展:4 个行情列默认隐藏)
const baseKeys = ['lastPrice','pctChange','lastClose','avgPrice','lastTradePrice','shares',
'upStopPrice','downStopPrice','open','high'];
const hiddenByDefault = ['upStopPrice','downStopPrice','open','high'];
assert(gridCols.length === 12, '默认 12 列(10 基础 + 2 字段)');
assert(baseKeys.every((k) => gridCols.some((c) => c.key === k)), '含全部基础列');
assert(gridCols[10].key === 'gridGap' && gridCols[11].key === 'riskLevel', '字段列按 configSchema 序追加末尾');
assert(gridCols.slice(0, 6).every((c) => c.visible !== false), '前 6 基础列默认可见');
assert(gridCols.filter((c) => hiddenByDefault.includes(c.key)).every((c) => c.visible === false), '4 个行情列默认隐藏(R-015');
assert(gridCols[0].key === 'lastPrice', '默认序:现价在前');
const manualCols = normalizeStrategyColumns(scope, 'manual-t');
assert(manualCols.length === 10, '无字段定义策略仅 10 基础列');
// ---------- 2. 覆盖生效 ----------
section('2. 覆盖(隐藏 + 排序)');
await updateStrategyColumns(scope, 'grid-supermarket', [
{ key: 'riskLevel', visible: true },
{ key: 'shares', visible: true },
{ key: 'gridGap', visible: false }, // 隐藏 gridGap
{ key: 'lastPrice', visible: false }, // 隐藏现价
{ key: 'pctChange', visible: true },
{ key: 'lastClose', visible: true },
{ key: 'avgPrice', visible: true },
{ key: 'lastTradePrice', visible: true },
]);
const after = getStrategyColumns(scope, 'grid-supermarket');
assert(after.length === 12, '读回 12 列(含不可见,保位)');
assert(after[0].key === 'riskLevel', '排序覆盖:riskLevel 提到最前');
assert(after.find((c) => c.key === 'gridGap').visible === false, 'gridGap 已隐藏');
assert(after.find((c) => c.key === 'lastPrice').visible === false, 'lastPrice 已隐藏');
assert(after[1].key === 'shares', '排序覆盖:shares 第二');
// ---------- 3. configSchema 增删跟随 ----------
section('3. configSchema 增删字段自动跟随');
await updateStrategyColumns(scope, 'grid-supermarket', after); // 已存覆盖
// 模拟新增字段 hasStop
scope._store.strategies = scope._store.strategies.map((s) => s.id === 'grid-supermarket'
? { ...s, configSchema: [...gridSchema, { key: 'hasStop', label: '设置止损', type: 'boolean', def: false }] } : s);
const afterAdd = normalizeStrategyColumns(scope, 'grid-supermarket');
assert(afterAdd.some((c) => c.key === 'hasStop'), '新增字段 hasStop 自动加入列配置');
assert(afterAdd.find((c) => c.key === 'hasStop').visible !== false, '新字段默认可见');
assert(afterAdd.find((c) => c.key === 'gridGap').visible === false, '原隐藏 gridGap 保留隐藏');
// 模拟删除字段 riskLevel
scope._store.strategies = scope._store.strategies.map((s) => s.id === 'grid-supermarket'
? { ...s, configSchema: gridSchema.filter((f) => f.key !== 'riskLevel') } : s);
const afterDel = normalizeStrategyColumns(scope, 'grid-supermarket');
assert(!afterDel.some((c) => c.key === 'riskLevel'), '删除字段 riskLevel 列自动移除');
assert(afterDel.some((c) => c.key === 'gridGap'), '其余列保留');
// ---------- 4. R-027:字段定义真相源 strategyFields ----------
section('4. 字段定义真相源(strategyFields 优先,旧 strategies 兜底)');
const newScope = makeScope({
strategies: [{ id: 'grid-supermarket', name: '网格超市', configSchema: [{ key: 'old', label: '旧字段', type: 'text' }] }],
strategyFields: { 'grid-supermarket': gridSchema },
});
const newCols = normalizeStrategyColumns(newScope, 'grid-supermarket');
assert(newCols.some((c) => c.key === 'gridGap') && !newCols.some((c) => c.key === 'old'), 'strategyFields 优先于旧 configSchema');
console.log("");
console.log('结果: ' + pass + ' 通过, ' + fail + ' 失败');
process.exit(fail > 0 ? 1 : 0);