Merged in monorepo-step-1 (pull request #1080)
Migrate to a monorepo setup - step 1 * Move web to subfolder /apps/scandic-web * Yarn + transitive deps - Move to yarn - design-system package removed for now since yarn doesn't support the parameter for token (ie project currently broken) - Add missing transitive dependencies as Yarn otherwise prevents these imports - VS Code doesn't pick up TS path aliases unless you open /apps/scandic-web instead of root (will be fixed with monorepo) * Pin framer-motion to temporarily fix typing issue https://github.com/adobe/react-spectrum/issues/7494 * Pin zod to avoid typ error There seems to have been a breaking change in the types returned by zod where error is now returned as undefined instead of missing in the type. We should just handle this but to avoid merge conflicts just pin the dependency for now. * Pin react-intl version Pin version of react-intl to avoid tiny type issue where formatMessage does not accept a generic any more. This will be fixed in a future commit, but to avoid merge conflicts just pin for now. * Pin typescript version Temporarily pin version as newer versions as stricter and results in a type error. Will be fixed in future commit after merge. * Setup workspaces * Add design-system as a monorepo package * Remove unused env var DESIGN_SYSTEM_ACCESS_TOKEN * Fix husky for monorepo setup * Update netlify.toml * Add lint script to root package.json * Add stub readme * Fix react-intl formatMessage types * Test netlify.toml in root * Remove root toml * Update netlify.toml publish path * Remove package-lock.json * Update build for branch/preview builds Approved-by: Linus Flood
This commit is contained in:
committed by
Linus Flood
parent
667cab6fb6
commit
80100e7631
392
apps/scandic-web/stores/enter-details/helpers.ts
Normal file
392
apps/scandic-web/stores/enter-details/helpers.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
import isEqual from "fast-deep-equal"
|
||||
|
||||
import { detailsStorageName } from "."
|
||||
|
||||
import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
||||
import type { Price } from "@/types/components/hotelReservation/price"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
import type {
|
||||
DetailsState,
|
||||
PersistedState,
|
||||
RoomState,
|
||||
RoomStatus,
|
||||
RoomStep,
|
||||
} from "@/types/stores/enter-details"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
|
||||
export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
||||
return {
|
||||
countryCode: user.address.countryCode?.toString(),
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
join: false,
|
||||
membershipNo: user.membership?.membershipNumber,
|
||||
phoneNumber: user.phoneNumber ?? "",
|
||||
}
|
||||
}
|
||||
|
||||
export function checkIsSameBedTypes(
|
||||
storedBedTypes: string,
|
||||
bedTypesData: string
|
||||
) {
|
||||
return storedBedTypes === bedTypesData
|
||||
}
|
||||
|
||||
export function checkIsSameBooking(
|
||||
prev: SelectRateSearchParams,
|
||||
next: SelectRateSearchParams
|
||||
) {
|
||||
const { rooms: prevRooms, ...prevBooking } = prev
|
||||
|
||||
const prevRoomsWithoutRateCodes = prevRooms.map(
|
||||
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
||||
)
|
||||
const { rooms: nextRooms, ...nextBooking } = next
|
||||
|
||||
const nextRoomsWithoutRateCodes = nextRooms.map(
|
||||
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
||||
)
|
||||
|
||||
return isEqual(
|
||||
{
|
||||
...prevBooking,
|
||||
rooms: prevRoomsWithoutRateCodes,
|
||||
},
|
||||
{
|
||||
...nextBooking,
|
||||
rooms: nextRoomsWithoutRateCodes,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function add(...nums: (number | string | undefined)[]) {
|
||||
return nums.reduce((total: number, num) => {
|
||||
if (typeof num === "undefined") {
|
||||
num = 0
|
||||
}
|
||||
total = total + parseInt(`${num}`)
|
||||
return total
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function subtract(...nums: (number | string | undefined)[]) {
|
||||
return nums.reduce((total: number, num, idx) => {
|
||||
if (typeof num === "undefined") {
|
||||
num = 0
|
||||
}
|
||||
if (idx === 0) {
|
||||
return parseInt(`${num}`)
|
||||
}
|
||||
total = total - parseInt(`${num}`)
|
||||
if (total < 0) {
|
||||
return 0
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function getRoomPrice(roomRate: RoomRate, isMember: boolean) {
|
||||
if (isMember && roomRate.memberRate) {
|
||||
return {
|
||||
perNight: {
|
||||
requested: roomRate.memberRate.requestedPrice && {
|
||||
currency: roomRate.memberRate.requestedPrice.currency,
|
||||
price: roomRate.memberRate.requestedPrice.pricePerNight,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.memberRate.localPrice.currency,
|
||||
price: roomRate.memberRate.localPrice.pricePerNight,
|
||||
},
|
||||
},
|
||||
perStay: {
|
||||
requested: roomRate.memberRate.requestedPrice && {
|
||||
currency: roomRate.memberRate.requestedPrice.currency,
|
||||
price: roomRate.memberRate.requestedPrice.pricePerStay,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.memberRate.localPrice.currency,
|
||||
price: roomRate.memberRate.localPrice.pricePerStay,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
perNight: {
|
||||
requested: roomRate.publicRate.requestedPrice && {
|
||||
currency: roomRate.publicRate.requestedPrice.currency,
|
||||
price: roomRate.publicRate.requestedPrice.pricePerNight,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.publicRate.localPrice.currency,
|
||||
price: roomRate.publicRate.localPrice.pricePerNight,
|
||||
},
|
||||
},
|
||||
perStay: {
|
||||
requested: roomRate.publicRate.requestedPrice && {
|
||||
currency: roomRate.publicRate.requestedPrice.currency,
|
||||
price: roomRate.publicRate.requestedPrice.pricePerStay,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.publicRate.localPrice.currency,
|
||||
price: roomRate.publicRate.localPrice.pricePerStay,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type TotalPrice = {
|
||||
requested: { currency: string; price: number } | undefined
|
||||
local: { currency: string; price: number }
|
||||
}
|
||||
|
||||
export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) {
|
||||
return roomRates.reduce<TotalPrice>(
|
||||
(total, roomRate, idx) => {
|
||||
const isFirstRoom = idx === 0
|
||||
const rate =
|
||||
isFirstRoom && isMember && roomRate.memberRate
|
||||
? roomRate.memberRate
|
||||
: roomRate.publicRate
|
||||
|
||||
return {
|
||||
requested: rate.requestedPrice
|
||||
? {
|
||||
currency: rate.requestedPrice.currency,
|
||||
price: add(
|
||||
total.requested?.price ?? 0,
|
||||
rate.requestedPrice.pricePerStay
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
local: {
|
||||
currency: rate.localPrice.currency,
|
||||
price: add(total.local.price ?? 0, rate.localPrice.pricePerStay),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
requested: undefined,
|
||||
local: {
|
||||
currency: roomRates[0].publicRate.localPrice.currency,
|
||||
price: 0,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function calcTotalPrice(
|
||||
rooms: RoomState[],
|
||||
totalPrice: Price,
|
||||
isMember: boolean
|
||||
) {
|
||||
return rooms.reduce<Price>(
|
||||
(acc, room, index) => {
|
||||
const isFirstRoomAndMember = index === 0 && isMember
|
||||
const join = Boolean(room.guest.join || room.guest.membershipNo)
|
||||
|
||||
const roomPrice = getRoomPrice(
|
||||
room.roomRate,
|
||||
isFirstRoomAndMember || join
|
||||
)
|
||||
|
||||
const breakfastRequestedPrice = room.breakfast
|
||||
? room.breakfast.requestedPrice?.totalPrice ?? 0
|
||||
: 0
|
||||
const breakfastLocalPrice = room.breakfast
|
||||
? room.breakfast.localPrice?.totalPrice ?? 0
|
||||
: 0
|
||||
|
||||
const roomFeaturesTotal = room.roomFeatures?.reduce((total, pkg) => {
|
||||
if (pkg.requestedPrice.totalPrice) {
|
||||
total = add(total, pkg.requestedPrice.totalPrice)
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
|
||||
const result: Price = {
|
||||
requested: roomPrice.perStay.requested
|
||||
? {
|
||||
currency: roomPrice.perStay.requested.currency,
|
||||
price: add(
|
||||
acc.requested?.price ?? 0,
|
||||
roomPrice.perStay.requested.price,
|
||||
breakfastRequestedPrice
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
local: {
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
price: add(
|
||||
acc.local.price,
|
||||
roomPrice.perStay.local.price,
|
||||
breakfastLocalPrice,
|
||||
roomFeaturesTotal
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
{
|
||||
requested: undefined,
|
||||
local: { currency: totalPrice.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 const selectNextStep = (roomStatus: RoomStatus) => {
|
||||
if (roomStatus.currentStep === null) {
|
||||
throw new Error("getNextStep: currentStep is null")
|
||||
}
|
||||
|
||||
if (!roomStatus.steps[roomStatus.currentStep]?.isValid) {
|
||||
return roomStatus.currentStep
|
||||
}
|
||||
|
||||
const stepsArray = Object.values(roomStatus.steps)
|
||||
const currentIndex = stepsArray.findIndex(
|
||||
(step) => step?.step === roomStatus.currentStep
|
||||
)
|
||||
if (currentIndex === stepsArray.length - 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nextInvalidStep = stepsArray
|
||||
.slice(currentIndex + 1)
|
||||
.find((step) => !step.isValid)
|
||||
|
||||
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)
|
||||
return Object.values(steps)
|
||||
.filter(Boolean)
|
||||
.every((step) => step.isValid)
|
||||
}
|
||||
|
||||
export function handleStepProgression(state: DetailsState) {
|
||||
const isAllRoomsCompleted = checkBookingProgress(state)
|
||||
if (isAllRoomsCompleted) {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
roomStatus.currentStep = null
|
||||
state.bookingProgress.canProceedToPayment = true
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export function readFromSessionStorage(): PersistedState | undefined {
|
||||
if (typeof window === "undefined") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const storedData = sessionStorage.getItem(detailsStorageName)
|
||||
if (!storedData) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const parsedData = JSON.parse(storedData) as PersistedState
|
||||
|
||||
if (
|
||||
!parsedData.booking ||
|
||||
!parsedData.rooms ||
|
||||
!parsedData.bookingProgress
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return parsedData
|
||||
} catch (error) {
|
||||
console.error("Error reading from session storage:", error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function writeToSessionStorage(state: PersistedState) {
|
||||
if (typeof window === "undefined") {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
sessionStorage.setItem(detailsStorageName, JSON.stringify(state))
|
||||
} catch (error) {
|
||||
console.error("Error writing to session storage:", error)
|
||||
}
|
||||
}
|
||||
|
||||
export function clearSessionStorage() {
|
||||
if (typeof window === "undefined") {
|
||||
return
|
||||
}
|
||||
sessionStorage.removeItem(detailsStorageName)
|
||||
}
|
||||
376
apps/scandic-web/stores/enter-details/index.ts
Normal file
376
apps/scandic-web/stores/enter-details/index.ts
Normal file
@@ -0,0 +1,376 @@
|
||||
import deepmerge from "deepmerge"
|
||||
import { produce } from "immer"
|
||||
import { useContext } from "react"
|
||||
import { create, useStore } from "zustand"
|
||||
|
||||
import { DetailsContext } from "@/contexts/Details"
|
||||
|
||||
import {
|
||||
add,
|
||||
calcTotalPrice,
|
||||
checkRoomProgress,
|
||||
extractGuestFromUser,
|
||||
getRoomPrice,
|
||||
getTotalPrice,
|
||||
handleStepProgression,
|
||||
selectPreviousSteps,
|
||||
selectRoom,
|
||||
selectRoomStatus,
|
||||
writeToSessionStorage,
|
||||
} from "./helpers"
|
||||
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
import type {
|
||||
DetailsState,
|
||||
InitialState,
|
||||
RoomState,
|
||||
RoomStatus,
|
||||
RoomStep,
|
||||
} from "@/types/stores/enter-details"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
|
||||
const defaultGuestState = {
|
||||
countryCode: "",
|
||||
dateOfBirth: "",
|
||||
email: "",
|
||||
firstName: "",
|
||||
join: false,
|
||||
lastName: "",
|
||||
membershipNo: "",
|
||||
phoneNumber: "",
|
||||
zipCode: "",
|
||||
}
|
||||
|
||||
export const detailsStorageName = "rooms-details-storage"
|
||||
|
||||
export function createDetailsStore(
|
||||
initialState: InitialState,
|
||||
searchParams: string,
|
||||
user: SafeUser
|
||||
) {
|
||||
const isMember = !!user
|
||||
|
||||
const initialTotalPrice = getTotalPrice(
|
||||
initialState.rooms.map((r) => r.roomRate),
|
||||
isMember
|
||||
)
|
||||
|
||||
initialState.rooms.forEach((room) => {
|
||||
if (room.roomFeatures) {
|
||||
room.roomFeatures.forEach((pkg) => {
|
||||
if (initialTotalPrice.requested) {
|
||||
initialTotalPrice.requested.price = add(
|
||||
initialTotalPrice.requested.price,
|
||||
pkg.requestedPrice.totalPrice
|
||||
)
|
||||
}
|
||||
initialTotalPrice.local.price = add(
|
||||
initialTotalPrice.local.price,
|
||||
pkg.localPrice.totalPrice
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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,
|
||||
isValid: !!room.bedType,
|
||||
},
|
||||
[StepEnum.breakfast]: {
|
||||
step: StepEnum.breakfast,
|
||||
isValid: false,
|
||||
},
|
||||
[StepEnum.details]: {
|
||||
step: StepEnum.details,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
if (initialState.breakfast === false) {
|
||||
delete steps[StepEnum.breakfast]
|
||||
}
|
||||
|
||||
const currentStep =
|
||||
idx === 0
|
||||
? Object.values(steps).find((step) => !step.isValid)?.step ??
|
||||
StepEnum.selectBed
|
||||
: null
|
||||
|
||||
return {
|
||||
isComplete: false,
|
||||
currentStep: currentStep,
|
||||
lastCompletedStep: undefined,
|
||||
steps,
|
||||
}
|
||||
})
|
||||
|
||||
return create<DetailsState>()((set, get) => ({
|
||||
searchParamString: searchParams,
|
||||
booking: initialState.booking,
|
||||
breakfast:
|
||||
initialState.breakfast === false ? initialState.breakfast : undefined,
|
||||
isSubmittingDisabled: false,
|
||||
isSummaryOpen: false,
|
||||
isPriceDetailsModalOpen: false,
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
setIsSubmittingDisabled(isSubmittingDisabled) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.isSubmittingDisabled = isSubmittingDisabled
|
||||
})
|
||||
)
|
||||
},
|
||||
setTotalPrice(totalPrice) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.totalPrice.requested = totalPrice.requested
|
||||
state.totalPrice.local = totalPrice.local
|
||||
})
|
||||
)
|
||||
},
|
||||
toggleSummaryOpen() {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.isSummaryOpen = !state.isSummaryOpen
|
||||
})
|
||||
)
|
||||
},
|
||||
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
|
||||
|
||||
const room = selectRoom(state)
|
||||
room.bedType = bedType
|
||||
|
||||
handleStepProgression(state)
|
||||
|
||||
writeToSessionStorage({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
bookingProgress: state.bookingProgress,
|
||||
})
|
||||
})
|
||||
)
|
||||
},
|
||||
updateBreakfast(breakfast) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
if (roomStatus.steps[StepEnum.breakfast]) {
|
||||
roomStatus.steps[StepEnum.breakfast].isValid = true
|
||||
}
|
||||
|
||||
const stateTotalRequestedPrice =
|
||||
state.totalPrice.requested?.price || 0
|
||||
|
||||
const stateTotalLocalPrice = state.totalPrice.local.price
|
||||
|
||||
const addToTotalPrice =
|
||||
(state.breakfast === undefined || state.breakfast === false) &&
|
||||
!!breakfast
|
||||
|
||||
const subtractFromTotalPrice =
|
||||
(state.breakfast === undefined || state.breakfast) &&
|
||||
breakfast === false
|
||||
|
||||
if (addToTotalPrice) {
|
||||
const breakfastTotalRequestedPrice = parseInt(
|
||||
breakfast.requestedPrice.totalPrice
|
||||
)
|
||||
const breakfastTotalPrice = parseInt(
|
||||
breakfast.localPrice.totalPrice
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const room = selectRoom(state)
|
||||
room.breakfast = breakfast
|
||||
|
||||
handleStepProgression(state)
|
||||
|
||||
writeToSessionStorage({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
bookingProgress: state.bookingProgress,
|
||||
})
|
||||
})
|
||||
)
|
||||
},
|
||||
updateDetails(data) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
const roomStatus = selectRoomStatus(state)
|
||||
roomStatus.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
|
||||
|
||||
if (data.join) {
|
||||
room.guest.membershipNo = undefined
|
||||
} else {
|
||||
room.guest.membershipNo = data.membershipNo
|
||||
}
|
||||
room.guest.phoneNumber = data.phoneNumber
|
||||
room.guest.zipCode = data.zipCode
|
||||
|
||||
room.roomPrice = getRoomPrice(
|
||||
room.roomRate,
|
||||
Boolean(data.join || data.membershipNo || isMember)
|
||||
)
|
||||
|
||||
state.totalPrice = calcTotalPrice(
|
||||
state.rooms,
|
||||
state.totalPrice,
|
||||
isMember
|
||||
)
|
||||
|
||||
const isAllStepsCompleted = checkRoomProgress(state)
|
||||
if (isAllStepsCompleted) {
|
||||
roomStatus.isComplete = true
|
||||
}
|
||||
|
||||
handleStepProgression(state)
|
||||
|
||||
writeToSessionStorage({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
bookingProgress: state.bookingProgress,
|
||||
})
|
||||
})
|
||||
)
|
||||
},
|
||||
updateSeachParamString(searchParamString) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.searchParamString = searchParamString
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export function useEnterDetailsStore<T>(selector: (store: DetailsState) => T) {
|
||||
const store = useContext(DetailsContext)
|
||||
|
||||
if (!store) {
|
||||
throw new Error("useEnterDetailsStore must be used within DetailsProvider")
|
||||
}
|
||||
|
||||
return useStore(store, selector)
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user