feat: #524 implement logo click navigation to current week view (#402)

- 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 <lenhanphung@Phung-Mac-M4.local>
This commit is contained in:
lenhanphung
2025-12-09 17:08:25 +07:00
committed by GitHub
parent bd33a93040
commit a5093f3fc4
2 changed files with 257 additions and 3 deletions
+216
View File
@@ -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(
<Menubar
calendarRef={mockCalendarRef}
onRefresh={mockOnRefresh}
currentDate={mockCurrentDate}
currentView="dayGridMonth"
onViewChange={mockOnViewChange}
onDateChange={mockOnDateChange}
/>,
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(
<Menubar
calendarRef={mockCalendarRef}
onRefresh={mockOnRefresh}
currentDate={mockCurrentDate}
currentView="timeGridDay"
onViewChange={mockOnViewChange}
onDateChange={mockOnDateChange}
/>,
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(
<Menubar
calendarRef={mockCalendarRef}
onRefresh={mockOnRefresh}
currentDate={mockCurrentDate}
currentView="timeGridWeek"
onViewChange={mockOnViewChange}
onDateChange={mockOnDateChange}
/>,
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(
<Menubar
calendarRef={mockCalendarRef}
onRefresh={mockOnRefresh}
currentDate={mockCurrentDate}
currentView="dayGridMonth"
onViewChange={mockOnViewChange}
onDateChange={mockOnDateChange}
/>,
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(
<Menubar
calendarRef={mockCalendarRef}
onRefresh={mockOnRefresh}
currentDate={mockCurrentDate}
currentView="dayGridMonth"
/>,
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();
});
});
+41 -3
View File
@@ -160,7 +160,12 @@ export function Menubar({
<header className="menubar">
<div className="left-menu">
<div className="menu-items">
<MainTitle />
<MainTitle
calendarRef={calendarRef}
currentView={currentView}
onViewChange={onViewChange}
onDateChange={onDateChange}
/>
</div>
<div className="menu-items">
<div className="navigation-controls">
@@ -365,16 +370,49 @@ export function Menubar({
);
}
export function MainTitle() {
export type MainTitleProps = {
calendarRef: React.RefObject<CalendarApi | null>;
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 (
<div className="menubar-item tc-home">
<img
className="logo"
src={logo}
alt={t("menubar.logoAlt")}
onClick={() => dispatch(setView("calendar"))}
onClick={handleLogoClick}
/>
</div>
);