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:
309
packages/booking-flow/lib/stores/enter-details/helpers.test.ts
Normal file
309
packages/booking-flow/lib/stores/enter-details/helpers.test.ts
Normal 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,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user