// The exact-decimal money helpers of the money field and list column // (Phase 18 D-07 with the amended D-09 and D-10): text in, text out, never a // JS number. No application names appear in framework tests. import { describe, expect, it } from 'vitest' import { decimalSeparator, formatMoney, parseMoneyInput, trimMoneyDisplay } from '../../src/app/money' const spaces = (s: string) => s.replace(/\s/g, ' ') describe('parseMoneyInput', () => { it('accepts a comma or a dot as the separator and strips spaces and no-break spaces', () => { expect(parseMoneyInput('1234,56', 4)).toEqual({ value: '1234.56', error: null }) expect(parseMoneyInput('1234.56', 4)).toEqual({ value: '1234.56', error: null }) expect(parseMoneyInput(' 1 234,5 ', 4)).toEqual({ value: '1234.5', error: null }) expect(parseMoneyInput('1 234,56', 4)).toEqual({ value: '1234.56', error: null }) expect(parseMoneyInput('7', 2)).toEqual({ value: '7', error: null }) }) it('keeps a leading minus and refuses a minus anywhere else', () => { expect(parseMoneyInput('-0,5', 4)).toEqual({ value: '-0.5', error: null }) expect(parseMoneyInput('5-', 4)).toEqual({ value: null, error: 'invalid' }) expect(parseMoneyInput('--5', 4)).toEqual({ value: null, error: 'invalid' }) expect(parseMoneyInput('+5', 4)).toEqual({ value: null, error: 'invalid' }) }) it('is null for empty or blank text', () => { expect(parseMoneyInput('', 2)).toEqual({ value: null, error: null }) expect(parseMoneyInput('   ', 2)).toEqual({ value: null, error: null }) }) it('refuses letters, exponents, two separators and lone separators as invalid', () => { for (const text of ['abc', '1e3', '1E3', '1.2.3', '1,234.56', '1.234,56', '.5', '5.', ',', '0x10', 'NaN', 'Infinity', '12 a']) { expect(parseMoneyInput(text, 4), text).toEqual({ value: null, error: 'invalid' }) } }) it('refuses more significant fraction digits than the scale, never rounding or truncating', () => { expect(parseMoneyInput('12.34567', 4)).toEqual({ value: null, error: 'too_precise' }) expect(parseMoneyInput('1,234', 2)).toEqual({ value: null, error: 'too_precise' }) expect(parseMoneyInput('1.234', 3)).toEqual({ value: '1.234', error: null }) expect(parseMoneyInput('0.0001', 4)).toEqual({ value: '0.0001', error: null }) }) it('accepts and keeps trailing zeros beyond the scale, as the server does', () => { expect(parseMoneyInput('12.5000', 2)).toEqual({ value: '12.5000', error: null }) expect(parseMoneyInput('39,840000', 4)).toEqual({ value: '39.840000', error: null }) expect(parseMoneyInput('12.5001', 2)).toEqual({ value: null, error: 'too_precise' }) }) it('keeps amounts beyond the float range digit for digit', () => { expect(parseMoneyInput('99999999999999999999.9999', 4)).toEqual({ value: '99999999999999999999.9999', error: null }) expect(parseMoneyInput('0.1', 4).value).toBe('0.1') }) }) describe('trimMoneyDisplay', () => { it('trims trailing fraction zeros down to two decimals and keeps the rest', () => { expect(trimMoneyDisplay('39.8400')).toBe('39.84') expect(trimMoneyDisplay('85.4193')).toBe('85.4193') expect(trimMoneyDisplay('85.4190')).toBe('85.419') expect(trimMoneyDisplay('5.0000')).toBe('5.00') expect(trimMoneyDisplay('-0.5000')).toBe('-0.50') expect(trimMoneyDisplay('7')).toBe('7') expect(trimMoneyDisplay('7.5')).toBe('7.5') }) }) describe('decimalSeparator', () => { it('is a dot in en and a comma in pl', () => { expect(decimalSeparator('en')).toBe('.') expect(decimalSeparator('pl')).toBe(',') }) }) describe('formatMoney', () => { it('formats en and pl with the ISO code and at least two decimals', () => { expect(spaces(formatMoney('39.8400', 'PLN', 'en'))).toBe('PLN 39.84') expect(spaces(formatMoney('39.8400', 'PLN', 'pl'))).toBe('39,84 PLN') expect(spaces(formatMoney('7', 'EUR', 'en'))).toBe('EUR 7.00') expect(spaces(formatMoney('1234567.5', 'EUR', 'pl'))).toBe('1 234 567,50 EUR') }) it('keeps up to scale decimals and never rounds a four-decimal amount', () => { expect(spaces(formatMoney('85.4193', 'PLN', 'en', 4))).toBe('PLN 85.4193') expect(spaces(formatMoney('99999999.9999', 'PLN', 'en', 4))).toBe('PLN 99,999,999.9999') expect(spaces(formatMoney('99999999.9999', 'PLN', 'pl', 4))).toBe('99 999 999,9999 PLN') expect(spaces(formatMoney('12.5', 'EUR', 'en', 2))).toBe('EUR 12.50') }) it('formats a negative amount', () => { expect(spaces(formatMoney('-0.5000', 'PLN', 'en'))).toBe('-PLN 0.50') }) it('formats the amount alone without a currency', () => { expect(spaces(formatMoney('12.5', '', 'en'))).toBe('12.50') expect(spaces(formatMoney('1234.5678', '', 'pl'))).toBe('1234,5678') }) it('appends an unknown code to the plain amount instead of throwing', () => { expect(spaces(formatMoney('12.5', 'ZZZZ', 'en'))).toBe('12.50 ZZZZ') }) })