Merged in feat/enter-details-multiroom (pull request #1280)
feat(SW-1259): Enter details multiroom * refactor: remove per-step URLs * WIP: map multiroom data * fix: lint errors in details page * fix: made useEnterDetailsStore tests pass * fix: WIP refactor enter details store * fix: WIP enter details store update * fix: added room index to select correct room * fix: added logic for navigating between steps and rooms * fix: update summary to work with store changes * fix: added room and total price calculation * fix: removed unused code and added test for breakfast included * refactor: move store selectors into helpers * refactor: session storage state for multiroom booking * feat: update enter details accordion navigation * fix: added room index to each form component so they select correct room * fix: added unique id to input to handle case when multiple inputs have same name * fix: update payment step with store changes * fix: rebase issues * fix: now you should only be able to go to a step if previous room is completed * refactor: cleanup * fix: if no availability just skip that room for now * fix: add select-rate Summary and adjust typings Approved-by: Arvid Norlin
This commit is contained in:
committed by
Arvid Norlin
parent
f43ee4a0e6
commit
b394d54c3f
@@ -8,11 +8,14 @@ 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 { StepEnum } from "@/types/enums/step"
|
||||
@@ -31,22 +34,60 @@ jest.mock("@/lib/api", () => ({
|
||||
fetchRetry: jest.fn((fn) => fn),
|
||||
}))
|
||||
|
||||
function Wrapper({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<EnterDetailsProvider
|
||||
bedTypes={[bedType.king, bedType.queen]}
|
||||
booking={booking}
|
||||
showBreakfastStep={true}
|
||||
packages={null}
|
||||
roomRate={roomRate}
|
||||
searchParamsStr=""
|
||||
step={StepEnum.selectBed}
|
||||
user={null}
|
||||
vat={0}
|
||||
>
|
||||
{children}
|
||||
</EnterDetailsProvider>
|
||||
)
|
||||
interface CreateWrapperParams {
|
||||
showBreakfastStep?: boolean
|
||||
breakfastIncluded?: boolean
|
||||
mustBeGuaranteed?: boolean
|
||||
onlyOneBedType?: boolean
|
||||
}
|
||||
|
||||
function createWrapper(params: Partial<CreateWrapperParams> = {}) {
|
||||
const {
|
||||
showBreakfastStep = true,
|
||||
breakfastIncluded = false,
|
||||
mustBeGuaranteed = false,
|
||||
onlyOneBedType = false,
|
||||
} = params
|
||||
|
||||
return function Wrapper({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<EnterDetailsProvider
|
||||
booking={booking}
|
||||
showBreakfastStep={showBreakfastStep}
|
||||
roomsData={[
|
||||
{
|
||||
bedTypes: onlyOneBedType
|
||||
? [bedType.king]
|
||||
: [bedType.king, bedType.queen],
|
||||
packages: null,
|
||||
mustBeGuaranteed,
|
||||
breakfastIncluded,
|
||||
cancellationText: "",
|
||||
rateDetails: [],
|
||||
roomType: "Standard",
|
||||
roomRate: roomRate,
|
||||
},
|
||||
{
|
||||
bedTypes: onlyOneBedType
|
||||
? [bedType.king]
|
||||
: [bedType.king, bedType.queen],
|
||||
packages: null,
|
||||
mustBeGuaranteed,
|
||||
breakfastIncluded,
|
||||
cancellationText: "",
|
||||
rateDetails: [],
|
||||
roomType: "Standard",
|
||||
roomRate: roomRate,
|
||||
},
|
||||
]}
|
||||
searchParamsStr=""
|
||||
user={null}
|
||||
vat={0}
|
||||
>
|
||||
{children}
|
||||
</EnterDetailsProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
describe("Enter Details Store", () => {
|
||||
@@ -58,27 +99,84 @@ describe("Enter Details Store", () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
const state = result.current
|
||||
|
||||
expect(state.currentStep).toBe(StepEnum.selectBed)
|
||||
expect(state.booking).toEqual(booking)
|
||||
expect(state.bedType).toEqual(undefined)
|
||||
expect(state.breakfast).toEqual(undefined)
|
||||
expect(Object.values(state.guest).every((value) => value === ""))
|
||||
|
||||
// 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 sessionStorage", async () => {
|
||||
test("initialize with correct values from session storage", () => {
|
||||
const storage: PersistedState = {
|
||||
bedType: {
|
||||
roomTypeCode: bedType.queen.value,
|
||||
description: bedType.queen.description,
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
breakfast: breakfastPackage,
|
||||
booking,
|
||||
guest: guestDetailsNonMember,
|
||||
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))
|
||||
@@ -86,26 +184,57 @@ describe("Enter Details Store", () => {
|
||||
const { result } = renderHook(
|
||||
() => useEnterDetailsStore((state) => state),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
const state = result.current
|
||||
|
||||
expect(state.bedType).toEqual(storage.bedType)
|
||||
expect(state.guest).toEqual(storage.guest)
|
||||
expect(state.booking).toEqual(storage.booking)
|
||||
expect(state.breakfast).toEqual(storage.breakfast)
|
||||
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: Wrapper,
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
expect(result.current.currentStep).toEqual(StepEnum.selectBed)
|
||||
// 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({
|
||||
@@ -114,24 +243,221 @@ describe("Enter Details Store", () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(result.current.isValid[StepEnum.selectBed]).toEqual(true)
|
||||
expect(result.current.currentStep).toEqual(StepEnum.breakfast)
|
||||
expect(window.location.pathname.slice(1)).toBe(StepEnum.breakfast)
|
||||
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)
|
||||
})
|
||||
|
||||
expect(result.current.isValid[StepEnum.breakfast]).toEqual(true)
|
||||
expect(result.current.currentStep).toEqual(StepEnum.details)
|
||||
expect(window.location.pathname.slice(1)).toBe(StepEnum.details)
|
||||
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.isValid[StepEnum.details]).toEqual(true)
|
||||
expect(result.current.currentStep).toEqual(StepEnum.payment)
|
||||
expect(window.location.pathname.slice(1)).toBe(StepEnum.payment)
|
||||
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("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)
|
||||
})
|
||||
|
||||
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({ onlyOneBedType: true }),
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user