/** * R-013 列显隐配置回归(迭代 11 扩展,2026-09-02) * 覆盖(纯内存 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'); const baseKeys = ['lastPrice','pctChange','lastClose','avgPrice','lastTradePrice','shares']; assert(gridCols.length === 8, '默认 8 列(6 基础 + 2 字段)'); assert(baseKeys.every((k) => gridCols.some((c) => c.key === k)), '含全部基础列'); assert(gridCols[6].key === 'gridGap' && gridCols[7].key === 'riskLevel', '字段列按 configSchema 序追加末尾'); assert(gridCols.every((c) => c.visible !== false), '默认全部可见'); assert(gridCols[0].key === 'lastPrice', '默认序:现价在前'); const manualCols = normalizeStrategyColumns(scope, 'manual-t'); assert(manualCols.length === 6, '旧策略(无 configSchema)仅 6 基础列'); // ---------- 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 === 8, '读回 8 列(含不可见,保位)'); 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'), '其余列保留'); console.log(""); console.log('结果: ' + pass + ' 通过, ' + fail + ' 失败'); process.exit(fail > 0 ? 1 : 0);