diff --git a/lib/utils/formatListing.js b/lib/utils/formatListing.js index 511cc1f78..09f0a8051 100644 --- a/lib/utils/formatListing.js +++ b/lib/utils/formatListing.js @@ -30,6 +30,20 @@ const ROOMS_LABEL = { tr: 'Odalar', }; +/** + * German digit grouping for every price, whatever the UI language: the amount is always in euros, + * and "1200000 €" is hard to read at a glance in a push notification. + */ +const PRICE_FORMAT = new Intl.NumberFormat('de-DE', { maximumFractionDigits: 2 }); + +/** + * Formats a price in euros with thousands separators, e.g. `1000000` becomes `1.000.000 €`. + * + * @param {number} price + * @returns {string} + */ +export const formatPrice = (price) => `${PRICE_FORMAT.format(price)} €`; + /** * Formats a listing's numerical fields (price, size, rooms) into strings with their respective units. * @@ -41,7 +55,7 @@ export const formatListing = (listing, language = 'en') => { const rooms = ROOMS_LABEL[language] ?? ROOMS_LABEL.en; return { ...listing, - price: listing.price != null ? `${listing.price} €` : null, + price: listing.price != null ? formatPrice(listing.price) : null, size: listing.size != null ? `${listing.size} m²` : null, rooms: listing.rooms != null ? `${listing.rooms} ${rooms}` : null, // Null unless a router actually answered for this listing. Adapters drop it with the same @@ -101,8 +115,8 @@ export const formatPriceChange = (change, language = 'en') => { const percent = Math.abs(change.changePercent).toFixed(1); return { ...formatListing(change.listing, language), - oldPrice: `${change.oldPrice} €`, - newPrice: `${change.newPrice} €`, + oldPrice: formatPrice(change.oldPrice), + newPrice: formatPrice(change.newPrice), // Signed and with the unit, so an adapter can drop it straight into a line of text. changePercent: `${change.changePercent < 0 ? '-' : '+'}${percent} %`, direction: change.direction, diff --git a/test/services/listings/priceHistoryService.test.js b/test/services/listings/priceHistoryService.test.js index 586ca9941..99f6d40fb 100644 --- a/test/services/listings/priceHistoryService.test.js +++ b/test/services/listings/priceHistoryService.test.js @@ -165,8 +165,8 @@ describe('services/listings/priceHistoryService', () => { await notifyPriceChanges([change('job1')]); expect(state.sent[0][1][0]).toMatchObject({ - oldPrice: '1200 €', - newPrice: '1100 €', + oldPrice: '1.200 €', + newPrice: '1.100 €', changePercent: '-8.3 %', changeHeadline: 'Price reduced', }); diff --git a/test/utils/formatListing.test.js b/test/utils/formatListing.test.js index 4fb82632a..90c81e9b3 100644 --- a/test/utils/formatListing.test.js +++ b/test/utils/formatListing.test.js @@ -4,7 +4,7 @@ */ import { describe, it, expect } from 'vitest'; -import { formatListing } from '../../lib/utils/formatListing.js'; +import { formatListing, formatPrice, formatPriceChange } from '../../lib/utils/formatListing.js'; /** * Notification text used to be German whatever the user had set the interface to, because the @@ -29,7 +29,7 @@ describe('formatListing', () => { it('formats price and size with their language-neutral units', () => { const formatted = formatListing(listing, 'de'); - expect(formatted.price).toBe('1200 €'); + expect(formatted.price).toBe('1.200 €'); expect(formatted.size).toBe('74 m²'); }); @@ -54,3 +54,44 @@ describe('formatListing', () => { expect(listing).toEqual(original); }); }); + +describe('formatPrice', () => { + it('groups thousands with dots so large prices stay readable', () => { + expect(formatPrice(1000000)).toBe('1.000.000 €'); + expect(formatPrice(100000)).toBe('100.000 €'); + expect(formatPrice(1200)).toBe('1.200 €'); + }); + + it('leaves small prices alone', () => { + expect(formatPrice(800)).toBe('800 €'); + expect(formatPrice(0)).toBe('0 €'); + }); + + it('uses a decimal comma and keeps at most two decimals', () => { + expect(formatPrice(1234.5)).toBe('1.234,5 €'); + expect(formatPrice(999.999)).toBe('1.000 €'); + }); + + it('uses German grouping even when the interface is English', () => { + expect(formatListing({ id: 'l3', title: 'House', price: 450000 }, 'en').price).toBe('450.000 €'); + }); +}); + +describe('formatPriceChange', () => { + it('formats old and new price the same way as a new listing', () => { + const formatted = formatPriceChange( + { + listing: { id: 'l4', title: 'House', price: 475000 }, + oldPrice: 499000, + newPrice: 475000, + changePercent: -4.81, + direction: 'down', + }, + 'de', + ); + expect(formatted.oldPrice).toBe('499.000 €'); + expect(formatted.newPrice).toBe('475.000 €'); + expect(formatted.price).toBe('475.000 €'); + expect(formatted.changePercent).toBe('-4.8 %'); + }); +});