Files
summercms/admin/tests/app/money.test.ts
Jakub Zych 0f35c9a0ca test(18-07): framework unit coverage of money, settings secrets, tabs, richeditor and danger actions (task 2)
- money_field_test.go: compile gates (moved from the smoke file and
  extended), model column gate, save-time value check, list projection
- settings secret projection and put without a database; tabs, settings
  richeditor sanitize and the danger flag as internal unit tests
- Postgres: TestMoneyFieldSave, TestSettingsSecretPostgres and the Phase 18
  payload cases of TestPhase10OpenAPIConformance
- vitest: money.ts helpers spec, MoneyField blur-only and server-error cases
- gate arrays and the cabana README Testing section name every test
2026-10-13 16:58:03 +02:00

102 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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')
})
})