diff --git a/.Jules/vantage.md b/.Jules/vantage.md index e63beebe..6e9850b5 100644 --- a/.Jules/vantage.md +++ b/.Jules/vantage.md @@ -13,3 +13,5 @@ ## 2026-05-15 - Standardizing Telemetry Resilience and Verifying Infrastructure Mocks | **Learning:** Telemetry must capture the full context of unknown errors via `String(e)` to avoid masking failure signals, and re-exporting mocks requires explicit execution to register in coverage reports. | **Action:** Updated `src/utils/github-releases.ts` to use `String(error)` in catch blocks and added an `exerciseMock` helper to `src/__tests__/mocks/astro-zod.ts` to achieve 100% coverage. ## 2026-05-20 - Hardening Telemetry Verification and Refining Stream Types | **Learning:** Index-based assertions in telemetry tests are brittle; using `.find()` ensures robust verification. Standardizing on `String(error)` across all API catch blocks prevents context loss during failure events. | **Action:** Refactored `chat-api.test.ts` and `github-releases.test.ts` to use search-based spy assertions, and added explicit return types to `chat-stream.ts` to resolve Astro compiler hints. + +## 2026-05-24 - Comprehensive Unit Testing for Visitor Changelog Formatting | **Learning:** Single-pass Edge transformations require dedicated unit tests covering edge cases like empty inputs, internal agent filtering, and fallback formatting to prevent silent regressions in SSR release streams. | **Action:** Added `src/utils/visitor-changelog.test.ts` to test `toVisitorChangelogTitle`, `stripChangelogChrome`, `toVisitorReleaseBody`, and `toVisitorRelease`. diff --git a/src/utils/visitor-changelog.test.ts b/src/utils/visitor-changelog.test.ts new file mode 100644 index 00000000..ee4715fb --- /dev/null +++ b/src/utils/visitor-changelog.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from 'vitest'; + +import { + stripChangelogChrome, + toVisitorChangelogTitle, + toVisitorRelease, + toVisitorReleaseBody, +} from './visitor-changelog'; + +describe('visitor-changelog utilities', () => { + describe('stripChangelogChrome', () => { + it('handles empty and whitespace-only strings', () => { + expect(stripChangelogChrome('')).toBe(''); + expect(stripChangelogChrome(' ')).toBe(''); + }); + + it('strips commit SHA, conventional commit prefix, and PR suffix', () => { + expect(stripChangelogChrome('a1b2c3d feat(ui): add new button (#123)')).toBe( + 'add new button' + ); + expect(stripChangelogChrome('fix: resolve alignment issue (#456)')).toBe( + 'resolve alignment issue' + ); + expect(stripChangelogChrome('chore(deps): update packages')).toBe('update packages'); + }); + + it('collapses multiple whitespace characters', () => { + expect(stripChangelogChrome('feat: multiple spaces ')).toBe('multiple spaces'); + }); + }); + + describe('toVisitorChangelogTitle', () => { + it('returns empty string for empty input', () => { + expect(toVisitorChangelogTitle('')).toBe(''); + expect(toVisitorChangelogTitle(' ')).toBe(''); + }); + + it('looks up known PR numbers', () => { + expect(toVisitorChangelogTitle('feat: add a live RSS feed (#520)')).toBe( + 'RSS feed of the work' + ); + }); + + it('looks up known subjects when PR number is omitted', () => { + expect(toVisitorChangelogTitle('add a live RSS feed for the work')).toBe( + 'RSS feed of the work' + ); + }); + + it('formats unknown commits by stripping chrome and capitalizing the first letter', () => { + expect( + toVisitorChangelogTitle('f1a2b3c feat(nav): improve keyboard accessibility (#999)') + ).toBe('Improve keyboard accessibility'); + }); + + it('returns already clean visitor titles as-is', () => { + expect(toVisitorChangelogTitle('Clean visitor title without chrome')).toBe( + 'Clean visitor title without chrome' + ); + }); + + it('falls back gracefully when chrome stripping leaves an empty subject', () => { + expect(toVisitorChangelogTitle('a1b2c3d feat:')).toBe('Feat:'); + }); + }); + + describe('toVisitorReleaseBody', () => { + it('returns empty string for empty body', () => { + expect(toVisitorReleaseBody('')).toBe(''); + expect(toVisitorReleaseBody(' ')).toBe(''); + }); + + it('converts bullet point lines into visitor titles', () => { + const input = + '- 520 feat: add a live RSS feed for the work (#520)\n* 518 feat: add a working web app manifest (#518)'; + const output = toVisitorReleaseBody(input); + expect(output).toBe('- RSS feed of the work\n- Web app manifest'); + }); + + it('filters out internal agent or tool items from bullet lists', () => { + const input = + '- feat: add public feature\n- chore(jules): internal sync\n- refactor(engine): internal logic'; + const output = toVisitorReleaseBody(input); + expect(output).toBe('- Add public feature'); + }); + + it('falls back to toVisitorChangelogTitle if body contains no bullet items', () => { + const input = 'a1b2c3d feat: add a live RSS feed for the work (#520)'; + expect(toVisitorReleaseBody(input)).toBe('RSS feed of the work'); + }); + + it('falls back to toVisitorChangelogTitle if all bullet items are filtered as internal', () => { + const input = '- chore(jules): internal update\n- refactor(engine): backend tweak'; + expect(toVisitorReleaseBody(input)).toBe( + '- chore(jules): internal update\n- refactor(engine): backend tweak' + ); + }); + }); + + describe('toVisitorRelease', () => { + it('transforms the release body while preserving other release properties', () => { + const release = { + version: 'v1.0.0', + publishedAt: '2026-05-01T12:00:00Z', + url: 'https://example.com/release/v1.0.0', + body: '- 520 feat: add a live RSS feed for the work (#520)', + }; + + const result = toVisitorRelease(release); + expect(result).toEqual({ + version: 'v1.0.0', + publishedAt: '2026-05-01T12:00:00Z', + url: 'https://example.com/release/v1.0.0', + body: '- RSS feed of the work', + }); + }); + }); +});