Merged in chore/add-tests-to-getAdditionalPrice (pull request #3065)

chore: Refactor types and add tests to parts of price calcuations

* Add tests to sumPackages

* Refactor types and add tests to getAdditionalPrice

* Don't always generate coverage

* Add tests and refactor types of getRedemptionPrice


Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2025-11-04 12:09:04 +00:00
parent fa10abbe78
commit dc42a22513
6 changed files with 566 additions and 15 deletions

View File

@@ -0,0 +1,309 @@
import { describe, expect, it } from "vitest"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { getAdditionalPrice, getRedemptionPrice } from "./helpers"
type GetAdditionalPriceParams = Parameters<typeof getAdditionalPrice>
describe("getAdditionalPrice", () => {
it("should calculate additional price correctly with only additional price", () => {
const total: GetAdditionalPriceParams[0] = {
local: {
additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.CC,
},
}
const adults = 1
const nights = 1
const breakfast = undefined
const packages = undefined
const additionalPrice = 100
const additionalPriceCurrency = CurrencyEnum.SEK
getAdditionalPrice(
total,
adults,
breakfast,
nights,
packages,
additionalPrice,
additionalPriceCurrency
)
expect(total.local.additionalPrice).toBe(100)
})
it("should set additional price currency correctly when missing in total", () => {
const total: GetAdditionalPriceParams[0] = {
local: {
additionalPrice: 0,
},
}
const adults = 1
const nights = 1
const breakfast = undefined
const packages = undefined
const additionalPrice = 100
const additionalPriceCurrency = CurrencyEnum.SEK
getAdditionalPrice(
total,
adults,
breakfast,
nights,
packages,
additionalPrice,
additionalPriceCurrency
)
expect(total.local.additionalPriceCurrency).toBe(CurrencyEnum.SEK)
})
it("should calculate price correctly with breakfast", () => {
const total: GetAdditionalPriceParams[0] = {
local: {
additionalPrice: 0,
},
}
const adults = 2
const nights = 2
const breakfast = { localPrice: { price: 50, currency: CurrencyEnum.SEK } }
const packages: never[] = []
getAdditionalPrice(total, adults, breakfast, nights, packages)
expect(total.local.additionalPrice).toBe(200)
})
it("should calculate price correctly with packages", () => {
const total: GetAdditionalPriceParams[0] = {
local: {
additionalPrice: 0,
},
}
const adults = 2
const nights = 2
const breakfast = undefined
const packages = [
{
localPrice: { totalPrice: 50, currency: CurrencyEnum.SEK },
},
{
localPrice: { totalPrice: 25, currency: CurrencyEnum.SEK },
},
]
getAdditionalPrice(total, adults, breakfast, nights, packages)
expect(total.local.additionalPrice).toBe(75)
})
it("should calculate price correctly with breakfast, packages and additionalPrice", () => {
const total: GetAdditionalPriceParams[0] = {
local: {
additionalPrice: 0,
},
}
const adults = 2
const nights = 2
const breakfast = { localPrice: { price: 50, currency: CurrencyEnum.SEK } }
const packages = [
{
localPrice: { totalPrice: 50, currency: CurrencyEnum.SEK },
},
{
localPrice: { totalPrice: 25, currency: CurrencyEnum.SEK },
},
]
const additionalPrice = 33
const additionalPriceCurrency = CurrencyEnum.SEK
getAdditionalPrice(
total,
adults,
breakfast,
nights,
packages,
additionalPrice,
additionalPriceCurrency
)
expect(total.local.additionalPrice).toBe(308)
})
})
describe("getRedemptionPrice", () => {
it("returns price 0 and default currency when rooms are empty", () => {
const result = getRedemptionPrice([], 1)
expect(result).toEqual({
local: { price: 0, currency: CurrencyEnum.POINTS },
requested: undefined,
})
})
it("returns price 0 and set currency when rooms are empty", () => {
const result = getRedemptionPrice([], 1, CurrencyEnum.EUROBONUS)
expect(result).toEqual({
local: { price: 0, currency: CurrencyEnum.EUROBONUS },
requested: undefined,
})
})
it("returns price for single room with redemption price", () => {
const nights = 1
const result = getRedemptionPrice(
[
{
adults: 1,
breakfast: false,
roomFeatures: [],
roomRate: {
redemption: {
localPrice: {
pointsPerStay: 100,
currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0,
},
},
},
},
],
nights
)
expect(result).toEqual({
local: {
price: 100,
currency: CurrencyEnum.POINTS,
additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS,
},
requested: undefined,
})
})
it("returns price for single room with multiple nights", () => {
const nights = 3
const result = getRedemptionPrice(
[
{
adults: 1,
breakfast: false,
roomFeatures: [],
roomRate: {
redemption: {
localPrice: {
pointsPerStay: 100,
currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0,
},
},
},
},
],
nights
)
expect(result).toEqual({
local: {
price: 100,
currency: CurrencyEnum.POINTS,
additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS,
},
requested: undefined,
})
})
it("returns price for multiple rooms with multiple nights", () => {
const nights = 3
const result = getRedemptionPrice(
[
{
adults: 1,
breakfast: false,
roomFeatures: [],
roomRate: {
redemption: {
localPrice: {
pointsPerStay: 100,
currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0,
},
},
},
},
{
adults: 1,
breakfast: false,
roomFeatures: [],
roomRate: {
redemption: {
localPrice: {
pointsPerStay: 150,
currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0,
},
},
},
},
],
nights
)
expect(result).toEqual({
local: {
price: 250,
currency: CurrencyEnum.POINTS,
additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS,
},
requested: undefined,
})
})
it("does not return price for room without redemption", () => {
const nights = 3
const result = getRedemptionPrice(
[
{
adults: 1,
breakfast: false,
roomFeatures: [],
roomRate: {
public: {
price: 150,
},
},
},
{
adults: 1,
breakfast: false,
roomFeatures: [],
roomRate: {
redemption: {
localPrice: {
pointsPerStay: 150,
currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0,
},
},
},
},
],
nights
)
expect(result).toEqual({
local: {
price: 150,
currency: CurrencyEnum.POINTS,
additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS,
},
requested: undefined,
})
})
})

View File

@@ -14,7 +14,6 @@ import type {
CorporateChequeProduct,
PriceProduct,
Product,
RedemptionProduct,
VoucherProduct,
} from "@scandic-hotels/trpc/types/roomAvailability"
import type { User } from "@scandic-hotels/trpc/types/user"
@@ -276,12 +275,23 @@ export function clearSessionStorage() {
sessionStorage.removeItem(detailsStorageName)
}
function getAdditionalPrice(
total: Price,
export function getAdditionalPrice(
total: {
local: {
additionalPrice?: number
additionalPriceCurrency?: CurrencyEnum
}
},
adults: number,
breakfast: BreakfastPackage | false | undefined,
breakfast:
| { localPrice: { price: number; currency?: CurrencyEnum } }
| false
| undefined,
nights: number,
packages: Packages | null,
packages:
| { localPrice: { totalPrice: number; currency?: CurrencyEnum } }[]
| null
| undefined,
additionalPrice = 0,
additionalPriceCurrency?: CurrencyEnum | null | undefined
) {
@@ -440,17 +450,40 @@ function getVoucherPrice(rooms: TRoom[], nights: number) {
)
}
interface TRoomRedemption extends TRoom {
roomRate: RedemptionProduct
type GetRedemptionPriceRoom = {
adults: number
breakfast:
| { localPrice: { price: number; currency?: CurrencyEnum } }
| false
| undefined
roomFeatures:
| {
localPrice: { totalPrice: number; currency?: CurrencyEnum }
}[]
| null
| undefined
// We don't care about roomRate unless it's RedemptionProduct
roomRate: object
}
type RedemptionRoom = GetRedemptionPriceRoom & {
roomRate: {
redemption: {
localPrice: {
pointsPerStay: number
additionalPricePerStay: number
currency?: CurrencyEnum
}
}
}
}
function getRedemptionPrice(
rooms: TRoom[],
export function getRedemptionPrice(
rooms: GetRedemptionPriceRoom[],
nights: number,
pointsCurrency?: CurrencyEnum
) {
return rooms
.filter((room): room is TRoomRedemption => "redemption" in room.roomRate)
.filter((room): room is RedemptionRoom => "redemption" in room.roomRate)
.reduce<Price>(
(total, room) => {
const redemption = room.roomRate.redemption

View File

@@ -1,9 +1,10 @@
import { describe, expect, it } from "vitest"
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { dt } from "@scandic-hotels/common/dt"
import { filterOverlappingDates } from "./index"
import { filterOverlappingDates, sumPackages } from "./index"
import type { specialAlertsSchema } from "@scandic-hotels/trpc/routers/hotels/schemas/hotel/specialAlerts"
import type { z } from "zod"
@@ -71,3 +72,29 @@ describe("filterOverlappingDates", () => {
expect(result).toHaveLength(0)
})
})
describe("sumPackages", () => {
it("returns 0 price for null packages", () => {
const result = sumPackages(null)
expect(result).toEqual({ currency: undefined, price: 0 })
})
it("returns 0 price for undefined packages", () => {
const result = sumPackages(undefined)
expect(result).toEqual({ currency: undefined, price: 0 })
})
it("returns 0 price for empty packages", () => {
const result = sumPackages([])
expect(result).toEqual({ currency: undefined, price: 0 })
})
it("sums prices of packages", () => {
const result = sumPackages([
{ localPrice: { totalPrice: 100, currency: CurrencyEnum.SEK } },
{ localPrice: { totalPrice: 200, currency: CurrencyEnum.SEK } },
{ localPrice: { totalPrice: 50, currency: CurrencyEnum.SEK } },
])
expect(result).toEqual({ currency: CurrencyEnum.SEK, price: 350 })
})
})

View File

@@ -8,7 +8,8 @@ import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
import { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import type { Package, Packages } from "@scandic-hotels/trpc/types/packages"
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { Packages } from "@scandic-hotels/trpc/types/packages"
import type { JSX } from "react"
import type { RoomPackageCodes } from "../../types/components/selectRate/roomFilter"
@@ -39,7 +40,10 @@ export const invertedBedTypeMap: Record<ChildBedTypeEnum, string> = {
}
export function sumPackages(
packages: Pick<Package, "localPrice">[] | undefined | null
packages:
| { localPrice: { totalPrice: number; currency?: CurrencyEnum } }[]
| null
| undefined
) {
if (!packages || !packages.length) {
return {

View File

@@ -86,6 +86,7 @@
"@types/react": "19.1.0",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"@vitest/coverage-v8": "^3.2.4",
"dotenv": "^16.5.0",
"eslint": "^9",
"eslint-plugin-formatjs": "^5.3.1",