Add calendar toolbox with weekly and monthly scheduling
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
This commit is contained in:
parent
4f73e0068b
commit
992665f4ea
39 changed files with 4331 additions and 36 deletions
|
|
@ -329,6 +329,77 @@ export function validateSiteContent(site) {
|
|||
"toolboxes.modules.timer.emptyCountdowns",
|
||||
"toolboxes.modules.timer.renameTitle",
|
||||
"toolboxes.modules.timer.deleteTitle",
|
||||
"toolboxes.modules.calendar.init.title",
|
||||
"toolboxes.modules.calendar.init.weekly",
|
||||
"toolboxes.modules.calendar.init.weeklyDescription",
|
||||
"toolboxes.modules.calendar.init.monthly",
|
||||
"toolboxes.modules.calendar.init.monthlyDescription",
|
||||
"toolboxes.modules.calendar.init.soon",
|
||||
"toolboxes.modules.calendar.gridLabel",
|
||||
"toolboxes.modules.calendar.monthGridLabel",
|
||||
"toolboxes.modules.calendar.titleLabel",
|
||||
"toolboxes.modules.calendar.titlePlaceholder",
|
||||
"toolboxes.modules.calendar.dayLabel",
|
||||
"toolboxes.modules.calendar.startDayLabel",
|
||||
"toolboxes.modules.calendar.endDayLabel",
|
||||
"toolboxes.modules.calendar.startLabel",
|
||||
"toolboxes.modules.calendar.endLabel",
|
||||
"toolboxes.modules.calendar.alertModeTitle",
|
||||
"toolboxes.modules.calendar.alertTypeLabel",
|
||||
"toolboxes.modules.calendar.alertOffTitle",
|
||||
"toolboxes.modules.calendar.alertVisibleTitle",
|
||||
"toolboxes.modules.calendar.alertSiteTitle",
|
||||
"toolboxes.modules.calendar.alertOffsetLabel",
|
||||
"toolboxes.modules.calendar.alertOffset10",
|
||||
"toolboxes.modules.calendar.alertOffset5",
|
||||
"toolboxes.modules.calendar.alertOffset0",
|
||||
"toolboxes.modules.calendar.alertMessage",
|
||||
"toolboxes.modules.calendar.resumeAlertMessage",
|
||||
"toolboxes.modules.calendar.colorLabel",
|
||||
"toolboxes.modules.calendar.colorViolet",
|
||||
"toolboxes.modules.calendar.colorGold",
|
||||
"toolboxes.modules.calendar.colorCyan",
|
||||
"toolboxes.modules.calendar.colorPink",
|
||||
"toolboxes.modules.calendar.colorGreen",
|
||||
"toolboxes.modules.calendar.colorGray",
|
||||
"toolboxes.modules.calendar.repeatableTitle",
|
||||
"toolboxes.modules.calendar.repeatLabel",
|
||||
"toolboxes.modules.calendar.repeatNone",
|
||||
"toolboxes.modules.calendar.repeatWeekly",
|
||||
"toolboxes.modules.calendar.repeatMonthly",
|
||||
"toolboxes.modules.calendar.monthlyRepeatLabel",
|
||||
"toolboxes.modules.calendar.monthlyRepeatMonthDay",
|
||||
"toolboxes.modules.calendar.monthlyRepeatWeekday",
|
||||
"toolboxes.modules.calendar.monthDatePickerLabel",
|
||||
"toolboxes.modules.calendar.previousMonthTitle",
|
||||
"toolboxes.modules.calendar.nextMonthTitle",
|
||||
"toolboxes.modules.calendar.currentMonthTitle",
|
||||
"toolboxes.modules.calendar.previousShortLabel",
|
||||
"toolboxes.modules.calendar.nextShortLabel",
|
||||
"toolboxes.modules.calendar.hideEmptyHoursLabel",
|
||||
"toolboxes.modules.calendar.importPlaceholder",
|
||||
"toolboxes.modules.calendar.importOpenButton",
|
||||
"toolboxes.modules.calendar.exportOpenButton",
|
||||
"toolboxes.modules.calendar.importModalTitle",
|
||||
"toolboxes.modules.calendar.exportModalTitle",
|
||||
"toolboxes.modules.calendar.importButton",
|
||||
"toolboxes.modules.calendar.exportButton",
|
||||
"toolboxes.modules.calendar.exportInfo",
|
||||
"toolboxes.modules.calendar.importSuccess",
|
||||
"toolboxes.modules.calendar.exportSuccess",
|
||||
"toolboxes.modules.calendar.importError",
|
||||
"toolboxes.modules.calendar.addButton",
|
||||
"toolboxes.modules.calendar.saveButton",
|
||||
"toolboxes.modules.calendar.cancelButton",
|
||||
"toolboxes.modules.calendar.editTitle",
|
||||
"toolboxes.modules.calendar.copyTitle",
|
||||
"toolboxes.modules.calendar.copiedTitle",
|
||||
"toolboxes.modules.calendar.pastedTitle",
|
||||
"toolboxes.modules.calendar.deleteTitle",
|
||||
"toolboxes.modules.calendar.deleteOccurrenceTitle",
|
||||
"toolboxes.modules.calendar.deleteSeriesTitle",
|
||||
"toolboxes.modules.calendar.requiredError",
|
||||
"toolboxes.modules.calendar.invalidRangeError",
|
||||
"toolboxes.modules.taskPlanner.settingsTitle",
|
||||
"toolboxes.modules.taskPlanner.settingsButtonLabel",
|
||||
"toolboxes.modules.taskPlanner.hideCompletedTitle",
|
||||
|
|
@ -484,7 +555,7 @@ export function validateSiteContent(site) {
|
|||
});
|
||||
|
||||
const libraryToolDocs = valueAt(site, "library.toolDocs");
|
||||
["notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "timer", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => {
|
||||
["notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "timer", "calendar", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => {
|
||||
const docs = libraryToolDocs?.[toolType];
|
||||
assert.equal(typeof docs, "object", `library.toolDocs.${toolType} must be an object`);
|
||||
assert.ok(Array.isArray(docs.controls), `library.toolDocs.${toolType}.controls must be an array`);
|
||||
|
|
@ -515,9 +586,11 @@ export function validateSiteContent(site) {
|
|||
});
|
||||
}
|
||||
});
|
||||
assert.equal(typeof docs.importFormat, "object", `library.toolDocs.${toolType}.importFormat must be an object`);
|
||||
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.text");
|
||||
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.example");
|
||||
if (docs.importFormat !== undefined) {
|
||||
assert.equal(typeof docs.importFormat, "object", `library.toolDocs.${toolType}.importFormat must be an object`);
|
||||
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.text");
|
||||
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.example");
|
||||
}
|
||||
});
|
||||
|
||||
const aboutLimits = valueAt(site, "about.limits");
|
||||
|
|
@ -695,7 +768,7 @@ export function validateLibraryData(payload) {
|
|||
assert.deepEqual(new Set(twoColumnIds), moduleIds, "library.toolbox.moduleOrder.two must contain every module exactly once");
|
||||
assert.equal(twoColumnIds.length, moduleIds.size, "library.toolbox.moduleOrder.two must not contain duplicate modules");
|
||||
|
||||
["notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "timer", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => {
|
||||
["notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => {
|
||||
assert.ok(payload.toolbox.modules.some((module) => module.type === type), `library must include a ${type} example`);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ test("vite entrypoint and app shell are wired", async () => {
|
|||
assert.match(modals, /export function CreateToolboxModal/);
|
||||
assert.match(modals, /export function LinkToolboxModal/);
|
||||
assert.match(modals, /export function NotificationToast/);
|
||||
assert.match(modals, /durationMs = 120000/);
|
||||
assert.match(modals, /setTimeout\(\(\) => onClose\(id\), durationMs\)/);
|
||||
assert.match(source, /SHORT_NOTIFICATION_DURATION_MS = 5000/);
|
||||
assert.match(source, /getNotificationDuration/);
|
||||
assert.match(modals, /useModalScrollLock/);
|
||||
assert.match(imageViewer, /export function ImageViewer/);
|
||||
assert.match(imageViewer, /function openImageInNewTab/);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import { applyDueResets, getTaskParentMap, getTaskTreeIds, moveTask } from "../w
|
|||
import { createInput, getInputLabel, normalizeCustomKey, parseHoldSeconds } from "../website/src/features/toolboxes/modules/combosUtils.js";
|
||||
import { getBoundsFromStartAndSize, getSelectionBounds, moveSelectionCells, parseTsvBlock, pasteCellBlock } from "../website/src/features/toolboxes/modules/tableUtils.js";
|
||||
import { canToggleAutoRefresh, getCountdownValidationError, isAlertGuarded } from "../website/src/features/toolboxes/modules/timerOptions.js";
|
||||
import { getCalendarOccurrences, getCurrentCalendarOccurrence, getMonthEnd, getMonthStart, getNextCalendarAlertTime, getWeekStart } from "../website/src/features/toolboxes/modules/calendarUtils.js";
|
||||
import { exportModuleText, importModuleText } from "../website/src/features/toolboxes/modules/textImport.js";
|
||||
import { compactCalendarDataForStorage, normalizeCalendarData } from "../website/src/features/toolboxes/storage/modules/calendar.js";
|
||||
|
||||
test("toolbox storage, cards and pages are wired", async () => {
|
||||
const source = await readFile("website/src/main.jsx", "utf8");
|
||||
|
|
@ -17,6 +20,7 @@ test("toolbox storage, cards and pages are wired", async () => {
|
|||
const storageCompactorRegistry = await readFile("website/src/features/toolboxes/storage/modules/index.js", "utf8");
|
||||
const checklistStorage = await readFile("website/src/features/toolboxes/storage/modules/checklist.js", "utf8");
|
||||
const timerStorage = await readFile("website/src/features/toolboxes/storage/modules/timer.js", "utf8");
|
||||
const calendarStorage = await readFile("website/src/features/toolboxes/storage/modules/calendar.js", "utf8");
|
||||
const equipmentStorage = await readFile("website/src/features/toolboxes/storage/modules/equipmentPlanner.js", "utf8");
|
||||
const indexedToolboxesHook = await readFile("website/src/features/toolboxes/storage/useIndexedToolboxes.js", "utf8");
|
||||
const storageQuota = await readFile("website/src/components/StorageQuota.jsx", "utf8");
|
||||
|
|
@ -56,6 +60,7 @@ test("toolbox storage, cards and pages are wired", async () => {
|
|||
assert.match(toolboxStorage, /normalizeCalculatorData/);
|
||||
assert.match(toolboxStorage, /normalizeTableData/);
|
||||
assert.match(toolboxStorage, /normalizeTimerData/);
|
||||
assert.match(toolboxStorage, /normalizeCalendarData/);
|
||||
assert.match(toolboxStorage, /normalizeTaskPlannerData/);
|
||||
assert.match(toolboxStorage, /normalizeEquipmentPlannerData/);
|
||||
assert.match(toolboxStorage, /summarizeEquipmentPlannerData/);
|
||||
|
|
@ -63,7 +68,10 @@ test("toolbox storage, cards and pages are wired", async () => {
|
|||
assert.match(toolboxStorage, /export function compactModuleDataForStorage\(type, value\)/);
|
||||
assert.match(storageCompactorRegistry, /export const MODULE_DATA_COMPACTORS/);
|
||||
assert.match(storageCompactorRegistry, /timer: compactTimerDataForStorage/);
|
||||
assert.match(storageCompactorRegistry, /calendar: compactCalendarDataForStorage/);
|
||||
assert.match(timerStorage, /scrollResults/);
|
||||
assert.match(calendarStorage, /normalizeCalendarData/);
|
||||
assert.match(toolboxStorage, /nextId\("calendar"\)/);
|
||||
assert.match(toolboxCard, /DEFAULT_TOOLBOX_ICON/);
|
||||
assert.match(toolboxCard, /export function ToolboxCard/);
|
||||
assert.match(toolboxCard, /export function ToolboxIconPicker/);
|
||||
|
|
@ -140,6 +148,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
const timerModule = await readFile("website/src/features/toolboxes/modules/TimerModule.jsx", "utf8");
|
||||
const timerParts = await readFile("website/src/features/toolboxes/modules/TimerParts.jsx", "utf8");
|
||||
const timerOptions = await readFile("website/src/features/toolboxes/modules/timerOptions.js", "utf8");
|
||||
const calendarModule = await readFile("website/src/features/toolboxes/modules/CalendarModule.jsx", "utf8");
|
||||
const calendarUtils = await readFile("website/src/features/toolboxes/modules/calendarUtils.js", "utf8");
|
||||
const taskPlannerModule = await readFile("website/src/features/toolboxes/modules/TaskPlannerModule.jsx", "utf8");
|
||||
const taskPlannerItems = await readFile("website/src/features/toolboxes/modules/TaskPlannerItems.jsx", "utf8");
|
||||
const taskPlannerUtils = await readFile("website/src/features/toolboxes/modules/taskPlannerUtils.js", "utf8");
|
||||
|
|
@ -177,6 +187,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(moduleRegistry, /table:/);
|
||||
assert.match(moduleRegistry, /table: \{ \.\.\.TOOLBOX_MODULE_DEFINITIONS\.table, Component: TableModule \}/);
|
||||
assert.match(moduleRegistry, /timer:/);
|
||||
assert.match(moduleRegistry, /calendar:/);
|
||||
assert.match(moduleRegistry, /taskPlanner:/);
|
||||
assert.match(moduleRegistry, /equipmentPlanner:/);
|
||||
assert.match(moduleMetadataRegistry, /Planificateur d'équipements/);
|
||||
|
|
@ -287,6 +298,35 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(timerModule, /useDraftForm/);
|
||||
assert.match(timerModule, /normalizeTimerData/);
|
||||
assert.match(timerModule, /TextExchangeActions/);
|
||||
assert.match(calendarModule, /export function CalendarModule/);
|
||||
assert.match(calendarModule, /normalizeCalendarData/);
|
||||
assert.match(calendarModule, /calendar-mode-choice/);
|
||||
assert.match(calendarModule, /calendar-week-grid/);
|
||||
assert.match(calendarModule, /calendar-month-grid/);
|
||||
assert.match(calendarModule, /getMonthOccurrenceSegments/);
|
||||
assert.match(calendarModule, /calendar-month-bar/);
|
||||
assert.match(calendarModule, /MonthDateDropdown/);
|
||||
assert.match(calendarModule, /MonthEventRepeatSwitch/);
|
||||
assert.match(calendarModule, /monthlyRepeatBy/);
|
||||
assert.match(calendarModule, /displayedMonth/);
|
||||
assert.match(calendarModule, /deleteOccurrence/);
|
||||
assert.match(calendarModule, /onPointerDown/);
|
||||
assert.match(calendarModule, /copyEvent/);
|
||||
assert.match(calendarModule, /calendar-repeat-switch/);
|
||||
assert.match(calendarModule, /layout-switch calendar-month-repeat-switch/);
|
||||
assert.match(calendarModule, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/);
|
||||
assert.match(calendarModule, /durationMinutes < 120/);
|
||||
assert.match(calendarModule, /is-compact/);
|
||||
assert.match(calendarModule, /handleKeyDown/);
|
||||
assert.match(calendarModule, /TextExchangeActions/);
|
||||
assert.match(calendarModule, /type="calendar"/);
|
||||
assert.match(calendarModule, /repeatLabel/);
|
||||
assert.match(calendarModule, /CalendarAlertModeSwitch/);
|
||||
assert.doesNotMatch(moduleRegistry, /calendarInitOpen/);
|
||||
assert.doesNotMatch(moduleRegistry, /calendar-init-modal/);
|
||||
assert.match(calendarUtils, /export function getCalendarOccurrences/);
|
||||
assert.match(calendarUtils, /CALENDAR_VIEW_MODES = \["week", "month"\]/);
|
||||
assert.match(calendarUtils, /export function getNextCalendarAlertTime/);
|
||||
assert.match(taskPlannerModule, /export function TaskPlannerModule/);
|
||||
assert.match(taskPlannerModule, /normalizeTaskPlannerData/);
|
||||
assert.match(taskPlannerModule, /applyDueResets/);
|
||||
|
|
@ -397,6 +437,9 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(textImport, /export function importModuleText/);
|
||||
assert.match(textImport, /export function hasTextExchangeContent/);
|
||||
assert.match(textImport, /line\.indexOf\(":"\)/);
|
||||
assert.match(textImport, /exportCalendarText/);
|
||||
assert.match(textImport, /importCalendarText/);
|
||||
assert.match(textImport, /CALENDAR_WEEKDAYS/);
|
||||
assert.match(imageViewer, /image-viewer-media/);
|
||||
assert.match(imageViewer, /image-viewer-image-frame/);
|
||||
assert.match(imageViewer, /image-viewer-marker/);
|
||||
|
|
@ -480,3 +523,111 @@ test("timer options keep alert guards and countdown validation stable", () => {
|
|||
assert.equal(getCountdownValidationError("duration", { duration: { hours: "", minutes: "", seconds: "" } }, {}), "Valeur requise.");
|
||||
assert.equal(getCountdownValidationError("daily_time", { time: { hours: "12", minutes: "30", seconds: "" } }, {}), "");
|
||||
});
|
||||
|
||||
test("calendar helpers keep normalization, occurrences and alerts stable", () => {
|
||||
const data = normalizeCalendarData({
|
||||
events: [
|
||||
{
|
||||
id: "h1",
|
||||
title: "Raid",
|
||||
dayIndex: 4,
|
||||
startMinutes: 1080,
|
||||
endMinutes: 1200,
|
||||
repeatable: false,
|
||||
alertMode: "site",
|
||||
alertOffsetMinutes: [10, 5, 0],
|
||||
color: "gold"
|
||||
},
|
||||
{ title: "", dayIndex: 9, startMinutes: -1 }
|
||||
]
|
||||
});
|
||||
assert.equal(data.events.length, 1);
|
||||
assert.equal(data.initialized, true);
|
||||
assert.equal(data.events[0].repeat, "none");
|
||||
assert.equal(data.events[0].repeatable, false);
|
||||
const compactCalendar = compactCalendarDataForStorage(data);
|
||||
assert.equal(compactCalendar.initialized, true);
|
||||
assert.deepEqual(compactCalendar.events[0], {
|
||||
id: "h1",
|
||||
title: "Raid",
|
||||
dayIndex: 4,
|
||||
startMinutes: 1080,
|
||||
endMinutes: 1200,
|
||||
repeatable: false,
|
||||
alertMode: "site",
|
||||
alertOffsetMinutes: [10, 5, 0],
|
||||
color: "gold"
|
||||
});
|
||||
|
||||
const weekStart = getWeekStart(new Date("2026-08-17T12:00:00.000Z"), 1);
|
||||
const occurrences = getCalendarOccurrences(data.events, weekStart, new Date(weekStart.getTime() + 7 * 24 * 60 * 60 * 1000));
|
||||
assert.equal(occurrences.length, 1);
|
||||
assert.equal(new Date(occurrences[0].startMs).getHours(), 18);
|
||||
assert.equal(getNextCalendarAlertTime(data.events[0], new Date(2026, 7, 14, 17, 49).getTime()), new Date(2026, 7, 14, 17, 50).getTime());
|
||||
assert.equal(getCurrentCalendarOccurrence(data.events[0], new Date(2026, 7, 14, 19, 0).getTime()).event.id, "h1");
|
||||
|
||||
const monthly = normalizeCalendarData({
|
||||
viewMode: "month",
|
||||
events: [
|
||||
{ id: "h2", title: "Prime", startDate: "2026-08-14", endDate: "2026-08-16", repeat: "monthly", alertMode: "visible", alertOffsetMinutes: [0] },
|
||||
{ id: "h3", title: "Premier lundi", startDate: "2026-08-03", endDate: "2026-08-05", repeat: "monthly", monthlyRepeatBy: "weekday" },
|
||||
{ id: "h6", title: "Hebdo", startDate: "2026-08-03", endDate: "2026-08-05", repeat: "weekly", monthlyRepeatBy: "weekday", excludedOccurrences: ["2026-08-10"] },
|
||||
{ id: "bad", title: "Sans cible", startMinutes: 720 }
|
||||
]
|
||||
});
|
||||
assert.equal(monthly.viewMode, "month");
|
||||
assert.equal(monthly.events.length, 3);
|
||||
assert.equal(monthly.events[0].repeat, "monthly");
|
||||
assert.equal(monthly.events[0].monthlyRepeatBy, "monthDay");
|
||||
assert.equal(monthly.events[1].monthlyRepeatBy, "weekday");
|
||||
assert.equal(monthly.events[2].repeat, "weekly");
|
||||
const compactMonthly = compactCalendarDataForStorage(monthly);
|
||||
assert.equal(compactMonthly.viewMode, "month");
|
||||
assert.equal(compactMonthly.events[0].startDate, "2026-08-14");
|
||||
assert.equal(compactMonthly.events[0].endDate, "2026-08-16");
|
||||
assert.equal("startMinutes" in compactMonthly.events[0], false);
|
||||
assert.equal(compactMonthly.events[1].monthlyRepeatBy, "weekday");
|
||||
assert.equal(compactMonthly.events[2].repeat, "weekly");
|
||||
assert.deepEqual(compactMonthly.events[2].excludedOccurrences, ["2026-08-10"]);
|
||||
|
||||
const augustStart = getMonthStart(new Date(2026, 7, 14));
|
||||
const augustOccurrences = getCalendarOccurrences(monthly.events, augustStart, getMonthEnd(augustStart));
|
||||
assert.equal(augustOccurrences.filter((occurrence) => occurrence.event.id === "h2").length, 1);
|
||||
assert.equal(augustOccurrences.filter((occurrence) => occurrence.event.id === "h3").length, 1);
|
||||
assert.equal(augustOccurrences.filter((occurrence) => occurrence.event.id === "h6").length, 4);
|
||||
assert.equal(new Date(augustOccurrences.find((occurrence) => occurrence.event.id === "h2").endMs).getDate(), 17);
|
||||
assert.equal(getNextCalendarAlertTime(monthly.events[0], new Date(2026, 7, 13, 23, 59).getTime()), new Date(2026, 7, 14, 0, 0).getTime());
|
||||
|
||||
const thirtyFirst = normalizeCalendarData({
|
||||
viewMode: "month",
|
||||
events: [{ id: "h4", title: "31", startDate: "2026-08-31", repeat: "monthly" }]
|
||||
});
|
||||
const septemberStart = getMonthStart(new Date(2026, 8, 1));
|
||||
assert.equal(getCalendarOccurrences(thirtyFirst.events, septemberStart, getMonthEnd(septemberStart)).length, 0);
|
||||
});
|
||||
|
||||
test("calendar text exchange imports and exports weekly and monthly calendars", () => {
|
||||
let uidIndex = 0;
|
||||
const context = {
|
||||
uid: (prefix) => `${prefix}${uidIndex += 1}`,
|
||||
normalizeCalendarData
|
||||
};
|
||||
const weekly = importModuleText("calendar", "@viewMode: week\nevent | title=Raid | day=vendredi | start=18:00 | end=20:00 | color=gold | alertMode=site | alerts=10,5", context);
|
||||
assert.equal(weekly.ok, true);
|
||||
assert.equal(weekly.data.viewMode, "week");
|
||||
assert.equal(weekly.data.events[0].dayIndex, 4);
|
||||
assert.equal(weekly.data.events[0].startMinutes, 1080);
|
||||
assert.deepEqual(weekly.data.events[0].alertOffsetMinutes, [10, 5]);
|
||||
assert.match(exportModuleText("calendar", weekly.data), /day=vendredi/);
|
||||
|
||||
const monthly = importModuleText("calendar", "@viewMode: month\nevent | title=Maintenance | repeatBy=weekday | repeat=monthly | startDate=2026-08-03 | endDate=2026-08-05 | color=pink\nevent | title=Event | repeatBy=monthDay | repeat=none | startDate=2026-08-20 | endDate=2026-08-27", context);
|
||||
assert.equal(monthly.ok, true);
|
||||
assert.equal(monthly.data.viewMode, "month");
|
||||
assert.equal(monthly.data.events[0].monthlyRepeatBy, "weekday");
|
||||
assert.equal(monthly.data.events[0].startDate, "2026-08-03");
|
||||
assert.equal(monthly.data.events[0].repeat, "monthly");
|
||||
assert.equal(monthly.data.events[1].startDate, "2026-08-20");
|
||||
assert.equal(monthly.data.events[1].repeat, "none");
|
||||
assert.match(exportModuleText("calendar", monthly.data), /startDate=2026-08-03/);
|
||||
assert.match(exportModuleText("calendar", monthly.data), /repeatBy=monthDay/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -786,6 +786,10 @@ test("notepad data migrates text, sanitizes html and compacts drawings", () => {
|
|||
assert.equal(normalized.drawings.strokes[0].width, 24);
|
||||
assert.deepEqual(normalized.drawings.strokes[0].points, [{ x: 4, y: 8 }, { x: 12.35, y: 16.79 }]);
|
||||
|
||||
const escaped = normalizeNotepadData({ html: "<p>"&amp;quot; & test</p>" });
|
||||
assert.equal(escaped.text, "\"\" & test");
|
||||
assert.equal(escaped.html, "<p>"" & test</p>");
|
||||
|
||||
const compactTemporary = compactModuleDataForStorage("notepad", {
|
||||
html: "<p>Note</p>",
|
||||
text: "Note",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue