From fc962057c0bd26a385c4114f8a06cd193158345d Mon Sep 17 00:00:00 2001 From: Camille Moussu <66134347+Eriikah@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:25:20 +0100 Subject: [PATCH] [#259] added check to prevent no applecolor from breaking the commponent (#260) Co-authored-by: Camille Moussu --- __test__/components/CalendarColors.test.tsx | 378 ++++++++++++++++++++ src/components/Calendar/CalendarSearch.tsx | 12 +- 2 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 __test__/components/CalendarColors.test.tsx diff --git a/__test__/components/CalendarColors.test.tsx b/__test__/components/CalendarColors.test.tsx new file mode 100644 index 0000000..8398e19 --- /dev/null +++ b/__test__/components/CalendarColors.test.tsx @@ -0,0 +1,378 @@ +import { fireEvent, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { act } from "react"; +import CalendarSearch from "../../src/components/Calendar/CalendarSearch"; +import * as CalendarApi from "../../src/features/Calendars/CalendarApi"; +import * as CalendarSlice from "../../src/features/Calendars/CalendarSlice"; +import { searchUsers } from "../../src/features/User/userAPI"; +import { renderWithProviders } from "../utils/Renderwithproviders"; + +jest.mock("../../src/features/User/userAPI"); +jest.mock("../../src/features/Calendars/CalendarApi"); + +const mockedSearchUsers = searchUsers as jest.MockedFunction< + typeof searchUsers +>; +const mockedGetCalendars = CalendarApi.getCalendars as jest.MockedFunction< + typeof CalendarApi.getCalendars +>; + +describe("CalendarSearch", () => { + const mockOnClose = jest.fn(); + const mockUser = { + email: "user@example.com", + displayName: "Test User", + avatarUrl: "https://example.com/avatar.jpg", + openpaasId: "user123", + }; + + const mockCalendar = { + "dav:name": "Test Calendar", + "apple:color": "#FF0000", + _links: { + self: { + href: "/calendars/user123/cal1.json", + }, + }, + }; + + const preloadedState = { + user: { + userData: { + sub: "test", + email: "test@test.com", + sid: "mockSid", + openpaasId: "user1", + }, + tokens: { accessToken: "token" }, + }, + calendars: { + list: { + "user1/cal1": { + name: "My Calendar", + id: "user1/cal1", + color: "#0000FF", + ownerEmails: ["test@test.com"], + events: {}, + }, + }, + pending: false, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("searches for users and displays their calendars", async () => { + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({ + _embedded: { + "dav:calendar": [mockCalendar], + }, + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect(mockedGetCalendars).toHaveBeenCalledWith( + "user123", + "sharedPublic=true&withRights=true" + ); + }); + + await waitFor(() => { + expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText("user@example.com")).toBeInTheDocument(); + }); + }); + + it("adds selected calendars on save", async () => { + const addSharedCalendarSpy = jest + .spyOn(CalendarSlice, "addSharedCalendarAsync") + .mockImplementation((payload) => { + return () => Promise.resolve(payload) as any; + }); + + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({ + _embedded: { + "dav:calendar": [mockCalendar], + }, + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + }); + + const addButton = screen.getByRole("button", { name: /add/i }); + await act(async () => { + fireEvent.click(addButton); + }); + + expect(addSharedCalendarSpy).toHaveBeenCalled(); + expect(mockOnClose).toHaveBeenCalled(); + }); + + it("does not add calendars that already exist", async () => { + const addSharedCalendarSpy = jest + .spyOn(CalendarSlice, "addSharedCalendarAsync") + .mockImplementation((payload) => { + return () => Promise.resolve(payload) as any; + }); + + const existingCalendar = { + ...mockCalendar, + _links: { + self: { + href: "/calendars/user1/cal1.json", + }, + }, + }; + + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({ + _embedded: { + "dav:calendar": [existingCalendar], + }, + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect( + screen.getByText("No more Calendar for Test User") + ).toBeInTheDocument(); + }); + + const addButton = screen.getByRole("button", { name: /add/i }); + await act(async () => { + fireEvent.click(addButton); + }); + + expect(addSharedCalendarSpy).not.toHaveBeenCalled(); + }); + + it("displays message when user has no publicly available calendars", async () => { + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({}); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect( + screen.getByText("No publicly available calendars for Test User") + ).toBeInTheDocument(); + }); + }); + + it("changes calendar color", async () => { + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({ + _embedded: { + "dav:calendar": [mockCalendar], + }, + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + }); + + // ColorPicker would need to be interacted with based on its implementation + // This is a placeholder for color change interaction + const colorPicker = document.querySelector('[data-testid="color-picker"]'); + if (colorPicker) { + await act(async () => { + fireEvent.click(colorPicker); + }); + } + }); + + it("handles multiple calendars from the same user", async () => { + const secondCalendar = { + "dav:name": "Second Calendar", + "apple:color": "#00FF00", + _links: { + self: { + href: "/calendars/user123/cal2.json", + }, + }, + }; + + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({ + _embedded: { + "dav:calendar": [mockCalendar, secondCalendar], + }, + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText("Second Calendar")).toBeInTheDocument(); + }); + }); + + it("does not call addSharedCalendarAsync when no calendars are selected", async () => { + const addSharedCalendarSpy = jest + .spyOn(CalendarSlice, "addSharedCalendarAsync") + .mockImplementation((payload) => { + return () => Promise.resolve(payload) as any; + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const addButton = screen.getByRole("button", { name: /add/i }); + await act(async () => { + fireEvent.click(addButton); + }); + + expect(addSharedCalendarSpy).not.toHaveBeenCalled(); + }); + + it("BUGFIX : handles calendar with no apple:color", async () => { + const mockCalendarNoColor = { + "dav:name": "Test Calendar", + _links: { + self: { + href: "/calendars/user123/cal1.json", + }, + }, + }; + mockedSearchUsers.mockResolvedValueOnce([mockUser]); + mockedGetCalendars.mockResolvedValueOnce({ + _embedded: { + "dav:calendar": [mockCalendarNoColor], + }, + }); + + await act(async () => { + renderWithProviders( + , + preloadedState + ); + }); + + const input = screen.getByRole("combobox"); + await act(async () => { + userEvent.type(input, "Test"); + }); + + const option = await screen.findByText("Test User"); + await act(async () => { + fireEvent.click(option); + }); + + await waitFor(() => { + expect(mockedGetCalendars).toHaveBeenCalledWith( + "user123", + "sharedPublic=true&withRights=true" + ); + }); + + await waitFor(() => { + expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText("user@example.com")).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Calendar/CalendarSearch.tsx b/src/components/Calendar/CalendarSearch.tsx index 12ebed5..50c4472 100644 --- a/src/components/Calendar/CalendarSearch.tsx +++ b/src/components/Calendar/CalendarSearch.tsx @@ -16,7 +16,7 @@ import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice"; import { ColorPicker } from "./CalendarColorPicker"; import { Calendars } from "../../features/Calendars/CalendarTypes"; import { User, PeopleSearch } from "../Attendees/PeopleSearch"; -import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils"; import { useTheme } from "@mui/material/styles"; import { ResponsiveDialog } from "../Dialog"; @@ -54,10 +54,10 @@ function CalendarItem({ src={cal.owner.avatarUrl} alt={cal.owner.email} style={{ - border: `2px solid ${cal.cal["apple:color"] ?? "transparent"}`, + border: `2px solid ${cal.cal["apple:color"] || defaultColors[0]}`, boxShadow: cal.cal["apple:color"] ? `0 0 0 2px ${cal.cal["apple:color"]}` - : "none", + : `0 0 0 2px ${defaultColors[0]}`, }} /> @@ -75,8 +75,10 @@ function CalendarItem({