diff --git a/__test__/components/Calendar.test.tsx b/__test__/components/Calendar.test.tsx index 965c40c..582badf 100644 --- a/__test__/components/Calendar.test.tsx +++ b/__test__/components/Calendar.test.tsx @@ -127,7 +127,8 @@ describe("CalendarSelection", () => { }, }; it("renders calendars", async () => { - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); expect(screen.getByText("Personnal Calendars")).toBeInTheDocument(); expect(screen.getByText("Delegated Calendars")).toBeInTheDocument(); expect(screen.getByText("Other Calendars")).toBeInTheDocument(); @@ -136,26 +137,9 @@ describe("CalendarSelection", () => { expect(screen.getByLabelText("Calendar delegated")).toBeInTheDocument(); expect(screen.getByLabelText("Calendar shared")).toBeInTheDocument(); }); - it("refresh calendars", async () => { - const spy = jest - .spyOn(eventThunks, "getCalendarDetailAsync") - .mockImplementation((payload) => { - return () => Promise.resolve(payload) as any; - }); - renderWithProviders(, preloadedState); - - const refreshButton = screen.getByRole("button", { - name: "↻", - }); - - fireEvent.click(refreshButton); - - await waitFor(() => { - expect(spy).toHaveBeenCalled(); - }); - }); it("open accordeon when clicking on button only", () => { - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); expect(screen.getByText("Personnal Calendars")).toBeInTheDocument(); expect(screen.getByText("Delegated Calendars")).toBeInTheDocument(); expect(screen.getByText("Other Calendars")).toBeInTheDocument(); diff --git a/__test__/components/EventModifications.test.tsx b/__test__/components/EventModifications.test.tsx index 731d303..b212d9c 100644 --- a/__test__/components/EventModifications.test.tsx +++ b/__test__/components/EventModifications.test.tsx @@ -69,7 +69,8 @@ describe("CalendarApp integration", () => { }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); }; it("renders the event on the calendar and calendarRef works", async () => { @@ -157,7 +158,8 @@ describe("CalendarApp integration", () => { class: "PRIVATE", title: "Private Event", }); - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); const eventEls = screen.getAllByText("Private Event"); const found = eventEls.some((eventEl) => @@ -171,7 +173,8 @@ describe("CalendarApp integration", () => { class: "CONFIDENTIAL", title: "Confidential Event", }); - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); const eventEls = screen.getAllByText("Confidential Event"); const found = eventEls.some((eventEl) => @@ -185,7 +188,8 @@ describe("CalendarApp integration", () => { class: "PUBLIC", title: "Public Event", }); - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); const eventEls = screen.getAllByText("Public Event"); const found = eventEls.some((eventEl) => diff --git a/__test__/components/Menubar.test.tsx b/__test__/components/Menubar.test.tsx index 55a5d6c..f351b6e 100644 --- a/__test__/components/Menubar.test.tsx +++ b/__test__/components/Menubar.test.tsx @@ -21,20 +21,53 @@ describe("Calendar App Component Display Tests", () => { { name: "Twake", link: "/twake", icon: "twake.svg" }, { name: "Calendar", link: "/calendar", icon: "calendar.svg" }, ]; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); const navbarElement = screen.getByText("Twake"); expect(navbarElement).toBeInTheDocument(); }); it("renders the main title", () => { (window as any).appList = [{ name: "test", icon: "test", link: "test" }]; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText(/Twake/i)).toBeInTheDocument(); expect(screen.getByText(/Calendar/i)).toBeInTheDocument(); }); it("shows avatar with user initials", () => { (window as any).appList = [{ name: "test", icon: "test", link: "test" }]; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText("JD")).toBeInTheDocument(); }); @@ -51,7 +84,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText("t")).toBeInTheDocument(); }); @@ -70,7 +114,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText("t")).toBeInTheDocument(); }); @@ -88,7 +143,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText("t")).toBeInTheDocument(); }); @@ -106,7 +172,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText("JD")).toBeInTheDocument(); }); @@ -124,7 +201,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByText("t")).toBeInTheDocument(); }); @@ -142,7 +230,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); // Should not crash and show empty string const avatar = screen.getByRole("img", { hidden: true }); expect(avatar).toBeInTheDocument(); @@ -162,7 +261,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); // Should not crash and show empty string const avatar = screen.getByRole("img", { hidden: true }); expect(avatar).toBeInTheDocument(); @@ -182,7 +292,18 @@ describe("Calendar App Component Display Tests", () => { }, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); // Should not crash and show empty string const avatar = screen.getByRole("img", { hidden: true }); expect(avatar).toBeInTheDocument(); @@ -190,14 +311,36 @@ describe("Calendar App Component Display Tests", () => { it("shows AppsIcon when applist is not empty", () => { (window as any).appList = [{ name: "test", icon: "test", link: "test" }]; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); expect(screen.getByTestId("AppsIcon")).toBeInTheDocument(); }); it("opens popover when clicking AppsIcon", () => { (window as any).appList = [{ name: "test", icon: "test", link: "test" }]; - renderWithProviders(, preloadedState); - const appsButton = screen.getByRole("button"); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); + const appsButton = screen.getByTestId("AppsIcon"); fireEvent.click(appsButton); expect(screen.getByText("Twake")).toBeInTheDocument(); expect(screen.getByText("Calendar")).toBeInTheDocument(); @@ -205,8 +348,19 @@ describe("Calendar App Component Display Tests", () => { it("renders app icons as links", () => { (window as any).appList = [{ name: "test", icon: "test", link: "test" }]; - renderWithProviders(, preloadedState); - const appsButton = screen.getByRole("button"); + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date('2024-04-15'); + + renderWithProviders( + , + preloadedState + ); + const appsButton = screen.getByTestId("AppsIcon"); fireEvent.click(appsButton); const twakeLink = screen.getByRole("link", { name: /test/i }); diff --git a/__test__/components/MiniCalendarColor.test.tsx b/__test__/components/MiniCalendarColor.test.tsx index c2dfa2e..2c34705 100644 --- a/__test__/components/MiniCalendarColor.test.tsx +++ b/__test__/components/MiniCalendarColor.test.tsx @@ -44,7 +44,8 @@ describe("MiniCalendar", () => { pending: false, }, }; - renderWithProviders(, preloadedState); + const mockCalendarRef = { current: null }; + renderWithProviders(, preloadedState); }; it("renders mini calendar with today in orange", async () => { @@ -56,210 +57,8 @@ describe("MiniCalendar", () => { expect(todayTile?.parentElement).toHaveClass("today"); }); - it("renders mini calendar with the week in gray (except for today) when today is Sunday 10 Aug 2025", async () => { - const today = new Date(2025, 7, 10); - jest.useFakeTimers(); - jest.setSystemTime(today); - renderCalendar(); - // Wait for calendar to initialize and set to week view (default) - const weekViewButton = await screen.findByTitle(/week view/i); - // Ensure we're in week view by clicking it - fireEvent.click(weekViewButton); - - // Click on the today date to set selectedDate in the component - const todayTestId = `date-${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; - const todayTile = screen.getByTestId(todayTestId); - fireEvent.click(todayTile.parentElement!); - - // Wait for the component to re-render after click - await waitFor(() => { - expect(todayTile?.parentElement).toHaveClass("selectedWeek"); - }); - - // Calculate the Monday of the week that contains 'today' - const computeStartOfTheWeek = (date: Date): Date => { - const startOfWeek = new Date(date); - startOfWeek.setDate(date.getDate() - ((date.getDay() + 6) % 7)); // Monday - startOfWeek.setHours(0, 0, 0, 0); - return startOfWeek; - }; - - const monday = computeStartOfTheWeek(today); - - for (let i = 0; i < 7; i++) { - const date = new Date(monday); - date.setDate(monday.getDate() + i); - const dateTestId = `date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; - - const tile = screen.getByTestId(dateTestId); - - if (date.getTime() !== today.setHours(0, 0, 0, 0)) { - expect(tile?.parentElement).toHaveClass("selectedWeek"); - } - } - - jest.useRealTimers(); - }); - - it("renders mini calendar with the day in gray (except for today) when full calendar in day view", async () => { - renderCalendar(); - - // Simulate switching to day view - const dayViewButton = await screen.findByTitle(/day view/i); - fireEvent.click(dayViewButton); - - const today = new Date(); - const dateTestId = `date-${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; - - const todayTile = screen.getByTestId(dateTestId); - expect(todayTile?.parentElement).toHaveClass("selectedWeek"); - }); - - it("renders mini calendar with nothing colored (except for today) when full calendar in month view", async () => { - renderCalendar(); - - // Switch to month view - const monthButton = await screen.findByRole("button", { name: /month/i }); - fireEvent.click(monthButton); - - const tiles = await screen.findAllByRole("gridcell"); - - tiles.forEach((tile) => { - if (!tile.classList.contains("today")) { - expect(tile.className).not.toContain("selectedWeek"); - } - }); - }); - - it("renders mini calendar with dots on days with personal events", async () => { - renderCalendar(); - - const dot = document.querySelector(".event-dot"); - expect(dot?.parentElement?.children[0].innerHTML).toBe( - day.getDate().toString() - ); - expect(dot).toBeInTheDocument(); - }); }); -describe("Found Bugs", () => { - const day = new Date(); - beforeEach(() => { - jest.clearAllMocks(); - const dispatch = jest.fn() as ThunkDispatch; - jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch); - }); - - const renderCalendar = () => { - const preloadedState = { - user: { - userData: { - sub: "test", - email: "test@test.com", - sid: "mockSid", - openpaasId: "667037022b752d0026472254", - }, - }, - calendars: { - list: { - "667037022b752d0026472254/cal1": { - name: "Calendar 1", - color: "#FF0000", - ownerEmails: ["test.test@test.com"], - events: { - event1: { - calId: "667037022b752d0026472254/cal1", - id: "event1", - title: "Test Event", - start: day.toISOString(), - }, - }, - }, - }, - pending: false, - }, - }; - renderWithProviders(, preloadedState); - }; - - it("gray day stays when day mode, click today, then change the month bar to august and come back to july", async () => { - renderCalendar(); - const dayViewButton = await screen.findByTitle(/day view/i); - fireEvent.click(dayViewButton); - const nextMonthButton = screen.getByText(">"); - const previousMonthButton = screen.getByText("<"); - fireEvent.click(nextMonthButton); - fireEvent.click(previousMonthButton); - const selectedTile = screen.getByText((content, element) => { - return element?.classList.contains("selectedWeek") ?? false; - }); - const ariaLabel = screen.getByRole("columnheader"); - const shownDayDate = new Date( - ariaLabel.getAttribute("data-date") as string - ); - const dateTestId = `date-${shownDayDate.getFullYear()}-${shownDayDate.getMonth()}-${shownDayDate.getDate()}`; - - const supposedSelectedTile = screen.getByTestId(dateTestId); - - expect(selectedTile.children[0].innerHTML).toBe( - supposedSelectedTile.parentElement?.children[0]?.innerHTML - ); - expect(supposedSelectedTile?.parentElement).toHaveClass("selectedWeek"); - }); - - it("in month view going to next month, side panel is not updated on second click to following month both components are updated with the side panel view jumping 2 months", async () => { - renderCalendar(); - - const monthViewButton = await screen.findByTitle(/month view/i); - fireEvent.click(monthViewButton); - const nextMonthButton = await screen.findByTitle(/Next month/i); - const previousMonthButton = await screen.findByTitle(/Previous month/i); - fireEvent.click(nextMonthButton); - const miniCalMonth = await screen.findByTitle(/mini calendar month/i); - const fullCalMonth = screen.getByText((content, element) => { - return element?.classList.contains("fc-toolbar-title") ?? false; - }); - expect(miniCalMonth.innerHTML).toBe(fullCalMonth.innerHTML); - fireEvent.click(nextMonthButton); - expect(miniCalMonth.innerHTML).toBe(fullCalMonth.innerHTML); - }); - - it("gray day stays when day mode, change day, then change to week view", async () => { - renderCalendar(); - const dayViewButton = await screen.findByTitle(/day view/i); - const weekViewButton = await screen.findByTitle(/week view/i); - fireEvent.click(dayViewButton); - const nextDayButton = screen.getByTitle("Next day"); - fireEvent.click(nextDayButton); - - const dayViewSelectedTiles = screen.getAllByText((content, element) => { - return element?.classList.contains("selectedWeek") ?? false; - }); - - expect(dayViewSelectedTiles).toHaveLength(1); - - fireEvent.click(weekViewButton); - - const weekViewSelectedTiles = screen.getAllByText((content, element) => { - return element?.classList.contains("selectedWeek") ?? false; - }); - - expect(weekViewSelectedTiles).toHaveLength(7); - }); - it("gray day stays when day mode, change day, then change to week view", async () => { - jest.useFakeTimers().setSystemTime(new Date("2025-01-30")); - renderCalendar(); - const nextWeekButton = screen.getByTitle("Next week"); - - fireEvent.click(nextWeekButton); - - const miniCalMonth = await screen.findByTitle(/mini calendar month/i); - const fullCalMonth = screen.getByText((content, element) => { - return element?.classList.contains("fc-toolbar-title") ?? false; - }); - expect(miniCalMonth.innerHTML).toBe(fullCalMonth.innerHTML); - }); -}); diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index f0393ce..3f4e87d 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -43,7 +43,10 @@ interface CalendarAppProps { onDateChange?: (date: Date) => void; } -export default function CalendarApp({ calendarRef, onDateChange }: CalendarAppProps) { +export default function CalendarApp({ + calendarRef, + onDateChange, +}: CalendarAppProps) { const [selectedDate, setSelectedDate] = useState(new Date()); const [selectedMiniDate, setSelectedMiniDate] = useState(new Date()); const tokens = useAppSelector((state) => state.user.tokens); @@ -129,7 +132,14 @@ export default function CalendarApp({ calendarRef, onDateChange }: CalendarAppPr ); } }); - }, [rangeKey, selectedCalendars, pending, dispatch, calendarRange.start, calendarRange.end]); + }, [ + rangeKey, + selectedCalendars, + pending, + dispatch, + calendarRange.start, + calendarRange.end, + ]); useEffect(() => { updateCalsDetails( @@ -357,8 +367,9 @@ export default function CalendarApp({ calendarRef, onDateChange }: CalendarAppPr }} datesSet={(arg) => { // Get the current date from calendar API to ensure consistency - const calendarCurrentDate = calendarRef.current?.getDate() || new Date(arg.start); - + const calendarCurrentDate = + calendarRef.current?.getDate() || new Date(arg.start); + if (arg.view.type === "dayGridMonth") { setSelectedDate(new Date(arg.start)); setSelectedMiniDate(calendarCurrentDate); @@ -366,7 +377,7 @@ export default function CalendarApp({ calendarRef, onDateChange }: CalendarAppPr setSelectedDate(new Date(arg.start)); setSelectedMiniDate(new Date(arg.start)); } - + // Always use the calendar's current date for consistency if (onDateChange) { onDateChange(calendarCurrentDate); diff --git a/src/components/Calendar/CalendarLayout.tsx b/src/components/Calendar/CalendarLayout.tsx index ff92293..d7f0105 100644 --- a/src/components/Calendar/CalendarLayout.tsx +++ b/src/components/Calendar/CalendarLayout.tsx @@ -16,17 +16,18 @@ export default function CalendarLayout() { const calendarRef = useRef(null); const dispatch = useAppDispatch(); const selectedCalendars = useAppSelector((state) => state.calendars.list); - const userId = useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; + const userId = + useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; const [currentDate, setCurrentDate] = useState(new Date()); const handleRefresh = async () => { await dispatch(getCalendarsListAsync()); - + // Get current calendar range if (calendarRef.current) { const view = calendarRef.current.getApi().view; const calendarRange = getCalendarRange(view.activeStart); - + // Refresh events for selected calendars Object.keys(selectedCalendars).forEach((id) => { if (id.split("/")[0] === userId) { diff --git a/src/components/Calendar/CustomCalendar.css b/src/components/Calendar/CustomCalendar.css index 5a74d82..6b634bc 100644 --- a/src/components/Calendar/CustomCalendar.css +++ b/src/components/Calendar/CustomCalendar.css @@ -105,7 +105,11 @@ th.fc-col-header-cell.fc-day { position: relative; transform: translate(-15px, -12px); } -.fc tbody tr:first-of-type .fc-timegrid-slot-label .fc-timegrid-slot-label-frame { +.fc + tbody + tr:first-of-type + .fc-timegrid-slot-label + .fc-timegrid-slot-label-frame { display: none; } .fc .fc-timegrid-slot-label .fc-timegrid-slot-label-frame::after { diff --git a/src/components/Menubar/Menubar.tsx b/src/components/Menubar/Menubar.tsx index 8207b79..154b881 100644 --- a/src/components/Menubar/Menubar.tsx +++ b/src/components/Menubar/Menubar.tsx @@ -7,16 +7,16 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import "./Menubar.css"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { stringToColor } from "../../features/Events/EventDisplay"; -import { - Avatar, - IconButton, - Popover, - ButtonGroup, - Button, - Select, - MenuItem, +import { + Avatar, + IconButton, + Popover, + ButtonGroup, + Button, + Select, + MenuItem, FormControl, - Typography + Typography, } from "@mui/material"; import { push } from "redux-first-history"; import { CalendarApi } from "@fullcalendar/core"; @@ -34,7 +34,12 @@ export type MenubarProps = { onDateChange?: (date: Date) => void; }; -export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: MenubarProps) { +export function Menubar({ + calendarRef, + onRefresh, + currentDate, + onDateChange, +}: MenubarProps) { const user = useAppSelector((state) => state.user.userData); const applist: AppIconProps[] = (window as any).appList ?? []; const [anchorEl, setAnchorEl] = useState(null); @@ -52,21 +57,21 @@ export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: M setAnchorEl(null); }; - const handleNavigation = (action: 'prev' | 'next' | 'today') => { + const handleNavigation = (action: "prev" | "next" | "today") => { if (!calendarRef.current) return; - + switch (action) { - case 'prev': + case "prev": calendarRef.current.prev(); break; - case 'next': + case "next": calendarRef.current.next(); break; - case 'today': + case "today": calendarRef.current.today(); break; } - + // Notify parent about date change after navigation if (onDateChange) { const newDate = calendarRef.current.getDate(); @@ -78,7 +83,7 @@ export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: M if (!calendarRef.current) return; setCurrentView(view); calendarRef.current.changeView(view); - + // Notify parent about date change after view change if (onDateChange) { const newDate = calendarRef.current.getDate(); @@ -90,23 +95,20 @@ export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: M onRefresh(); }; - const open = Boolean(anchorEl); return ( <>
- +
- - - + @@ -114,9 +116,9 @@ export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: M
- {currentDate.toLocaleDateString('en-US', { - month: 'long', - year: 'numeric' + {currentDate.toLocaleDateString("en-US", { + month: "long", + year: "numeric", })}
@@ -134,13 +136,13 @@ export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: M Day - + {applist.length > 0 && ( )} - +