Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/lib/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,63 @@ describe("OfflineAnalytics", () => {
error
);
});

it("should cancel pending debounced sync when manual sync is triggered", async () => {
// Simulate a debounced sync being scheduled
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");

// Track an event to trigger debounced sync
await analytics!.trackPageView("/test", "Test");

// Verify debounced sync was scheduled
expect(analytics!["syncTimeout"]).toBeDefined();
const scheduledTimeoutId = analytics!["syncTimeout"];

// Manually trigger sync (like when coming online)
await analytics!.syncEvents();

// Verify pending timeout was cleared
expect(clearTimeoutSpy).toHaveBeenCalledWith(scheduledTimeoutId);
expect(analytics!["syncTimeout"]).toBeUndefined();

clearTimeoutSpy.mockRestore();
});

it("should not create duplicate syncs from debounce and manual trigger", async () => {
const mockEvents = [
{
id: 1,
type: "page_view",
category: "test",
action: "test",
synced: false,
timestamp: Date.now(),
sessionId: "test",
},
];

vi.mocked(db.analytics!.where).mockReturnValue({
equals: vi.fn().mockReturnValue({
toArray: vi.fn().mockResolvedValue(mockEvents),
and: vi.fn(),
}),
} as never);

// Track an event (schedules debounced sync)
await analytics!.trackPageView("/test", "Test");

// Manually trigger sync immediately (before debounce fires)
await analytics!.syncEvents();

// Verify sync was called (via bulkUpdate)
expect(db.analytics!.bulkUpdate).toHaveBeenCalledTimes(1);

// Wait for debounce timeout to potentially fire
await new Promise((resolve) => setTimeout(resolve, 1100));

// Verify sync was NOT called again (debounce was cancelled)
expect(db.analytics!.bulkUpdate).toHaveBeenCalledTimes(1);
});
});

describe("getStats", () => {
Expand Down
7 changes: 7 additions & 0 deletions src/lib/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,13 @@ class OfflineAnalytics {
async syncEvents(): Promise<void> {
if (!this.isOnline || this.isDestroyed) return;

// Clear any pending debounced sync to prevent duplicate sync attempts
// This ensures manual sync (e.g., from handleOnline) cancels debounced sync
if (this.syncTimeout) {
clearTimeout(this.syncTimeout);
this.syncTimeout = undefined;
}

// Prevent concurrent syncs - atomic check and set
if (this.isSyncing) {
console.log("Sync already in progress, skipping...");
Expand Down
Loading