DEV-C P2: an active autofiller never opens the popup, so under the old rule it got force-locked despite continuous use. Inverts the rule: reset on all messages except a documented exclusion set (only get_autofill_candidates today). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
import * as timer from '../session-timer';
|
|
import { READ_ONLY_CONTENT_CALLABLE } from '../session-timer';
|
|
|
|
describe('session-timer', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
// Reset to default config for each test
|
|
timer.setConfig({ mode: 'inactivity', minutes: 15 });
|
|
timer.stopTimer();
|
|
});
|
|
|
|
afterEach(() => {
|
|
timer.stopTimer();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('fires callback after inactivity timeout', () => {
|
|
const cb = vi.fn();
|
|
timer.onExpired(cb);
|
|
timer.resetTimer();
|
|
|
|
// Default is 15 minutes = 900_000 ms
|
|
vi.advanceTimersByTime(899_999);
|
|
expect(cb).not.toHaveBeenCalled();
|
|
|
|
vi.advanceTimersByTime(1);
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('resets timer on each resetTimer() call', () => {
|
|
const cb = vi.fn();
|
|
timer.onExpired(cb);
|
|
timer.resetTimer();
|
|
|
|
// Advance 10 minutes
|
|
vi.advanceTimersByTime(600_000);
|
|
expect(cb).not.toHaveBeenCalled();
|
|
|
|
// Reset — clock should restart
|
|
timer.resetTimer();
|
|
|
|
// Advance another 10 minutes (20 total, but only 10 since last reset)
|
|
vi.advanceTimersByTime(600_000);
|
|
expect(cb).not.toHaveBeenCalled();
|
|
|
|
// Advance remaining 5 minutes to hit the 15-minute mark from last reset
|
|
vi.advanceTimersByTime(300_000);
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not fire when mode is every_time', () => {
|
|
const cb = vi.fn();
|
|
timer.onExpired(cb);
|
|
timer.setConfig({ mode: 'every_time' });
|
|
timer.resetTimer();
|
|
|
|
// Advance well past the default 15 minutes
|
|
vi.advanceTimersByTime(60 * 60 * 1000);
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('respects updated minutes', () => {
|
|
const cb = vi.fn();
|
|
timer.onExpired(cb);
|
|
timer.setConfig({ mode: 'inactivity', minutes: 5 });
|
|
timer.resetTimer();
|
|
|
|
vi.advanceTimersByTime(4 * 60 * 1000);
|
|
expect(cb).not.toHaveBeenCalled();
|
|
|
|
vi.advanceTimersByTime(60 * 1000);
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('getConfig returns current config', () => {
|
|
// Default
|
|
expect(timer.getConfig()).toEqual({ mode: 'inactivity', minutes: 15 });
|
|
|
|
// After set
|
|
timer.setConfig({ mode: 'every_time' });
|
|
expect(timer.getConfig()).toEqual({ mode: 'every_time' });
|
|
|
|
timer.setConfig({ mode: 'inactivity', minutes: 30 });
|
|
expect(timer.getConfig()).toEqual({ mode: 'inactivity', minutes: 30 });
|
|
});
|
|
|
|
it('stopTimer prevents firing', () => {
|
|
const cb = vi.fn();
|
|
timer.onExpired(cb);
|
|
timer.resetTimer();
|
|
|
|
vi.advanceTimersByTime(600_000); // 10 minutes in
|
|
timer.stopTimer();
|
|
|
|
// Advance past what would have been the 15-minute mark
|
|
vi.advanceTimersByTime(600_000);
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('READ_ONLY_CONTENT_CALLABLE — inversion exclusion set', () => {
|
|
// The SW handler invokes resetTimer() on every message whose type is NOT
|
|
// in this set. These cases encode the documented inversion contract from
|
|
// Plan C Phase 5: popup-only messages reset, content-callable writes
|
|
// reset, only passive content reads (currently just get_autofill_candidates)
|
|
// do NOT reset.
|
|
|
|
it('popup-only message would reset the timer (not in exclusion set)', () => {
|
|
// e.g. list_items — popup interaction is unambiguously active use
|
|
expect(READ_ONLY_CONTENT_CALLABLE.has('list_items')).toBe(false);
|
|
});
|
|
|
|
it('content-callable get_autofill_candidates does NOT reset (in exclusion set)', () => {
|
|
expect(READ_ONLY_CONTENT_CALLABLE.has('get_autofill_candidates')).toBe(true);
|
|
});
|
|
|
|
it('content-callable capture_save_login DOES reset (write op = active use)', () => {
|
|
expect(READ_ONLY_CONTENT_CALLABLE.has('capture_save_login')).toBe(false);
|
|
});
|
|
|
|
it('content-callable check_credential DOES reset', () => {
|
|
// Asking "is this credential already saved" is user-initiated.
|
|
expect(READ_ONLY_CONTENT_CALLABLE.has('check_credential')).toBe(false);
|
|
});
|
|
});
|