import { describe, expect, it } from 'vitest' import { artifactLabel, collectionOf, dayLabel, didOf, relativeTime, rkeyOf, summarize } from './format.js' const NOW = '2026-07-25T14:18:00Z' describe('relativeTime', () => { it('reads in the unit a human would say out loud', () => { expect(relativeTime('2026-07-25T14:17:40Z', NOW)).toBe('now') expect(relativeTime('2026-07-25T14:12:00Z', NOW)).toBe('6m') expect(relativeTime('2026-07-25T11:30:00Z', NOW)).toBe('3h') expect(relativeTime('2026-07-21T09:14:00Z', NOW)).toBe('4d') }) it('falls back to a date once "N days ago" stops being useful', () => { expect(relativeTime('2026-06-12T09:15:00Z', NOW)).toMatch(/Jun/) }) it('never reports a future record as negative', () => { expect(relativeTime('2026-07-25T15:00:00Z', NOW)).toBe('now') }) }) describe('dayLabel', () => { it('names the two days a human has words for', () => { expect(dayLabel(NOW, NOW)).toBe('Today') expect(dayLabel('2026-07-24T09:00:00Z', '2026-07-25T09:00:00Z')).toBe('Yesterday') expect(dayLabel('2026-07-21T09:00:00Z', NOW)).toMatch(/Jul/) }) }) describe('AT URI parts', () => { const uri = 'at://did:plc:qv7hjr2mzk4x/com.disnetdev.radial.goal/3lbq2xk7wf22h' it('splits into the pieces a route and a chip need', () => { expect(didOf(uri)).toBe('did:plc:qv7hjr2mzk4x') expect(collectionOf(uri)).toBe('com.disnetdev.radial.goal') expect(rkeyOf(uri)).toBe('3lbq2xk7wf22h') }) }) describe('artifactLabel', () => { const body = '## Context\n\n0004 chose polling. It was right at the time.' it('uses the artifact’s own title, whitespace and all', () => { expect(artifactLabel({ title: ' 0008 · Jetstream first ', body })).toBe('0008 · Jetstream first') }) it("falls back to the body's heading for a record written before titles existed", () => { // Immutable and un-retitleable, so this is the row a real space keeps showing for years. It is // display-only — nothing persists it, and nothing treats it as protocol data. expect(artifactLabel({ body })).toBe('Context') expect(artifactLabel({ title: ' ', body })).toBe('Context') }) it('uses the derived summary when a legacy body has no heading', () => { const headingless = '0004 chose polling. It was right at the time.' expect(artifactLabel({ body: headingless })).toBe(summarize(headingless)) }) it('truncates a long-but-valid title rather than letting it run the width of a row', () => { // 200 characters is valid on the wire; a row is narrower than that. const label = artifactLabel({ title: 'x'.repeat(200), body }, 80) expect(label.length).toBe(80) expect(label.endsWith('…')).toBe(true) }) }) describe('summarize', () => { // The legacy fallback: an artifact written before `title` existed has no name of its own, so a // row's one line has to come out of the prose. it('skips headings and takes the first sentence', () => { expect(summarize('## Approach\n\nAdd a `/queue` route. It filters the index.')).toBe( 'Add a /queue route.', ) }) it("skips an ADR's Status: line, which is a convention rather than a summary", () => { expect(summarize('Status: accepted · supersedes 0004\n\n## Context\n\n0004 chose polling.')).toBe( '0004 chose polling.', ) }) it('truncates rather than letting a paragraph run the width of a row', () => { const long = `${'word '.repeat(60)}end.` expect(summarize(long).length).toBeLessThanOrEqual(140) expect(summarize(long).endsWith('…')).toBe(true) }) it('returns nothing for a body that is only headings', () => { expect(summarize('## Only a heading')).toBe('') }) // A plan opens with its own `# ` title, and the row already shows the artifact's type. it("skips a body's own title and the fence under it", () => { expect(summarize('# Plan: implement multiply\n\n```js\nconst x = 1\n```\n\nReplace the stub.')).toBe( 'Replace the stub.', ) }) })