/** 视图模版填数 —— domain 测试零 mock(AM 07:模版是内容不是代码) */
import { describe, expect, it } from 'vitest';
import { displayValue, escapeHtml, renderView, resolveSlot } from '../internal/domain/view-render.ts';
import { templateIsSafe } from '../internal/domain/control-handover.ts';
describe('取值与展示', () => {
it('点号下行 + 数组下标 —— 与步声明的写法一致', () => {
const scope = { records: [{ pn: 'LM358DR' }], n: 3 };
expect(resolveSlot(scope, 'records.0.pn')).toBe('LM358DR');
expect(resolveSlot(scope, 'n')).toBe(3);
expect(resolveSlot(scope, 'nope.deep')).toBeUndefined();
});
it('空值显示为「—」,免得画面留空洞', () => {
expect(displayValue(undefined)).toBe('—');
expect(displayValue('')).toBe('—');
expect(displayValue(null)).toBe('—');
});
it('数组显示条数,布尔显示是/否,小数收敛', () => {
expect(displayValue([1, 2, 3])).toBe('3');
expect(displayValue(true)).toBe('是');
expect(displayValue(0.123456789)).toBe('0.1235');
});
});
describe('填数只做取值与转义 —— 模版不得成为代码', () => {
it('填入的值被转义,注不进标签', () => {
const html = '';
const out = renderView(html, { x: '' });
// 标签没能成型:尖括号全被转义,留下的是惰性文本
expect(out).not.toContain('
<img src=x onerror=alert(1)>');
});
it('净化门查的是**作者写的模版**,不是填完数据的成品', () => {
// 模版本身必须无脚本、无事件处理器、无外链
expect(templateIsSafe('')).toBe(true);
expect(templateIsSafe('')).toBe(false);
// 成品里出现「onerror=」这串字面文本是转义后的惰性文本,不该再拿模版规则去卡它 ——
// 否则任何正文提到事件处理器的业务数据都会被误伤。安全由「模版过门 + 沙箱容器」两道保证。
expect(renderView('', { x: 'onclick=1' })).toBe('onclick=1');
});
it('未命中的槽位填「—」,不把占位文字漏到成品上', () => {
expect(renderView('占位', {})).toBe('—');
});
it('填充消费槽位 —— 交出去的是干净 HTML,不带模板属性', () => {
expect(renderView('', { x: 1 })).toBe('1');
});
});
describe('行模版 —— 逐条清单不写死条数', () => {
const html = '