Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,7 +16,7 @@ import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice";
|
|||||||
import { ColorPicker } from "./CalendarColorPicker";
|
import { ColorPicker } from "./CalendarColorPicker";
|
||||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||||
import { User, PeopleSearch } from "../Attendees/PeopleSearch";
|
import { User, PeopleSearch } from "../Attendees/PeopleSearch";
|
||||||
import { getAccessiblePair } from "./utils/calendarColorsUtils";
|
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { ResponsiveDialog } from "../Dialog";
|
import { ResponsiveDialog } from "../Dialog";
|
||||||
|
|
||||||
@@ -54,10 +54,10 @@ function CalendarItem({
|
|||||||
src={cal.owner.avatarUrl}
|
src={cal.owner.avatarUrl}
|
||||||
alt={cal.owner.email}
|
alt={cal.owner.email}
|
||||||
style={{
|
style={{
|
||||||
border: `2px solid ${cal.cal["apple:color"] ?? "transparent"}`,
|
border: `2px solid ${cal.cal["apple:color"] || defaultColors[0]}`,
|
||||||
boxShadow: cal.cal["apple:color"]
|
boxShadow: cal.cal["apple:color"]
|
||||||
? `0 0 0 2px ${cal.cal["apple:color"]}`
|
? `0 0 0 2px ${cal.cal["apple:color"]}`
|
||||||
: "none",
|
: `0 0 0 2px ${defaultColors[0]}`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box>
|
<Box>
|
||||||
@@ -75,8 +75,10 @@ function CalendarItem({
|
|||||||
<Box display="flex" alignItems="center" gap={1}>
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
selectedColor={{
|
selectedColor={{
|
||||||
light: cal.cal["apple:color"],
|
light: cal.cal["apple:color"] ?? defaultColors[0].light,
|
||||||
dark: getAccessiblePair(cal.cal["apple:color"], theme),
|
dark: cal.cal["apple:color"]
|
||||||
|
? getAccessiblePair(cal.cal["apple:color"], theme)
|
||||||
|
: defaultColors[0].dark,
|
||||||
}}
|
}}
|
||||||
onChange={onColorChange}
|
onChange={onColorChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user