From 7790f171978cb57f18af86daf6071d4a935ff290 Mon Sep 17 00:00:00 2001 From: Cliff Hill Date: Fri, 19 Sep 2025 15:35:49 -0400 Subject: [PATCH] Getting things done. Signed-off-by: Cliff Hill --- README.md | 4 +- .../__tests__/components/BookingForm.test.tsx | 16 +++++-- .../__tests__/components/RoomSelect.test.tsx | 15 +++++- .../helpers/getRoomBookingsForDate.test.ts | 1 - frontend/src/__tests__/helpers/room.test.ts | 1 - .../src/__tests__/pages/BookingPage.test.tsx | 16 +++++-- .../__tests__/pages/ConfirmationPage.test.tsx | 46 ++++++++++++++----- .../src/__tests__/pages/LandingPage.test.tsx | 16 +++++-- frontend/src/__tests__/utils/logger.test.ts | 11 +++++ frontend/src/components/BookingForm.tsx | 9 ++-- frontend/src/components/BookingList.tsx | 4 +- frontend/src/components/CalendarView.tsx | 7 ++- frontend/src/components/RoomDetailsModal.tsx | 14 +----- frontend/src/components/RoomList.tsx | 4 +- frontend/src/components/RoomSelect.tsx | 4 +- frontend/src/pages/BookingPage.tsx | 4 +- frontend/src/pages/ConfirmationPage.tsx | 7 ++- frontend/src/pages/LandingPage.tsx | 5 +- frontend/src/setupTests.ts | 5 ++ 19 files changed, 123 insertions(+), 66 deletions(-) delete mode 100644 frontend/src/__tests__/helpers/getRoomBookingsForDate.test.ts delete mode 100644 frontend/src/__tests__/helpers/room.test.ts diff --git a/README.md b/README.md index 1dff3b0a..6990ecb6 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,8 @@ This is a full-stack application simulating an online booking system for confere ### Testing and Code Quality -- Pytest, coverage, pre-commit hooks (including prettier), and code style enforcement for backend -- Jest and React Testing Library for comprehensive frontend tests (run with `yarn run jest` and maintained in `frontend/src/__tests__/`). +- Pytest, coverage, pre-commit hooks, and code style enforcement for backend +- Jest and React Testing Library, code style enforcement and additional pre-commit hooks for frontend ### Docker Compose for Orchestration diff --git a/frontend/src/__tests__/components/BookingForm.test.tsx b/frontend/src/__tests__/components/BookingForm.test.tsx index d3816f64..61903a7e 100644 --- a/frontend/src/__tests__/components/BookingForm.test.tsx +++ b/frontend/src/__tests__/components/BookingForm.test.tsx @@ -7,7 +7,15 @@ import { UserProvider } from "../../context/UserContext"; describe("BookingForm", () => { beforeAll(() => { - class EventSourceMock { + /** + * Mock implementation of EventSource for tests. + * Implements the minimal EventSource interface. + */ + /** + * Mock implementation of EventSource for tests. + * TypeScript: 'as any' is used to bypass constructor signature requirements. + */ + class EventSourceMock implements EventSource { static readonly CONNECTING = 0; static readonly OPEN = 1; static readonly CLOSED = 2; @@ -23,9 +31,9 @@ describe("BookingForm", () => { dispatchEvent() { return true; } - onmessage = null; - onerror = null; - onopen = null; + onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null; + onerror: ((this: EventSource, ev: Event) => any) | null = null; + onopen: ((this: EventSource, ev: Event) => any) | null = null; } window.EventSource = EventSourceMock as any; }); diff --git a/frontend/src/__tests__/components/RoomSelect.test.tsx b/frontend/src/__tests__/components/RoomSelect.test.tsx index ef1b3a26..6a632694 100644 --- a/frontend/src/__tests__/components/RoomSelect.test.tsx +++ b/frontend/src/__tests__/components/RoomSelect.test.tsx @@ -1,3 +1,8 @@ +/** + * @file RoomSelect.test.tsx + * Unit tests for the RoomSelect component. + * Ensures dropdown renders and onChange is called. + */ import React from "react"; import { render, screen, fireEvent } from "@testing-library/react"; import RoomSelect from "../../components/RoomSelect"; @@ -5,8 +10,11 @@ import { RoomProvider } from "../../context/RoomContext"; describe("RoomSelect", () => { it("renders the room select dropdown", () => { + const rooms = [ + { id: "1", name: "Test Room", capacity: 2, amenities: [], price: 100 }, + ]; render( - + {}} /> ); @@ -15,8 +23,11 @@ describe("RoomSelect", () => { it("calls onChange when a room is selected", () => { const onChange = jest.fn(); + const rooms = [ + { id: "1", name: "Test Room", capacity: 2, amenities: [], price: 100 }, + ]; render( - + ); diff --git a/frontend/src/__tests__/helpers/getRoomBookingsForDate.test.ts b/frontend/src/__tests__/helpers/getRoomBookingsForDate.test.ts deleted file mode 100644 index b285a7b7..00000000 --- a/frontend/src/__tests__/helpers/getRoomBookingsForDate.test.ts +++ /dev/null @@ -1 +0,0 @@ -// getRoomBookingsForDate is not exported from helpers/getRoomBookingsForDate.ts diff --git a/frontend/src/__tests__/helpers/room.test.ts b/frontend/src/__tests__/helpers/room.test.ts deleted file mode 100644 index 8174d342..00000000 --- a/frontend/src/__tests__/helpers/room.test.ts +++ /dev/null @@ -1 +0,0 @@ -// No exported getRoomName function in helpers/room.ts diff --git a/frontend/src/__tests__/pages/BookingPage.test.tsx b/frontend/src/__tests__/pages/BookingPage.test.tsx index 72b7fdf5..5ffaaed9 100644 --- a/frontend/src/__tests__/pages/BookingPage.test.tsx +++ b/frontend/src/__tests__/pages/BookingPage.test.tsx @@ -8,7 +8,15 @@ import { MemoryRouter } from "react-router-dom"; describe("BookingPage", () => { beforeAll(() => { - class EventSourceMock { + /** + * Mock implementation of EventSource for tests. + * Implements the minimal EventSource interface. + */ + /** + * Mock implementation of EventSource for tests. + * TypeScript: 'as any' is used to bypass constructor signature requirements. + */ + class EventSourceMock implements EventSource { static readonly CONNECTING = 0; static readonly OPEN = 1; static readonly CLOSED = 2; @@ -24,9 +32,9 @@ describe("BookingPage", () => { dispatchEvent() { return true; } - onmessage = null; - onerror = null; - onopen = null; + onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null; + onerror: ((this: EventSource, ev: Event) => any) | null = null; + onopen: ((this: EventSource, ev: Event) => any) | null = null; } window.EventSource = EventSourceMock as any; }); diff --git a/frontend/src/__tests__/pages/ConfirmationPage.test.tsx b/frontend/src/__tests__/pages/ConfirmationPage.test.tsx index bce0dccf..ed98ecb2 100644 --- a/frontend/src/__tests__/pages/ConfirmationPage.test.tsx +++ b/frontend/src/__tests__/pages/ConfirmationPage.test.tsx @@ -2,11 +2,20 @@ import React from "react"; import { MemoryRouter } from "react-router-dom"; import { render, screen } from "@testing-library/react"; import ConfirmationPage from "../../pages/ConfirmationPage"; +import * as roomsApi from "../../apis/rooms"; import { BookingContext } from "../../context/BookingContext"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; describe("ConfirmationPage", () => { beforeAll(() => { - class EventSourceMock { + /** + * Mock implementation of EventSource for tests. + * Implements the minimal EventSource interface. + */ + /** + * Mock implementation of EventSource for tests. + * TypeScript: 'as any' is used to bypass constructor signature requirements. + */ + class EventSourceMock implements EventSource { static readonly CONNECTING = 0; static readonly OPEN = 1; static readonly CLOSED = 2; @@ -22,35 +31,48 @@ describe("ConfirmationPage", () => { dispatchEvent() { return true; } - onmessage = null; - onerror = null; - onopen = null; + onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null; + onerror: ((this: EventSource, ev: Event) => any) | null = null; + onopen: ((this: EventSource, ev: Event) => any) | null = null; } window.EventSource = EventSourceMock as any; }); - it("renders confirmation page heading", () => { + it("renders confirmation page heading", async () => { + // Setup required mocks and context + jest.spyOn(roomsApi, "getRooms").mockResolvedValue([ + { + id: "1", + name: "Test Room", + capacity: 2, + amenities: [], + price: 100, + }, + ]); const queryClient = new QueryClient(); - // Provide a valid booking to prevent redirect const mockBooking = { - id: "test-id", + id: "b1", roomId: "1", - date: "2025-09-18", - name: "Test User", - email: "test@example.com", + guestName: "Test Guest", + date: "2025-09-19", start_time: "10:00", end_time: "11:00", + status: "confirmed", }; render( - + ); - expect(screen.getByText(/Booking Confirmed/i)).toBeInTheDocument(); + expect(await screen.findByText(/Booking Confirmed/i)).toBeInTheDocument(); }); }); diff --git a/frontend/src/__tests__/pages/LandingPage.test.tsx b/frontend/src/__tests__/pages/LandingPage.test.tsx index 7df4885e..c3b471b6 100644 --- a/frontend/src/__tests__/pages/LandingPage.test.tsx +++ b/frontend/src/__tests__/pages/LandingPage.test.tsx @@ -9,7 +9,15 @@ import { MemoryRouter } from "react-router-dom"; describe("LandingPage", () => { beforeAll(() => { - class EventSourceMock { + /** + * Mock implementation of EventSource for tests. + * Implements the minimal EventSource interface. + */ + /** + * Mock implementation of EventSource for tests. + * TypeScript: 'as any' is used to bypass constructor signature requirements. + */ + class EventSourceMock implements EventSource { static readonly CONNECTING = 0; static readonly OPEN = 1; static readonly CLOSED = 2; @@ -25,9 +33,9 @@ describe("LandingPage", () => { dispatchEvent() { return true; } - onmessage = null; - onerror = null; - onopen = null; + onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null; + onerror: ((this: EventSource, ev: Event) => any) | null = null; + onopen: ((this: EventSource, ev: Event) => any) | null = null; } window.EventSource = EventSourceMock as any; }); diff --git a/frontend/src/__tests__/utils/logger.test.ts b/frontend/src/__tests__/utils/logger.test.ts index c5299028..22742fe0 100644 --- a/frontend/src/__tests__/utils/logger.test.ts +++ b/frontend/src/__tests__/utils/logger.test.ts @@ -1,5 +1,16 @@ +/** + * @file logger.test.ts + * Unit tests for the logger utility. + * Ensures logger methods do not throw and are properly typed. + */ import { logger } from "../../utils/logger"; + describe("logger utility", () => { + beforeAll(() => { + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "debug").mockImplementation(() => {}); + jest.spyOn(console, "error").mockImplementation(() => {}); + }); it("logs info messages", () => { expect(() => logger.info("test info")).not.toThrow(); }); diff --git a/frontend/src/components/BookingForm.tsx b/frontend/src/components/BookingForm.tsx index a4291fe5..c6613ea6 100644 --- a/frontend/src/components/BookingForm.tsx +++ b/frontend/src/components/BookingForm.tsx @@ -62,9 +62,9 @@ const BookingForm: React.FC = ({ onBookingSuccess, }) => { React.useEffect(() => { - logger.info("[BookingForm] Mounted"); + // [BookingForm] Mounted log removed return () => { - logger.info("[BookingForm] Unmounted"); + // [BookingForm] Unmounted log removed }; }, []); @@ -265,9 +265,8 @@ const BookingForm: React.FC = ({ invitees, }; // Log payload for debugging - if (window && window.console) { - console.info("[BookingForm] updateBooking payload:", updatePayload); - } + // Use logger for controlled logging + logger.info("[BookingForm] updateBooking payload:", updatePayload); bookingResult = await updateBooking(editBooking.id, updatePayload); } if (onBookingSuccess && bookingResult) onBookingSuccess(bookingResult); diff --git a/frontend/src/components/BookingList.tsx b/frontend/src/components/BookingList.tsx index fafafb62..21fb3d8e 100644 --- a/frontend/src/components/BookingList.tsx +++ b/frontend/src/components/BookingList.tsx @@ -32,9 +32,9 @@ const BookingList: React.FC = ({ bookings, onSelect }) => { const { users } = useUsers(); React.useEffect(() => { - logger.info("[BookingList] Mounted"); + // [BookingList] Mounted log removed return () => { - logger.info("[BookingList] Unmounted"); + // [BookingList] Unmounted log removed }; }, []); diff --git a/frontend/src/components/CalendarView.tsx b/frontend/src/components/CalendarView.tsx index 9adbdca1..4e520214 100644 --- a/frontend/src/components/CalendarView.tsx +++ b/frontend/src/components/CalendarView.tsx @@ -8,7 +8,6 @@ */ // Utility/helper imports -import { logger } from "../utils/logger"; // External imports import React, { useEffect } from "react"; @@ -46,14 +45,14 @@ const CalendarView: React.FC = ({ initialDate, }) => { useEffect(() => { - logger.info("[CalendarView] Mounted"); + // [CalendarView] Mounted log removed return () => { - logger.info("[CalendarView] Unmounted"); + // [CalendarView] Unmounted log removed }; }, []); useEffect(() => { - logger.debug("[CalendarView] events updated", events); + // [CalendarView] events updated log removed }, [events]); // Custom event content with tooltip const renderEventContent = (arg: { event: BookingEvent }) => { diff --git a/frontend/src/components/RoomDetailsModal.tsx b/frontend/src/components/RoomDetailsModal.tsx index 1f36b528..7911a4ef 100644 --- a/frontend/src/components/RoomDetailsModal.tsx +++ b/frontend/src/components/RoomDetailsModal.tsx @@ -8,10 +8,9 @@ */ // Utility/helper imports -import { logger } from "../utils/logger"; // External imports -import React, { useEffect } from "react"; +import React from "react"; // MUI imports import { @@ -32,16 +31,7 @@ const RoomDetailsModal: React.FC = ({ onClose, room, }) => { - useEffect(() => { - logger.info("[RoomDetailsModal] Mounted"); - return () => { - logger.info("[RoomDetailsModal] Unmounted"); - }; - }, []); - - useEffect(() => { - logger.debug("[RoomDetailsModal] room updated", room); - }, [room]); + // Lifecycle and debug logging removed if (!room) return null; return ( diff --git a/frontend/src/components/RoomList.tsx b/frontend/src/components/RoomList.tsx index b8057c5b..7c44470c 100644 --- a/frontend/src/components/RoomList.tsx +++ b/frontend/src/components/RoomList.tsx @@ -36,9 +36,9 @@ const RoomList: FC = ({ onSelectRoom, }) => { useEffect(() => { - logger.info("[RoomList] Mounted"); + // [RoomList] Mounted log removed return () => { - logger.info("[RoomList] Unmounted"); + // [RoomList] Unmounted log removed }; }, []); diff --git a/frontend/src/components/RoomSelect.tsx b/frontend/src/components/RoomSelect.tsx index d52b4853..283147aa 100644 --- a/frontend/src/components/RoomSelect.tsx +++ b/frontend/src/components/RoomSelect.tsx @@ -39,9 +39,9 @@ const RoomSelect: React.FC = ({ const roomsContext = useRooms(); const rooms = roomsContext.rooms ?? []; React.useEffect(() => { - logger.info(`[RoomSelect] Mounted with ${rooms.length} rooms.`); + // [RoomSelect] Mounted log removed return () => { - logger.info("[RoomSelect] Unmounted"); + // [RoomSelect] Unmounted log removed }; }, [rooms.length]); return ( diff --git a/frontend/src/pages/BookingPage.tsx b/frontend/src/pages/BookingPage.tsx index b5f0595e..dcf61862 100644 --- a/frontend/src/pages/BookingPage.tsx +++ b/frontend/src/pages/BookingPage.tsx @@ -62,9 +62,9 @@ const BookingPage: React.FC = () => { const [roomModalOpen, setRoomModalOpen] = useState(false); /** Track mount/unmount for analytics */ useEffect(() => { - logger.info("[BookingPage] Mounted"); + // [BookingPage] Mounted log removed return () => { - logger.info("[BookingPage] Unmounted"); + // [BookingPage] Unmounted log removed }; }, []); /** Get rooms from context (should be provided via context or props) */ diff --git a/frontend/src/pages/ConfirmationPage.tsx b/frontend/src/pages/ConfirmationPage.tsx index 890071d8..37dbdd2f 100644 --- a/frontend/src/pages/ConfirmationPage.tsx +++ b/frontend/src/pages/ConfirmationPage.tsx @@ -28,7 +28,6 @@ import { getRooms } from "../apis/rooms"; import { useBookings } from "../context/BookingContext"; // Utility/helper imports -import { logger } from "../utils/logger"; // Type-only imports import type { Booking } from "../interfaces"; @@ -54,14 +53,14 @@ const ConfirmationPage: React.FC = () => { ); React.useEffect(() => { - logger.info("[ConfirmationPage] Mounted"); + // [ConfirmationPage] Mounted log removed return () => { - logger.info("[ConfirmationPage] Unmounted"); + // [ConfirmationPage] Unmounted log removed }; }, []); React.useEffect(() => { - logger.debug("[ConfirmationPage] booking updated", booking); + // [ConfirmationPage] booking updated log removed }, [booking]); /** * Subscribe to SSE for live booking status updates. diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index c28f6a87..ab15ed7e 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -27,16 +27,15 @@ import { useRooms } from "../context/RoomContext"; import { useBookings } from "../context/BookingContext"; // Utility/helper imports -import { logger } from "../utils/logger"; const LandingPage: FC = () => { // Log when LandingPage mounts/unmounts /** * Log when LandingPage mounts/unmounts. */ useEffect(() => { - logger.info("[LandingPage] Mounted"); + // [LandingPage] Mounted log removed return () => { - logger.info("[LandingPage] Unmounted"); + // [LandingPage] Unmounted log removed }; }, []); diff --git a/frontend/src/setupTests.ts b/frontend/src/setupTests.ts index dbb492a2..e45c699c 100644 --- a/frontend/src/setupTests.ts +++ b/frontend/src/setupTests.ts @@ -10,3 +10,8 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import "@testing-library/jest-dom"; +// Suppress noisy logs in test output globally before any test or import +jest.spyOn(global.console, "info").mockImplementation(() => {}); +jest.spyOn(global.console, "warn").mockImplementation(() => {}); +jest.spyOn(global.console, "error").mockImplementation(() => {}); +jest.spyOn(global.console, "debug").mockImplementation(() => {});