ec27228a8e
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
81 lines
3.0 KiB
TypeScript
81 lines
3.0 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
||
import { formatAmount, formatWholeKr, formatDateTime, formatDate, isSaneDateString } from '@/lib/utils'
|
||
|
||
// Intl sv-SE groups thousands with a non-breaking / narrow space (U+00A0 or
|
||
// U+202F depending on ICU version) and may render negatives with U+2212. Both
|
||
// vary across Node builds, so normalize them to plain ASCII before asserting:
|
||
// the test cares about format shape, not the exact whitespace codepoint.
|
||
const norm = (s: string) => s.replace(/\s/g, ' ').replace(/−/g, '-')
|
||
|
||
describe('formatAmount', () => {
|
||
it('renders two decimals with sv-SE grouping and no currency symbol', () => {
|
||
expect(norm(formatAmount(1234.5))).toBe('1 234,50')
|
||
expect(norm(formatAmount(0))).toBe('0,00')
|
||
expect(norm(formatAmount(-1234.56))).toBe('-1 234,56')
|
||
})
|
||
|
||
it('does not include "kr" or the SEK symbol', () => {
|
||
expect(formatAmount(100)).not.toMatch(/kr|SEK/)
|
||
})
|
||
})
|
||
|
||
describe('formatWholeKr', () => {
|
||
it('rounds to whole krona with grouping, no decimals', () => {
|
||
expect(norm(formatWholeKr(1234.56))).toBe('1 235')
|
||
expect(norm(formatWholeKr(999.4))).toBe('999')
|
||
expect(norm(formatWholeKr(0))).toBe('0')
|
||
})
|
||
})
|
||
|
||
describe('formatDateTime', () => {
|
||
it('renders ISO-ordered date and time', () => {
|
||
expect(formatDateTime('2026-05-11T14:30:00')).toBe('2026-05-11 14:30')
|
||
})
|
||
|
||
it('accepts a Date instance', () => {
|
||
expect(formatDateTime(new Date('2026-01-02T09:05:00'))).toBe('2026-01-02 09:05')
|
||
})
|
||
|
||
it('stays date-aligned with formatDate on the date portion', () => {
|
||
const iso = '2026-12-31T23:59:00'
|
||
expect(formatDateTime(iso).startsWith(formatDate(iso))).toBe(true)
|
||
})
|
||
})
|
||
|
||
describe('formatDate', () => {
|
||
it('formats a valid ISO date', () => {
|
||
expect(formatDate('2026-05-11')).toBe('2026-05-11')
|
||
})
|
||
|
||
it('fails closed on a malformed date instead of throwing', () => {
|
||
// Regression: a 6-digit year ('202403-02-05', the real incident) parses to
|
||
// an Invalid Date; date-fns format() throws on that and used to crash the
|
||
// whole transactions route via the error boundary. It must degrade, not throw.
|
||
expect(() => formatDate('202403-02-05')).not.toThrow()
|
||
expect(formatDate('202403-02-05')).toBe('-')
|
||
expect(formatDate('not-a-date')).toBe('-')
|
||
})
|
||
})
|
||
|
||
describe('isSaneDateString', () => {
|
||
it('accepts real, in-range YYYY-MM-DD dates', () => {
|
||
expect(isSaneDateString('2026-06-04')).toBe(true)
|
||
expect(isSaneDateString('2024-03-02')).toBe(true)
|
||
})
|
||
|
||
it('rejects the 6-digit-year corruption from native date inputs', () => {
|
||
expect(isSaneDateString('202403-02-05')).toBe(false)
|
||
})
|
||
|
||
it('rejects impossible and out-of-range dates', () => {
|
||
expect(isSaneDateString('2024-13-40')).toBe(false) // month 13 / day 40
|
||
expect(isSaneDateString('1899-12-31')).toBe(false) // below floor
|
||
expect(isSaneDateString('2101-01-01')).toBe(false) // above ceiling
|
||
})
|
||
|
||
it('rejects empty and non-date strings', () => {
|
||
expect(isSaneDateString('')).toBe(false)
|
||
expect(isSaneDateString('not-a-date')).toBe(false)
|
||
})
|
||
})
|