import { describe, it, expect } from 'vitest'; import { createPointer, advance, enterSubPlan, exitSubPlan, currentPath, isDone } from './step-pointer.mjs'; describe('step-pointer (волны: линейный → дерево указателей, D6/OQ1)', () => { it('линейное продвижение даёт путь [n]', () => { let p = createPointer({ length: 3 }); expect(currentPath(p)).toEqual([0]); p = advance(p); expect(currentPath(p)).toEqual([1]); }); it('вход в под-план углубляет путь, выход возвращает', () => { let p = createPointer({ length: 2 }); p = enterSubPlan(p, { length: 2 }); expect(currentPath(p)).toEqual([0, 0]); p = advance(p); expect(currentPath(p)).toEqual([0, 1]); p = exitSubPlan(p); p = advance(p); expect(currentPath(p)).toEqual([1]); }); it('isDone когда корень исчерпан', () => { let p = createPointer({ length: 1 }); expect(isDone(p)).toBe(false); p = advance(p); expect(isDone(p)).toBe(true); }); it('линейный режим обратно совместим (один уровень = число indexAtRoot)', () => { let p = createPointer({ length: 5 }); p = advance(advance(p)); expect(p.indexAtRoot).toBe(2); }); }); // ── R-08: сериализация + дерево-навигация ── import { serializePointer, deserializePointer, nodeAt, isContainer, normalizeToLeaf, advanceOverTree, MAX_TREE_DEPTH } from './step-pointer.mjs'; const FLAT = [{ n: 1, op: 'Write', object: 'a' }, { n: 2, op: 'Bash', object: 'b' }]; const TREE = [ { n: 1, op: 'Write', object: 'a' }, { n: 2, substeps: [{ n: '2.1', op: 'Edit', object: 'x' }, { n: '2.2', op: 'Bash', object: 'y' }] }, { n: 3, op: 'Write', object: 'c' }, ]; describe('serialize/deserialize (§4.0: depth-1 = целое)', () => { it('depth-1 ↔ целое', () => { expect(serializePointer(createPointer({ length: 3 }))).toBe(0); expect(serializePointer(advance(createPointer({ length: 3 })))).toBe(1); expect(deserializePointer(1, FLAT).stack).toEqual([{ index: 1, length: 2 }]); }); it('глубже ↔ массив индексов', () => { let p = enterSubPlan(advance(createPointer({ length: 3 })), { length: 2 }); p = advance(p); expect(serializePointer(p)).toEqual([1, 1]); const d = deserializePointer([1, 1], TREE); expect(d.stack).toEqual([{ index: 1, length: 3 }, { index: 1, length: 2 }]); }); it('строгий deserialize: мусор → null (SE-5)', () => { expect(deserializePointer('x', FLAT)).toBe(null); expect(deserializePointer([0, 'a'], TREE)).toBe(null); expect(deserializePointer(-1, FLAT)).toBe(null); expect(deserializePointer([1, 0], FLAT)).toBe(null); }); }); describe('nodeAt / isContainer', () => { it('nodeAt следует currentPath', () => { expect(nodeAt(TREE, deserializePointer(0, TREE)).n).toBe(1); expect(nodeAt(TREE, deserializePointer([1, 1], TREE)).n).toBe('2.2'); expect(nodeAt(TREE, deserializePointer(9, TREE))).toBe(null); }); it('isContainer', () => { expect(isContainer(TREE[1])).toBe(true); expect(isContainer(TREE[0])).toBe(false); expect(isContainer({ substeps: [] })).toBe(true); }); }); describe('normalizeToLeaf (спуск к листу, SE-2)', () => { it('лист → сам', () => { expect(nodeAt(TREE, normalizeToLeaf(TREE, deserializePointer(0, TREE))).n).toBe(1); }); it('контейнер → спуск к первому листу', () => { const p = normalizeToLeaf(TREE, deserializePointer(1, TREE)); expect(serializePointer(p)).toEqual([1, 0]); expect(nodeAt(TREE, p).n).toBe('2.1'); }); it('за концом → null', () => { expect(normalizeToLeaf(TREE, deserializePointer(9, TREE))).toBe(null); }); it('плоский план: normalizeToLeaf — тождество', () => { expect(serializePointer(normalizeToLeaf(FLAT, deserializePointer(1, FLAT)))).toBe(1); }); }); describe('advanceOverTree (depth-first, SE-4/SE-6)', () => { it('плоский: +1 как целое', () => { const p = deserializePointer(0, FLAT); expect(serializePointer(advanceOverTree(FLAT, p))).toBe(1); }); it('depth-first полный обход листьев TREE (snapshot, без пропусков/повторов)', () => { let p = normalizeToLeaf(TREE, deserializePointer(0, TREE)); const seq = []; let guard = 0; while (p && nodeAt(TREE, p) && guard++ < 20) { seq.push(nodeAt(TREE, p).n); p = advanceOverTree(TREE, p); } expect(seq).toEqual([1, '2.1', '2.2', 3]); }); it('предел глубины → throw (SE-4)', () => { const deep = (d) => d === 0 ? [{ n: 'leaf', op: 'Write', object: 'z' }] : [{ substeps: deep(d - 1) }]; const bad = deep(MAX_TREE_DEPTH + 2); expect(() => normalizeToLeaf(bad, createPointer({ length: bad.length }))).toThrow(); }); });