From 67898fa2183d408b21789de9063a19e5dfa9f533 Mon Sep 17 00:00:00 2001 From: Camille Moussu <66134347+Eriikah@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:50:43 +0100 Subject: [PATCH] [fix] removed added calendars old naming convention (#567) Co-authored-by: Camille Moussu --- __test__/components/Calendar.test.tsx | 21 +++++----- __test__/components/CalendarColors.test.tsx | 28 ++++++------- .../components/CalendarSelection.test.tsx | 28 +++++++++++-- .../features/Calendars/CalendarAPI.test.tsx | 11 +++-- .../Calendar/CalendarColorPicker.tsx | 3 +- src/components/Calendar/CalendarModal.tsx | 2 +- src/components/Calendar/CalendarName.tsx | 31 ++++++++++++-- src/components/Calendar/CalendarSearch.tsx | 3 +- src/components/Calendar/CalendarSelection.tsx | 40 ++++++++++++++++--- src/components/Calendar/OwnerCaption.tsx | 23 +++++++++++ .../Calendar/utils/calendarColorsUtils.tsx | 8 +--- src/components/Event/EventFormFields.tsx | 35 ++++++++++++++-- src/features/Calendars/CalendarApi.ts | 5 --- .../services/getCalendarDetailAsync.ts | 2 +- .../services/getCalendarsListAsync.ts | 11 +++-- .../Calendars/utils/extractCalendarEvents.ts | 2 +- src/features/Search/SearchResultsPage.tsx | 3 +- src/utils/defaultColors.tsx | 6 +++ ...makeDisplayName.tsx => makeDisplayName.ts} | 0 src/utils/renameDefault.ts | 3 ++ 20 files changed, 198 insertions(+), 67 deletions(-) create mode 100644 src/components/Calendar/OwnerCaption.tsx create mode 100644 src/utils/defaultColors.tsx rename src/utils/{makeDisplayName.tsx => makeDisplayName.ts} (100%) diff --git a/__test__/components/Calendar.test.tsx b/__test__/components/Calendar.test.tsx index 29596e8..75e68ec 100644 --- a/__test__/components/Calendar.test.tsx +++ b/__test__/components/Calendar.test.tsx @@ -76,7 +76,7 @@ describe("CalendarSelection", () => { delegated: true, id: "user2/cal1", color: { light: "#FF0000", dark: "#000" }, - owner: { emails: ["alice@example.com"] }, + owner: { emails: ["alice@example.com"], lastname: "alice" }, events: { event1: { id: "event1", @@ -107,7 +107,7 @@ describe("CalendarSelection", () => { name: "Calendar shared", id: "user3/cal1", color: { light: "#FF0000", dark: "#000" }, - owner: { emails: ["alice@example.com"] }, + owner: { emails: ["alice@example.com"], lastname: "alice" }, events: { event1: { id: "event1", @@ -709,10 +709,10 @@ describe("calendar Availability search", () => { ); const selectedCalls = spy.mock.calls.filter( - (call: { calId: string }[]) => call[0].calId === "user1/cal1" + (call) => call[0].calId === "user1/cal1" ); - const hiddenCalls = spy.mock.calls.filter( - (call: { calId: string }[]) => + spy.mock.calls.filter( + (call) => call[0].calId === "user1/cal2" || call[0].calId === "user1/cal3" ); @@ -745,13 +745,12 @@ describe("calendar Availability search", () => { ); const callsForCal1 = spy.mock.calls.filter( - (call: { calId: string }[]) => call[0].calId === "user1/cal1" + (call) => call[0].calId === "user1/cal1" ); const uniqueRanges = new Set( callsForCal1.map( - (call: { match: { end: string; start: string } }[]) => - `${call[0].match.start}_${call[0].match.end}` + (call) => `${call[0].match.start}_${call[0].match.end}` ) ); @@ -762,8 +761,8 @@ describe("calendar Availability search", () => { const stateWithUndefinedCalendars = { ...preloadedState, calendars: { - list: undefined as any, - templist: undefined as any, + list: undefined, + templist: undefined, pending: false, }, }; @@ -783,7 +782,7 @@ describe("calendar Availability search", () => { ...preloadedState, calendars: { ...preloadedState.calendars, - templist: undefined as any, + templist: undefined, }, }; diff --git a/__test__/components/CalendarColors.test.tsx b/__test__/components/CalendarColors.test.tsx index 73843b2..45b6c25 100644 --- a/__test__/components/CalendarColors.test.tsx +++ b/__test__/components/CalendarColors.test.tsx @@ -73,7 +73,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -96,7 +96,7 @@ describe("CalendarSearch", () => { }); await waitFor(() => { - expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument(); expect(screen.getByText("user@example.com")).toBeInTheDocument(); }); }); @@ -117,7 +117,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -133,7 +133,7 @@ describe("CalendarSearch", () => { }); await waitFor(() => { - expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument(); }); const addButton = screen.getByRole("button", { name: /add/i }); @@ -170,7 +170,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -205,7 +205,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -237,7 +237,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -253,7 +253,7 @@ describe("CalendarSearch", () => { }); await waitFor(() => { - expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument(); }); // ColorPicker would need to be interacted with based on its implementation @@ -286,7 +286,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -302,8 +302,8 @@ describe("CalendarSearch", () => { }); await waitFor(() => { - expect(screen.getByText("Test Calendar")).toBeInTheDocument(); - expect(screen.getByText("Second Calendar")).toBeInTheDocument(); + expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument(); + expect(screen.getByText(/Second Calendar/i)).toBeInTheDocument(); }); }); @@ -316,7 +316,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -351,7 +351,7 @@ describe("CalendarSearch", () => { await act(async () => { renderWithProviders( - , + , preloadedState ); }); @@ -378,7 +378,7 @@ describe("CalendarSearch", () => { }); await waitFor(() => { - expect(screen.getByText("Test Calendar")).toBeInTheDocument(); + expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument(); expect(screen.getByText("user@example.com")).toBeInTheDocument(); }); diff --git a/__test__/components/CalendarSelection.test.tsx b/__test__/components/CalendarSelection.test.tsx index 2e94896..2d216c1 100644 --- a/__test__/components/CalendarSelection.test.tsx +++ b/__test__/components/CalendarSelection.test.tsx @@ -24,20 +24,28 @@ describe("CalendarSelection", () => { name: "Calendar personal", id: "user1/cal1", color: "#FF0000", - owner: { emails: ["alice@example.com"] }, + owner: { emails: ["alice@example.com"], lastname: "alice" }, }, "user2/cal1": { name: "Calendar delegated", delegated: true, id: "user2/cal1", color: "#00FF00", - owner: { emails: ["bob@example.com"] }, + owner: { + firstname: "Bob", + lastname: "Builder", + emails: ["bob@example.com"], + }, }, "user3/cal1": { name: "Calendar shared", id: "user3/cal1", color: "#0000FF", - owner: { emails: ["charlie@example.com"] }, + owner: { + firstname: "Charlie", + lastname: "Chaplin", + emails: ["charlie@example.com"], + }, }, }; beforeAll(() => { @@ -273,4 +281,18 @@ describe("CalendarSelection", () => { fireEvent.click(delegatedAccordionSummary!); expect(delegatedAccordionSummary).toHaveAttribute("aria-expanded", "false"); }); + + it("renders owner name caption for non-personal, non-default calendars", () => { + renderWithProviders( + , + { user: baseUser, calendars: { list: calendarsMock, pending: false } } + ); + expect(screen.getByText("Bob Builder")).toBeInTheDocument(); + expect(screen.getByText("Charlie Chaplin")).toBeInTheDocument(); + // personal calendar should NOT show a caption + expect(screen.queryByText("alice")).not.toBeInTheDocument(); + }); }); diff --git a/__test__/features/Calendars/CalendarAPI.test.tsx b/__test__/features/Calendars/CalendarAPI.test.tsx index 8384793..21bdb22 100644 --- a/__test__/features/Calendars/CalendarAPI.test.tsx +++ b/__test__/features/Calendars/CalendarAPI.test.tsx @@ -114,7 +114,7 @@ describe("Calendar API", () => { }); }); - it("get secret link ", async () => { + it("get secret link without reset", async () => { const calLink = "/calendars/calId.json"; (api.get as jest.Mock).mockReturnValue({ json: jest.fn().mockResolvedValue("link"), @@ -131,7 +131,7 @@ describe("Calendar API", () => { } ); }); - it("get secret link ", async () => { + it("get secret link with reset", async () => { const calLink = "/calendars/calId.json"; (api.get as jest.Mock).mockReturnValue({ json: jest.fn().mockResolvedValue("link"), @@ -162,7 +162,7 @@ describe("Calendar API", () => { }); }); - it("When adding a sharedCal with #default as a name a new name is sent to the back", async () => { + it("When adding a sharedCal with #default #default is preserved", async () => { const mockApiPost = jest.spyOn(api, "post"); const calData = { @@ -192,12 +192,11 @@ describe("Calendar API", () => { expect(mockApiPost).toHaveBeenCalledWith( "dav/calendars/currentUserId.json", expect.objectContaining({ - body: expect.stringContaining('"dav:name":"John Doe\'s calendar"'), + body: expect.stringContaining('"dav:name":"#default"'), }) ); const callBody = JSON.parse(String(mockApiPost.mock.calls[0][1]?.body)); - expect(callBody["dav:name"]).toBe("John Doe's calendar"); - expect(callBody["dav:name"]).not.toBe("#default"); + expect(callBody["dav:name"]).toBe("#default"); }); }); diff --git a/src/components/Calendar/CalendarColorPicker.tsx b/src/components/Calendar/CalendarColorPicker.tsx index c856fd2..eba4f83 100644 --- a/src/components/Calendar/CalendarColorPicker.tsx +++ b/src/components/Calendar/CalendarColorPicker.tsx @@ -11,7 +11,8 @@ import { import { useState, useEffect } from "react"; import { HexColorPicker } from "react-colorful"; import { useI18n } from "twake-i18n"; -import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils"; +import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { defaultColors } from "@/utils/defaultColors"; export function ColorPicker({ selectedColor, diff --git a/src/components/Calendar/CalendarModal.tsx b/src/components/Calendar/CalendarModal.tsx index 195cd8b..5686f38 100644 --- a/src/components/Calendar/CalendarModal.tsx +++ b/src/components/Calendar/CalendarModal.tsx @@ -14,7 +14,7 @@ import { ResponsiveDialog } from "../Dialog"; import { AccessTab } from "./AccessTab"; import { ImportTab } from "./ImportTab"; import { SettingsTab } from "./SettingsTab"; -import { defaultColors } from "./utils/calendarColorsUtils"; +import { defaultColors } from "@/utils/defaultColors"; function CalendarPopover({ open, diff --git a/src/components/Calendar/CalendarName.tsx b/src/components/Calendar/CalendarName.tsx index 70006e9..8c81105 100644 --- a/src/components/Calendar/CalendarName.tsx +++ b/src/components/Calendar/CalendarName.tsx @@ -1,17 +1,42 @@ import { Calendar } from "@/features/Calendars/CalendarTypes"; import { Box, Typography } from "@linagora/twake-mui"; import SquareRoundedIcon from "@mui/icons-material/SquareRounded"; +import { defaultColors } from "@/utils/defaultColors"; +import { makeDisplayName } from "@/utils/makeDisplayName"; +import { OwnerCaption } from "./OwnerCaption"; +import { useAppSelector } from "@/app/hooks"; + export function CalendarName({ calendar }: { calendar: Calendar }) { + const userData = useAppSelector((state) => state.user.userData); + const showCaption = + calendar.name !== "#default" && + userData.openpaasId !== calendar.id.split("/")[0]; + return ( - + - {calendar.name} + + + {calendar.name} + + + ); } diff --git a/src/components/Calendar/CalendarSearch.tsx b/src/components/Calendar/CalendarSearch.tsx index d543e5e..11045ad 100644 --- a/src/components/Calendar/CalendarSearch.tsx +++ b/src/components/Calendar/CalendarSearch.tsx @@ -20,7 +20,8 @@ import { PeopleSearch, User } from "../Attendees/PeopleSearch"; import { ResponsiveDialog } from "../Dialog"; import { stringAvatar } from "../Event/utils/eventUtils"; import { ColorPicker } from "./CalendarColorPicker"; -import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils"; +import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { defaultColors } from "@/utils/defaultColors"; interface CalendarWithOwner { cal: CalendarData; diff --git a/src/components/Calendar/CalendarSelection.tsx b/src/components/Calendar/CalendarSelection.tsx index 1eb6243..9174897 100644 --- a/src/components/Calendar/CalendarSelection.tsx +++ b/src/components/Calendar/CalendarSelection.tsx @@ -2,6 +2,7 @@ import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { Calendar } from "@/features/Calendars/CalendarTypes"; import { removeCalendarAsync } from "@/features/Calendars/services"; import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid"; +import { makeDisplayName } from "@/utils/makeDisplayName"; import { renameDefault } from "@/utils/renameDefault"; import { trimLongTextWithoutSpace } from "@/utils/textUtils"; import { @@ -23,6 +24,7 @@ import { useI18n } from "twake-i18n"; import CalendarPopover from "./CalendarModal"; import CalendarSearch from "./CalendarSearch"; import { DeleteCalendarDialog } from "./DeleteCalendarDialog"; +import { OwnerCaption } from "./OwnerCaption"; function CalendarAccordion({ title, @@ -260,6 +262,20 @@ function CalendarSelector({ () => trimLongTextWithoutSpace(calendars[id].name), [calendars, id] ); + + const ownerDisplayName = useMemo( + () => makeDisplayName(calendars[id]), + [calendars, id] + ); + + const displayName = useMemo( + () => renameDefault(trimmedName, ownerDisplayName ?? "", t, isPersonal), + [trimmedName, ownerDisplayName, t, isPersonal] + ); + + const showCaption = + !isPersonal && trimmedName !== "#default" && ownerDisplayName != null; + return ( <> handleCalendarToggle(id)} + inputProps={{ "aria-label": displayName }} /> - - {renameDefault(trimmedName, calendars[id].owner, t, isPersonal)} - + + {displayName} + + + diff --git a/src/components/Calendar/OwnerCaption.tsx b/src/components/Calendar/OwnerCaption.tsx new file mode 100644 index 0000000..fb2e67b --- /dev/null +++ b/src/components/Calendar/OwnerCaption.tsx @@ -0,0 +1,23 @@ +import { Typography } from "@linagora/twake-mui"; + +export function OwnerCaption({ + showCaption, + ownerDisplayName, +}: { + showCaption: boolean; + ownerDisplayName: string; +}) { + return ( + + {showCaption && ownerDisplayName} + + ); +} diff --git a/src/components/Calendar/utils/calendarColorsUtils.tsx b/src/components/Calendar/utils/calendarColorsUtils.tsx index 978701c..9ca27cd 100644 --- a/src/components/Calendar/utils/calendarColorsUtils.tsx +++ b/src/components/Calendar/utils/calendarColorsUtils.tsx @@ -2,6 +2,7 @@ import { AppDispatch } from "@/app/store"; import { updateCalColor } from "@/features/Calendars/CalendarSlice"; import { Calendar } from "@/features/Calendars/CalendarTypes"; import { darken, getContrastRatio, lighten, Theme } from "@linagora/twake-mui"; +import { defaultColors } from "@/utils/defaultColors"; export function updateDarkColor( calendars: Record, @@ -53,10 +54,3 @@ export function getAccessiblePair(baseColor: string, theme: Theme): string { return theme.palette.getContrastText(baseColor); } - -export const defaultColors = [ - { light: "#D0ECDA", dark: "#329655" }, - { light: "#FAE3CE", dark: "#E15300" }, - { light: "#F5CFD0", dark: "#BE0103" }, - { light: "#AFCBEF", dark: "#0654B1" }, -]; diff --git a/src/components/Event/EventFormFields.tsx b/src/components/Event/EventFormFields.tsx index e60b010..f4f59d5 100644 --- a/src/components/Event/EventFormFields.tsx +++ b/src/components/Event/EventFormFields.tsx @@ -2,6 +2,8 @@ import { Calendar } from "@/features/Calendars/CalendarTypes"; import { RepetitionObject } from "@/features/Events/EventsTypes"; import { userAttendee } from "@/features/User/models/attendee"; import iconCamera from "@/static/images/icon-camera.svg"; +import { defaultColors } from "@/utils/defaultColors"; +import { makeDisplayName } from "@/utils/makeDisplayName"; import { addVideoConferenceToDescription, generateMeetingLink, @@ -35,6 +37,7 @@ import React from "react"; import { useI18n } from "twake-i18n"; import AttendeeSelector from "../Attendees/AttendeeSearch"; import { CalendarItemList } from "../Calendar/CalendarItemList"; +import { OwnerCaption } from "../Calendar/OwnerCaption"; import { SnackbarAlert } from "../Loading/SnackBarAlert"; import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete"; import { AddDescButton } from "./AddDescButton"; @@ -775,7 +778,7 @@ export default function EventFormFields({ sx={{ color: userPersonalCalendars.find((cal) => cal.id === calendarid) - ?.color?.light ?? "#3788D8", + ?.color?.light ?? defaultColors[0].light, width: 24, height: 24, }} @@ -783,8 +786,34 @@ export default function EventFormFields({ } onClick={() => setHasClickedCalendarSection(true)} > - {userPersonalCalendars.find((cal) => cal.id === calendarid)?.name || - t("event.form.calendar")} + {userPersonalCalendars.find((cal) => cal.id === calendarid) + ?.name ? ( + + + { + userPersonalCalendars.find((cal) => cal.id === calendarid) + ?.name + } + + cal.id === calendarid) !== + undefined && + userPersonalCalendars.find((cal) => cal.id === calendarid) + ?.name !== "#default" + } + ownerDisplayName={ + makeDisplayName( + userPersonalCalendars.find( + (cal) => cal.id === calendarid + ) ?? ({} as Calendar) + ) ?? "" + } + /> + + ) : ( + t("event.form.calendar") + )} ) : ( diff --git a/src/features/Calendars/CalendarApi.ts b/src/features/Calendars/CalendarApi.ts index e8dec88..ab69619 100644 --- a/src/features/Calendars/CalendarApi.ts +++ b/src/features/Calendars/CalendarApi.ts @@ -69,15 +69,10 @@ export async function addSharedCalendar( body: JSON.stringify({ id: calId, ...cal.cal, - "dav:name": - cal.cal["dav:name"] === "#default" - ? (cal.owner?.displayName ?? "Unknown") + "'s calendar" - : cal.cal["dav:name"], "calendarserver:source": { acl: cal.cal.acl, calendarHomeId: cal.cal.id, color: cal.cal["apple:color"], - description: cal.cal["caldav:description"], href: cal.cal._links.self.href, id: cal.cal.id, diff --git a/src/features/Calendars/services/getCalendarDetailAsync.ts b/src/features/Calendars/services/getCalendarDetailAsync.ts index 84c067f..ed685ac 100644 --- a/src/features/Calendars/services/getCalendarDetailAsync.ts +++ b/src/features/Calendars/services/getCalendarDetailAsync.ts @@ -8,7 +8,7 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import { getCalendar } from "../CalendarApi"; import { RejectedError } from "../types/RejectedError"; import { extractCalendarEvents } from "../utils/extractCalendarEvents"; -import { defaultColors } from "@/components/Calendar/utils/calendarColorsUtils"; +import { defaultColors } from "@/utils/defaultColors"; import { type RootState } from "@/app/store"; export const getCalendarDetailAsync = createAsyncThunk< diff --git a/src/features/Calendars/services/getCalendarsListAsync.ts b/src/features/Calendars/services/getCalendarsListAsync.ts index ab08bfb..71b908a 100644 --- a/src/features/Calendars/services/getCalendarsListAsync.ts +++ b/src/features/Calendars/services/getCalendarsListAsync.ts @@ -1,4 +1,5 @@ import { RootState } from "@/app/store"; +import { defaultColors } from "@/utils/defaultColors"; import { OpenPaasUserData } from "@/features/User/type/OpenPaasUserData"; import { getOpenPaasUser, getUserDetails } from "@/features/User/userAPI"; import { formatReduxError } from "@/utils/errorUtils"; @@ -75,10 +76,12 @@ export const getCalendarsListAsync = createAsyncThunk< emails: [], }; - const color = { - light: cal["apple:color"] ?? "#006BD8", - dark: "#FFF", - }; + const color = cal["apple:color"] + ? { + light: cal["apple:color"], + dark: "#FFF", + } + : defaultColors[0]; fetchedCalendars[id] = { id, name: cal["dav:name"] ?? "", diff --git a/src/features/Calendars/utils/extractCalendarEvents.ts b/src/features/Calendars/utils/extractCalendarEvents.ts index 5fedf56..3eb7df6 100644 --- a/src/features/Calendars/utils/extractCalendarEvents.ts +++ b/src/features/Calendars/utils/extractCalendarEvents.ts @@ -1,4 +1,4 @@ -import { defaultColors } from "@/components/Calendar/utils/calendarColorsUtils"; +import { defaultColors } from "@/utils/defaultColors"; import { CalendarEvent } from "@/features/Events/EventsTypes"; import { parseCalendarEvent } from "@/features/Events/eventUtils"; import { CalDavItem } from "../api/types"; diff --git a/src/features/Search/SearchResultsPage.tsx b/src/features/Search/SearchResultsPage.tsx index 318911a..022baca 100644 --- a/src/features/Search/SearchResultsPage.tsx +++ b/src/features/Search/SearchResultsPage.tsx @@ -1,5 +1,6 @@ import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { AppDispatch } from "@/app/store"; +import { defaultColors } from "@/utils/defaultColors"; import { browserDefaultTimeZone } from "@/utils/timezone"; import { Box, @@ -239,7 +240,7 @@ function ResultItem({ string, isOwnCalendar?: boolean ) { + if (!ownerName) { + if (davName && davName !== "#default") return davName; + } if (!davName) { return t("calendar.defaultCalendarName", { name: ownerName }); }