mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
fix(ical): prevent race condition in lazy loader
Use Promise-based singleton pattern to ensure concurrent calls share the same loading promise instead of triggering multiple imports. Also fix pre-existing test bug using await in sync function.
This commit is contained in:
parent
1cb1e1c742
commit
6d82c1982d
3 changed files with 57 additions and 16 deletions
|
|
@ -828,16 +828,14 @@ END:VEVENT
|
|||
END:VCALENDAR`;
|
||||
|
||||
// Should not throw an error
|
||||
expect(() => {
|
||||
const events = await getRelevantEventsForCalendarIntegrationFromIcal(
|
||||
icalData,
|
||||
calProviderId,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].title).toBe('Office 365 Meeting');
|
||||
}).not.toThrow();
|
||||
const events = await getRelevantEventsForCalendarIntegrationFromIcal(
|
||||
icalData,
|
||||
calProviderId,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].title).toBe('Office 365 Meeting');
|
||||
});
|
||||
|
||||
it('should handle iCal with TZID reference to unknown timezone gracefully', async () => {
|
||||
|
|
|
|||
35
src/app/features/schedule/ical/ical-lazy-loader.spec.ts
Normal file
35
src/app/features/schedule/ical/ical-lazy-loader.spec.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { loadIcalModule } from './ical-lazy-loader';
|
||||
|
||||
describe('ical-lazy-loader', () => {
|
||||
describe('loadIcalModule', () => {
|
||||
it('should load the ical.js module', async () => {
|
||||
const ICAL = await loadIcalModule();
|
||||
|
||||
expect(ICAL).toBeDefined();
|
||||
expect(ICAL.parse).toBeDefined();
|
||||
expect(ICAL.Component).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return the same module instance on subsequent calls', async () => {
|
||||
const first = await loadIcalModule();
|
||||
const second = await loadIcalModule();
|
||||
|
||||
expect(first).toBe(second);
|
||||
});
|
||||
|
||||
it('should handle concurrent calls without race conditions', async () => {
|
||||
const results = await Promise.all([
|
||||
loadIcalModule(),
|
||||
loadIcalModule(),
|
||||
loadIcalModule(),
|
||||
loadIcalModule(),
|
||||
loadIcalModule(),
|
||||
]);
|
||||
|
||||
const firstResult = results[0];
|
||||
results.forEach((result) => {
|
||||
expect(result).toBe(firstResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -5,18 +5,26 @@
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let icalModule: any = null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let loadingPromise: Promise<any> | null = null;
|
||||
|
||||
/**
|
||||
* Lazily loads the ical.js module on first use.
|
||||
* Subsequent calls return the cached module.
|
||||
* Concurrent calls share the same loading promise to prevent race conditions.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const loadIcalModule = async (): Promise<any> => {
|
||||
if (!icalModule) {
|
||||
// @ts-ignore - ical.js exports default
|
||||
const mod = await import('ical.js');
|
||||
// Handle both ESM default export and CommonJS module.exports
|
||||
icalModule = mod.default || mod;
|
||||
if (icalModule) {
|
||||
return icalModule;
|
||||
}
|
||||
return icalModule;
|
||||
if (!loadingPromise) {
|
||||
loadingPromise = import('ical.js').then((mod) => {
|
||||
// @ts-ignore - ical.js exports default
|
||||
// Handle both ESM default export and CommonJS module.exports
|
||||
icalModule = mod.default || mod;
|
||||
return icalModule;
|
||||
});
|
||||
}
|
||||
return loadingPromise;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue