feat(test): implement Playwright E2E and Dockerized testing infrastructure
Some checks failed
Build SilverBullet Plug / build (push) Has been cancelled

This commit is contained in:
2026-02-21 13:13:06 -08:00
parent 29ce643ac1
commit 90ab92421a
17 changed files with 5202 additions and 99 deletions

View File

@@ -196,7 +196,7 @@ export async function resolveEventStart(icsEvent: any): Promise<Date | null> {
/**
* Expands recurring events into individual occurrences.
*/
export function expandRecurrences(icsEvent: any, windowDays = 365): any[] {
export function expandRecurrences(icsEvent: any, windowDays = 365, now = new Date()): any[] {
const rruleStr = icsEvent.rrule || (icsEvent as any).recurrenceRule;
if (!rruleStr) return [icsEvent];
@@ -238,17 +238,13 @@ export function expandRecurrences(icsEvent: any, windowDays = 365): any[] {
set.exdate(new Date(exdate.includes("Z") ? exdate : exdate + "Z"));
}
const now = new Date();
// Start our visible window 7 days ago to catch recent past events
const filterStart = new Date(now.getTime() - 7 * 86400000);
const windowEnd = new Date(now.getTime() + windowDays * 86400000);
// Expand from the event's actual start date to ensure all recurrences are calculated correctly
// but only take occurrences between (now - 7 days) and (now + windowDays)
// Expand from the event's actual start date up to the window end.
// This provides "no limit" lookback (bound only by the event's own start date).
const occurrences = set.between(dtstart, windowEnd, true);
return occurrences
.filter(occurrenceDate => occurrenceDate >= filterStart)
const mapped = occurrences
.map(occurrenceDate => {
const localIso = localDateString(occurrenceDate);
return {
@@ -258,6 +254,7 @@ export function expandRecurrences(icsEvent: any, windowDays = 365): any[] {
rrule: undefined,
};
});
return mapped;
} catch (err) {
console.error(`[iCalendar] Error expanding recurrence for ${icsEvent.summary}:`, err);
return [icsEvent];
@@ -265,7 +262,6 @@ export function expandRecurrences(icsEvent: any, windowDays = 365): any[] {
}
async function fetchAndParseCalendar(source: any, windowDays = 365): Promise<any[]> {
console.log(`[iCalendar] Fetching from: ${source.url}`);
try {
const response = await fetch(source.url);
if (!response.ok) {
@@ -286,29 +282,31 @@ async function fetchAndParseCalendar(source: any, windowDays = 365): Promise<any
if (!finalDate) continue;
const localIso = localDateString(finalDate);
const rawTz = icsEvent.start?.local?.timezone || (icsEvent.start as any)?.timezone || "UTC";
const baseEvent = {
...icsEvent,
name: icsEvent.summary || "Untitled Event",
start: localIso,
tag: "ical-event",
sourceName: source.name
sourceName: source.name,
timezone: rawTz
};
const rawTz = icsEvent.start?.local?.timezone || (icsEvent.start as any)?.timezone || "UTC";
if (rawTz !== "UTC" && rawTz !== "None" && !resolveIanaName(rawTz)) {
baseEvent.description = `(Warning: Unknown timezone "${rawTz}") ${baseEvent.description || ""}`;
}
const expanded = expandRecurrences(baseEvent, windowDays);
for (const occurrence of expanded) {
const uniqueKey = `${occurrence.start}${occurrence.uid || occurrence.summary || ''}`;
// Use summary in key to avoid collisions for meetings sharing UID/Start
const uniqueKey = `${occurrence.start}${occurrence.uid || ''}${occurrence.summary || ''}`;
occurrence.ref = await sha256Hash(uniqueKey);
events.push(convertDatesToStrings(occurrence));
}
}
return events;
} catch (err) {
console.error(`[iCalendar] Error fetching/parsing ${source.name}:`, err);
} catch (err: any) {
console.error(`[iCalendar] Error fetching/parsing ${source.name}:`, err.message || err, err.stack || "");
return [];
}
}