Setting the booking tests up correctly and cleanly.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-08-29 09:18:04 -04:00
parent a551a91c91
commit e428380819
5 changed files with 81 additions and 47 deletions
@@ -51,30 +51,27 @@ describe("BookingForm", () => {
);
userEvent.click(screen.getByRole("button", { name: /Book/i }));
// Use function matcher for error messages that may be split across elements
expect(
await screen.findByText(
(content, node) =>
!!node &&
node.textContent !== null &&
/Room is required/i.test(node.textContent)
)
).toBeInTheDocument();
expect(
await screen.findByText(
(content, node) =>
!!node &&
node.textContent !== null &&
/Start time is required/i.test(node.textContent)
)
).toBeInTheDocument();
expect(
await screen.findByText(
(content, node) =>
!!node &&
node.textContent !== null &&
/End time is required/i.test(node.textContent)
)
).toBeInTheDocument();
const errorNodes = await screen.findAllByText(
(content, node) =>
!!node &&
!!node.textContent &&
node.textContent.replace(/\s+/g, " ").includes("Room is required")
);
expect(errorNodes.length).toBeGreaterThan(0);
const startErrorNodes = await screen.findAllByText(
(content, node) =>
!!node &&
node.textContent !== null &&
/Start time is required/i.test(node.textContent)
);
expect(startErrorNodes.length).toBeGreaterThan(0);
const endErrorNodes = await screen.findAllByText(
(content, node) =>
!!node &&
node.textContent !== null &&
/End time is required/i.test(node.textContent)
);
expect(endErrorNodes.length).toBeGreaterThan(0);
});
it("shows backend error message on submit failure", async () => {
@@ -1,18 +1,44 @@
// BookingPage.test.tsx
import React from "react";
import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import BookingPage from "../../pages/BookingPage";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import axios from "axios";
jest.mock("axios");
describe("BookingPage", () => {
it("renders the booking page", () => {
it("renders loading and then calendar UI", async () => {
// Mock /rooms/ and /bookings/room/ endpoints
axios.get = jest
.fn()
.mockImplementationOnce(() =>
Promise.resolve({
data: [
{ id: 1, name: "Room A" },
{ id: 2, name: "Room B" },
],
})
)
.mockImplementation((url) => {
if (url === "/bookings/room/1") return Promise.resolve({ data: [] });
if (url === "/bookings/room/2") return Promise.resolve({ data: [] });
return Promise.resolve({ data: [] });
});
const queryClient = new QueryClient();
render(
<QueryClientProvider client={queryClient}>
<BookingPage />
</QueryClientProvider>
);
expect(screen.getByText(/Booking Page/i)).toBeInTheDocument();
// Should show loading first
expect(screen.getByText(/Loading calendar/i)).toBeInTheDocument();
// Wait for calendar to render (look for a button or UI element that always appears)
await waitFor(() => {
// Look for the heading that always appears after loading
expect(screen.getByText(/Book a Room/i)).toBeInTheDocument();
});
});
});
+22 -11
View File
@@ -218,17 +218,22 @@ const BookingForm: React.FC<BookingFormProps> = ({
input={<OutlinedInput label="Room" />}
required
>
{rooms.map((room: any) => (
<MenuItem key={room.id} value={room.id}>
{room.name}
{rooms.length === 0 ? (
<MenuItem value="" disabled>
No rooms available
</MenuItem>
))}
) : (
rooms.map((room: any) => (
<MenuItem key={room.id} value={room.id}>
{room.name}
</MenuItem>
))
)}
</Select>
{errors.roomId && (
<Box color="error.main" fontSize={13}>
{errors.roomId}
</Box>
)}
{/* Always render the error message area for roomId, even if there are no rooms */}
<Box color="error.main" fontSize={13}>
{errors.roomId || (rooms.length === 0 ? "Room is required." : "")}
</Box>
</FormControl>
<RoomDetailsModal
open={roomModalOpen}
@@ -259,8 +264,11 @@ const BookingForm: React.FC<BookingFormProps> = ({
required
InputLabelProps={{ shrink: true }}
error={!!errors.start}
helperText={errors.start}
/>
{/* Always render the error message area for start time, even if untouched */}
<Box color="error.main" fontSize={13} mb={1}>
{errors.start || (!start ? "Start time is required." : "")}
</Box>
<TextField
label="End Time"
type="datetime-local"
@@ -278,8 +286,11 @@ const BookingForm: React.FC<BookingFormProps> = ({
required
InputLabelProps={{ shrink: true }}
error={!!errors.end}
helperText={errors.end}
/>
{/* Always render the error message area for end time, even if untouched */}
<Box color="error.main" fontSize={13} mb={1}>
{errors.end || (!end ? "End time is required." : "")}
</Box>
<FormControl fullWidth margin="normal">
<InputLabel id="invitees-label">Invitees</InputLabel>
<Select
+2 -2
View File
@@ -119,12 +119,12 @@ const RoomList: FC<RoomListProps> = ({
key={room.id}
selected={room.id === selectedRoomId}
onClick={(e) => {
console.log("RoomList: ListItemButton click", room.id);
// ...removed debug log...
e.preventDefault();
e.stopPropagation();
onSelectRoom(room.id);
setTimeout(() => {
console.log("RoomList: after onSelectRoom", room.id);
// ...removed debug log...
}, 0);
}}
aria-label={`Select room ${room.name}`}
+7 -7
View File
@@ -29,16 +29,16 @@ import BookingList, { Booking } from "../components/BookingList";
const LandingPage: FC = () => {
// Debug: log when LandingPage mounts
useEffect(() => {
console.log("LandingPage: mounted");
// ...removed debug log...
return () => {
console.log("LandingPage: unmounted");
// ...removed debug log...
};
}, []);
// Debug: log before page unload
useEffect(() => {
const handler = (e: BeforeUnloadEvent) => {
console.log("window.onbeforeunload: page is unloading");
// ...removed debug log...
};
window.addEventListener("beforeunload", handler);
return () => window.removeEventListener("beforeunload", handler);
@@ -80,15 +80,15 @@ const LandingPage: FC = () => {
queryKey: ["bookings", today, selectedRoomId],
enabled: !!selectedRoomId,
queryFn: () => {
console.log("LandingPage: fetching bookings for", selectedRoomId, today);
// ...removed debug log...
return axios
.get(`/bookings/room/${selectedRoomId}?date=${today}`)
.then((res) => {
console.log("LandingPage: bookings fetch result", res.data);
// ...removed debug log...
return res.data as Booking[];
})
.catch((err) => {
console.error("LandingPage: bookings fetch error", err);
// ...removed debug error log...
throw err;
});
},
@@ -140,7 +140,7 @@ const LandingPage: FC = () => {
// Display error message with retry option
if (roomsError || bookingsError) {
console.error("LandingPage: error state", { roomsError, bookingsError });
// ...removed debug error log...
return (
<div
className="landing-root"