Public Access
mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-11 10:28:45 -04:00
Setting the booking tests up correctly and cleanly.
Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user