Merged in fix/refactor-currency-display (pull request #3434)
fix(SW-3616): Handle EuroBonus point type everywhere * Add tests to formatPrice * formatPrice * More work replacing config with api points type * More work replacing config with api points type * More fixing with currency * maybe actually fixed it * Fix MyStay * Clean up * Fix comments * Merge branch 'master' into fix/refactor-currency-display * Fix calculateTotalPrice for EB points + SF points + cash Approved-by: Joakim Jäderberg
This commit is contained in:
200
packages/common/utils/numberFormatting.test.ts
Normal file
200
packages/common/utils/numberFormatting.test.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { createIntl, createIntlCache } from "react-intl"
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { CurrencyEnum } from "../constants/currency"
|
||||
import { PointType } from "../constants/pointType"
|
||||
import { formatPrice } from "./numberFormatting"
|
||||
|
||||
const cache = createIntlCache()
|
||||
|
||||
const createTestIntl = (locale: string = "en-US") =>
|
||||
createIntl({ locale, messages: {}, onError: () => {} }, cache)
|
||||
|
||||
describe("formatPrice", () => {
|
||||
it("should format price with currency", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 1000, CurrencyEnum.SEK)
|
||||
|
||||
expect(result).toBe("1,000 SEK")
|
||||
})
|
||||
|
||||
it("should format price with decimal values", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 99.5, CurrencyEnum.EUR)
|
||||
|
||||
expect(result).toBe("99.5 EUR")
|
||||
})
|
||||
|
||||
it("should format price with additional price and currency", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 500, CurrencyEnum.NOK, 100, "SEK")
|
||||
|
||||
expect(result).toBe("500 NOK + 100 SEK")
|
||||
})
|
||||
|
||||
it("should not include additional price when only additionalPrice is provided without currency", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 500, CurrencyEnum.DKK, 100, undefined)
|
||||
|
||||
expect(result).toBe("500 DKK")
|
||||
})
|
||||
|
||||
it("should not include additional price when only additionalPriceCurrency is provided without amount", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 500, CurrencyEnum.PLN, undefined, "SEK")
|
||||
|
||||
expect(result).toBe("500 PLN")
|
||||
})
|
||||
|
||||
it("should format Voucher currency with plural form", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 2, CurrencyEnum.Voucher)
|
||||
|
||||
expect(result).toBe("2 Vouchers")
|
||||
})
|
||||
|
||||
it("should format single Voucher correctly", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 1, CurrencyEnum.Voucher)
|
||||
|
||||
expect(result).toBe("1 Voucher")
|
||||
})
|
||||
|
||||
it("should handle string currency codes", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 250, "USD")
|
||||
|
||||
expect(result).toBe("250 USD")
|
||||
})
|
||||
|
||||
it("should handle zero price", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 0, CurrencyEnum.SEK)
|
||||
|
||||
expect(result).toBe("0 SEK")
|
||||
})
|
||||
|
||||
it("should handle large numbers", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 1000000, CurrencyEnum.EUR)
|
||||
|
||||
expect(result).toBe("1,000,000 EUR")
|
||||
})
|
||||
|
||||
it("should format numbers according to locale", () => {
|
||||
const intl = createTestIntl("sv-SE")
|
||||
|
||||
const result = formatPrice(intl, 1000, CurrencyEnum.SEK)
|
||||
|
||||
// Swedish locale uses non-breaking space as thousands separator
|
||||
expect(result).toBe("1\u00a0000 SEK")
|
||||
})
|
||||
|
||||
it("should format POINTS currency without pointType", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(intl, 5000, CurrencyEnum.POINTS)
|
||||
|
||||
expect(result).toBe("5,000 Points")
|
||||
})
|
||||
|
||||
it("should format POINTS currency with Scandic pointType and plural", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(
|
||||
intl,
|
||||
5000,
|
||||
CurrencyEnum.POINTS,
|
||||
undefined,
|
||||
undefined,
|
||||
PointType.SCANDIC
|
||||
)
|
||||
|
||||
expect(result).toBe("5,000 Points")
|
||||
})
|
||||
|
||||
it("should format POINTS currency with Scandic pointType and singular", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(
|
||||
intl,
|
||||
1,
|
||||
CurrencyEnum.POINTS,
|
||||
undefined,
|
||||
undefined,
|
||||
PointType.SCANDIC
|
||||
)
|
||||
|
||||
expect(result).toBe("1 Point")
|
||||
})
|
||||
|
||||
it("should format POINTS currency with EuroBonus pointType and plural", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(
|
||||
intl,
|
||||
10000,
|
||||
CurrencyEnum.POINTS,
|
||||
undefined,
|
||||
undefined,
|
||||
PointType.EUROBONUS
|
||||
)
|
||||
|
||||
expect(result).toBe("10,000 EB Points")
|
||||
})
|
||||
|
||||
it("should format POINTS currency with EuroBonus pointType and singular", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(
|
||||
intl,
|
||||
1,
|
||||
CurrencyEnum.POINTS,
|
||||
undefined,
|
||||
undefined,
|
||||
PointType.EUROBONUS
|
||||
)
|
||||
|
||||
expect(result).toBe("1 EB Point")
|
||||
})
|
||||
|
||||
it("should format POINTS with additional price and Scandic pointType", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(
|
||||
intl,
|
||||
5000,
|
||||
CurrencyEnum.POINTS,
|
||||
100,
|
||||
"SEK",
|
||||
PointType.SCANDIC
|
||||
)
|
||||
|
||||
expect(result).toBe("5,000 Points + 100 SEK")
|
||||
})
|
||||
|
||||
it("should format POINTS with additional price and EuroBonus pointType", () => {
|
||||
const intl = createTestIntl("en-US")
|
||||
|
||||
const result = formatPrice(
|
||||
intl,
|
||||
5000,
|
||||
CurrencyEnum.POINTS,
|
||||
100,
|
||||
"SEK",
|
||||
PointType.EUROBONUS
|
||||
)
|
||||
|
||||
expect(result).toBe("5,000 EB Points + 100 SEK")
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,6 @@
|
||||
import { CurrencyEnum } from "../constants/currency"
|
||||
import { PointType } from "../constants/pointType"
|
||||
import { logger } from "../logger"
|
||||
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
@@ -18,6 +20,7 @@ export function getSingleDecimal(n: Number | string) {
|
||||
* @param currency - currency code
|
||||
* @param additionalPrice - number (obtained in reward nights and Corporate cheque scenarios)
|
||||
* @param additionalPriceCurrency - currency code (obtained in reward nights and Corporate cheque scenarios)
|
||||
* @param pointsType - type of points when currency is points
|
||||
* @returns localized and formatted number in string type with currency
|
||||
*/
|
||||
export function formatPrice(
|
||||
@@ -25,7 +28,8 @@ export function formatPrice(
|
||||
price: number,
|
||||
currency: string | CurrencyEnum,
|
||||
additionalPrice?: number,
|
||||
additionalPriceCurrency?: string
|
||||
additionalPriceCurrency?: string,
|
||||
pointsType?: PointType | null
|
||||
) {
|
||||
const localizedPrice = intl.formatNumber(price, {
|
||||
minimumFractionDigits: 0,
|
||||
@@ -41,19 +45,66 @@ export function formatPrice(
|
||||
formattedAdditionalPrice = ` + ${localizedAdditionalPrice} ${additionalPriceCurrency}`
|
||||
}
|
||||
|
||||
const currencyText =
|
||||
currency === CurrencyEnum.Voucher
|
||||
? intl.formatMessage(
|
||||
{
|
||||
id: "price.numberOfVouchers",
|
||||
defaultMessage:
|
||||
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
|
||||
},
|
||||
{
|
||||
numberOfVouchers: price,
|
||||
}
|
||||
)
|
||||
: currency
|
||||
const currencyText = getCurrencyText(intl, price, currency, pointsType)
|
||||
|
||||
return `${localizedPrice} ${currencyText}${formattedAdditionalPrice}`
|
||||
}
|
||||
|
||||
function getCurrencyText(
|
||||
intl: IntlShape,
|
||||
price: number,
|
||||
currency: string | CurrencyEnum,
|
||||
pointsType?: PointType | null
|
||||
) {
|
||||
if (currency === CurrencyEnum.Voucher) {
|
||||
return intl.formatMessage(
|
||||
{
|
||||
id: "price.numberOfVouchers",
|
||||
defaultMessage:
|
||||
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
|
||||
},
|
||||
{
|
||||
numberOfVouchers: price,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (currency === CurrencyEnum.POINTS) {
|
||||
if (!pointsType) return currency
|
||||
|
||||
switch (pointsType) {
|
||||
case PointType.SCANDIC: {
|
||||
return intl.formatMessage(
|
||||
{
|
||||
id: "price.numberOfScandicPoints",
|
||||
defaultMessage:
|
||||
"{numberOfScandicPoints, plural, one {Point} other {Points}}",
|
||||
},
|
||||
{
|
||||
numberOfScandicPoints: price,
|
||||
}
|
||||
)
|
||||
}
|
||||
case PointType.EUROBONUS: {
|
||||
return intl.formatMessage(
|
||||
{
|
||||
id: "price.numberOfEuroBonusPoints",
|
||||
defaultMessage:
|
||||
"{numberOfEuroBonusPoints, plural, one {EB Point} other {EB Points}}",
|
||||
},
|
||||
{
|
||||
numberOfEuroBonusPoints: price,
|
||||
}
|
||||
)
|
||||
}
|
||||
default: {
|
||||
const _exhaustiveCheck: never = pointsType
|
||||
void _exhaustiveCheck
|
||||
logger.warn(`Unknown point type provided: ${pointsType}`)
|
||||
return currency
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currency
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user