[#265] prefills title + trad + test (#293)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-11-12 20:42:05 +01:00
committed by GitHub
parent 991cb6e56e
commit f886846a2b
10 changed files with 188 additions and 179 deletions
@@ -82,6 +82,13 @@ describe("Event Preview Display", () => {
end: day.toISOString(), end: day.toISOString(),
organizer: { cn: "test", cal_address: "test@test.com" }, organizer: { cn: "test", cal_address: "test@test.com" },
}, },
event3: {
uid: "event3",
calId: "667037022b752d0026472254/cal1",
start: day.toISOString(),
end: day.toISOString(),
organizer: { cn: "test", cal_address: "test@test.com" },
},
}, },
ownerEmails: ["test@test.com"], ownerEmails: ["test@test.com"],
}, },
@@ -797,7 +804,21 @@ describe("Event Preview Display", () => {
}).not.toThrow(); }).not.toThrow();
}); });
}); });
it("renders correctly event data event with empty title", () => {
renderWithProviders(
<EventPreviewModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event3"}
/>,
preloadedState
);
expect(screen.getByText("event.untitled")).toBeInTheDocument();
expect(screen.getByText("Calendar")).toBeInTheDocument();
});
describe("BUGFIX", () => { describe("BUGFIX", () => {
it("doesnt render anything next to date of all day preview", () => { it("doesnt render anything next to date of all day preview", () => {
const allDayState = { const allDayState = {
@@ -1,11 +1,9 @@
import { DateSelectArg } from "@fullcalendar/core"; import { DateSelectArg } from "@fullcalendar/core";
import { act, fireEvent, screen, waitFor } from "@testing-library/react"; import { act, fireEvent, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import preview from "jest-preview";
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice"; import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
import EventPopover from "../../../src/features/Events/EventModal"; import EventPopover from "../../../src/features/Events/EventModal";
import { api } from "../../../src/utils/apiUtils"; import { api } from "../../../src/utils/apiUtils";
import { formatDateToYYYYMMDDTHHMMSS } from "../../../src/utils/dateUtils";
import { renderWithProviders } from "../../utils/Renderwithproviders"; import { renderWithProviders } from "../../utils/Renderwithproviders";
jest.mock("../../../src/utils/apiUtils"); jest.mock("../../../src/utils/apiUtils");
+14 -2
View File
@@ -6,6 +6,7 @@ import { SlotLabelContentArg } from "@fullcalendar/core";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { refreshSingularCalendar } from "../../Event/utils/eventUtils"; import { refreshSingularCalendar } from "../../Event/utils/eventUtils";
import { ThunkDispatch } from "@reduxjs/toolkit"; import { ThunkDispatch } from "@reduxjs/toolkit";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
export const updateSlotLabelVisibility = ( export const updateSlotLabelVisibility = (
currentTime: Date, currentTime: Date,
@@ -58,13 +59,24 @@ export const eventToFullCalendarFormat = (
filteredTempEvents: CalendarEvent[], filteredTempEvents: CalendarEvent[],
userId: string | undefined userId: string | undefined
) => { ) => {
const { t } = useI18n();
return filteredEvents return filteredEvents
.concat(filteredTempEvents.map((e) => ({ ...e, temp: true }))) .concat(filteredTempEvents.map((e) => ({ ...e, temp: true })))
.map((e) => { .map((e) => {
if (e.calId.split("/")[0] === userId) { if (e.calId.split("/")[0] === userId) {
return { ...e, colors: e.color, editable: true }; return {
...e,
title: e.title ? e.title : t("event.untitled"),
colors: e.color,
editable: true,
};
} }
return { ...e, colors: e.color, editable: false }; return {
...e,
title: e.title ? e.title : t("event.untitled"),
colors: e.color,
editable: false,
};
}); });
}; };
+1 -11
View File
@@ -323,7 +323,6 @@ export default function EventFormFields({
// Validation logic // Validation logic
const validateForm = React.useCallback(() => { const validateForm = React.useCallback(() => {
return validateEventForm({ return validateEventForm({
title,
startDate, startDate,
startTime, startTime,
endDate, endDate,
@@ -410,14 +409,7 @@ export default function EventFormFields({
return ( return (
<> <>
<FieldWithLabel <FieldWithLabel label={t("event.form.title")} isExpanded={showMore}>
label={
<>
{t("event.form.title")} <span style={{ color: "red" }}>*</span>
</>
}
isExpanded={showMore}
>
<TextField <TextField
fullWidth fullWidth
label={!showMore ? t("event.form.title") : ""} label={!showMore ? t("event.form.title") : ""}
@@ -426,8 +418,6 @@ export default function EventFormFields({
onChange={(e) => { onChange={(e) => {
setTitle(e.target.value); setTitle(e.target.value);
}} }}
error={!!validation.errors.title}
helperText={validation.errors.title}
size="small" size="small"
margin="dense" margin="dense"
inputRef={titleInputRef} inputRef={titleInputRef}
+1 -8
View File
@@ -4,7 +4,6 @@ import { combineDateTime } from "./dateTimeHelpers";
* Validation parameters for event form * Validation parameters for event form
*/ */
export interface ValidationParams { export interface ValidationParams {
title: string;
startDate: string; startDate: string;
startTime: string; startTime: string;
endDate: string; endDate: string;
@@ -21,7 +20,6 @@ export interface ValidationParams {
export interface ValidationResult { export interface ValidationResult {
isValid: boolean; isValid: boolean;
errors: { errors: {
title: string;
dateTime: string; dateTime: string;
}; };
} }
@@ -33,7 +31,6 @@ export interface ValidationResult {
*/ */
export function validateEventForm(params: ValidationParams): ValidationResult { export function validateEventForm(params: ValidationParams): ValidationResult {
const { const {
title,
startDate, startDate,
startTime, startTime,
endDate, endDate,
@@ -44,9 +41,6 @@ export function validateEventForm(params: ValidationParams): ValidationResult {
showMore = false, showMore = false,
} = params; } = params;
const isTitleValid = title.trim().length > 0;
const shouldShowTitleError = showValidationErrors && !isTitleValid;
let isDateTimeValid = true; let isDateTimeValid = true;
let dateTimeError = ""; let dateTimeError = "";
@@ -154,12 +148,11 @@ export function validateEventForm(params: ValidationParams): ValidationResult {
} }
} }
const isValid = isTitleValid && isDateTimeValid; const isValid = isDateTimeValid;
return { return {
isValid, isValid,
errors: { errors: {
title: shouldShowTitleError ? "Title is required" : "",
dateTime: showValidationErrors ? dateTimeError : "", dateTime: showValidationErrors ? dateTimeError : "",
}, },
}; };
+1
View File
@@ -254,6 +254,7 @@ export function Menubar({
value={lang} value={lang}
onChange={(e) => { onChange={(e) => {
dispatch(setLanguage(e.target.value)); dispatch(setLanguage(e.target.value));
handleLangClose();
}} }}
variant="outlined" variant="outlined"
aria-label={t("menubar.languageSelector")} aria-label={t("menubar.languageSelector")}
+147 -153
View File
@@ -141,175 +141,169 @@ export default function EventPreviewModal({
actionsJustifyContent="center" actionsJustifyContent="center"
style={{ overflow: "auto" }} style={{ overflow: "auto" }}
title={ title={
event.title && ( <>
<> <DialogActions>
<DialogActions> <Box>
<Box> {(window as any).DEBUG && (
{(window as any).DEBUG && ( <IconButton
<IconButton onClick={async () => {
onClick={async () => { const icsContent = await dlEvent(event);
const icsContent = await dlEvent(event); const blob = new Blob([icsContent], {
const blob = new Blob([icsContent], { type: "text/calendar",
type: "text/calendar", });
}); const url = URL.createObjectURL(blob);
const url = URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement("a");
link.href = url; link.href = url;
link.download = `${eventId}.ics`; link.download = `${eventId}.ics`;
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}} }}
> >
<FileDownloadOutlinedIcon /> <FileDownloadOutlinedIcon />
</IconButton> </IconButton>
)} )}
{user.userData.email === event.organizer?.cal_address && {user.userData.email === event.organizer?.cal_address &&
calendar.ownerEmails?.includes(user.userData.email) && ( calendar.ownerEmails?.includes(user.userData.email) && (
<IconButton
size="small"
onClick={() => {
if (isRecurring) {
setAfterChoiceFunc(() => () => {
setHidePreview(true);
setOpenUpdateModal(true);
});
setOpenEditModePopup("edit");
} else {
setHidePreview(true);
setOpenUpdateModal(true);
}
}}
>
<EditIcon />
</IconButton>
)}
{((event.class !== "PRIVATE" && !isOwn) || isOwn) && (
<IconButton <IconButton
size="small" size="small"
onClick={(e) => setToggleActionMenu(e.currentTarget)} onClick={() => {
if (isRecurring) {
setAfterChoiceFunc(() => () => {
setHidePreview(true);
setOpenUpdateModal(true);
});
setOpenEditModePopup("edit");
} else {
setHidePreview(true);
setOpenUpdateModal(true);
}
}}
> >
<MoreVertIcon /> <EditIcon />
</IconButton> </IconButton>
)} )}
<Menu {((event.class !== "PRIVATE" && !isOwn) || isOwn) && (
open={Boolean(toggleActionMenu)}
onClose={() => setToggleActionMenu(null)}
anchorEl={toggleActionMenu}
>
{mailSpaUrl && attendees.length > 0 && (
<MenuItem
onClick={() =>
window.open(
`${mailSpaUrl}/mailto/?uri=mailto:${event.attendee
.map((a) => a.cal_address)
.filter((mail) => mail !== user.userData.email)
.join(",")}?subject=${event.title}`
)
}
>
{t("eventPreview.emailAttendees")}
</MenuItem>
)}
<EventDuplication
event={event}
onClose={() => setToggleActionMenu(null)}
onOpenDuplicate={() => {
setToggleActionMenu(null);
setHidePreview(true);
setOpenDuplicateModal(true);
}}
/>
{user.userData.email === event.organizer?.cal_address && (
<MenuItem
onClick={async () => {
if (isRecurring) {
setAfterChoiceFunc(
() =>
(typeOfAction?: "solo" | "all" | undefined) =>
handleDelete(
isRecurring,
typeOfAction,
onClose,
dispatch,
calendar,
event,
calId,
eventId
)
);
setOpenEditModePopup("edit");
} else {
onClose({}, "backdropClick");
await dispatch(
deleteEventAsync({
calId,
eventId,
eventURL: event.URL,
})
);
}
updateTempList();
}}
>
{t("eventPreview.deleteEvent")}
</MenuItem>
)}
</Menu>
<IconButton <IconButton
size="small" size="small"
onClick={() => onClose({}, "backdropClick")} onClick={(e) => setToggleActionMenu(e.currentTarget)}
> >
<CloseIcon /> <MoreVertIcon />
</IconButton> </IconButton>
</Box> )}
</DialogActions> <Menu
<Box display="flex" flexDirection="row"> open={Boolean(toggleActionMenu)}
{event.class === "PRIVATE" && onClose={() => setToggleActionMenu(null)}
(isOwn ? ( anchorEl={toggleActionMenu}
<Tooltip
title={t("eventPreview.privateEvent.tooltipOwn")}
placement="top"
>
<LockOutlineIcon />
</Tooltip>
) : (
<LockOutlineIcon />
))}
<Typography
variant="h5"
sx={{
fontSize: "24px",
fontWeight: 600,
wordBreak: "break-word",
fontFamily: "Inter, sans-serif",
}}
gutterBottom
> >
{event.title} {mailSpaUrl && attendees.length > 0 && (
</Typography> <MenuItem
{event.transp === "TRANSPARENT" && ( onClick={() =>
window.open(
`${mailSpaUrl}/mailto/?uri=mailto:${event.attendee
.map((a) => a.cal_address)
.filter((mail) => mail !== user.userData.email)
.join(",")}?subject=${event.title}`
)
}
>
{t("eventPreview.emailAttendees")}
</MenuItem>
)}
<EventDuplication
event={event}
onClose={() => setToggleActionMenu(null)}
onOpenDuplicate={() => {
setToggleActionMenu(null);
setHidePreview(true);
setOpenDuplicateModal(true);
}}
/>
{user.userData.email === event.organizer?.cal_address && (
<MenuItem
onClick={async () => {
if (isRecurring) {
setAfterChoiceFunc(
() => (typeOfAction?: "solo" | "all" | undefined) =>
handleDelete(
isRecurring,
typeOfAction,
onClose,
dispatch,
calendar,
event,
calId,
eventId
)
);
setOpenEditModePopup("edit");
} else {
onClose({}, "backdropClick");
await dispatch(
deleteEventAsync({
calId,
eventId,
eventURL: event.URL,
})
);
}
updateTempList();
}}
>
{t("eventPreview.deleteEvent")}
</MenuItem>
)}
</Menu>
<IconButton
size="small"
onClick={() => onClose({}, "backdropClick")}
>
<CloseIcon />
</IconButton>
</Box>
</DialogActions>
<Box display="flex" flexDirection="row">
{event.class === "PRIVATE" &&
(isOwn ? (
<Tooltip <Tooltip
title={t("eventPreview.free.tooltip")} title={t("eventPreview.privateEvent.tooltipOwn")}
placement="top" placement="top"
> >
<Chip <LockOutlineIcon />
icon={<CircleIcon color="success" fontSize="small" />}
label={t("eventPreview.free.label")}
/>
</Tooltip> </Tooltip>
)} ) : (
</Box> <LockOutlineIcon />
<Typography color="text.secondaryContainer" gutterBottom> ))}
{formatDate(event.start, t, event.allday)} <Typography
{event.end && variant="h5"
formatEnd(event.start, event.end, t, event.allday) && sx={{
` ${formatEnd(event.start, event.end, t, event.allday)} ${!event.allday ? getTimezoneOffset(timezone) : ""}`} fontSize: "24px",
fontWeight: 600,
wordBreak: "break-word",
fontFamily: "Inter, sans-serif",
}}
gutterBottom
>
{event.title ? event.title : t("event.untitled")}
</Typography> </Typography>
</> {event.transp === "TRANSPARENT" && (
) <Tooltip title={t("eventPreview.free.tooltip")} placement="top">
<Chip
icon={<CircleIcon color="success" fontSize="small" />}
label={t("eventPreview.free.label")}
/>
</Tooltip>
)}
</Box>
<Typography color="text.secondaryContainer" gutterBottom>
{formatDate(event.start, t, event.allday)}
{event.end &&
formatEnd(event.start, event.end, t, event.allday) &&
` ${formatEnd(event.start, event.end, t, event.allday)} ${!event.allday ? getTimezoneOffset(timezone) : ""}`}
</Typography>
</>
} }
actions={ actions={
currentUserAttendee && currentUserAttendee &&
+1 -3
View File
@@ -33,7 +33,6 @@ import { addDays } from "../../components/Event/utils/dateRules";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n"; import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
function EventPopover({ function EventPopover({
anchorEl,
open, open,
onClose, onClose,
selectedRange, selectedRange,
@@ -50,6 +49,7 @@ function EventPopover({
event?: CalendarEvent; event?: CalendarEvent;
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t, lang } = useI18n();
const organizer = useAppSelector((state) => state.user.organiserData); const organizer = useAppSelector((state) => state.user.organiserData);
const userId = const userId =
@@ -673,8 +673,6 @@ function EventPopover({
await updateTempCalendar(tempList, newEvent, dispatch, calendarRange); await updateTempCalendar(tempList, newEvent, dispatch, calendarRange);
} }
}; };
const { t } = useI18n();
const dialogActions = ( const dialogActions = (
<Box display="flex" justifyContent="space-between" width="100%" px={2}> <Box display="flex" justifyContent="space-between" width="100%" px={2}>
{!showMore && ( {!showMore && (
+1
View File
@@ -65,6 +65,7 @@
"createEvent": "Create Event", "createEvent": "Create Event",
"updateEvent": "Update Event", "updateEvent": "Update Event",
"organizer": "Organizer", "organizer": "Organizer",
"untitled": "Untitled",
"repeat": { "repeat": {
"repeatEvery": "Repeat every", "repeatEvery": "Repeat every",
"frequency": { "frequency": {
+1
View File
@@ -65,6 +65,7 @@
"createEvent": "Créer un événement", "createEvent": "Créer un événement",
"updateEvent": "Mettre à jour un événement", "updateEvent": "Mettre à jour un événement",
"organizer": "Organisateur", "organizer": "Organisateur",
"untitled": "Sans titre",
"repeat": { "repeat": {
"repeatEvery": "Répéter tous les", "repeatEvery": "Répéter tous les",
"frequency": { "frequency": {