diff --git a/__test__/components/MiniCalendarColor.test.tsx b/__test__/components/MiniCalendarColor.test.tsx index 4bbed9e..4624c2b 100644 --- a/__test__/components/MiniCalendarColor.test.tsx +++ b/__test__/components/MiniCalendarColor.test.tsx @@ -4,6 +4,7 @@ import { jest } from "@jest/globals"; import CalendarApp from "../../src/components/Calendar/Calendar"; import * as appHooks from "../../src/app/hooks"; import { ThunkDispatch } from "@reduxjs/toolkit"; +import preview from "jest-preview"; describe("MiniCalendar", () => { const day = new Date(); @@ -54,6 +55,8 @@ describe("MiniCalendar", () => { it("renders mini calendar with the week in gray (except for today) when full calendar in week view", async () => { renderCalendar(); + preview.debug(); + const today = new Date(); const sunday = new Date(today); sunday.setDate(today.getDate() - today.getDay()); @@ -61,9 +64,9 @@ describe("MiniCalendar", () => { for (let i = 0; i < 7; i++) { const date = new Date(sunday); date.setDate(sunday.getDate() + i); - const tile = screen - .getAllByText(date.getDate()) - .find((el) => el.tagName.toLowerCase() === "abbr"); + const tile = (await screen.findAllByText(date.getDate())).find( + (el) => el.tagName.toLowerCase() === "abbr" + ); if (date.getTime() !== today.setHours(0, 0, 0, 0)) { expect(tile?.parentElement).toHaveClass("selectedWeek"); } @@ -104,10 +107,95 @@ describe("MiniCalendar", () => { renderCalendar(); const dot = document.querySelector(".event-dot"); - console.log(dot?.parentElement?.children[0].innerHTML); 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", + events: { + event1: { + 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); + preview.debug(); + const shownDay = screen.getByText((content, element) => { + return ( + element?.className.toLowerCase().includes("fc-daygrid-day-number") ?? + false + ); + }); + const selectedTile = screen.getByText((content, element) => { + return element?.className.includes("selectedWeek") ?? false; + }); + const supposedSelectedTile = screen + .getAllByText((content, element) => { + return element?.tagName.toLowerCase() === "abbr"; + }) + .find((el) => el.innerHTML === shownDay.innerHTML); + + expect(selectedTile.children[0].innerHTML).toBe( + supposedSelectedTile?.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?.className.includes("fc-toolbar-title") ?? false; + }); + expect(miniCalMonth.innerHTML).toBe(fullCalMonth.innerHTML); + fireEvent.click(nextMonthButton); + expect(miniCalMonth.innerHTML).toBe(fullCalMonth.innerHTML); + }); +}); diff --git a/src/components/Calendar/Calendar.css b/src/components/Calendar/Calendar.css index 7be5439..1d53771 100644 --- a/src/components/Calendar/Calendar.css +++ b/src/components/Calendar/Calendar.css @@ -114,6 +114,9 @@ main { .selectedWeek { background-color: lightgrey; } +.react-calendar__month-view__days__day--neighboringMonth { + color: gray; +} /* sidebar header */ .sidebar-calendar h2 { diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index 42c3403..b6851f2 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -24,6 +24,7 @@ import { push } from "redux-first-history"; export default function CalendarApp() { const calendarRef = useRef(null); const [selectedDate, setSelectedDate] = useState(new Date()); + const [selectedMiniDate, setSelectedMiniDate] = useState(new Date()); const tokens = useAppSelector((state) => state.user.tokens); const dispatch = useAppDispatch(); @@ -56,7 +57,6 @@ export default function CalendarApp() { const [selectedCalendars, setSelectedCalendars] = useState( Object.keys(calendars).filter((id) => id.split("/")[0] === userId) ); - const fetchedIdsRef = useRef>(new Set()); const calendarRange = getCalendarRange(selectedDate); @@ -108,56 +108,45 @@ export default function CalendarApp() { setSelectedRange(null); }; + const handleMonthUp = () => { + setSelectedMiniDate( + new Date(selectedMiniDate.getFullYear(), selectedMiniDate.getMonth() - 1) + ); + }; + const handleMonthDown = () => { + setSelectedMiniDate( + new Date(selectedMiniDate.getFullYear(), selectedMiniDate.getMonth() + 1) + ); + }; return (
- - {selectedDate.toLocaleDateString("us-us", { + + {selectedMiniDate.toLocaleDateString("en-us", { month: "long", year: "numeric", })}
- - + +
date.toLocaleDateString(locale, { weekday: "narrow" }) } - value={selectedDate} + value={selectedMiniDate} onClickDay={(date) => { setSelectedDate(date); + setSelectedMiniDate(date); calendarRef.current?.gotoDate(date); }} prevLabel={null} + showNeighboringMonth={true} nextLabel={null} showNavigation={false} tileClassName={({ date }) => { @@ -169,7 +158,10 @@ export default function CalendarApp() { } const selected = new Date(selectedDate); selected.setHours(0, 0, 0, 0); - if (calendarRef.current?.view.type === "timeGridWeek") { + if ( + calendarRef.current?.view.type === "timeGridWeek" || + calendarRef.current?.view.type === undefined + ) { const startOfWeek = new Date(selected); startOfWeek.setDate(selected.getDate() - selected.getDay()); // Sunday startOfWeek.setHours(0, 0, 0, 0); @@ -246,10 +238,24 @@ export default function CalendarApp() { hour12: false, }} datesSet={(arg) => { - const today = new Date(); - setSelectedDate( - today > arg.start && today < arg.end ? today : arg.start - ); + if (arg.view.type === "timeGridDay") { + setSelectedDate(new Date(arg.start)); + setSelectedMiniDate(new Date(arg.start)); + } else if (arg.view.type === "timeGridWeek") { + // In week view, retain selectedDate if it's in current range, otherwise set to start + if (selectedDate < arg.start || selectedDate > arg.end) { + setSelectedDate(new Date(arg.start)); + setSelectedMiniDate(new Date(arg.start)); + } + } else if (arg.view.type === "dayGridMonth") { + setSelectedDate(new Date(arg.start)); + const midTimestamp = + (arg.start.getTime() + arg.end.getTime()) / 2; + setSelectedMiniDate(new Date(midTimestamp)); + } else { + setSelectedDate(new Date(arg.start)); + setSelectedMiniDate(new Date(arg.start)); + } }} dayHeaderContent={(arg) => { const date = arg.date.getDate();