From a5093f3fc46eab5a5805d7bbf530a34f45fc2278 Mon Sep 17 00:00:00 2001 From: lenhanphung <44486647+lenhanphung@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:08:25 +0700 Subject: [PATCH] feat: #524 implement logo click navigation to current week view (#402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add logo click handler to navigate to current week view from any view (month, day, or week) - Update MainTitle component to accept calendarRef and view change callbacks - Implement handleLogoClick that changes view to week and navigates to current week - Add comprehensive test cases for logo click behavior from all view types Co-authored-by: Lê Nhân Phụng --- __test__/components/Menubar.test.tsx | 216 +++++++++++++++++++++++++++ src/components/Menubar/Menubar.tsx | 44 +++++- 2 files changed, 257 insertions(+), 3 deletions(-) diff --git a/__test__/components/Menubar.test.tsx b/__test__/components/Menubar.test.tsx index 429110c..99efe32 100644 --- a/__test__/components/Menubar.test.tsx +++ b/__test__/components/Menubar.test.tsx @@ -760,3 +760,219 @@ describe("Menubar logout flow", () => { expect(sessionStorage.length).toBe(0); }); }); + +describe("Logo click navigation to current week", () => { + const preloadedState = { + user: { + userData: { + sub: "test", + email: "test@test.com", + family_name: "Doe", + name: "John", + sid: "mockSid", + openpaasId: "667037022b752d0026472254", + }, + }, + settings: { + view: "calendar", + language: "en", + }, + }; + + beforeEach(() => { + (window as any).appList = []; + }); + + it("navigates to week view and current week when clicking logo from month view", async () => { + const mockChangeView = jest.fn(); + const mockToday = jest.fn(); + const mockGetDate = jest.fn(() => new Date()); + const mockCalendarApi = { + changeView: mockChangeView, + today: mockToday, + getDate: mockGetDate, + }; + const mockCalendarRef = { current: mockCalendarApi as any }; + const mockOnRefresh = jest.fn(); + const mockOnViewChange = jest.fn(); + const mockOnDateChange = jest.fn(); + const mockCurrentDate = new Date("2024-04-15"); + + const { store } = renderWithProviders( + , + preloadedState + ); + + const logoElement = screen.getByAltText("menubar.logoAlt"); + await act(async () => { + fireEvent.click(logoElement); + }); + + await waitFor(() => { + expect(store.getState().settings.view).toBe("calendar"); + }); + + expect(mockChangeView).toHaveBeenCalledWith("timeGridWeek"); + expect(mockToday).toHaveBeenCalled(); + expect(mockOnViewChange).toHaveBeenCalledWith("timeGridWeek"); + expect(mockOnDateChange).toHaveBeenCalled(); + }); + + it("navigates to week view and current week when clicking logo from day view", async () => { + const mockChangeView = jest.fn(); + const mockToday = jest.fn(); + const mockGetDate = jest.fn(() => new Date()); + const mockCalendarApi = { + changeView: mockChangeView, + today: mockToday, + getDate: mockGetDate, + }; + const mockCalendarRef = { current: mockCalendarApi as any }; + const mockOnRefresh = jest.fn(); + const mockOnViewChange = jest.fn(); + const mockOnDateChange = jest.fn(); + const mockCurrentDate = new Date("2024-04-15"); + + const { store } = renderWithProviders( + , + preloadedState + ); + + const logoElement = screen.getByAltText("menubar.logoAlt"); + await act(async () => { + fireEvent.click(logoElement); + }); + + await waitFor(() => { + expect(store.getState().settings.view).toBe("calendar"); + }); + + expect(mockChangeView).toHaveBeenCalledWith("timeGridWeek"); + expect(mockToday).toHaveBeenCalled(); + expect(mockOnViewChange).toHaveBeenCalledWith("timeGridWeek"); + expect(mockOnDateChange).toHaveBeenCalled(); + }); + + it("navigates to current week when clicking logo from week view (not current week)", async () => { + const mockChangeView = jest.fn(); + const mockToday = jest.fn(); + const mockGetDate = jest.fn(() => new Date()); + const mockCalendarApi = { + changeView: mockChangeView, + today: mockToday, + getDate: mockGetDate, + }; + const mockCalendarRef = { current: mockCalendarApi as any }; + const mockOnRefresh = jest.fn(); + const mockOnViewChange = jest.fn(); + const mockOnDateChange = jest.fn(); + const mockCurrentDate = new Date("2024-04-15"); + + const { store } = renderWithProviders( + , + preloadedState + ); + + const logoElement = screen.getByAltText("menubar.logoAlt"); + await act(async () => { + fireEvent.click(logoElement); + }); + + await waitFor(() => { + expect(store.getState().settings.view).toBe("calendar"); + }); + + expect(mockChangeView).not.toHaveBeenCalled(); + expect(mockToday).toHaveBeenCalled(); + expect(mockOnDateChange).toHaveBeenCalled(); + }); + + it("does not crash when calendarRef.current is null", async () => { + const mockCalendarRef = { current: null }; + const mockOnRefresh = jest.fn(); + const mockOnViewChange = jest.fn(); + const mockOnDateChange = jest.fn(); + const mockCurrentDate = new Date("2024-04-15"); + + const { store } = renderWithProviders( + , + preloadedState + ); + + const logoElement = screen.getByAltText("menubar.logoAlt"); + await act(async () => { + fireEvent.click(logoElement); + }); + + await waitFor(() => { + expect(store.getState().settings.view).toBe("calendar"); + }); + + expect(mockOnViewChange).not.toHaveBeenCalled(); + expect(mockOnDateChange).not.toHaveBeenCalled(); + }); + + it("handles missing onViewChange and onDateChange callbacks", async () => { + const mockChangeView = jest.fn(); + const mockToday = jest.fn(); + const mockGetDate = jest.fn(() => new Date()); + const mockCalendarApi = { + changeView: mockChangeView, + today: mockToday, + getDate: mockGetDate, + }; + const mockCalendarRef = { current: mockCalendarApi as any }; + const mockOnRefresh = jest.fn(); + const mockCurrentDate = new Date("2024-04-15"); + + const { store } = renderWithProviders( + , + preloadedState + ); + + const logoElement = screen.getByAltText("menubar.logoAlt"); + await act(async () => { + fireEvent.click(logoElement); + }); + + await waitFor(() => { + expect(store.getState().settings.view).toBe("calendar"); + }); + + expect(mockChangeView).toHaveBeenCalledWith("timeGridWeek"); + expect(mockToday).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Menubar/Menubar.tsx b/src/components/Menubar/Menubar.tsx index 277ad41..4c0b0c7 100644 --- a/src/components/Menubar/Menubar.tsx +++ b/src/components/Menubar/Menubar.tsx @@ -160,7 +160,12 @@ export function Menubar({
- +
@@ -365,16 +370,49 @@ export function Menubar({ ); } -export function MainTitle() { +export type MainTitleProps = { + calendarRef: React.RefObject; + currentView: string; + onViewChange?: (view: string) => void; + onDateChange?: (date: Date) => void; +}; + +export function MainTitle({ + calendarRef, + currentView, + onViewChange, + onDateChange, +}: MainTitleProps) { const { t } = useI18n(); const dispatch = useAppDispatch(); + + const handleLogoClick = async () => { + if (!calendarRef.current) return; + + await dispatch(setView("calendar")); + + if (currentView !== "timeGridWeek") { + calendarRef.current.changeView("timeGridWeek"); + if (onViewChange) { + onViewChange("timeGridWeek"); + } + } + + calendarRef.current.today(); + + if (onDateChange) { + const newDate = calendarRef.current.getDate(); + onDateChange(newDate); + } + }; + return (
{t("menubar.logoAlt")} dispatch(setView("calendar"))} + onClick={handleLogoClick} />
);