[#259] added check to prevent no applecolor from breaking the commponent (#260)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-10-29 11:25:20 +01:00
committed by GitHub
parent 2355ee8771
commit fc962057c0
2 changed files with 385 additions and 5 deletions
+378
View File
@@ -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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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(
<CalendarSearch anchorEl={null} open={true} onClose={mockOnClose} />,
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();
});
});
});
+7 -5
View File
@@ -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]}`,
}}
/>
<Box>
@@ -75,8 +75,10 @@ function CalendarItem({
<Box display="flex" alignItems="center" gap={1}>
<ColorPicker
selectedColor={{
light: cal.cal["apple:color"],
dark: getAccessiblePair(cal.cal["apple:color"], theme),
light: cal.cal["apple:color"] ?? defaultColors[0].light,
dark: cal.cal["apple:color"]
? getAccessiblePair(cal.cal["apple:color"], theme)
: defaultColors[0].dark,
}}
onChange={onColorChange}
/>