Archived
feat(loop-engine): 配方表达式求值 —— 八种形状,S-FEEL 仍由外面注入
地址四种、取不到一律空数组(首拍很多表本来就没行,抛了等于开局交人); 比值一律按字符串(台账里数字与文本混着存);谓词算不出来走 else,不拖垮整拍。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { evalExpr, evalText, rowsAt } from '../internal/domain/auto-expr.ts';
|
||||
|
||||
/** 假的 S-FEEL:只认这份测试用得到的两句,够验证「条件走的是注入进来的求值器」 */
|
||||
const condition = (expr: string, scope: Readonly<Record<string, unknown>>): 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('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user