Files
accounted/lib/bokslut/__tests__/rounding.test.ts
T
Jakob Wennberg ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
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>
2026-07-04 15:58:06 +02:00

66 lines
2.2 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { roundOre, ORE_TOLERANCE } from '../rounding'
describe('roundOre', () => {
it('rounds typical positive amounts to two decimals', () => {
expect(roundOre(1.234)).toBe(1.23)
expect(roundOre(1.235)).toBe(1.24)
expect(roundOre(1.236)).toBe(1.24)
})
it('rounds negative amounts symmetrically', () => {
// Math.round rounds half toward +∞: -1.235 -> -1.23.
// Verified behavior so callers can rely on it.
expect(roundOre(-1.234)).toBe(-1.23)
expect(roundOre(-1.236)).toBe(-1.24)
})
it('returns 0 unchanged', () => {
expect(roundOre(0)).toBe(0)
// Math.round(-0 * 100) preserves the negative-zero sign; treat it as
// numerically equal to 0 rather than enforcing Object.is equality.
expect(roundOre(-0)).toEqual(-0)
expect(Math.abs(roundOre(-0))).toBe(0)
})
it('exposes a half-öre tolerance constant', () => {
expect(ORE_TOLERANCE).toBe(0.005)
})
it('sum of rounded parts equals rounded sum for representative cases', () => {
const cases: number[][] = [
[100, 200, 300],
[1.11, 2.22, 3.33],
[1.005, 2.005, 3.005],
[-100, 50, 50],
[-1.234, 2.345, -3.456],
[0.1, 0.2, 0.3], // classic IEEE 754 trap
[12345.67, -12345.67],
[1_000_000.01, 2_000_000.02, 3_000_000.03],
]
// Half-up rounding doesn't preserve sums exactly: each part can shift
// by up to half an öre, so cumulative drift over N parts is bounded by
// N * ORE_TOLERANCE. The pathological case is [1.005, 2.005, 3.005]:
// three exact-half values that all round up to .01, drifting the sum
// by one öre versus summing then rounding.
for (const parts of cases) {
const summedThenRounded = roundOre(parts.reduce((a, b) => a + b, 0))
const roundedThenSummed = roundOre(
parts.map(roundOre).reduce((a, b) => a + b, 0)
)
expect(
Math.abs(summedThenRounded - roundedThenSummed),
`parts=${JSON.stringify(parts)}`
).toBeLessThanOrEqual(ORE_TOLERANCE * parts.length)
}
})
it('roundOre is idempotent', () => {
const samples = [1.005, -2.345, 99.999, -0.005]
for (const s of samples) {
expect(roundOre(roundOre(s))).toBe(roundOre(s))
}
})
})