import { describe, expect, it } from 'vitest'; import { evalExpr, evalText, rowsAt } from '../internal/domain/auto-expr.ts'; /** 假的 S-FEEL:只认这份测试用得到的两句,够验证「条件走的是注入进来的求值器」 */ const condition = (expr: string, scope: Readonly>): boolean => { if (expr === 'found_ref != ""') return String(scope['found_ref'] ?? '') !== ''; if (expr === 'n > 0') return Number(scope['n'] ?? 0) > 0; throw new Error(`没教过这句:${expr}`); }; const base = { scope: {}, lets: {}, condition }; describe('rowsAt:地址取行', () => { const scope = { 'parts.profile': [{ internal_pn: 'A', chip_type_id: 1 }], 'mine:sub_eval.part': [{ internal_pn: 'B' }], pns: ['X', 'Y'], 'result:items': [{ param_name: 'p' }], }; it('域.对象 取任务视图整表', () => { expect(rowsAt(scope, 'parts.profile')).toEqual([{ internal_pn: 'A', chip_type_id: 1 }]); }); it('mine: 只取本单草稿', () => { expect(rowsAt(scope, 'mine:sub_eval.part')).toEqual([{ internal_pn: 'B' }]); }); it('in: 取入参数组;字符串元素归一成 { value }', () => { expect(rowsAt(scope, 'in:pns')).toEqual([{ value: 'X' }, { value: 'Y' }]); }); it('result: 取本拍 AI 出参', () => { expect(rowsAt(scope, 'result:items')).toEqual([{ param_name: 'p' }]); }); it('取不到就是空数组,不抛 —— 首拍很多表本来就还没有行', () => { expect(rowsAt(scope, 'nope.nope')).toEqual([]); }); }); describe('evalExpr:八种形状', () => { it('常量原样', () => { expect(evalExpr('W05', base)).toBe('W05'); expect(evalExpr(7, base)).toBe(7); expect(evalExpr(true, base)).toBe(true); }); it('{in} 先看 let,再看作用域', () => { const ctx = { ...base, scope: { pn: 'FROM-SCOPE', other: 'S' }, lets: { pn: 'FROM-LET' } }; expect(evalExpr({ in: 'pn' }, ctx)).toBe('FROM-LET'); expect(evalExpr({ in: 'other' }, ctx)).toBe('S'); }); it('{col} 取本行,{outer} 取外层那一行', () => { const ctx = { ...base, row: { a: 1 }, outer: { b: 2 } }; expect(evalExpr({ col: 'a' }, ctx)).toBe(1); expect(evalExpr({ outer: 'b' }, ctx)).toBe(2); }); it('取不到的列是空串,不是 undefined —— 空串写进草稿层等于「这一列没写」', () => { expect(evalExpr({ col: 'nope' }, { ...base, row: {} })).toBe(''); }); it('{cat} 按字符串拼', () => { expect(evalExpr({ cat: ['W05-', { in: 'd' }] }, { ...base, lets: { d: 20260829 } })).toBe('W05-20260829'); }); it('{fmt as digits} 只留数字', () => { expect(evalExpr({ fmt: { in: 't' }, as: 'digits' }, { ...base, lets: { t: '2026-08-29T21:51:03Z' } })).toBe('20260829215103'); }); it('{lookup} 查另一张表的一列', () => { const ctx = { ...base, scope: { 'parts.profile': [{ internal_pn: 'A', chip_type_id: 3 }] }, row: { pn: 'A' } }; expect(evalExpr({ lookup: 'parts.profile', by: { internal_pn: { col: 'pn' } }, take: 'chip_type_id' }, ctx)).toBe(3); }); it('{lookup} 查不到给 else,缺省空串', () => { const ctx = { ...base, scope: { 'parts.profile': [] }, row: { pn: 'A' } }; expect(evalExpr({ lookup: 'parts.profile', by: { internal_pn: { col: 'pn' } }, take: 'chip_type_id' }, ctx)).toBe(''); expect(evalExpr({ lookup: 'parts.profile', by: { internal_pn: { col: 'pn' } }, take: 'x', else: 'absent' }, ctx)).toBe('absent'); }); it('{lookup} 比值按字符串 —— 台账里数字与文本混着存', () => { const ctx = { ...base, scope: { 'a.b': [{ k: 1, v: 'hit' }] }, row: { k: '1' } }; expect(evalExpr({ lookup: 'a.b', by: { k: { col: 'k' } }, take: 'v' }, ctx)).toBe('hit'); }); it('{when} 走注入的 S-FEEL,已算出的同行字段进它的作用域', () => { const ctx = { ...base, computed: { found_ref: 'LM2596S-5.0' } }; expect(evalExpr({ when: 'found_ref != ""', then: 'found', else: 'absent' }, ctx)).toBe('found'); expect(evalExpr({ when: 'found_ref != ""', then: 'found', else: 'absent' }, { ...base, computed: { found_ref: '' } })).toBe('absent'); }); it('{when} 谓词算不出来时走 else,不把整拍拖垮', () => { expect(evalExpr({ when: '这句没教过', then: 'a', else: 'b' }, base)).toBe('b'); }); }); describe('evalText:一律出字符串', () => { it('数字与布尔照实转', () => { expect(evalText(3, base)).toBe('3'); expect(evalText(true, base)).toBe('true'); }); it('空与 undefined 都是空串', () => { expect(evalText({ col: 'nope' }, { ...base, row: {} })).toBe(''); }); });