151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest';
|
||
|
|
import {
|
||
|
|
EM_DASH,
|
||
|
|
formatCurrency,
|
||
|
|
formatDate,
|
||
|
|
formatDuration,
|
||
|
|
formatInteger,
|
||
|
|
formatPrice,
|
||
|
|
formatPricePerSqm,
|
||
|
|
formatPricePerSqmShort,
|
||
|
|
isFiniteNumber,
|
||
|
|
} from '@/utils/format';
|
||
|
|
|
||
|
|
describe('formatCurrency', () => {
|
||
|
|
it('formats values >= 1000 in compact k notation', () => {
|
||
|
|
expect(formatCurrency(1500)).toBe('£1.5k');
|
||
|
|
expect(formatCurrency(2500)).toBe('£2.5k');
|
||
|
|
expect(formatCurrency(1000000)).toBe('£1000.0k');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('formats sub-thousand values as rounded integers', () => {
|
||
|
|
expect(formatCurrency(950)).toBe('£950');
|
||
|
|
expect(formatCurrency(123.4)).toBe('£123');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatDuration', () => {
|
||
|
|
it('renders minutes for sub-hour durations', () => {
|
||
|
|
expect(formatDuration(0)).toBe('0m');
|
||
|
|
expect(formatDuration(120)).toBe('2m');
|
||
|
|
expect(formatDuration(3540)).toBe('59m');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('renders hours-and-minutes for multi-hour durations', () => {
|
||
|
|
expect(formatDuration(3600)).toBe('1h');
|
||
|
|
expect(formatDuration(5400)).toBe('1h30m');
|
||
|
|
expect(formatDuration(86400)).toBe('24h');
|
||
|
|
});
|
||
|
|
|
||
|
|
// B9 regression cases
|
||
|
|
it('returns em-dash for negative seconds (was "-2m")', () => {
|
||
|
|
expect(formatDuration(-120)).toBe(EM_DASH);
|
||
|
|
expect(formatDuration(-1)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns em-dash for null and undefined (was "0m")', () => {
|
||
|
|
expect(formatDuration(null)).toBe(EM_DASH);
|
||
|
|
expect(formatDuration(undefined)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns em-dash for NaN (was "NaNh")', () => {
|
||
|
|
expect(formatDuration(NaN)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns em-dash for non-finite values (Infinity)', () => {
|
||
|
|
expect(formatDuration(Infinity)).toBe(EM_DASH);
|
||
|
|
expect(formatDuration(-Infinity)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('caps absurdly large values at >24h', () => {
|
||
|
|
expect(formatDuration(86401)).toBe('>24h');
|
||
|
|
expect(formatDuration(90000)).toBe('>24h');
|
||
|
|
expect(formatDuration(31536000)).toBe('>24h'); // 1 year
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatDate', () => {
|
||
|
|
it('formats valid ISO dates', () => {
|
||
|
|
expect(formatDate('2025-01-03T00:00:00Z')).toContain('2025');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns the input string for invalid dates', () => {
|
||
|
|
expect(formatDate('not-a-date')).toBe('not-a-date');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatPricePerSqm', () => {
|
||
|
|
it('returns the formatted string when both values are valid', () => {
|
||
|
|
expect(formatPricePerSqm(500000, 100)).toBe('£5,000/m²');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns null when sqm is missing', () => {
|
||
|
|
expect(formatPricePerSqm(500000, null)).toBe(null);
|
||
|
|
expect(formatPricePerSqm(500000, undefined)).toBe(null);
|
||
|
|
expect(formatPricePerSqm(500000, 0)).toBe(null);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('isFiniteNumber', () => {
|
||
|
|
it('returns true for finite numbers', () => {
|
||
|
|
expect(isFiniteNumber(0)).toBe(true);
|
||
|
|
expect(isFiniteNumber(-1)).toBe(true);
|
||
|
|
expect(isFiniteNumber(1.5)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns false for non-finite, null, undefined, and other types', () => {
|
||
|
|
expect(isFiniteNumber(null)).toBe(false);
|
||
|
|
expect(isFiniteNumber(undefined)).toBe(false);
|
||
|
|
expect(isFiniteNumber(NaN)).toBe(false);
|
||
|
|
expect(isFiniteNumber(Infinity)).toBe(false);
|
||
|
|
expect(isFiniteNumber('5')).toBe(false);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatPrice', () => {
|
||
|
|
it('formats finite numbers with thousands separators', () => {
|
||
|
|
expect(formatPrice(0)).toBe('£0');
|
||
|
|
expect(formatPrice(2500)).toBe('£2,500');
|
||
|
|
expect(formatPrice(500000)).toBe('£500,000');
|
||
|
|
expect(formatPrice(1234.7)).toBe('£1,235');
|
||
|
|
});
|
||
|
|
|
||
|
|
// B10 regression: null/undefined must NOT render as "£0"
|
||
|
|
it('returns em-dash for null/undefined/non-finite', () => {
|
||
|
|
expect(formatPrice(null)).toBe(EM_DASH);
|
||
|
|
expect(formatPrice(undefined)).toBe(EM_DASH);
|
||
|
|
expect(formatPrice(NaN)).toBe(EM_DASH);
|
||
|
|
expect(formatPrice(Infinity)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatPricePerSqmShort', () => {
|
||
|
|
it('formats valid positive values', () => {
|
||
|
|
expect(formatPricePerSqmShort(38)).toBe('£38/m²');
|
||
|
|
expect(formatPricePerSqmShort(4500)).toBe('£4500/m²');
|
||
|
|
});
|
||
|
|
|
||
|
|
// B10: zero & negative are missing-data sentinels, not real values
|
||
|
|
it('returns em-dash for null/undefined/non-finite/zero/negative', () => {
|
||
|
|
expect(formatPricePerSqmShort(null)).toBe(EM_DASH);
|
||
|
|
expect(formatPricePerSqmShort(undefined)).toBe(EM_DASH);
|
||
|
|
expect(formatPricePerSqmShort(NaN)).toBe(EM_DASH);
|
||
|
|
expect(formatPricePerSqmShort(0)).toBe(EM_DASH);
|
||
|
|
expect(formatPricePerSqmShort(-5)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatInteger', () => {
|
||
|
|
it('rounds and stringifies finite numbers', () => {
|
||
|
|
expect(formatInteger(0)).toBe('0');
|
||
|
|
expect(formatInteger(3)).toBe('3');
|
||
|
|
expect(formatInteger(65.7)).toBe('66');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns em-dash for null/undefined/non-finite', () => {
|
||
|
|
expect(formatInteger(null)).toBe(EM_DASH);
|
||
|
|
expect(formatInteger(undefined)).toBe(EM_DASH);
|
||
|
|
expect(formatInteger(NaN)).toBe(EM_DASH);
|
||
|
|
});
|
||
|
|
});
|