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({