Files
brain/tools/secretary-protocol.test.mjs
T
Дмитрий b527f26a37 feat(secretary): разделитель сессий в разделе «Шаги»
В сквозной тетради дела между ходами разных сессий рендер вставляет строку
«—— сессия <id> ——» (граница рабочего захода) — навигация по длинному делу.
Не перед первой сессией; только на смене session между соседними шагами. TDD, свод зелёный.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:46:57 +03:00

103 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import { renderProtocol, EMPTY_PROTOCOL } from './secretary-protocol.mjs';
describe('EMPTY_PROTOCOL', () => {
it('пустой протокол со всеми 9 разделами', () => {
expect(EMPTY_PROTOCOL()).toEqual({
subject: '', status: 'открыто',
decisions: [], alternatives: [], consequences: [],
will: [], open: [], doneNext: [], history: [], steps: [],
});
});
});
describe('renderProtocol — 9 категорий + шаги', () => {
const proto = {
subject: 'фоновый секретарь', status: 'открыто',
decisions: [{ text: 'D', why: 'w', turns: [7], session: '69992620-x' }],
alternatives: [{ text: 'ALT', turns: [8], session: '69992620-x' }],
consequences: [{ text: 'CONS', turns: [9], session: '69992620-x' }],
will: [{ text: 'W', turns: [10], session: '69992620-x' }],
open: [{ text: 'Q', turns: [11], session: '69992620-x' }],
doneNext: [{ text: 'N', done: false, turns: [12], session: '69992620-x' }],
history: [],
};
it('шапка «Дело» со статусом/хозяином/целью (по opts)', () => {
const md = renderProtocol(proto, { work: 'создание-секретаря', date: '2026-06-22 11:00' });
expect(md).toContain('**Дело:** создание-секретаря');
expect(md).toContain('**Статус:** открыто');
expect(md).toContain('**Хозяин:** владелец');
expect(md).toContain('**Цель:** фоновый секретарь');
});
it('разделы Альтернативы и Последствия / цена', () => {
const md = renderProtocol(proto);
expect(md).toContain('## Альтернативы');
expect(md).toContain('- ALT');
expect(md).toContain('## Последствия / цена');
expect(md).toContain('- CONS');
});
it('провенанс [→N] без метки файла @ (имя файла — только в Шагах)', () => {
const md = renderProtocol(proto);
expect(md).toContain('- D — w [→7]');
expect(md).not.toContain('@69992620');
expect(md).toContain('## Твоя воля / запреты');
expect(md).toContain('## Открытые вопросы');
});
it('история: тайм-линия toggle (внёс →, вынес ←, вернул →, снова вынес ←)', () => {
const md = renderProtocol({
subject: '', status: 'открыто', steps: [],
decisions: [], alternatives: [], consequences: [], will: [], open: [], doneNext: [],
history: [{ text: 'пункт X', events: [{ turn: 41, dir: 'in' }, { turn: 43, dir: 'out' }, { turn: 55, dir: 'in' }, { turn: 70, dir: 'out' }] }],
});
expect(md).toContain('~~пункт X~~ [→41] [←43] [→55] [←70]');
});
it('провенанс с несколькими ходами: [→33], [50]', () => {
const md = renderProtocol({
subject: '', status: 'открыто', steps: [], alternatives: [], consequences: [], will: [], open: [], doneNext: [], history: [],
decisions: [{ text: 'Y', why: null, turns: [33, 50] }],
});
expect(md).toContain('- Y [→33], [50]');
});
it('зачёркивание во всех корзинах', () => {
const md = renderProtocol({
subject: '', status: 'открыто', history: [],
decisions: [{ text: 'D', struck: true }], alternatives: [{ text: 'A', struck: true }],
consequences: [{ text: 'C', struck: true }], will: [{ text: 'W', struck: true }],
open: [{ text: 'Q', struck: true }], doneNext: [{ text: 'N', struck: true, done: false }],
});
for (const t of ['~~D~~', '~~A~~', '~~C~~', '~~W~~', '~~Q~~', '~~N~~']) expect(md).toContain(t);
});
it('раздел Шаги: ссылка на отдельный файл хода (s.file) вместо общего лога', () => {
const md = renderProtocol({
subject: '', status: 'открыто', history: [],
decisions: [], alternatives: [], consequences: [], will: [], open: [], doneNext: [],
steps: [{ turn: 1, session: 'sess', file: 'ходы/turn-1.log', text: 'Ход 1 — я: x · ты: y · делал: —' }],
});
expect(md).toContain('Ход 1 — я: x · ты: y · делал: — · ходы/turn-1.log');
expect(md).not.toContain('· sess.log');
});
it('раздел Шаги (Слой 1): строка на ход + название файла полного хода в конце строки', () => {
const md = renderProtocol({
subject: '', status: 'открыто', history: [],
decisions: [], alternatives: [], consequences: [], will: [], open: [], doneNext: [],
steps: [{ turn: 1, session: '69992620-x', text: 'Ход 1 — я: про оглавление · ты: тема+время · делал: читал хук' }],
});
expect(md).toContain('## Шаги (Слой 1)');
expect(md).toContain('Ход 1 — я: про оглавление · ты: тема+время · делал: читал хук · 69992620-x.log');
});
it('Шаги: разделитель «—— сессия X ——» при смене сессии (не перед первой)', () => {
const md = renderProtocol({
subject: '', status: 'открыто', history: [],
decisions: [], alternatives: [], consequences: [], will: [], open: [], doneNext: [],
steps: [
{ turn: 1, session: 'sА', text: 'Ход 1 — я: a · ты: b · делал: —' },
{ turn: 2, session: 'sА', text: 'Ход 2 — я: c · ты: d · делал: —' },
{ turn: 3, session: 'sБ', text: 'Ход 3 — я: e · ты: f · делал: —' },
],
});
expect(md).toContain('—— сессия sБ ——');
expect(md).not.toContain('—— сессия sА ——'); // перед первой сессией разделителя нет
expect(md.indexOf('—— сессия sБ ——')).toBeLessThan(md.indexOf('Ход 3'));
});
});