Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -0,0 +1,157 @@
|
|||||||
|
import { makeRecurrenceString } from "@/features/Events/EventPreview/utils/makeRecurrenceString";
|
||||||
|
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
||||||
|
|
||||||
|
describe("makeRecurrenceString", () => {
|
||||||
|
const mockT = jest.fn((key: string, params?: any) => {
|
||||||
|
if (params) {
|
||||||
|
if (key === "eventPreview.everyInterval") {
|
||||||
|
return `Every ${params.interval} ${params.unit}`;
|
||||||
|
}
|
||||||
|
if (key === "eventPreview.recurrenceOnDays") {
|
||||||
|
return `on ${params.days}`;
|
||||||
|
}
|
||||||
|
if (key === "eventPreview.forOccurrences") {
|
||||||
|
return `for ${params.count} times`;
|
||||||
|
}
|
||||||
|
if (key === "eventPreview.until") {
|
||||||
|
return `until ${params.date}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const translations: Record<string, string> = {
|
||||||
|
"event.repeat.frequency.days": "days",
|
||||||
|
"event.repeat.frequency.weeks": "weeks",
|
||||||
|
"event.repeat.frequency.months": "months",
|
||||||
|
"event.repeat.frequency.years": "years",
|
||||||
|
"eventPreview.onDays.MO": "Monday",
|
||||||
|
"eventPreview.onDays.TU": "Tuesday",
|
||||||
|
"eventPreview.onDays.WE": "Wednesday",
|
||||||
|
"eventPreview.onDays.TH": "Thursday",
|
||||||
|
"eventPreview.onDays.FR": "Friday",
|
||||||
|
"eventPreview.onDays.SA": "Saturday",
|
||||||
|
"eventPreview.onDays.SU": "Sunday",
|
||||||
|
locale: "en-US",
|
||||||
|
};
|
||||||
|
|
||||||
|
return translations[key] || key;
|
||||||
|
});
|
||||||
|
|
||||||
|
const startText = "Repeats";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockT.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats string for interval === 1 when enableStrForOneTimeInterval is true", () => {
|
||||||
|
const repetition: RepetitionObject = { freq: "daily", interval: 1 };
|
||||||
|
const result = makeRecurrenceString({
|
||||||
|
repetition,
|
||||||
|
t: mockT,
|
||||||
|
startText,
|
||||||
|
enableStrForOneTimeInterval: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("Repeats, days");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats string when interval is undefined", () => {
|
||||||
|
const repetition: RepetitionObject = { freq: "daily" };
|
||||||
|
const result = makeRecurrenceString({
|
||||||
|
repetition,
|
||||||
|
t: mockT,
|
||||||
|
startText,
|
||||||
|
enableStrForOneTimeInterval: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("Repeats, days");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats string for interval > 1", () => {
|
||||||
|
const repetition: RepetitionObject = { freq: "weekly", interval: 2 };
|
||||||
|
const result = makeRecurrenceString({ repetition, t: mockT, startText });
|
||||||
|
|
||||||
|
expect(result).toBe("Repeats, Every 2 weeks");
|
||||||
|
expect(mockT).toHaveBeenCalledWith("eventPreview.everyInterval", {
|
||||||
|
interval: 2,
|
||||||
|
unit: "weeks",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats string with specific days and sorts them according to WEEK_DAYS order", () => {
|
||||||
|
const repetition: RepetitionObject = {
|
||||||
|
freq: "weekly",
|
||||||
|
interval: 1,
|
||||||
|
byday: ["WE", "MO", "FR"],
|
||||||
|
};
|
||||||
|
const result = makeRecurrenceString({ repetition, t: mockT, startText });
|
||||||
|
|
||||||
|
// Expected order: MO, WE, FR
|
||||||
|
expect(result).toBe("Repeats, on Monday, Wednesday, Friday");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats string with occurrences count", () => {
|
||||||
|
const repetition: RepetitionObject = {
|
||||||
|
freq: "daily",
|
||||||
|
interval: 1,
|
||||||
|
occurrences: 5,
|
||||||
|
};
|
||||||
|
const result = makeRecurrenceString({ repetition, t: mockT, startText });
|
||||||
|
|
||||||
|
expect(result).toBe("Repeats, for 5 times");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats string with end date", () => {
|
||||||
|
const endDate = "2025-12-31T00:00:00.000Z";
|
||||||
|
const repetition: RepetitionObject = {
|
||||||
|
freq: "daily",
|
||||||
|
interval: 1,
|
||||||
|
endDate,
|
||||||
|
};
|
||||||
|
const result = makeRecurrenceString({ repetition, t: mockT, startText });
|
||||||
|
|
||||||
|
const formattedDate = new Date(endDate).toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(`Repeats, until ${formattedDate}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("combines multiple properties", () => {
|
||||||
|
const endDate = "2025-12-31T00:00:00.000Z";
|
||||||
|
const repetition: RepetitionObject = {
|
||||||
|
freq: "monthly",
|
||||||
|
interval: 3,
|
||||||
|
byday: ["TU", "TH"],
|
||||||
|
endDate,
|
||||||
|
};
|
||||||
|
const result = makeRecurrenceString({ repetition, t: mockT, startText });
|
||||||
|
|
||||||
|
const formattedDate = new Date(endDate).toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
`Repeats, Every 3 months, on Tuesday, Thursday, until ${formattedDate}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses custom joinChar when provided", () => {
|
||||||
|
const repetition: RepetitionObject = {
|
||||||
|
freq: "weekly",
|
||||||
|
interval: 2,
|
||||||
|
occurrences: 10,
|
||||||
|
};
|
||||||
|
const result = makeRecurrenceString({
|
||||||
|
repetition,
|
||||||
|
t: mockT,
|
||||||
|
startText,
|
||||||
|
joinChar: ";",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("Repeats; Every 2 weeks; for 10 times");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,7 @@ import { getTimezoneOffset } from "@/utils/timezone";
|
|||||||
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
||||||
import { LONG_DATE_FORMAT } from "../utils/dateTimeFormatters";
|
import { LONG_DATE_FORMAT } from "../utils/dateTimeFormatters";
|
||||||
import { SectionPreviewRow } from "./SectionPreviewRow";
|
import { SectionPreviewRow } from "./SectionPreviewRow";
|
||||||
|
import { makeRecurrenceString } from "@/features/Events/EventPreview/utils/makeRecurrenceString";
|
||||||
|
|
||||||
interface DateTimeSummaryProps {
|
interface DateTimeSummaryProps {
|
||||||
startDate: string;
|
startDate: string;
|
||||||
@@ -99,21 +100,15 @@ export const DateTimeSummary: React.FC<DateTimeSummaryProps> = ({
|
|||||||
return t("event.repeat.doesNotRepeat");
|
return t("event.repeat.doesNotRepeat");
|
||||||
}
|
}
|
||||||
|
|
||||||
const interval = rep.interval || 1;
|
return (
|
||||||
const freqMap: { [key: string]: string } = {
|
makeRecurrenceString({
|
||||||
daily: t("event.repeat.frequency.days"),
|
repetition: rep,
|
||||||
weekly: t("event.repeat.frequency.weeks"),
|
t,
|
||||||
monthly: t("event.repeat.frequency.months"),
|
startText: rep.interval === 1 ? t("event.repeat.every") : "",
|
||||||
yearly: t("event.repeat.frequency.years"),
|
joinChar: "",
|
||||||
};
|
enableStrForOneTimeInterval: true,
|
||||||
|
}) || ""
|
||||||
const freqText = freqMap[rep.freq] || rep.freq;
|
);
|
||||||
|
|
||||||
if (interval === 1) {
|
|
||||||
return `${t("event.repeat.every")} ${freqText}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${t("event.repeat.every")} ${interval} ${freqText}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format date text: show both start and end date if showEndDate is true
|
// Format date text: show both start and end date if showEndDate is true
|
||||||
|
|||||||
@@ -244,7 +244,14 @@ export function EventPreviewDetails({
|
|||||||
<RepeatIcon />
|
<RepeatIcon />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
text={makeRecurrenceString(event, t)}
|
text={makeRecurrenceString({
|
||||||
|
repetition: event.repetition,
|
||||||
|
t,
|
||||||
|
startText: `${t("eventPreview.recurrentEvent")} · ${t(
|
||||||
|
`eventPreview.freq.${event.repetition.freq}`,
|
||||||
|
event.repetition.freq
|
||||||
|
)}`,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import { CalendarEvent } from "../../EventsTypes";
|
import { RepetitionObject } from "../../EventsTypes";
|
||||||
|
|
||||||
export function makeRecurrenceString(
|
const WEEK_DAYS = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||||
event: CalendarEvent,
|
|
||||||
t: (k: string, p?: string | object) => string
|
|
||||||
): string | undefined {
|
|
||||||
if (!event.repetition) return;
|
|
||||||
|
|
||||||
const recur: string[] = [
|
interface MakeRecurrenceStringParams {
|
||||||
`${t("eventPreview.recurrentEvent")} · ${t(
|
repetition: RepetitionObject;
|
||||||
`eventPreview.freq.${event.repetition.freq}`,
|
t: (k: string, p?: string | object) => string;
|
||||||
event.repetition.freq
|
startText: string;
|
||||||
)}`,
|
joinChar?: string;
|
||||||
];
|
enableStrForOneTimeInterval?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeRecurrenceString({
|
||||||
|
repetition,
|
||||||
|
t,
|
||||||
|
startText,
|
||||||
|
joinChar,
|
||||||
|
enableStrForOneTimeInterval,
|
||||||
|
}: MakeRecurrenceStringParams): string | undefined {
|
||||||
|
if (!repetition) return;
|
||||||
|
|
||||||
|
const recur: string[] = [startText];
|
||||||
|
|
||||||
const recurType: Record<string, string> = {
|
const recurType: Record<string, string> = {
|
||||||
daily: t("event.repeat.frequency.days"),
|
daily: t("event.repeat.frequency.days"),
|
||||||
@@ -20,45 +28,51 @@ export function makeRecurrenceString(
|
|||||||
yearly: t("event.repeat.frequency.years"),
|
yearly: t("event.repeat.frequency.years"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (event.repetition.interval && event.repetition.interval > 1) {
|
const interval = repetition.interval ?? 1;
|
||||||
|
|
||||||
|
if (interval === 1 && enableStrForOneTimeInterval) {
|
||||||
|
recur.push(recurType[repetition.freq]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interval > 1) {
|
||||||
recur.push(
|
recur.push(
|
||||||
t("eventPreview.everyInterval", {
|
t("eventPreview.everyInterval", {
|
||||||
interval: event.repetition.interval,
|
interval,
|
||||||
unit: recurType[event.repetition.freq] ?? event.repetition.freq,
|
unit: recurType[repetition.freq] ?? repetition.freq,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.repetition.byday) {
|
if (repetition.byday) {
|
||||||
|
const weekDaysByOrder = WEEK_DAYS.filter((day) =>
|
||||||
|
repetition.byday?.includes(day)
|
||||||
|
);
|
||||||
recur.push(
|
recur.push(
|
||||||
t("eventPreview.recurrenceOnDays", {
|
t("eventPreview.recurrenceOnDays", {
|
||||||
days: event.repetition.byday
|
days: weekDaysByOrder
|
||||||
.map((s) => t(`eventPreview.onDays.${s}`))
|
.map((s) => t(`eventPreview.onDays.${s}`))
|
||||||
.join(", "),
|
.join(", "),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.repetition.occurrences) {
|
if (repetition.occurrences) {
|
||||||
recur.push(
|
recur.push(
|
||||||
t("eventPreview.forOccurrences", {
|
t("eventPreview.forOccurrences", {
|
||||||
count: event.repetition.occurrences,
|
count: repetition.occurrences,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (event.repetition.endDate) {
|
if (repetition.endDate) {
|
||||||
recur.push(
|
recur.push(
|
||||||
t("eventPreview.until", {
|
t("eventPreview.until", {
|
||||||
date: new Date(event.repetition.endDate).toLocaleDateString(
|
date: new Date(repetition.endDate).toLocaleDateString(t("locale"), {
|
||||||
t("locale"),
|
year: "numeric",
|
||||||
{
|
month: "long",
|
||||||
year: "numeric",
|
day: "numeric",
|
||||||
month: "long",
|
}),
|
||||||
day: "numeric",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return recur.join(", ");
|
return recur.filter(Boolean).join(`${joinChar ?? ","} `);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -318,7 +318,7 @@
|
|||||||
"SU": "sunday"
|
"SU": "sunday"
|
||||||
},
|
},
|
||||||
"recurrenceOnDays": "on %{days}",
|
"recurrenceOnDays": "on %{days}",
|
||||||
"everyInterval": "every %{interval} %{unit}",
|
"everyInterval": "Every %{interval} %{unit}",
|
||||||
"forOccurrences": "for %{count} occurrences",
|
"forOccurrences": "for %{count} occurrences",
|
||||||
"until": "until %{date}",
|
"until": "until %{date}",
|
||||||
"days": "days",
|
"days": "days",
|
||||||
|
|||||||
+1
-1
@@ -319,7 +319,7 @@
|
|||||||
"SU": "dimanche"
|
"SU": "dimanche"
|
||||||
},
|
},
|
||||||
"recurrenceOnDays": "le %{days}",
|
"recurrenceOnDays": "le %{days}",
|
||||||
"everyInterval": "tous les %{interval} %{unit}",
|
"everyInterval": "Tous les %{interval} %{unit}",
|
||||||
"forOccurrences": "pour %{count} occurrences",
|
"forOccurrences": "pour %{count} occurrences",
|
||||||
"until": "jusqu'au %{date}",
|
"until": "jusqu'au %{date}",
|
||||||
"days": "jours",
|
"days": "jours",
|
||||||
|
|||||||
+1
-1
@@ -319,7 +319,7 @@
|
|||||||
"SU": "ВС"
|
"SU": "ВС"
|
||||||
},
|
},
|
||||||
"recurrenceOnDays": "по %{days}",
|
"recurrenceOnDays": "по %{days}",
|
||||||
"everyInterval": "каждые %{interval} %{unit}",
|
"everyInterval": "Каждые %{interval} %{unit}",
|
||||||
"forOccurrences": "в течение %{count} повторений",
|
"forOccurrences": "в течение %{count} повторений",
|
||||||
"until": "до %{date}",
|
"until": "до %{date}",
|
||||||
"days": "дни",
|
"days": "дни",
|
||||||
|
|||||||
+1
-1
@@ -317,7 +317,7 @@
|
|||||||
"SU": "chủ nhật"
|
"SU": "chủ nhật"
|
||||||
},
|
},
|
||||||
"recurrenceOnDays": "vào %{days}",
|
"recurrenceOnDays": "vào %{days}",
|
||||||
"everyInterval": "mỗi %{interval} %{unit}",
|
"everyInterval": "Mỗi %{interval} %{unit}",
|
||||||
"forOccurrences": "trong %{count} lần",
|
"forOccurrences": "trong %{count} lần",
|
||||||
"until": "đến %{date}",
|
"until": "đến %{date}",
|
||||||
"days": "ngày",
|
"days": "ngày",
|
||||||
|
|||||||
Reference in New Issue
Block a user