feat: add multiroom signup
This commit is contained in:
@@ -0,0 +1,657 @@
|
||||
// import { describe, expect, test } from "@jest/globals"
|
||||
// import { act, renderHook, waitFor } from "@testing-library/react"
|
||||
// import { type PropsWithChildren } from "react"
|
||||
|
||||
// import { BedTypeEnum } from "@/constants/booking"
|
||||
// import { Lang } from "@/constants/languages"
|
||||
|
||||
// import {
|
||||
// bedType,
|
||||
// booking,
|
||||
// breakfastPackage,
|
||||
// guestDetailsMember,
|
||||
// guestDetailsNonMember,
|
||||
// roomPrice,
|
||||
// roomRate,
|
||||
// } from "@/__mocks__/hotelReservation"
|
||||
// import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||
|
||||
// import { detailsStorageName, useEnterDetailsStore } from "."
|
||||
|
||||
// import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||
// import type { BreakfastPackages } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||
// import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
// import { PackageTypeEnum } from "@/types/enums/packages"
|
||||
// import { StepEnum } from "@/types/enums/step"
|
||||
// import type { PersistedState } from "@/types/stores/enter-details"
|
||||
|
||||
// jest.mock("react", () => ({
|
||||
// ...jest.requireActual("react"),
|
||||
// cache: jest.fn(),
|
||||
// }))
|
||||
|
||||
// jest.mock("@/server/utils", () => ({
|
||||
// toLang: () => Lang.en,
|
||||
// }))
|
||||
|
||||
// jest.mock("@/lib/api", () => ({
|
||||
// fetchRetry: jest.fn((fn) => fn),
|
||||
// }))
|
||||
|
||||
// interface CreateWrapperParams {
|
||||
// bedTypes?: BedTypeSelection[]
|
||||
// bookingParams?: SelectRateSearchParams
|
||||
// breakfastIncluded?: boolean
|
||||
// breakfastPackages?: BreakfastPackages | null
|
||||
// mustBeGuaranteed?: boolean
|
||||
// }
|
||||
|
||||
// function createWrapper(params: Partial<CreateWrapperParams> = {}) {
|
||||
// const {
|
||||
// breakfastIncluded = false,
|
||||
// breakfastPackages = null,
|
||||
// mustBeGuaranteed = false,
|
||||
// bookingParams = booking,
|
||||
// bedTypes = [bedType.king, bedType.queen],
|
||||
// } = params
|
||||
|
||||
// return function Wrapper({ children }: PropsWithChildren) {
|
||||
// return (
|
||||
// <EnterDetailsProvider
|
||||
// booking={bookingParams}
|
||||
// breakfastPackages={breakfastPackages}
|
||||
// rooms={[
|
||||
// {
|
||||
// bedTypes,
|
||||
// packages: null,
|
||||
// mustBeGuaranteed,
|
||||
// breakfastIncluded,
|
||||
// cancellationText: "",
|
||||
// rateDetails: [],
|
||||
// roomType: "Standard",
|
||||
// roomTypeCode: "QS",
|
||||
// roomRate: roomRate,
|
||||
// },
|
||||
// {
|
||||
// bedTypes,
|
||||
// packages: null,
|
||||
// mustBeGuaranteed,
|
||||
// breakfastIncluded,
|
||||
// cancellationText: "",
|
||||
// rateDetails: [],
|
||||
// roomType: "Standard",
|
||||
// roomTypeCode: "QS",
|
||||
// roomRate: roomRate,
|
||||
// },
|
||||
// ]}
|
||||
// searchParamsStr=""
|
||||
// user={null}
|
||||
// vat={0}
|
||||
// >
|
||||
// {children}
|
||||
// </EnterDetailsProvider>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// describe("Enter Details Store", () => {
|
||||
// beforeEach(() => {
|
||||
// window.sessionStorage.clear()
|
||||
// })
|
||||
|
||||
// describe("initial state", () => {
|
||||
// test("initialize with correct default values", () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
// const state = result.current
|
||||
|
||||
// expect(state.booking).toEqual(booking)
|
||||
|
||||
// // room 1
|
||||
// const room1 = result.current.rooms[0]
|
||||
|
||||
// expect(room1.currentStep).toBe(StepEnum.selectBed)
|
||||
|
||||
// expect(room1.room.roomPrice.perNight.local.price).toEqual(
|
||||
// roomRate.publicRate.localPrice.pricePerNight
|
||||
// )
|
||||
// expect(room1.room.bedType).toEqual(undefined)
|
||||
// expect(Object.values(room1.room.guest).every((value) => value === ""))
|
||||
|
||||
// // room 2
|
||||
// const room2 = result.current.rooms[1]
|
||||
|
||||
// expect(room2.currentStep).toBe(null)
|
||||
// expect(room2.room.roomPrice.perNight.local.price).toEqual(
|
||||
// room2.room.roomRate.publicRate.localPrice.pricePerNight
|
||||
// )
|
||||
// expect(room2.room.bedType).toEqual(undefined)
|
||||
// expect(Object.values(room2.room.guest).every((value) => value === ""))
|
||||
// })
|
||||
|
||||
// test("initialize with correct values from session storage", () => {
|
||||
// const storage: PersistedState = {
|
||||
// activeRoom: 0,
|
||||
// booking: booking,
|
||||
// rooms: [
|
||||
// {
|
||||
// currentStep: StepEnum.selectBed,
|
||||
// isComplete: false,
|
||||
// room: {
|
||||
// adults: 1,
|
||||
// bedType: {
|
||||
// description: bedType.king.description,
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// },
|
||||
// bedTypes: [
|
||||
// {
|
||||
// description: bedType.king.description,
|
||||
// extraBed: undefined,
|
||||
// size: {
|
||||
// min: 100,
|
||||
// max: 120,
|
||||
// },
|
||||
// type: BedTypeEnum.King,
|
||||
// value: bedType.king.value,
|
||||
// },
|
||||
// ],
|
||||
// breakfastIncluded: false,
|
||||
// breakfast: breakfastPackage,
|
||||
// cancellationText: "Non-refundable",
|
||||
// childrenInRoom: [],
|
||||
// guest: guestDetailsNonMember,
|
||||
// rateDetails: [],
|
||||
// roomFeatures: null,
|
||||
// roomPrice: roomPrice,
|
||||
// roomRate: roomRate,
|
||||
// roomType: "Classic Double",
|
||||
// roomTypeCode: "QS",
|
||||
// },
|
||||
// steps: {
|
||||
// [StepEnum.selectBed]: {
|
||||
// step: StepEnum.selectBed,
|
||||
// isValid: true,
|
||||
// },
|
||||
// [StepEnum.breakfast]: {
|
||||
// step: StepEnum.breakfast,
|
||||
// isValid: true,
|
||||
// },
|
||||
// [StepEnum.details]: {
|
||||
// step: StepEnum.details,
|
||||
// isValid: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
|
||||
// window.sessionStorage.setItem(detailsStorageName, JSON.stringify(storage))
|
||||
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// expect(result.current.booking).toEqual(storage.booking)
|
||||
// expect(result.current.rooms[0]).toEqual(storage.rooms[0])
|
||||
// })
|
||||
// })
|
||||
|
||||
// test("add bedtype and proceed to next step", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// let room1 = result.current.rooms[0]
|
||||
// expect(room1.currentStep).toEqual(StepEnum.selectBed)
|
||||
|
||||
// const selectedBedType = {
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// }
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBedType(0)(selectedBedType)
|
||||
// })
|
||||
|
||||
// room1 = result.current.rooms[0]
|
||||
|
||||
// expect(room1.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
// expect(room1.room.bedType).toEqual(selectedBedType)
|
||||
|
||||
// expect(room1.currentStep).toEqual(StepEnum.breakfast)
|
||||
// })
|
||||
|
||||
// test("complete step and navigate to next step", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// // Room 1
|
||||
// expect(result.current.activeRoom).toEqual(0)
|
||||
|
||||
// let room1 = result.current.rooms[0]
|
||||
// expect(room1.currentStep).toEqual(StepEnum.selectBed)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBedType(0)({
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// })
|
||||
// })
|
||||
|
||||
// room1 = result.current.rooms[0]
|
||||
// expect(room1.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
// expect(room1.currentStep).toEqual(StepEnum.breakfast)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBreakfast(0)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// room1 = result.current.rooms[0]
|
||||
// expect(room1.steps[StepEnum.breakfast]?.isValid).toEqual(true)
|
||||
// expect(room1.currentStep).toEqual(StepEnum.details)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateDetails(0)(guestDetailsNonMember)
|
||||
// })
|
||||
|
||||
// expect(result.current.canProceedToPayment).toBe(false)
|
||||
|
||||
// // Room 2
|
||||
// expect(result.current.activeRoom).toEqual(1)
|
||||
|
||||
// let room2 = result.current.rooms[1]
|
||||
// expect(room2.currentStep).toEqual(StepEnum.selectBed)
|
||||
|
||||
// await act(async () => {
|
||||
// const selectedBedType = {
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// }
|
||||
// result.current.actions.updateBedType(1)(selectedBedType)
|
||||
// })
|
||||
|
||||
// room2 = result.current.rooms[1]
|
||||
// expect(room2.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
// expect(room2.currentStep).toEqual(StepEnum.breakfast)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBreakfast(1)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// room2 = result.current.rooms[1]
|
||||
// expect(room2.steps[StepEnum.breakfast]?.isValid).toEqual(true)
|
||||
// expect(room2.currentStep).toEqual(StepEnum.details)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateDetails(1)(guestDetailsNonMember)
|
||||
// })
|
||||
|
||||
// expect(result.current.canProceedToPayment).toBe(true)
|
||||
// })
|
||||
|
||||
// test("all steps needs to be completed before going to next room", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateDetails(0)(guestDetailsNonMember)
|
||||
// })
|
||||
|
||||
// expect(result.current.activeRoom).toEqual(1)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.setStep(StepEnum.breakfast)
|
||||
// })
|
||||
|
||||
// expect(result.current.activeRoom).toEqual(1)
|
||||
// })
|
||||
|
||||
// test("can go back and modify room 1 after completion", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBedType(0)({
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// })
|
||||
// result.current.actions.updateBreakfast(0)(breakfastPackage)
|
||||
// result.current.actions.updateDetails(0)(guestDetailsNonMember)
|
||||
// })
|
||||
|
||||
// // now we are at room 2
|
||||
// expect(result.current.activeRoom).toEqual(1)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.setStep(StepEnum.breakfast) // click "modify"
|
||||
// })
|
||||
|
||||
// expect(result.current.activeRoom).toEqual(1)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBreakfast(1)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// // going back to room 2
|
||||
// expect(result.current.activeRoom).toEqual(1)
|
||||
// })
|
||||
|
||||
// test("breakfast step should be hidden when breakfast is included", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({ breakfastPackages: null }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const room1 = result.current.rooms[0]
|
||||
// expect(Object.keys(room1.steps)).not.toContain(StepEnum.breakfast)
|
||||
|
||||
// const room2 = result.current.rooms[1]
|
||||
// expect(Object.keys(room2.steps)).not.toContain(StepEnum.breakfast)
|
||||
// })
|
||||
|
||||
// test("select bed step should be skipped when there is only one bedtype", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({
|
||||
// bedTypes: [bedType.queen],
|
||||
// breakfastPackages: [
|
||||
// {
|
||||
// code: "TEST",
|
||||
// description: "Description",
|
||||
// localPrice: {
|
||||
// currency: "SEK",
|
||||
// price: "100",
|
||||
// totalPrice: "100",
|
||||
// },
|
||||
// requestedPrice: {
|
||||
// currency: "SEK",
|
||||
// price: "100",
|
||||
// totalPrice: "100",
|
||||
// },
|
||||
// packageType: PackageTypeEnum.BreakfastAdult,
|
||||
// },
|
||||
// ],
|
||||
// }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const room1 = result.current.rooms[0]
|
||||
// expect(room1.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
// expect(room1.currentStep).toEqual(StepEnum.breakfast)
|
||||
|
||||
// const room2 = result.current.rooms[1]
|
||||
// expect(room2.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
// expect(room2.currentStep).toEqual(null)
|
||||
// })
|
||||
|
||||
// describe("price calculation", () => {
|
||||
// test("total price should be set properly", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const publicRate = roomRate.publicRate.localPrice.pricePerStay
|
||||
// const memberRate = roomRate.memberRate?.localPrice.pricePerStay ?? 0
|
||||
|
||||
// const initialTotalPrice = publicRate * result.current.rooms.length
|
||||
// expect(result.current.totalPrice.local.price).toEqual(initialTotalPrice)
|
||||
|
||||
// // room 1
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBedType(0)({
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// })
|
||||
// result.current.actions.updateBreakfast(0)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// let expectedTotalPrice =
|
||||
// initialTotalPrice + Number(breakfastPackage.localPrice.price)
|
||||
// expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateDetails(0)(guestDetailsMember)
|
||||
// })
|
||||
|
||||
// expectedTotalPrice =
|
||||
// memberRate + publicRate + Number(breakfastPackage.localPrice.price)
|
||||
// expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
|
||||
// // room 2
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateBedType(1)({
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// })
|
||||
// result.current.actions.updateBreakfast(1)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// expectedTotalPrice =
|
||||
// memberRate + publicRate + Number(breakfastPackage.localPrice.price) * 2
|
||||
// expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateDetails(1)(guestDetailsNonMember)
|
||||
// })
|
||||
|
||||
// expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
// })
|
||||
|
||||
// test("room price should be set properly", async () => {
|
||||
// const { result } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper(),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const publicRate = roomRate.publicRate.localPrice.pricePerStay
|
||||
// const memberRate = roomRate.memberRate?.localPrice.pricePerStay ?? 0
|
||||
|
||||
// let room1 = result.current.rooms[0]
|
||||
// expect(room1.room.roomPrice.perStay.local.price).toEqual(publicRate)
|
||||
|
||||
// let room2 = result.current.rooms[0]
|
||||
// expect(room2.room.roomPrice.perStay.local.price).toEqual(publicRate)
|
||||
|
||||
// await act(async () => {
|
||||
// result.current.actions.updateDetails(0)(guestDetailsMember)
|
||||
// })
|
||||
|
||||
// room1 = result.current.rooms[0]
|
||||
// expect(room1.room.roomPrice.perStay.local.price).toEqual(memberRate)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("change room", () => {
|
||||
// test("changing to room with new bedtypes requires selecting bed again", async () => {
|
||||
// const { result: firstRun } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({ bedTypes: [bedType.king, bedType.queen] }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const selectedBedType = {
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// }
|
||||
|
||||
// // add bedtype
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateBedType(0)(selectedBedType)
|
||||
// })
|
||||
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateBreakfast(0)(false) // 'no breakfast' selected
|
||||
// })
|
||||
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateDetails(0)(guestDetailsNonMember)
|
||||
// })
|
||||
|
||||
// const updatedBooking = {
|
||||
// ...booking,
|
||||
// rooms: booking.rooms.map((r) => ({
|
||||
// ...r,
|
||||
// roomTypeCode: "NEW",
|
||||
// })),
|
||||
// }
|
||||
|
||||
// // render again to change the bedtypes
|
||||
// const { result: secondRun } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({
|
||||
// bookingParams: updatedBooking,
|
||||
// bedTypes: [bedType.single, bedType.queen],
|
||||
// }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// await waitFor(() => {
|
||||
// const secondRunRoom = secondRun.current.rooms[0]
|
||||
|
||||
// // bed type should be unset since the bed types have changed
|
||||
// expect(secondRunRoom.room.bedType).toEqual(undefined)
|
||||
|
||||
// // bed step should be unselected
|
||||
// expect(secondRunRoom.currentStep).toBe(StepEnum.selectBed)
|
||||
// expect(secondRunRoom.steps[StepEnum.selectBed].isValid).toBe(false)
|
||||
|
||||
// // other steps should still be selected
|
||||
// expect(secondRunRoom.room.breakfast).toBe(false)
|
||||
// expect(secondRunRoom.steps[StepEnum.breakfast]?.isValid).toBe(true)
|
||||
// expect(secondRunRoom.room.guest).toEqual(guestDetailsNonMember)
|
||||
// expect(secondRunRoom.steps[StepEnum.details].isValid).toBe(true)
|
||||
// })
|
||||
// })
|
||||
|
||||
// test("changing to room with single bedtype option should skip step", async () => {
|
||||
// const { result: firstRun } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({ bedTypes: [bedType.king, bedType.queen] }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const selectedBedType = {
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// }
|
||||
|
||||
// // add bedtype
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateBedType(0)(selectedBedType)
|
||||
// })
|
||||
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateBreakfast(0)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// const updatedBooking = {
|
||||
// ...booking,
|
||||
// rooms: booking.rooms.map((r) => ({
|
||||
// ...r,
|
||||
// roomTypeCode: "NEW",
|
||||
// })),
|
||||
// }
|
||||
|
||||
// // render again to change the bedtypes
|
||||
// const { result: secondRun } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({
|
||||
// bookingParams: updatedBooking,
|
||||
// bedTypes: [bedType.queen],
|
||||
// }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// await waitFor(() => {
|
||||
// const secondRunRoom = secondRun.current.rooms[0]
|
||||
|
||||
// expect(secondRunRoom.room.bedType).toEqual({
|
||||
// roomTypeCode: bedType.queen.value,
|
||||
// description: bedType.queen.description,
|
||||
// })
|
||||
|
||||
// expect(secondRunRoom.steps[StepEnum.selectBed].isValid).toBe(true)
|
||||
// expect(secondRunRoom.steps[StepEnum.breakfast]?.isValid).toBe(true)
|
||||
|
||||
// expect(secondRunRoom.steps[StepEnum.details].isValid).toBe(false)
|
||||
// expect(secondRunRoom.currentStep).toBe(StepEnum.details)
|
||||
// })
|
||||
// })
|
||||
|
||||
// test("if booking has changed, stored values should be discarded", async () => {
|
||||
// const { result: firstRun } = renderHook(
|
||||
// () => useEnterDetailsStore((state) => state),
|
||||
// {
|
||||
// wrapper: createWrapper({ bedTypes: [bedType.king, bedType.queen] }),
|
||||
// }
|
||||
// )
|
||||
|
||||
// const selectedBedType = {
|
||||
// roomTypeCode: bedType.king.value,
|
||||
// description: bedType.king.description,
|
||||
// }
|
||||
|
||||
// // add bedtype
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateBedType(0)(selectedBedType)
|
||||
// })
|
||||
|
||||
// await act(async () => {
|
||||
// firstRun.current.actions.updateBreakfast(0)(breakfastPackage)
|
||||
// })
|
||||
|
||||
// const updatedBooking = {
|
||||
// ...booking,
|
||||
// hotelId: "0001",
|
||||
// fromDate: "2030-01-01",
|
||||
// toDate: "2030-01-02",
|
||||
// }
|
||||
|
||||
// renderHook(() => useEnterDetailsStore((state) => state), {
|
||||
// wrapper: createWrapper({
|
||||
// bookingParams: updatedBooking,
|
||||
// bedTypes: [bedType.queen],
|
||||
// }),
|
||||
// })
|
||||
|
||||
// await waitFor(() => {
|
||||
// const storageItem = window.sessionStorage.getItem(detailsStorageName)
|
||||
// expect(storageItem).toBe(null)
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
@@ -10,8 +10,6 @@ import type {
|
||||
DetailsState,
|
||||
PersistedState,
|
||||
RoomState,
|
||||
RoomStatus,
|
||||
RoomStep,
|
||||
} from "@/types/stores/enter-details"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
|
||||
@@ -160,7 +158,7 @@ export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) {
|
||||
rate.requestedPrice.pricePerStay
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
: total.requested,
|
||||
local: {
|
||||
currency: rate.localPrice.currency,
|
||||
price: add(total.local.price ?? 0, rate.localPrice.pricePerStay),
|
||||
@@ -179,11 +177,12 @@ export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) {
|
||||
|
||||
export function calcTotalPrice(
|
||||
rooms: RoomState[],
|
||||
totalPrice: Price,
|
||||
isMember: boolean
|
||||
currency: Price["local"]["currency"],
|
||||
isMember: boolean,
|
||||
nights: number
|
||||
) {
|
||||
return rooms.reduce<Price>(
|
||||
(acc, room, index) => {
|
||||
(acc, { room }, index) => {
|
||||
const isFirstRoomAndMember = index === 0 && isMember
|
||||
const join = Boolean(room.guest.join || room.guest.membershipNo)
|
||||
|
||||
@@ -193,10 +192,10 @@ export function calcTotalPrice(
|
||||
)
|
||||
|
||||
const breakfastRequestedPrice = room.breakfast
|
||||
? room.breakfast.requestedPrice?.totalPrice ?? 0
|
||||
? parseInt(room.breakfast.requestedPrice?.price ?? 0)
|
||||
: 0
|
||||
const breakfastLocalPrice = room.breakfast
|
||||
? room.breakfast.localPrice?.totalPrice ?? 0
|
||||
? parseInt(room.breakfast.localPrice?.price ?? 0)
|
||||
: 0
|
||||
|
||||
const roomFeaturesTotal = room.roomFeatures?.reduce((total, pkg) => {
|
||||
@@ -213,7 +212,7 @@ export function calcTotalPrice(
|
||||
price: add(
|
||||
acc.requested?.price ?? 0,
|
||||
roomPrice.perStay.requested.price,
|
||||
breakfastRequestedPrice
|
||||
breakfastRequestedPrice * room.adults * nights
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
@@ -222,7 +221,7 @@ export function calcTotalPrice(
|
||||
price: add(
|
||||
acc.local.price,
|
||||
roomPrice.perStay.local.price,
|
||||
breakfastLocalPrice,
|
||||
breakfastLocalPrice * room.adults * nights,
|
||||
roomFeaturesTotal
|
||||
),
|
||||
},
|
||||
@@ -232,57 +231,40 @@ export function calcTotalPrice(
|
||||
},
|
||||
{
|
||||
requested: undefined,
|
||||
local: { currency: totalPrice.local.currency, price: 0 },
|
||||
local: { currency, price: 0 },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const selectRoomStatus = (state: DetailsState, index?: number) =>
|
||||
state.bookingProgress.roomStatuses[
|
||||
index ?? state.bookingProgress.currentRoomIndex
|
||||
]
|
||||
|
||||
export const selectRoom = (state: DetailsState, index?: number) =>
|
||||
state.rooms[index ?? state.bookingProgress.currentRoomIndex]
|
||||
|
||||
export const selectRoomSteps = (state: DetailsState, index?: number) =>
|
||||
state.bookingProgress.roomStatuses[
|
||||
index ?? state.bookingProgress.currentRoomIndex
|
||||
].steps
|
||||
|
||||
export const selectPreviousSteps = (
|
||||
state: DetailsState,
|
||||
index?: number
|
||||
): {
|
||||
[StepEnum.selectBed]?: RoomStep
|
||||
[StepEnum.breakfast]?: RoomStep
|
||||
[StepEnum.details]?: RoomStep
|
||||
} => {
|
||||
const roomStatus =
|
||||
state.bookingProgress.roomStatuses[
|
||||
index ?? state.bookingProgress.currentRoomIndex
|
||||
]
|
||||
const stepKeys = Object.keys(roomStatus.steps)
|
||||
const currentStepIndex = stepKeys.indexOf(`${roomStatus.currentStep}`)
|
||||
return Object.entries(roomStatus.steps)
|
||||
.slice(0, currentStepIndex)
|
||||
.reduce((acc, [key, value]) => {
|
||||
return { ...acc, [key]: value }
|
||||
}, {})
|
||||
export function getFirstInteractiveStepOfRoom(room: RoomState["room"]) {
|
||||
if (!room.bedType) {
|
||||
return StepEnum.selectBed
|
||||
}
|
||||
if (room.breakfast !== false) {
|
||||
return StepEnum.breakfast
|
||||
}
|
||||
return StepEnum.details
|
||||
}
|
||||
|
||||
export const selectNextStep = (roomStatus: RoomStatus) => {
|
||||
if (roomStatus.currentStep === null) {
|
||||
export function findNextInvalidStep(roomState: RoomState) {
|
||||
return (
|
||||
Object.values(roomState.steps).find((stp) => !stp.isValid)?.step ??
|
||||
getFirstInteractiveStepOfRoom(roomState.room)
|
||||
)
|
||||
}
|
||||
|
||||
export const selectNextStep = (room: RoomState) => {
|
||||
if (room.currentStep === null) {
|
||||
throw new Error("getNextStep: currentStep is null")
|
||||
}
|
||||
|
||||
if (!roomStatus.steps[roomStatus.currentStep]?.isValid) {
|
||||
return roomStatus.currentStep
|
||||
if (!room.steps[room.currentStep]?.isValid) {
|
||||
return room.currentStep
|
||||
}
|
||||
|
||||
const stepsArray = Object.values(roomStatus.steps)
|
||||
const stepsArray = Object.values(room.steps)
|
||||
const currentIndex = stepsArray.findIndex(
|
||||
(step) => step?.step === roomStatus.currentStep
|
||||
(step) => step?.step === room.currentStep
|
||||
)
|
||||
if (currentIndex === stepsArray.length - 1) {
|
||||
return null
|
||||
@@ -295,52 +277,27 @@ export const selectNextStep = (roomStatus: RoomStatus) => {
|
||||
return nextInvalidStep?.step ?? null
|
||||
}
|
||||
|
||||
export const selectBookingProgress = (state: DetailsState) =>
|
||||
state.bookingProgress
|
||||
|
||||
export const checkBookingProgress = (state: DetailsState) => {
|
||||
return state.bookingProgress.roomStatuses.every((r) => r.isComplete)
|
||||
}
|
||||
|
||||
export const checkRoomProgress = (state: DetailsState) => {
|
||||
const steps = selectRoomSteps(state)
|
||||
export const checkRoomProgress = (steps: RoomState["steps"]) => {
|
||||
return Object.values(steps)
|
||||
.filter(Boolean)
|
||||
.every((step) => step.isValid)
|
||||
}
|
||||
|
||||
export function handleStepProgression(state: DetailsState) {
|
||||
const isAllRoomsCompleted = checkBookingProgress(state)
|
||||
export function handleStepProgression(room: RoomState, state: DetailsState) {
|
||||
const isAllRoomsCompleted = state.rooms.every((r) => r.isComplete)
|
||||
if (isAllRoomsCompleted) {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
roomStatus.currentStep = null
|
||||
state.bookingProgress.canProceedToPayment = true
|
||||
return
|
||||
}
|
||||
room.currentStep = null
|
||||
state.canProceedToPayment = true
|
||||
} else if (room.isComplete) {
|
||||
room.currentStep = null
|
||||
const nextRoomIndex = state.rooms.findIndex((r) => !r.isComplete)
|
||||
state.activeRoom = nextRoomIndex
|
||||
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
if (roomStatus.isComplete) {
|
||||
const nextRoomIndex = state.bookingProgress.roomStatuses.findIndex(
|
||||
(room) => !room.isComplete
|
||||
)
|
||||
roomStatus.lastCompletedStep = roomStatus.currentStep ?? undefined
|
||||
roomStatus.currentStep = null
|
||||
const nextRoomStatus = selectRoomStatus(state, nextRoomIndex)
|
||||
nextRoomStatus.currentStep =
|
||||
Object.values(nextRoomStatus.steps).find((step) => !step.isValid)?.step ??
|
||||
StepEnum.selectBed
|
||||
|
||||
const nextStep = selectNextStep(nextRoomStatus)
|
||||
nextRoomStatus.currentStep = nextStep
|
||||
state.bookingProgress.currentRoomIndex = nextRoomIndex
|
||||
return
|
||||
}
|
||||
|
||||
const nextStep = selectNextStep(roomStatus)
|
||||
if (nextStep !== null && roomStatus.currentStep !== null) {
|
||||
roomStatus.lastCompletedStep = roomStatus.currentStep
|
||||
roomStatus.currentStep = nextStep
|
||||
return
|
||||
const nextRoom = state.rooms[nextRoomIndex]
|
||||
const nextStep = selectNextStep(nextRoom)
|
||||
nextRoom.currentStep = nextStep
|
||||
} else if (selectNextStep(room)) {
|
||||
room.currentStep = selectNextStep(room)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,11 +314,7 @@ export function readFromSessionStorage(): PersistedState | undefined {
|
||||
|
||||
const parsedData = JSON.parse(storedData) as PersistedState
|
||||
|
||||
if (
|
||||
!parsedData.booking ||
|
||||
!parsedData.rooms ||
|
||||
!parsedData.bookingProgress
|
||||
) {
|
||||
if (!parsedData.booking || !parsedData.rooms) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { produce } from "immer"
|
||||
import { useContext } from "react"
|
||||
import { create, useStore } from "zustand"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import { DetailsContext } from "@/contexts/Details"
|
||||
|
||||
import {
|
||||
@@ -10,22 +12,20 @@ import {
|
||||
calcTotalPrice,
|
||||
checkRoomProgress,
|
||||
extractGuestFromUser,
|
||||
findNextInvalidStep,
|
||||
getRoomPrice,
|
||||
getTotalPrice,
|
||||
handleStepProgression,
|
||||
selectPreviousSteps,
|
||||
selectRoom,
|
||||
selectRoomStatus,
|
||||
writeToSessionStorage,
|
||||
} from "./helpers"
|
||||
|
||||
import type { BreakfastPackages } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
import type {
|
||||
DetailsState,
|
||||
InitialState,
|
||||
RoomState,
|
||||
RoomStatus,
|
||||
RoomStep,
|
||||
} from "@/types/stores/enter-details"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
|
||||
@@ -46,7 +46,8 @@ export const detailsStorageName = "rooms-details-storage"
|
||||
export function createDetailsStore(
|
||||
initialState: InitialState,
|
||||
searchParams: string,
|
||||
user: SafeUser
|
||||
user: SafeUser,
|
||||
breakfastPackages: BreakfastPackages | null
|
||||
) {
|
||||
const isMember = !!user
|
||||
|
||||
@@ -73,21 +74,6 @@ export function createDetailsStore(
|
||||
})
|
||||
|
||||
const rooms: RoomState[] = initialState.rooms.map((room, idx) => {
|
||||
return {
|
||||
...room,
|
||||
adults: initialState.booking.rooms[idx].adults,
|
||||
childrenInRoom: initialState.booking.rooms[idx].childrenInRoom,
|
||||
bedType: room.bedType,
|
||||
breakfast:
|
||||
initialState.breakfast === false ? initialState.breakfast : undefined,
|
||||
guest: isMember
|
||||
? deepmerge(defaultGuestState, extractGuestFromUser(user))
|
||||
: defaultGuestState,
|
||||
roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
|
||||
}
|
||||
})
|
||||
|
||||
const roomStatuses: RoomStatus[] = initialState.rooms.map((room, idx) => {
|
||||
const steps: RoomStatus["steps"] = {
|
||||
[StepEnum.selectBed]: {
|
||||
step: StepEnum.selectBed,
|
||||
@@ -103,69 +89,112 @@ export function createDetailsStore(
|
||||
},
|
||||
}
|
||||
|
||||
if (initialState.breakfast === false) {
|
||||
if (room.breakfastIncluded || !breakfastPackages?.length) {
|
||||
delete steps[StepEnum.breakfast]
|
||||
}
|
||||
|
||||
const currentStep =
|
||||
idx === 0
|
||||
? Object.values(steps).find((step) => !step.isValid)?.step ??
|
||||
StepEnum.selectBed
|
||||
: null
|
||||
Object.values(steps).find((step) => !step.isValid)?.step ??
|
||||
StepEnum.selectBed
|
||||
|
||||
return {
|
||||
room: {
|
||||
...room,
|
||||
adults: initialState.booking.rooms[idx].adults,
|
||||
childrenInRoom: initialState.booking.rooms[idx].childrenInRoom,
|
||||
bedType: room.bedType,
|
||||
breakfast:
|
||||
!breakfastPackages?.length || room.breakfastIncluded
|
||||
? false
|
||||
: undefined,
|
||||
guest:
|
||||
isMember && idx === 0
|
||||
? deepmerge(defaultGuestState, extractGuestFromUser(user))
|
||||
: defaultGuestState,
|
||||
roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
|
||||
},
|
||||
|
||||
currentStep,
|
||||
isComplete: false,
|
||||
currentStep: currentStep,
|
||||
lastCompletedStep: undefined,
|
||||
steps,
|
||||
}
|
||||
})
|
||||
|
||||
return create<DetailsState>()((set, get) => ({
|
||||
searchParamString: searchParams,
|
||||
return create<DetailsState>()((set) => ({
|
||||
activeRoom: 0,
|
||||
booking: initialState.booking,
|
||||
breakfast:
|
||||
initialState.breakfast === false ? initialState.breakfast : undefined,
|
||||
breakfastPackages,
|
||||
canProceedToPayment: false,
|
||||
isSubmittingDisabled: false,
|
||||
isSummaryOpen: false,
|
||||
isPriceDetailsModalOpen: false,
|
||||
lastRoom: initialState.booking.rooms.length - 1,
|
||||
rooms,
|
||||
searchParamString: searchParams,
|
||||
totalPrice: initialTotalPrice,
|
||||
vat: initialState.vat,
|
||||
rooms,
|
||||
bookingProgress: {
|
||||
currentRoomIndex: 0,
|
||||
roomStatuses,
|
||||
canProceedToPayment: false,
|
||||
},
|
||||
|
||||
actions: {
|
||||
setStep(step: StepEnum | null, roomIndex?: number) {
|
||||
if (step === null) {
|
||||
return
|
||||
}
|
||||
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const currentRoomIndex =
|
||||
roomIndex ?? state.bookingProgress.currentRoomIndex
|
||||
const previousSteps = selectPreviousSteps(state, roomIndex)
|
||||
const arePreviousStepsCompleted = Object.values(
|
||||
previousSteps
|
||||
).every((step: RoomStep) => step.isValid)
|
||||
const arePreviousRoomsCompleted = state.bookingProgress.roomStatuses
|
||||
.slice(0, currentRoomIndex)
|
||||
.every((room) => room.isComplete)
|
||||
const roomStatus = selectRoomStatus(state, roomIndex)
|
||||
|
||||
if (arePreviousRoomsCompleted && arePreviousStepsCompleted) {
|
||||
roomStatus.currentStep = step
|
||||
|
||||
if (roomIndex !== undefined) {
|
||||
state.bookingProgress.currentRoomIndex = roomIndex
|
||||
setStep(idx) {
|
||||
return function (step) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const isSameRoom = idx === state.activeRoom
|
||||
const room = state.rooms[idx]
|
||||
if (isSameRoom) {
|
||||
// Closed same accordion as was open
|
||||
if (step === room.currentStep) {
|
||||
if (room.isComplete) {
|
||||
// Room is complete, move to next room or payment
|
||||
const nextRoomIdx = state.rooms.findIndex(
|
||||
(r) => !r.isComplete
|
||||
)
|
||||
state.activeRoom = nextRoomIdx
|
||||
// Done, proceed to payment
|
||||
if (nextRoomIdx === -1) {
|
||||
room.currentStep = null
|
||||
} else {
|
||||
const nextRoom = state.rooms[nextRoomIdx]
|
||||
const nextInvalidStep = findNextInvalidStep(nextRoom)
|
||||
nextRoom.currentStep = nextInvalidStep
|
||||
}
|
||||
} else {
|
||||
room.currentStep = findNextInvalidStep(room)
|
||||
}
|
||||
} else {
|
||||
if (room.steps[step]?.isValid) {
|
||||
room.currentStep = step
|
||||
} else {
|
||||
room.currentStep = findNextInvalidStep(room)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const arePreviousRoomsCompleted = state.rooms
|
||||
.slice(0, idx)
|
||||
.every((room) => room.isComplete)
|
||||
if (arePreviousRoomsCompleted) {
|
||||
state.activeRoom = idx
|
||||
if (room.steps[step]?.isValid) {
|
||||
room.currentStep = step
|
||||
} else {
|
||||
room.currentStep = findNextInvalidStep(room)
|
||||
}
|
||||
} else {
|
||||
const firstIncompleteRoom = state.rooms.findIndex(
|
||||
(r) => !r.isComplete
|
||||
)
|
||||
state.activeRoom = firstIncompleteRoom
|
||||
if (firstIncompleteRoom === -1) {
|
||||
// All rooms are done, proceed to payment
|
||||
room.currentStep = null
|
||||
} else {
|
||||
const nextRoom = state.rooms[firstIncompleteRoom]
|
||||
nextRoom.currentStep = findNextInvalidStep(nextRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
setIsSubmittingDisabled(isSubmittingDisabled) {
|
||||
return set(
|
||||
@@ -189,170 +218,240 @@ export function createDetailsStore(
|
||||
})
|
||||
)
|
||||
},
|
||||
togglePriceDetailsModalOpen() {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.isPriceDetailsModalOpen = !state.isPriceDetailsModalOpen
|
||||
})
|
||||
)
|
||||
},
|
||||
updateBedType(bedType) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
roomStatus.steps[StepEnum.selectBed].isValid = true
|
||||
updateBedType(idx) {
|
||||
return function (bedType) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.rooms[idx].steps[StepEnum.selectBed].isValid = true
|
||||
state.rooms[idx].room.bedType = bedType
|
||||
|
||||
const room = selectRoom(state)
|
||||
room.bedType = bedType
|
||||
handleStepProgression(state.rooms[idx], state)
|
||||
|
||||
handleStepProgression(state)
|
||||
|
||||
writeToSessionStorage({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
bookingProgress: state.bookingProgress,
|
||||
writeToSessionStorage({
|
||||
activeRoom: state.activeRoom,
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
updateBreakfast(breakfast) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
if (roomStatus.steps[StepEnum.breakfast]) {
|
||||
roomStatus.steps[StepEnum.breakfast].isValid = true
|
||||
}
|
||||
updateBreakfast(idx) {
|
||||
return function (breakfast) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const currentRoom = state.rooms[idx]
|
||||
if (currentRoom.steps[StepEnum.breakfast]) {
|
||||
currentRoom.steps[StepEnum.breakfast].isValid = true
|
||||
}
|
||||
|
||||
const stateTotalRequestedPrice =
|
||||
state.totalPrice.requested?.price || 0
|
||||
const currentTotalPriceRequested = state.totalPrice.requested
|
||||
let stateTotalRequestedPrice = 0
|
||||
if (currentTotalPriceRequested) {
|
||||
stateTotalRequestedPrice = currentTotalPriceRequested.price
|
||||
}
|
||||
|
||||
const stateTotalLocalPrice = state.totalPrice.local.price
|
||||
const stateTotalLocalPrice = state.totalPrice.local.price
|
||||
|
||||
const addToTotalPrice =
|
||||
(state.breakfast === undefined || state.breakfast === false) &&
|
||||
!!breakfast
|
||||
const addToTotalPrice =
|
||||
(currentRoom.room.breakfast === undefined ||
|
||||
currentRoom.room.breakfast === false) &&
|
||||
!!breakfast
|
||||
|
||||
const subtractFromTotalPrice =
|
||||
(state.breakfast === undefined || state.breakfast) &&
|
||||
breakfast === false
|
||||
const subtractFromTotalPrice =
|
||||
currentRoom.room.breakfast && breakfast === false
|
||||
|
||||
if (addToTotalPrice) {
|
||||
const breakfastTotalRequestedPrice = parseInt(
|
||||
breakfast.requestedPrice.totalPrice
|
||||
)
|
||||
const breakfastTotalPrice = parseInt(
|
||||
breakfast.localPrice.totalPrice
|
||||
const nights = dt(state.booking.toDate).diff(
|
||||
state.booking.fromDate,
|
||||
"days"
|
||||
)
|
||||
|
||||
state.totalPrice = {
|
||||
requested: state.totalPrice.requested && {
|
||||
currency: state.totalPrice.requested.currency,
|
||||
price:
|
||||
stateTotalRequestedPrice + breakfastTotalRequestedPrice,
|
||||
},
|
||||
local: {
|
||||
currency: breakfast.localPrice.currency,
|
||||
price: stateTotalLocalPrice + breakfastTotalPrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (subtractFromTotalPrice) {
|
||||
let currency = state.totalPrice.local.currency
|
||||
let currentBreakfastTotalPrice = 0
|
||||
let currentBreakfastTotalRequestedPrice = 0
|
||||
if (state.breakfast) {
|
||||
currentBreakfastTotalPrice = parseInt(
|
||||
state.breakfast.localPrice.totalPrice
|
||||
)
|
||||
currentBreakfastTotalRequestedPrice = parseInt(
|
||||
state.breakfast.requestedPrice.totalPrice
|
||||
)
|
||||
currency = state.breakfast.localPrice.currency
|
||||
if (addToTotalPrice) {
|
||||
const breakfastTotalRequestedPrice =
|
||||
parseInt(breakfast.requestedPrice.price) *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
const breakfastTotalPrice =
|
||||
parseInt(breakfast.localPrice.price) *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
state.totalPrice = {
|
||||
requested: state.totalPrice.requested && {
|
||||
currency: state.totalPrice.requested.currency,
|
||||
price:
|
||||
stateTotalRequestedPrice + breakfastTotalRequestedPrice,
|
||||
},
|
||||
local: {
|
||||
currency: breakfast.localPrice.currency,
|
||||
price: stateTotalLocalPrice + breakfastTotalPrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let requestedPrice =
|
||||
stateTotalRequestedPrice - currentBreakfastTotalRequestedPrice
|
||||
if (requestedPrice < 0) {
|
||||
requestedPrice = 0
|
||||
}
|
||||
let localPrice = stateTotalLocalPrice - currentBreakfastTotalPrice
|
||||
if (localPrice < 0) {
|
||||
localPrice = 0
|
||||
if (subtractFromTotalPrice) {
|
||||
let currency = state.totalPrice.local.currency
|
||||
let currentBreakfastTotalPrice = 0
|
||||
let currentBreakfastTotalRequestedPrice = 0
|
||||
if (currentRoom.room.breakfast) {
|
||||
currentBreakfastTotalPrice =
|
||||
parseInt(currentRoom.room.breakfast.localPrice.price) *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
currentBreakfastTotalRequestedPrice =
|
||||
parseInt(
|
||||
currentRoom.room.breakfast.requestedPrice.totalPrice
|
||||
) *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
currency = currentRoom.room.breakfast.localPrice.currency
|
||||
}
|
||||
|
||||
let requestedPrice =
|
||||
stateTotalRequestedPrice - currentBreakfastTotalRequestedPrice
|
||||
if (requestedPrice < 0) {
|
||||
requestedPrice = 0
|
||||
}
|
||||
let localPrice =
|
||||
stateTotalLocalPrice - currentBreakfastTotalPrice
|
||||
if (localPrice < 0) {
|
||||
localPrice = 0
|
||||
}
|
||||
|
||||
state.totalPrice = {
|
||||
requested: state.totalPrice.requested && {
|
||||
currency: state.totalPrice.requested.currency,
|
||||
price: requestedPrice,
|
||||
},
|
||||
local: {
|
||||
currency,
|
||||
price: localPrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
state.totalPrice = {
|
||||
requested: state.totalPrice.requested && {
|
||||
currency: state.totalPrice.requested.currency,
|
||||
price: requestedPrice,
|
||||
},
|
||||
local: {
|
||||
currency,
|
||||
price: localPrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
currentRoom.room.breakfast = breakfast
|
||||
|
||||
const room = selectRoom(state)
|
||||
room.breakfast = breakfast
|
||||
handleStepProgression(currentRoom, state)
|
||||
|
||||
handleStepProgression(state)
|
||||
|
||||
writeToSessionStorage({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
bookingProgress: state.bookingProgress,
|
||||
writeToSessionStorage({
|
||||
activeRoom: state.activeRoom,
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
updateDetails(data) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
roomStatus.steps[StepEnum.details].isValid = true
|
||||
updateDetails(idx) {
|
||||
return function (data) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.rooms[idx].steps[StepEnum.details].isValid = true
|
||||
|
||||
const room = selectRoom(state)
|
||||
room.guest.countryCode = data.countryCode
|
||||
room.guest.dateOfBirth = data.dateOfBirth
|
||||
room.guest.email = data.email
|
||||
room.guest.firstName = data.firstName
|
||||
room.guest.join = data.join
|
||||
room.guest.lastName = data.lastName
|
||||
state.rooms[idx].room.guest.countryCode = data.countryCode
|
||||
state.rooms[idx].room.guest.dateOfBirth = data.dateOfBirth
|
||||
state.rooms[idx].room.guest.email = data.email
|
||||
state.rooms[idx].room.guest.firstName = data.firstName
|
||||
state.rooms[idx].room.guest.join = data.join
|
||||
state.rooms[idx].room.guest.lastName = data.lastName
|
||||
|
||||
if (data.join) {
|
||||
room.guest.membershipNo = undefined
|
||||
} else {
|
||||
room.guest.membershipNo = data.membershipNo
|
||||
}
|
||||
room.guest.phoneNumber = data.phoneNumber
|
||||
room.guest.zipCode = data.zipCode
|
||||
if (data.join) {
|
||||
state.rooms[idx].room.guest.membershipNo = undefined
|
||||
} else {
|
||||
state.rooms[idx].room.guest.membershipNo = data.membershipNo
|
||||
}
|
||||
state.rooms[idx].room.guest.phoneNumber = data.phoneNumber
|
||||
state.rooms[idx].room.guest.zipCode = data.zipCode
|
||||
|
||||
room.roomPrice = getRoomPrice(
|
||||
room.roomRate,
|
||||
Boolean(data.join || data.membershipNo || isMember)
|
||||
)
|
||||
state.rooms[idx].room.roomPrice = getRoomPrice(
|
||||
state.rooms[idx].room.roomRate,
|
||||
Boolean(data.join || data.membershipNo || isMember)
|
||||
)
|
||||
|
||||
state.totalPrice = calcTotalPrice(
|
||||
state.rooms,
|
||||
state.totalPrice,
|
||||
isMember
|
||||
)
|
||||
const nights = dt(state.booking.toDate).diff(
|
||||
state.booking.fromDate,
|
||||
"days"
|
||||
)
|
||||
|
||||
const isAllStepsCompleted = checkRoomProgress(state)
|
||||
if (isAllStepsCompleted) {
|
||||
roomStatus.isComplete = true
|
||||
}
|
||||
state.totalPrice = calcTotalPrice(
|
||||
state.rooms,
|
||||
state.totalPrice.local.currency,
|
||||
isMember,
|
||||
nights
|
||||
)
|
||||
|
||||
handleStepProgression(state)
|
||||
const isAllStepsCompleted = checkRoomProgress(
|
||||
state.rooms[idx].steps
|
||||
)
|
||||
if (isAllStepsCompleted) {
|
||||
state.rooms[idx].isComplete = true
|
||||
}
|
||||
|
||||
writeToSessionStorage({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
bookingProgress: state.bookingProgress,
|
||||
handleStepProgression(state.rooms[idx], state)
|
||||
|
||||
writeToSessionStorage({
|
||||
activeRoom: state.activeRoom,
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
updateMultiroomDetails(idx) {
|
||||
return function (data) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.rooms[idx].steps[StepEnum.details].isValid = true
|
||||
|
||||
state.rooms[idx].room.guest.countryCode = data.countryCode
|
||||
state.rooms[idx].room.guest.email = data.email
|
||||
state.rooms[idx].room.guest.firstName = data.firstName
|
||||
state.rooms[idx].room.guest.join = data.join
|
||||
state.rooms[idx].room.guest.lastName = data.lastName
|
||||
|
||||
if (data.join) {
|
||||
state.rooms[idx].room.guest.membershipNo = undefined
|
||||
} else {
|
||||
state.rooms[idx].room.guest.membershipNo = data.membershipNo
|
||||
}
|
||||
state.rooms[idx].room.guest.phoneNumber = data.phoneNumber
|
||||
|
||||
const getMemberPrice = Boolean(data.join || data.membershipNo)
|
||||
state.rooms[idx].room.roomPrice = getRoomPrice(
|
||||
state.rooms[idx].room.roomRate,
|
||||
getMemberPrice
|
||||
)
|
||||
|
||||
const nights = dt(state.booking.toDate).diff(
|
||||
state.booking.fromDate,
|
||||
"days"
|
||||
)
|
||||
|
||||
state.totalPrice = calcTotalPrice(
|
||||
state.rooms,
|
||||
state.totalPrice.local.currency,
|
||||
getMemberPrice,
|
||||
nights
|
||||
)
|
||||
|
||||
const isAllStepsCompleted = checkRoomProgress(
|
||||
state.rooms[idx].steps
|
||||
)
|
||||
if (isAllStepsCompleted) {
|
||||
state.rooms[idx].isComplete = true
|
||||
}
|
||||
|
||||
handleStepProgression(state.rooms[idx], state)
|
||||
|
||||
writeToSessionStorage({
|
||||
activeRoom: state.activeRoom,
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
updateSeachParamString(searchParamString) {
|
||||
return set(
|
||||
|
||||
@@ -1,633 +0,0 @@
|
||||
import { describe, expect, test } from "@jest/globals"
|
||||
import { act, renderHook, waitFor } from "@testing-library/react"
|
||||
import { type PropsWithChildren } from "react"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import {
|
||||
bedType,
|
||||
booking,
|
||||
breakfastPackage,
|
||||
guestDetailsMember,
|
||||
guestDetailsNonMember,
|
||||
roomPrice,
|
||||
roomRate,
|
||||
} from "@/__mocks__/hotelReservation"
|
||||
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||
|
||||
import { selectRoom, selectRoomStatus } from "./helpers"
|
||||
import { detailsStorageName, useEnterDetailsStore } from "."
|
||||
|
||||
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
import type { PersistedState } from "@/types/stores/enter-details"
|
||||
|
||||
jest.mock("react", () => ({
|
||||
...jest.requireActual("react"),
|
||||
cache: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock("@/server/utils", () => ({
|
||||
toLang: () => Lang.en,
|
||||
}))
|
||||
|
||||
jest.mock("@/lib/api", () => ({
|
||||
fetchRetry: jest.fn((fn) => fn),
|
||||
}))
|
||||
|
||||
interface CreateWrapperParams {
|
||||
showBreakfastStep?: boolean
|
||||
breakfastIncluded?: boolean
|
||||
mustBeGuaranteed?: boolean
|
||||
bookingParams?: SelectRateSearchParams
|
||||
bedTypes?: BedTypeSelection[]
|
||||
}
|
||||
|
||||
function createWrapper(params: Partial<CreateWrapperParams> = {}) {
|
||||
const {
|
||||
showBreakfastStep = true,
|
||||
breakfastIncluded = false,
|
||||
mustBeGuaranteed = false,
|
||||
bookingParams = booking,
|
||||
bedTypes = [bedType.king, bedType.queen],
|
||||
} = params
|
||||
|
||||
return function Wrapper({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<EnterDetailsProvider
|
||||
booking={bookingParams}
|
||||
showBreakfastStep={showBreakfastStep}
|
||||
roomsData={[
|
||||
{
|
||||
bedTypes,
|
||||
packages: null,
|
||||
mustBeGuaranteed,
|
||||
breakfastIncluded,
|
||||
cancellationText: "",
|
||||
rateDetails: [],
|
||||
roomType: "Standard",
|
||||
roomRate: roomRate,
|
||||
},
|
||||
{
|
||||
bedTypes,
|
||||
packages: null,
|
||||
mustBeGuaranteed,
|
||||
breakfastIncluded,
|
||||
cancellationText: "",
|
||||
rateDetails: [],
|
||||
roomType: "Standard",
|
||||
roomRate: roomRate,
|
||||
},
|
||||
]}
|
||||
searchParamsStr=""
|
||||
user={null}
|
||||
vat={0}
|
||||
>
|
||||
{children}
|
||||
</EnterDetailsProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
describe("Enter Details Store", () => {
|
||||
beforeEach(() => {
|
||||
window.sessionStorage.clear()
|
||||
})
|
||||
|
||||
describe("initial state", () => {
|
||||
test("initialize with correct default values", () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
const state = result.current
|
||||
|
||||
expect(state.booking).toEqual(booking)
|
||||
expect(state.breakfast).toEqual(undefined)
|
||||
|
||||
// room 1
|
||||
const room1Status = selectRoomStatus(result.current, 0)
|
||||
const room1 = selectRoom(result.current, 0)
|
||||
|
||||
expect(room1Status.currentStep).toBe(StepEnum.selectBed)
|
||||
|
||||
expect(room1.roomPrice.perNight.local.price).toEqual(
|
||||
roomRate.publicRate.localPrice.pricePerNight
|
||||
)
|
||||
expect(room1.bedType).toEqual(undefined)
|
||||
expect(Object.values(room1.guest).every((value) => value === ""))
|
||||
|
||||
// room 2
|
||||
const room2Status = selectRoomStatus(result.current, 1)
|
||||
const room2 = selectRoom(result.current, 1)
|
||||
|
||||
expect(room2Status.currentStep).toBe(null)
|
||||
expect(room2.roomPrice.perNight.local.price).toEqual(
|
||||
room2.roomRate.publicRate.localPrice.pricePerNight
|
||||
)
|
||||
expect(room2.bedType).toEqual(undefined)
|
||||
expect(Object.values(room2.guest).every((value) => value === ""))
|
||||
})
|
||||
|
||||
test("initialize with correct values from session storage", () => {
|
||||
const storage: PersistedState = {
|
||||
booking: booking,
|
||||
bookingProgress: {
|
||||
currentRoomIndex: 0,
|
||||
canProceedToPayment: true,
|
||||
roomStatuses: [
|
||||
{
|
||||
isComplete: false,
|
||||
currentStep: StepEnum.selectBed,
|
||||
lastCompletedStep: undefined,
|
||||
steps: {
|
||||
[StepEnum.selectBed]: {
|
||||
step: StepEnum.selectBed,
|
||||
isValid: true,
|
||||
},
|
||||
[StepEnum.breakfast]: {
|
||||
step: StepEnum.breakfast,
|
||||
isValid: true,
|
||||
},
|
||||
[StepEnum.details]: {
|
||||
step: StepEnum.details,
|
||||
isValid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
rooms: [
|
||||
{
|
||||
roomFeatures: null,
|
||||
roomRate: roomRate,
|
||||
roomType: "Classic Double",
|
||||
cancellationText: "Non-refundable",
|
||||
rateDetails: [],
|
||||
bedType: {
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
},
|
||||
adults: 1,
|
||||
childrenInRoom: [],
|
||||
breakfast: breakfastPackage,
|
||||
guest: guestDetailsNonMember,
|
||||
roomPrice: roomPrice,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
window.sessionStorage.setItem(detailsStorageName, JSON.stringify(storage))
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
expect(result.current.booking).toEqual(storage.booking)
|
||||
expect(result.current.rooms[0]).toEqual(storage.rooms[0])
|
||||
expect(result.current.bookingProgress).toEqual(storage.bookingProgress)
|
||||
})
|
||||
})
|
||||
|
||||
test("add bedtype and proceed to next step", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
let roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.selectBed)
|
||||
|
||||
const selectedBedType = {
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateBedType(selectedBedType)
|
||||
})
|
||||
|
||||
roomStatus = selectRoomStatus(result.current)
|
||||
const room = selectRoom(result.current)
|
||||
|
||||
expect(roomStatus.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
expect(room.bedType).toEqual(selectedBedType)
|
||||
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.breakfast)
|
||||
})
|
||||
|
||||
test("complete step and navigate to next step", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
// Room 1
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(0)
|
||||
|
||||
let roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.selectBed)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateBedType({
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
})
|
||||
})
|
||||
|
||||
roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.breakfast)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.steps[StepEnum.breakfast]?.isValid).toEqual(true)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.details)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateDetails(guestDetailsNonMember)
|
||||
})
|
||||
|
||||
expect(result.current.bookingProgress.canProceedToPayment).toBe(false)
|
||||
|
||||
// Room 2
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(1)
|
||||
|
||||
roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.selectBed)
|
||||
|
||||
await act(async () => {
|
||||
const selectedBedType = {
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
}
|
||||
result.current.actions.updateBedType(selectedBedType)
|
||||
})
|
||||
|
||||
roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.breakfast)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
roomStatus = selectRoomStatus(result.current)
|
||||
expect(roomStatus.steps[StepEnum.breakfast]?.isValid).toEqual(true)
|
||||
expect(roomStatus.currentStep).toEqual(StepEnum.details)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateDetails(guestDetailsNonMember)
|
||||
})
|
||||
|
||||
expect(result.current.bookingProgress.canProceedToPayment).toBe(true)
|
||||
})
|
||||
|
||||
test("all steps needs to be completed before going to next room", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateDetails(guestDetailsNonMember)
|
||||
})
|
||||
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(0)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.setStep(StepEnum.breakfast, 1)
|
||||
})
|
||||
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(0)
|
||||
})
|
||||
|
||||
test("can go back and modify room 1 after completion", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateBedType({
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
})
|
||||
result.current.actions.updateBreakfast(breakfastPackage)
|
||||
result.current.actions.updateDetails(guestDetailsNonMember)
|
||||
})
|
||||
|
||||
// now we are at room 2
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(1)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.setStep(StepEnum.breakfast, 0) // click "modify"
|
||||
})
|
||||
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(0)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
// going back to room 2
|
||||
expect(result.current.bookingProgress.currentRoomIndex).toEqual(1)
|
||||
})
|
||||
|
||||
test("breakfast step should be hidden when breakfast is included", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({ showBreakfastStep: false }),
|
||||
}
|
||||
)
|
||||
|
||||
const room1Status = selectRoomStatus(result.current, 0)
|
||||
expect(Object.keys(room1Status.steps)).not.toContain(StepEnum.breakfast)
|
||||
|
||||
const room2Status = selectRoomStatus(result.current, 1)
|
||||
expect(Object.keys(room2Status.steps)).not.toContain(StepEnum.breakfast)
|
||||
})
|
||||
|
||||
test("select bed step should be skipped when there is only one bedtype", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({ bedTypes: [bedType.queen] }),
|
||||
}
|
||||
)
|
||||
|
||||
const room1Status = selectRoomStatus(result.current, 0)
|
||||
expect(room1Status.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
expect(room1Status.currentStep).toEqual(StepEnum.breakfast)
|
||||
|
||||
const room2Status = selectRoomStatus(result.current, 1)
|
||||
expect(room2Status.steps[StepEnum.selectBed].isValid).toEqual(true)
|
||||
expect(room2Status.currentStep).toEqual(null)
|
||||
})
|
||||
|
||||
describe("price calculation", () => {
|
||||
test("total price should be set properly", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
const publicRate = roomRate.publicRate.localPrice.pricePerStay
|
||||
const memberRate = roomRate.memberRate?.localPrice.pricePerStay ?? 0
|
||||
|
||||
const initialTotalPrice = publicRate * result.current.rooms.length
|
||||
expect(result.current.totalPrice.local.price).toEqual(initialTotalPrice)
|
||||
|
||||
// room 1
|
||||
await act(async () => {
|
||||
result.current.actions.updateBedType({
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
})
|
||||
result.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
let expectedTotalPrice =
|
||||
initialTotalPrice + Number(breakfastPackage.localPrice.price)
|
||||
expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateDetails(guestDetailsMember)
|
||||
})
|
||||
|
||||
expectedTotalPrice =
|
||||
memberRate + publicRate + Number(breakfastPackage.localPrice.price)
|
||||
expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
|
||||
// room 2
|
||||
await act(async () => {
|
||||
result.current.actions.updateBedType({
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
})
|
||||
result.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
expectedTotalPrice =
|
||||
memberRate + publicRate + Number(breakfastPackage.localPrice.price) * 2
|
||||
expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateDetails(guestDetailsNonMember)
|
||||
})
|
||||
|
||||
expect(result.current.totalPrice.local.price).toEqual(expectedTotalPrice)
|
||||
})
|
||||
|
||||
test("room price should be set properly", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
const publicRate = roomRate.publicRate.localPrice.pricePerStay
|
||||
const memberRate = roomRate.memberRate?.localPrice.pricePerStay ?? 0
|
||||
|
||||
let room1 = selectRoom(result.current, 0)
|
||||
expect(room1.roomPrice.perStay.local.price).toEqual(publicRate)
|
||||
|
||||
let room2 = selectRoom(result.current, 0)
|
||||
expect(room2.roomPrice.perStay.local.price).toEqual(publicRate)
|
||||
|
||||
await act(async () => {
|
||||
result.current.actions.updateDetails(guestDetailsMember)
|
||||
})
|
||||
|
||||
room1 = selectRoom(result.current, 0)
|
||||
expect(room1.roomPrice.perStay.local.price).toEqual(memberRate)
|
||||
})
|
||||
})
|
||||
|
||||
describe("change room", () => {
|
||||
test("changing to room with new bedtypes requires selecting bed again", async () => {
|
||||
const { result: firstRun } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({ bedTypes: [bedType.king, bedType.queen] }),
|
||||
}
|
||||
)
|
||||
|
||||
const selectedBedType = {
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
}
|
||||
|
||||
// add bedtype
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateBedType(selectedBedType)
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateBreakfast(false) // 'no breakfast' selected
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateDetails(guestDetailsNonMember)
|
||||
})
|
||||
|
||||
const updatedBooking = {
|
||||
...booking,
|
||||
rooms: booking.rooms.map((r) => ({
|
||||
...r,
|
||||
roomTypeCode: "NEW",
|
||||
})),
|
||||
}
|
||||
|
||||
// render again to change the bedtypes
|
||||
const { result: secondRun } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({
|
||||
bookingParams: updatedBooking,
|
||||
bedTypes: [bedType.single, bedType.queen],
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const room = selectRoom(secondRun.current, 0)
|
||||
const roomStatus = selectRoomStatus(secondRun.current, 0)
|
||||
|
||||
// bed type should be unset since the bed types have changed
|
||||
expect(room.bedType).toEqual(undefined)
|
||||
|
||||
// bed step should be unselected
|
||||
expect(roomStatus.currentStep).toBe(StepEnum.selectBed)
|
||||
expect(roomStatus.steps[StepEnum.selectBed].isValid).toBe(false)
|
||||
|
||||
// other steps should still be selected
|
||||
expect(room.breakfast).toBe(false)
|
||||
expect(roomStatus.steps[StepEnum.breakfast]?.isValid).toBe(true)
|
||||
expect(room.guest).toEqual(guestDetailsNonMember)
|
||||
expect(roomStatus.steps[StepEnum.details].isValid).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test("changing to room with single bedtype option should skip step", async () => {
|
||||
const { result: firstRun } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({ bedTypes: [bedType.king, bedType.queen] }),
|
||||
}
|
||||
)
|
||||
|
||||
const selectedBedType = {
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
}
|
||||
|
||||
// add bedtype
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateBedType(selectedBedType)
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
const updatedBooking = {
|
||||
...booking,
|
||||
rooms: booking.rooms.map((r) => ({
|
||||
...r,
|
||||
roomTypeCode: "NEW",
|
||||
})),
|
||||
}
|
||||
|
||||
// render again to change the bedtypes
|
||||
const { result: secondRun } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({
|
||||
bookingParams: updatedBooking,
|
||||
bedTypes: [bedType.queen],
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const room = selectRoom(secondRun.current, 0)
|
||||
const roomStatus = selectRoomStatus(secondRun.current, 0)
|
||||
|
||||
expect(room.bedType).toEqual({
|
||||
roomTypeCode: bedType.queen.value,
|
||||
description: bedType.queen.description,
|
||||
})
|
||||
|
||||
expect(roomStatus.steps[StepEnum.selectBed].isValid).toBe(true)
|
||||
expect(roomStatus.steps[StepEnum.breakfast]?.isValid).toBe(true)
|
||||
|
||||
expect(roomStatus.steps[StepEnum.details].isValid).toBe(false)
|
||||
expect(roomStatus.currentStep).toBe(StepEnum.details)
|
||||
})
|
||||
})
|
||||
|
||||
test("if booking has changed, stored values should be discarded", async () => {
|
||||
const { result: firstRun } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: createWrapper({ bedTypes: [bedType.king, bedType.queen] }),
|
||||
}
|
||||
)
|
||||
|
||||
const selectedBedType = {
|
||||
roomTypeCode: bedType.king.value,
|
||||
description: bedType.king.description,
|
||||
}
|
||||
|
||||
// add bedtype
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateBedType(selectedBedType)
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
firstRun.current.actions.updateBreakfast(breakfastPackage)
|
||||
})
|
||||
|
||||
const updatedBooking = {
|
||||
...booking,
|
||||
hotelId: "0001",
|
||||
fromDate: "2030-01-01",
|
||||
toDate: "2030-01-02",
|
||||
}
|
||||
|
||||
renderHook(() => useEnterDetailsStore((state) => state), {
|
||||
wrapper: createWrapper({
|
||||
bookingParams: updatedBooking,
|
||||
bedTypes: [bedType.queen],
|
||||
}),
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
const storageItem = window.sessionStorage.getItem(detailsStorageName)
|
||||
expect(storageItem).toBe(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -200,7 +200,7 @@ export function createRatesStore({
|
||||
`room[${idx}].counterratecode`,
|
||||
isMemberRate
|
||||
? selectedRate.product.productType.public.rateCode
|
||||
: selectedRate.product.productType.member?.rateCode ?? ""
|
||||
: (selectedRate.product.productType.member?.rateCode ?? "")
|
||||
)
|
||||
searchParams.set(
|
||||
`room[${idx}].ratecode`,
|
||||
@@ -236,6 +236,7 @@ export function createRatesStore({
|
||||
booking,
|
||||
filterOptions,
|
||||
hotelType,
|
||||
isUserLoggedIn,
|
||||
packages,
|
||||
pathname,
|
||||
petRoomPackage: packages.find(
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import { create } from "zustand"
|
||||
|
||||
import { calculateRoomSummary } from "./helper"
|
||||
|
||||
import type {
|
||||
RoomPackageCodeEnum,
|
||||
RoomPackages,
|
||||
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type {
|
||||
Child,
|
||||
Rate,
|
||||
RateCode,
|
||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
|
||||
export interface RateSummaryParams {
|
||||
getFilteredRooms: (roomIndex: number) => RoomConfiguration[]
|
||||
availablePackages: RoomPackages
|
||||
roomCategories: Array<{ name: string; roomTypes: Array<{ code: string }> }>
|
||||
selectedPackagesByRoom: Record<number, RoomPackageCodeEnum[]>
|
||||
}
|
||||
|
||||
interface RateSelectionState {
|
||||
selectedRates: (RateCode | undefined)[]
|
||||
rateSummary: (Rate | null)[]
|
||||
isPriceDetailsModalOpen: boolean
|
||||
isSummaryOpen: boolean
|
||||
guestsInRooms: { adults: number; children?: Child[] }[]
|
||||
modifyRateIndex: number | null
|
||||
modifyRate: (index: number) => void
|
||||
closeModifyRate: () => void
|
||||
selectRate: (index: number, rate: RateCode | undefined) => void
|
||||
initializeRates: (count: number) => void
|
||||
calculateRateSummary: ({
|
||||
getFilteredRooms,
|
||||
availablePackages,
|
||||
roomCategories,
|
||||
}: RateSummaryParams) => void
|
||||
getSelectedRateSummary: () => Rate[]
|
||||
togglePriceDetailsModalOpen: () => void
|
||||
toggleSummaryOpen: () => void
|
||||
setGuestsInRooms: (index: number, adults: number, children?: Child[]) => void
|
||||
}
|
||||
|
||||
export const useRateSelectionStore = create<RateSelectionState>((set, get) => ({
|
||||
selectedRates: [],
|
||||
rateSummary: [],
|
||||
isPriceDetailsModalOpen: false,
|
||||
isSummaryOpen: false,
|
||||
guestsInRooms: [{ adults: 1 }],
|
||||
modifyRateIndex: null,
|
||||
modifyRate: (index) => set({ modifyRateIndex: index }),
|
||||
closeModifyRate: () => set({ modifyRateIndex: null }),
|
||||
selectRate: (index, rate) => {
|
||||
set((state) => {
|
||||
const newRates = [...state.selectedRates]
|
||||
newRates[index] = rate
|
||||
return {
|
||||
selectedRates: newRates,
|
||||
}
|
||||
})
|
||||
},
|
||||
initializeRates: (count) =>
|
||||
set({ selectedRates: new Array(count).fill(undefined) }),
|
||||
calculateRateSummary: (params) => {
|
||||
const { selectedRates } = get()
|
||||
|
||||
const summaries = selectedRates.map((selectedRate, roomIndex) => {
|
||||
if (!selectedRate) return null
|
||||
|
||||
return calculateRoomSummary({
|
||||
selectedRate,
|
||||
roomIndex,
|
||||
...params,
|
||||
})
|
||||
})
|
||||
|
||||
set({ rateSummary: summaries })
|
||||
},
|
||||
getSelectedRateSummary: () => {
|
||||
const { rateSummary } = get()
|
||||
return rateSummary.filter((summary): summary is Rate => summary !== null)
|
||||
},
|
||||
togglePriceDetailsModalOpen: () => {
|
||||
set((state) => ({
|
||||
isPriceDetailsModalOpen: !state.isPriceDetailsModalOpen,
|
||||
}))
|
||||
},
|
||||
|
||||
toggleSummaryOpen: () => {
|
||||
set((state) => ({
|
||||
isSummaryOpen: !state.isSummaryOpen,
|
||||
}))
|
||||
},
|
||||
setGuestsInRooms: (index, adults, children) => {
|
||||
set((state) => ({
|
||||
guestsInRooms: [
|
||||
...state.guestsInRooms.slice(0, index),
|
||||
{ adults, children },
|
||||
...state.guestsInRooms.slice(index + 1),
|
||||
],
|
||||
}))
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user