Remove 8 inline formatPrice/formatVND/formatPriceM2 functions scattered across components and pages, replacing them with imports from @/lib/currency. Add formatVNDFull (full locale, no compact notation) for chuyen-nhuong pages. Fix price-history-chart off-by-1000 bug caused by double-dividing through priceToMillions then formatMillions. Add k/m² branch to formatPricePerM2 for sub-million values. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
152 lines
4.9 KiB
TypeScript
152 lines
4.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
formatPrice,
|
|
formatVND,
|
|
formatVNDFull,
|
|
formatPricePerM2,
|
|
parseVND,
|
|
} from '../currency';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// formatPrice — compact notation without currency suffix
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('formatPrice', () => {
|
|
it('formats billions as "X ty"', () => {
|
|
expect(formatPrice(1_000_000_000)).toBe('1 t\u1ef7');
|
|
expect(formatPrice(1_500_000_000)).toBe('1.5 t\u1ef7');
|
|
expect(formatPrice(3_500_000_000)).toBe('3.5 t\u1ef7');
|
|
expect(formatPrice(10_000_000_000)).toBe('10 t\u1ef7');
|
|
});
|
|
|
|
it('formats millions as "X trieu"', () => {
|
|
expect(formatPrice(1_000_000)).toBe('1 tri\u1ec7u');
|
|
expect(formatPrice(1_500_000)).toBe('1.5 tri\u1ec7u');
|
|
expect(formatPrice(150_000_000)).toBe('150 tri\u1ec7u');
|
|
expect(formatPrice(800_000_000)).toBe('800 tri\u1ec7u');
|
|
expect(formatPrice(999_000_000)).toBe('999 tri\u1ec7u');
|
|
});
|
|
|
|
it('formats values below 1 million with locale separator', () => {
|
|
expect(formatPrice(500_000)).toMatch(/500/);
|
|
expect(formatPrice(1_000)).toMatch(/1/);
|
|
expect(formatPrice(0)).toBe('0');
|
|
});
|
|
|
|
it('accepts string inputs', () => {
|
|
expect(formatPrice('3500000000')).toBe('3.5 t\u1ef7');
|
|
expect(formatPrice('150000000')).toBe('150 tri\u1ec7u');
|
|
});
|
|
|
|
it('handles edge cases gracefully', () => {
|
|
expect(formatPrice(-1)).toBe('0');
|
|
expect(formatPrice(NaN)).toBe('0');
|
|
expect(formatPrice(Infinity)).toBe('0');
|
|
expect(formatPrice('')).toBe('0');
|
|
});
|
|
|
|
it('strips trailing .0', () => {
|
|
expect(formatPrice(2_000_000_000)).toBe('2 t\u1ef7');
|
|
expect(formatPrice(5_000_000)).toBe('5 tri\u1ec7u');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// formatVND — with currency suffix "d"
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('formatVND', () => {
|
|
it('returns "Mien phi" for zero', () => {
|
|
expect(formatVND(0)).toBe('Mi\u1ec5n ph\u00ed');
|
|
});
|
|
|
|
it('formats billions with suffix', () => {
|
|
expect(formatVND(1_500_000_000)).toBe('1.5 t\u1ef7 \u0111');
|
|
});
|
|
|
|
it('formats millions with suffix', () => {
|
|
expect(formatVND(5_000_000)).toBe('5 tri\u1ec7u \u0111');
|
|
expect(formatVND(4_990_000)).toBe('5 tri\u1ec7u \u0111');
|
|
});
|
|
|
|
it('formats values below 1 million with suffix', () => {
|
|
expect(formatVND(500_000)).toMatch(/\u0111$/);
|
|
});
|
|
|
|
it('accepts string input', () => {
|
|
expect(formatVND('1500000000')).toBe('1.5 t\u1ef7 \u0111');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// formatPricePerM2 — price per square metre
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('formatPricePerM2', () => {
|
|
it('formats millions as "X tr/m\u00b2"', () => {
|
|
expect(formatPricePerM2(50_500_000)).toBe('50.5 tr/m\u00b2');
|
|
expect(formatPricePerM2(1_000_000)).toBe('1 tr/m\u00b2');
|
|
});
|
|
|
|
it('formats billions as "X ty/m\u00b2"', () => {
|
|
expect(formatPricePerM2(1_500_000_000)).toBe('1.5 t\u1ef7/m\u00b2');
|
|
});
|
|
|
|
it('formats values below 1 million with "/m\u00b2" suffix', () => {
|
|
expect(formatPricePerM2(500_000)).toMatch(/m\u00b2$/);
|
|
});
|
|
|
|
it('accepts string input', () => {
|
|
expect(formatPricePerM2('50500000')).toBe('50.5 tr/m\u00b2');
|
|
});
|
|
|
|
it('handles edge cases', () => {
|
|
expect(formatPricePerM2(0)).toBe('0 \u0111/m\u00b2');
|
|
expect(formatPricePerM2(-1)).toBe('0 \u0111/m\u00b2');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// formatVNDFull — full locale format, no compact notation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('formatVNDFull', () => {
|
|
it('formats with full locale number + đ suffix', () => {
|
|
expect(formatVNDFull(4_990_000)).toMatch(/4.*990.*000.*đ/);
|
|
});
|
|
|
|
it('never uses compact notation', () => {
|
|
const result = formatVNDFull(1_500_000_000);
|
|
expect(result).not.toContain('tỷ');
|
|
expect(result).toContain('đ');
|
|
});
|
|
|
|
it('handles zero and negatives', () => {
|
|
expect(formatVNDFull(0)).toMatch(/0.*đ/);
|
|
expect(formatVNDFull(-1)).toBe('0 đ');
|
|
});
|
|
|
|
it('accepts string input', () => {
|
|
expect(formatVNDFull('1500000')).toMatch(/1.*500.*000.*đ/);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// parseVND — reverse parse
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('parseVND', () => {
|
|
it('parses formatted number back', () => {
|
|
expect(parseVND('500.000')).toBe(500000);
|
|
});
|
|
|
|
it('returns null for empty / non-numeric input', () => {
|
|
expect(parseVND('')).toBeNull();
|
|
expect(parseVND('abc')).toBeNull();
|
|
});
|
|
|
|
it('strips non-digit characters', () => {
|
|
expect(parseVND('1.500.000 \u0111')).toBe(1500000);
|
|
});
|
|
});
|