// @ts-nocheck import { describe, it, beforeEach, afterEach, mock } from 'node:test'; import assert from 'node:assert/strict'; import { makeChromeMock } from './chrome-mock'; import { AutoSaveManager } from '../src/commands/autosave'; let chromeMock; let session; // Flush pending microtasks/promise chains after advancing fake timers. // setImmediate stays real (only setTimeout is mocked), so it yields a // macrotask boundary that drains the microtask queue between iterations. async function drain() { for (let i = 0; i < 10; i++) await new Promise(r => setImmediate(r)); } async function tick(ms) { mock.timers.tick(ms); await drain(); } beforeEach(() => { chromeMock = makeChromeMock(); globalThis.chrome = chromeMock; mock.timers.enable({ apis: ['setTimeout'] }); session = new AutoSaveManager(); }); afterEach(async () => { // Clear any pending debounce timer + instance state before tearing down timers. await session.setEnabled(false); await drain(); mock.timers.reset(); delete globalThis.chrome; }); const TAB_EVENTS = ['onCreated', 'onRemoved', 'onMoved', 'onAttached', 'onDetached']; describe('sessionAutoSave listener wiring', () => { it('registers a handler on every tab mutation event + tabGroups when enabled', async () => { await session.setEnabled(true); for (const ev of TAB_EVENTS) { assert.equal(chromeMock.tabs[ev]._size(), 1, `tabs.${ev}`); } assert.equal(chromeMock.tabs.onUpdated._size(), 1); assert.equal(chromeMock.tabGroups.onUpdated._size(), 1); }); it('removes all listeners when disabled (no leaked subscriptions)', async () => { await session.setEnabled(true); await session.setEnabled(false); for (const ev of TAB_EVENTS) { assert.equal(chromeMock.tabs[ev]._size(), 0, `tabs.${ev}`); } assert.equal(chromeMock.tabs.onUpdated._size(), 0); assert.equal(chromeMock.tabGroups.onUpdated._size(), 0); }); it('does not double-register when enabled twice', async () => { await session.setEnabled(true); await session.setEnabled(true); for (const ev of TAB_EVENTS) { assert.equal(chromeMock.tabs[ev]._size(), 1, `tabs.${ev}`); } }); }); describe('autosave debounce / coalescing', () => { it('collapses a burst of tab events into a single snapshot+save', async () => { await session.setEnabled(true); chromeMock.tabs.query.mock.resetCalls(); // Simulate rapidly opening/closing several tabs. await session.autoSaveHandler(); await session.autoSaveHandler(); await session.autoSaveHandler(); await session.autoSaveHandler(); // Nothing should have run yet — still inside the debounce window. assert.equal(chromeMock.tabs.query.mock.callCount(), 0); await tick(3000); // The whole burst produced exactly one snapshot read. assert.equal(chromeMock.tabs.query.mock.callCount(), 1); }); it('does not fire before the (raised) 3s debounce window elapses', async () => { await session.setEnabled(true); chromeMock.tabs.query.mock.resetCalls(); await session.autoSaveHandler(); await tick(2999); assert.equal(chromeMock.tabs.query.mock.callCount(), 0); await tick(1); assert.equal(chromeMock.tabs.query.mock.callCount(), 1); }); it('ignores tab updates that do not change the URL (no save scheduled)', async () => { await session.setEnabled(true); chromeMock.tabs.query.mock.resetCalls(); // status/favicon/title churn — not a URL change. await session.autoSaveUpdatedHandler(1, { status: 'loading' }); await session.autoSaveUpdatedHandler(1, { favIconUrl: 'x' }); await session.autoSaveUpdatedHandler(1, { title: 'y' }); await tick(3000); assert.equal(chromeMock.tabs.query.mock.callCount(), 0); }); it('schedules a save when a URL change is observed', async () => { await session.setEnabled(true); chromeMock.tabs.query.mock.resetCalls(); await session.autoSaveUpdatedHandler(1, { url: 'https://example.com' }); await tick(3000); assert.equal(chromeMock.tabs.query.mock.callCount(), 1); }); it('does nothing when autosave is disabled even if a handler fires', async () => { // Never enabled: storage has no autoSave flag. await session.autoSaveHandler(); await tick(3000); assert.equal(chromeMock.tabs.query.mock.callCount(), 0); }); });