moment-fence.test.ts3.7 KBView on GitHub import { parseMoment } from '@/modules/documents/coaching/moment-fence';
const BODY = [
'deal: Works Progress Architecture',
'meeting: Pirros | Works Progress',
'criterion: D2',
'at: 04:10',
'verdict: bad',
'coaching: Stay on the PyRevit claim.',
'',
'## context',
'**Michael** (03:50) We have a spreadsheet for each product type.',
'',
'## moment',
'**Michael** (04:10) I am fairly confident we may not be moving forward.',
'**Keenan** (05:02) So it sounds like there is a need for something.',
'',
'## reaction',
'**Michael** (05:40) Well, we have a number of resources.',
].join('\n');
describe('moment fence', () => {
it('parses the header, all three sections and speaker/timestamp', () => {
const m = parseMoment(BODY)!;
expect(m.deal).toBe('Works Progress Architecture');
expect(m.criterion).toBe('D2');
expect(m.verdict).toBe('bad');
expect(m.coaching).toBe('Stay on the PyRevit claim.');
expect(m.context).toHaveLength(1);
expect(m.moment).toHaveLength(2);
expect(m.reaction).toHaveLength(1);
expect(m.moment[0].speaker).toBe('Michael');
expect(m.moment[0].at).toBe('04:10');
expect(m.moment[0].text).toContain('may not be moving forward');
});
it('treats context and reaction as optional', () => {
const minimal = ['deal: A', 'meeting: B', 'verdict: good', 'coaching: Do it again.', '', '## moment', '**X** (1:00) hi'].join('\n');
const m = parseMoment(minimal)!;
expect(m.context).toEqual([]);
expect(m.reaction).toEqual([]);
expect(m.moment).toHaveLength(1);
});
it('returns null when a required key or the moment section is missing', () => {
expect(parseMoment('deal: A\nmeeting: B\nverdict: good\n\n## moment\n')).toBeNull();
expect(parseMoment('deal: A\nmeeting: B\ncoaching: x\n\n## moment\n**X** hi')).toBeNull();
expect(parseMoment('nonsense')).toBeNull();
});
it('rejects an unknown verdict rather than rendering an untoned card', () => {
expect(parseMoment('deal: A\nmeeting: B\nverdict: spicy\ncoaching: x\n\n## moment\n**X** hi')).toBeNull();
});
});
describe('moment header fields', () => {
const FULL = [
'deal: Cushing Terrell',
'conversation: 8999bd96-498d-4430-abba-ff25e36ea592',
'meeting: Pirros | Cushing Terrell',
'date: 2026-08-21',
'criterion: D2 Genuinely curious',
'at: 05:09',
'recording: https://fathom.video/share/BKMeBJCpLyTqR',
'verdict: good',
'coaching: The feelings question got him to name the real risk.',
'',
'## moment',
'**Xander** (05:09) What are your feelings about that?',
].join('\n');
it('parses conversation, date and the Fathom recording link', () => {
const m = parseMoment(FULL)!;
expect(m.conversationId).toBe('8999bd96-498d-4430-abba-ff25e36ea592');
expect(m.date).toBe('2026-08-21');
expect(m.recording).toContain('fathom.video/share/');
});
it('splits the criterion into its id and its readable name', () => {
const m = parseMoment(FULL)!;
expect(m.criterion).toBe('D2');
expect(m.criterionName).toBe('Genuinely curious');
});
it('keeps a name-only criterion as the name, with no id', () => {
const m = parseMoment(FULL.replace('criterion: D2 Genuinely curious', 'criterion: End of call'))!;
expect(m.criterion).toBeUndefined();
expect(m.criterionName).toBe('End of call');
});
it('still parses when the optional header fields are absent', () => {
const minimal = ['deal: A', 'meeting: B', 'verdict: bad', 'coaching: x', '', '## moment', '**X** (1:00) hi'].join('\n');
const m = parseMoment(minimal)!;
expect(m.conversationId).toBeUndefined();
expect(m.recording).toBeUndefined();
expect(m.moment).toHaveLength(1);
});
});