evdboom_Bindery/bindery-core/test/formatting.test.ts
copilot-swe-agent[bot] 9dd9820646
feat: add bindery-core shared TypeScript package
Extracts pure logic from vscode-ext and mcp-ts into a shared package:

- formatting.ts: updateTypography() + applyTypography() alias
- settings.ts: WorkspaceSettings, LanguageConfig, DialectConfig types, path helpers, readers, and accessors
- translations.ts: TranslationsFile types, UkReplacement, all translation read/write/mutate helpers
- templates.ts + src/templates/: all AI instruction file templates (canonical source of truth)
- index.ts: barrel export of all modules
- test/formatting.test.ts: 25 tests ported from vscode-ext format-on-save tests

Build: tsc (commonjs, ES2022, strict)
Tests: vitest (25/25 passing)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: evdboom <18037882+evdboom@users.noreply.github.com>
2026-04-27 23:01:58 +00:00

183 lines
8.4 KiB
TypeScript

/**
* Unit tests for formatting.ts — updateTypography() / applyTypography() pure functions.
*
* No VS Code extension host, no commands, no file I/O.
*/
import { describe, expect, it } from 'vitest';
import { updateTypography, applyTypography } from '../src/formatting';
describe('updateTypography()', () => {
// ─── Edge cases ───────────────────────────────────────────────────────────
it('empty string returns empty string', () => {
expect(updateTypography('')).toBe('');
});
it('preserves newlines in multi-line input', () => {
const input = 'line one\nline two\nline three';
const result = updateTypography(input);
expect(result.split('\n')).toHaveLength(3);
expect(result).toContain('\n');
});
// ─── Ellipsis ─────────────────────────────────────────────────────────────
it('converts triple dots to ellipsis character', () => {
expect(updateTypography('And then...')).toBe('And then\u2026');
});
// ─── Em-dash ──────────────────────────────────────────────────────────────
it('converts double-dash to em-dash', () => {
expect(updateTypography('It was--perfect.')).toBe('It was\u2014perfect.');
});
it('does not convert triple-dash (markdown HR)', () => {
const input = '---\nSome text\n---';
const result = updateTypography(input);
expect(result).toContain('---');
expect(result).not.toContain('\u2014\u2014\u2014');
});
// ─── Double quotes ────────────────────────────────────────────────────────
it('converts straight double quotes to curly quotes', () => {
expect(updateTypography('"Hello"')).toBe('\u201CHello\u201D');
});
it('already-correct curly double quotes are not double-processed', () => {
const input = '\u201CHello,\u201D she said.';
expect(updateTypography(input)).toBe(input);
});
// ─── Single quotes / apostrophes ──────────────────────────────────────────
it("converts apostrophe: it's → it\u2019s", () => {
expect(updateTypography("it's")).toBe('it\u2019s');
});
// ─── HTML comment protection ──────────────────────────────────────────────
it('preserves double-dash inside HTML comments (no em-dash conversion)', () => {
const input = '<!-- keep -- this --> "outside"';
const result = updateTypography(input);
// Double-dash inside the comment must survive intact
expect(result).toContain('<!-- keep -- this -->');
// Content outside the comment is still formatted
expect(result).toContain('\u201Coutside\u201D');
});
it('does not alter HTML comment content while formatting surrounding text', () => {
const input = 'before <!-- comment -- here --> after--end';
const result = updateTypography(input);
expect(result).toContain('<!-- comment -- here -->');
expect(result).toContain('\u2014end');
});
// ─── Fenced code blocks ───────────────────────────────────────────────────
it('backtick code fence delimiters are not corrupted by any transform', () => {
// Triple backticks are not matched by ellipsis (/\.\.\./) or em-dash (/--/) regexes
const input = '```typescript\nconst x = 1;\n```';
const result = updateTypography(input);
expect(result).toContain('```typescript');
expect(result).toContain('```');
});
// ─── Mixed content ────────────────────────────────────────────────────────
it('applies all transforms together on mixed content', () => {
const input = '"Wait..." she said--stepping back.';
const result = updateTypography(input);
expect(result).toContain('\u201C'); // opening curly double quote
expect(result).toContain('\u201D'); // closing curly double quote
expect(result).toContain('\u2026'); // ellipsis
expect(result).toContain('\u2014'); // em-dash
expect(result).not.toContain('"');
expect(result).not.toContain('...');
expect(result).not.toContain('--');
});
it('already-formatted content is returned unchanged (idempotent)', () => {
const input = '\u201CHello,\u201D she said\u2014stepping back\u2026';
expect(updateTypography(input)).toBe(input);
});
// ─── Opening quotes after various opening characters ──────────────────────
it('opens double quote after opening parenthesis', () => {
expect(updateTypography('("Hello")')).toContain('\u201CHello\u201D');
});
it('opens double quote after opening bracket', () => {
expect(updateTypography('["Hello"]')).toContain('\u201CHello\u201D');
});
it('opens double quote after em-dash', () => {
const result = updateTypography('\u2014"Hello"');
expect(result).toContain('\u201CHello\u201D');
});
it('opens single quote after parenthesis', () => {
const result = updateTypography("('test')");
expect(result).toContain('\u2018test\u2019');
});
// ─── Closing double quote after em-dash ───────────────────────────────────
it('corrects closing quote after em-dash at end of sentence', () => {
// Input: "word--" should become "word—" (em-dash + closing quote)
const input = 'He said "fine--"';
const result = updateTypography(input);
expect(result).toContain('\u2014\u201D');
});
it('corrects closing quote after em-dash followed by punctuation', () => {
const input = 'He said "fine--" and left.';
const result = updateTypography(input);
expect(result).toContain('\u2014\u201D');
});
// ─── Multiple HTML comments ───────────────────────────────────────────────
it('handles multiple HTML comments in one document', () => {
const input = '<!-- first -- comment -->\nText--here.\n<!-- second -- comment -->';
const result = updateTypography(input);
expect(result).toContain('<!-- first -- comment -->');
expect(result).toContain('<!-- second -- comment -->');
expect(result).toContain('Text\u2014here.');
});
// ─── Multi-line quotes ────────────────────────────────────────────────────
it('handles quote at start of a line', () => {
const result = updateTypography('"Start of line."');
expect(result).toBe('\u201CStart of line.\u201D');
});
// ─── No-op for content without formattable characters ─────────────────────
it('returns plain text unchanged when no formatting is needed', () => {
const input = 'Plain text without anything special.';
expect(updateTypography(input)).toBe(input);
});
});
describe('applyTypography()', () => {
it('is an alias for updateTypography — same output', () => {
const input = '"Hello..." she said--stepping back.';
expect(applyTypography(input)).toBe(updateTypography(input));
});
it('empty string returns empty string', () => {
expect(applyTypography('')).toBe('');
});
it('applies all transforms', () => {
const result = applyTypography('"Test..." value--end.');
expect(result).toContain('\u201C');
expect(result).toContain('\u2026');
expect(result).toContain('\u2014');
});
});