Files
brain/tools/step-pointer.test.mjs
T

113 lines
4.9 KiB
JavaScript

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();
});
});