ec02a791ee
- 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 通过
106 lines
5.4 KiB
JavaScript
106 lines
5.4 KiB
JavaScript
/**
|
||
* 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); |