* [#533] updated localisation of field + removed empty display for repetition * [#533 & #545] fixed bug date in repetition field & set limit to repetition end to not be before start date
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
|
import { RootState } from "@/app/store";
|
||||||
import RepeatEvent from "@/components/Event/EventRepeat";
|
import RepeatEvent from "@/components/Event/EventRepeat";
|
||||||
import * as eventThunks from "@/features/Calendars/services";
|
import * as eventThunks from "@/features/Calendars/services";
|
||||||
import EventPopover from "@/features/Events/EventModal";
|
import EventPopover from "@/features/Events/EventModal";
|
||||||
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
||||||
import { DateSelectArg } from "@fullcalendar/core";
|
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
||||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||||
|
|
||||||
@@ -15,7 +16,9 @@ const baseRepetition: RepetitionObject = {
|
|||||||
|
|
||||||
const mockOnClose = jest.fn();
|
const mockOnClose = jest.fn();
|
||||||
const mockSetSelectedRange = jest.fn();
|
const mockSetSelectedRange = jest.fn();
|
||||||
const mockCalendarRef = { current: { select: jest.fn() } } as any;
|
const mockCalendarRef = {
|
||||||
|
current: { select: jest.fn() } as unknown as CalendarApi,
|
||||||
|
};
|
||||||
|
|
||||||
const preloadedState = {
|
const preloadedState = {
|
||||||
user: {
|
user: {
|
||||||
@@ -56,7 +59,10 @@ const defaultSelectedRange = {
|
|||||||
resource: undefined,
|
resource: undefined,
|
||||||
} as unknown as DateSelectArg;
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
function setupRepeatEvent(props?: Partial<RepetitionObject>, state?: any) {
|
function setupRepeatEvent(
|
||||||
|
props?: Partial<RepetitionObject>,
|
||||||
|
state?: RootState
|
||||||
|
) {
|
||||||
const setRepetition = jest.fn();
|
const setRepetition = jest.fn();
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
<RepeatEvent
|
<RepeatEvent
|
||||||
@@ -70,9 +76,7 @@ function setupRepeatEvent(props?: Partial<RepetitionObject>, state?: any) {
|
|||||||
return { setRepetition };
|
return { setRepetition };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupEventPopover(
|
async function setupEventPopover() {
|
||||||
overrides?: Partial<{ start: string; end: string }>
|
|
||||||
) {
|
|
||||||
jest
|
jest
|
||||||
.spyOn(crypto, "randomUUID")
|
.spyOn(crypto, "randomUUID")
|
||||||
.mockReturnValue("fixed-uuid-with-correct-format");
|
.mockReturnValue("fixed-uuid-with-correct-format");
|
||||||
@@ -140,10 +144,10 @@ describe("RepeatEvent Component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders with no repetition by default", () => {
|
it("renders with no repetition by default", () => {
|
||||||
const { setRepetition } = setupRepeatEvent();
|
setupRepeatEvent();
|
||||||
|
|
||||||
// Check that interval input shows default value
|
// Check that interval input shows default value
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
const intervalInput = screen.getByTestId("repeat-interval");
|
||||||
expect(intervalInput).toBeInTheDocument();
|
expect(intervalInput).toBeInTheDocument();
|
||||||
|
|
||||||
// Check that frequency dropdown shows default value
|
// Check that frequency dropdown shows default value
|
||||||
@@ -170,14 +174,14 @@ describe("RepeatEvent Component", () => {
|
|||||||
it("renders interval input when frequency is selected", () => {
|
it("renders interval input when frequency is selected", () => {
|
||||||
setupRepeatEvent({ freq: "daily" });
|
setupRepeatEvent({ freq: "daily" });
|
||||||
|
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
const intervalInput = screen.getByTestId("repeat-interval");
|
||||||
expect(intervalInput).toBeInTheDocument();
|
expect(intervalInput).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates interval value", () => {
|
it("updates interval value", () => {
|
||||||
const { setRepetition } = setupRepeatEvent();
|
const { setRepetition } = setupRepeatEvent();
|
||||||
|
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
const intervalInput = screen.getByTestId("repeat-interval");
|
||||||
fireEvent.change(intervalInput, { target: { value: "3" } });
|
fireEvent.change(intervalInput, { target: { value: "3" } });
|
||||||
|
|
||||||
expect(setRepetition).toHaveBeenCalledWith(
|
expect(setRepetition).toHaveBeenCalledWith(
|
||||||
@@ -233,7 +237,7 @@ describe("Repeat Event Integration Tests", () => {
|
|||||||
fireEvent.click(dailyOption);
|
fireEvent.click(dailyOption);
|
||||||
|
|
||||||
// Set interval to 2
|
// Set interval to 2
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
const intervalInput = screen.getByTestId("repeat-interval");
|
||||||
fireEvent.change(intervalInput, { target: { value: "2" } });
|
fireEvent.change(intervalInput, { target: { value: "2" } });
|
||||||
|
|
||||||
await expectRRule({ freq: "daily", interval: 2 });
|
await expectRRule({ freq: "daily", interval: 2 });
|
||||||
@@ -326,7 +330,7 @@ describe("Repeat Event Integration Tests", () => {
|
|||||||
fireEvent.click(weeklyOption);
|
fireEvent.click(weeklyOption);
|
||||||
|
|
||||||
// Set interval to 3
|
// Set interval to 3
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
const intervalInput = screen.getByTestId("repeat-interval");
|
||||||
fireEvent.change(intervalInput, { target: { value: "3" } });
|
fireEvent.change(intervalInput, { target: { value: "3" } });
|
||||||
|
|
||||||
await expectRRule({ freq: "weekly", interval: 3 });
|
await expectRRule({ freq: "weekly", interval: 3 });
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import "dayjs/locale/en";
|
|||||||
import "dayjs/locale/fr";
|
import "dayjs/locale/fr";
|
||||||
import "dayjs/locale/ru";
|
import "dayjs/locale/ru";
|
||||||
import "dayjs/locale/vi";
|
import "dayjs/locale/vi";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useI18n } from "twake-i18n";
|
import { useI18n } from "twake-i18n";
|
||||||
import { ReadOnlyDateField } from "./components/ReadOnlyPickerField";
|
import { ReadOnlyDateField } from "./components/ReadOnlyPickerField";
|
||||||
import { LONG_DATE_FORMAT } from "./utils/dateTimeFormatters";
|
import { LONG_DATE_FORMAT } from "./utils/dateTimeFormatters";
|
||||||
@@ -46,31 +45,24 @@ export default function RepeatEvent({
|
|||||||
padding: "0 15px",
|
padding: "0 15px",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// derive endOption based on repetition
|
|
||||||
|
// Fully derived — occurrences takes priority over endDate
|
||||||
const getEndOption = () => {
|
const getEndOption = () => {
|
||||||
if (repetition.occurrences && repetition.occurrences > 0) return "after";
|
if (repetition.occurrences) return "after";
|
||||||
if (repetition.endDate) return "on";
|
if (repetition.endDate) return "on";
|
||||||
return "never";
|
return "never";
|
||||||
};
|
};
|
||||||
|
|
||||||
const [endOption, setEndOption] = useState(getEndOption());
|
const endOption = getEndOption();
|
||||||
|
|
||||||
// keep endOption in sync if repetition changes from parent
|
const defaultEndDate = dayjs(eventStart).add(1, "day").format("YYYY-MM-DD");
|
||||||
useEffect(() => {
|
|
||||||
const newEndOption = getEndOption();
|
|
||||||
if (endOption !== newEndOption) {
|
|
||||||
setEndOption(newEndOption);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [repetition.occurrences, repetition.endDate]);
|
|
||||||
|
|
||||||
const handleDayChange = (day: string) => {
|
const handleDayChange = (dayCode: string) => {
|
||||||
const currentDays = repetition.byday || [];
|
const currentDays = repetition.byday || [];
|
||||||
const updatedDays = currentDays.includes(day)
|
const updatedDays = currentDays.includes(dayCode)
|
||||||
? currentDays.filter((d) => d !== day)
|
? currentDays.filter((d) => d !== dayCode)
|
||||||
: [...currentDays, day];
|
: [...currentDays, dayCode];
|
||||||
|
|
||||||
// Only set byday if there are selected days, otherwise set to null
|
|
||||||
setRepetition({
|
setRepetition({
|
||||||
...repetition,
|
...repetition,
|
||||||
byday: updatedDays.length > 0 ? updatedDays : null,
|
byday: updatedDays.length > 0 ? updatedDays : null,
|
||||||
@@ -110,6 +102,7 @@ export default function RepeatEvent({
|
|||||||
style={{ width: 80 }}
|
style={{ width: 80 }}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
min: 1,
|
min: 1,
|
||||||
|
"data-testid": "repeat-interval",
|
||||||
style: {
|
style: {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
@@ -122,16 +115,14 @@ export default function RepeatEvent({
|
|||||||
disabled={!isOwn}
|
disabled={!isOwn}
|
||||||
onChange={(e: SelectChangeEvent) => {
|
onChange={(e: SelectChangeEvent) => {
|
||||||
if (e.target.value === "weekly") {
|
if (e.target.value === "weekly") {
|
||||||
// Adjust day index for MO-SU (0-6) to match JS getDay() (0-6, SU is 0)
|
const jsDay = day.getDay();
|
||||||
const jsDay = day.getDay(); // 0 for Sunday, 1 for Monday, ..., 6 for Saturday
|
const icsDay = days[(jsDay + 6) % 7];
|
||||||
const icsDay = days[(jsDay + 6) % 7]; // MO is 0, TU is 1, ..., SU is 6
|
|
||||||
setRepetition({
|
setRepetition({
|
||||||
...repetition,
|
...repetition,
|
||||||
freq: e.target.value,
|
freq: e.target.value,
|
||||||
byday: [icsDay], // Use byday instead of selectedDays
|
byday: [icsDay],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// For non-weekly frequencies, clear byday
|
|
||||||
setRepetition({
|
setRepetition({
|
||||||
...repetition,
|
...repetition,
|
||||||
freq: e.target.value,
|
freq: e.target.value,
|
||||||
@@ -218,8 +209,6 @@ export default function RepeatEvent({
|
|||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
if (value === endOption) return;
|
if (value === endOption) return;
|
||||||
|
|
||||||
setEndOption(value);
|
|
||||||
|
|
||||||
if (value === "never") {
|
if (value === "never") {
|
||||||
setRepetition({
|
setRepetition({
|
||||||
...repetition,
|
...repetition,
|
||||||
@@ -241,8 +230,7 @@ export default function RepeatEvent({
|
|||||||
setRepetition({
|
setRepetition({
|
||||||
...repetition,
|
...repetition,
|
||||||
occurrences: null,
|
occurrences: null,
|
||||||
endDate:
|
endDate: repetition.endDate || defaultEndDate,
|
||||||
repetition.endDate ?? new Date().toISOString().slice(0, 10),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -282,8 +270,11 @@ export default function RepeatEvent({
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
sx={{ width: "100%" }}
|
sx={{ width: "100%" }}
|
||||||
format={LONG_DATE_FORMAT}
|
format={LONG_DATE_FORMAT}
|
||||||
|
minDate={dayjs(eventStart)}
|
||||||
value={
|
value={
|
||||||
repetition.endDate ? dayjs(repetition.endDate) : null
|
repetition.endDate
|
||||||
|
? dayjs(repetition.endDate)
|
||||||
|
: dayjs(defaultEndDate)
|
||||||
}
|
}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!value || !value.isValid()) return;
|
if (!value || !value.isValid()) return;
|
||||||
@@ -293,26 +284,14 @@ export default function RepeatEvent({
|
|||||||
occurrences: null,
|
occurrences: null,
|
||||||
endDate: newDateStr,
|
endDate: newDateStr,
|
||||||
});
|
});
|
||||||
if (endOption !== "on") {
|
|
||||||
setEndOption("on");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onOpen={() => {
|
onOpen={() => {
|
||||||
if (!isOwn || endOption === "on") return;
|
if (!isOwn || endOption === "on") return;
|
||||||
setEndOption("on");
|
setRepetition({
|
||||||
if (!repetition.endDate) {
|
...repetition,
|
||||||
setRepetition({
|
occurrences: null,
|
||||||
...repetition,
|
endDate: repetition.endDate || defaultEndDate,
|
||||||
occurrences: null,
|
});
|
||||||
endDate: new Date().toISOString().slice(0, 10),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setRepetition({
|
|
||||||
...repetition,
|
|
||||||
occurrences: null,
|
|
||||||
endDate: repetition.endDate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
slots={{ field: ReadOnlyDateField }}
|
slots={{ field: ReadOnlyDateField }}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -339,7 +318,6 @@ export default function RepeatEvent({
|
|||||||
gap={1}
|
gap={1}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isOwn || endOption === "after") return;
|
if (!isOwn || endOption === "after") return;
|
||||||
setEndOption("after");
|
|
||||||
setRepetition({
|
setRepetition({
|
||||||
...repetition,
|
...repetition,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
@@ -357,7 +335,7 @@ export default function RepeatEvent({
|
|||||||
type="number"
|
type="number"
|
||||||
inputProps={{ min: 1, "data-testid": "occurrences-input" }}
|
inputProps={{ min: 1, "data-testid": "occurrences-input" }}
|
||||||
size="small"
|
size="small"
|
||||||
value={repetition.occurrences ?? 1}
|
value={repetition.occurrences || 1}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = Number(e.target.value);
|
const value = Number(e.target.value);
|
||||||
setRepetition({
|
setRepetition({
|
||||||
@@ -365,9 +343,6 @@ export default function RepeatEvent({
|
|||||||
endDate: null,
|
endDate: null,
|
||||||
occurrences: value > 0 ? value : 1,
|
occurrences: value > 0 ? value : 1,
|
||||||
});
|
});
|
||||||
if (endOption !== "after") {
|
|
||||||
setEndOption("after");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
style={{ width: 100 }}
|
style={{ width: 100 }}
|
||||||
disabled={!isOwn}
|
disabled={!isOwn}
|
||||||
|
|||||||
Reference in New Issue
Block a user