import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { startAutoLock, stopAutoLock } from '../../../src/lib/stores/security.svelte.js' import { app } from '../../../src/lib/stores/app.svelte.js' import { settings } from '../../../src/lib/stores/settings.svelte.js' // Reset singleton state before each test function resetState() { app.isUnlocked = false app.encryptionKey = null app.salt = null settings.autoLockMinutes = 5 settings.lockOnTabSwitch = true } beforeEach(() => { resetState() stopAutoLock() }) afterEach(() => { stopAutoLock() resetState() }) describe('startAutoLock / stopAutoLock', () => { it('should register and clean up activity listeners', () => { // startAutoLock registers event listeners startAutoLock() // stopAutoLock should remove them stopAutoLock() // No error means cleanup worked expect(true).toBe(true) }) it('should register and clean up visibility listener', () => { startAutoLock() stopAutoLock() expect(true).toBe(true) }) it('should register and clean up beforeunload listener', () => { startAutoLock() stopAutoLock() expect(true).toBe(true) }) it('should not throw when called multiple times', () => { startAutoLock() startAutoLock() stopAutoLock() stopAutoLock() expect(true).toBe(true) }) }) describe('auto-lock behavior', () => { it('should lock vault after inactivity timeout', async () => { vi.useFakeTimers() settings.autoLockMinutes = 1 app.isUnlocked = true app.encryptionKey = { mock: 'key' } startAutoLock() // Wait for the timer (1 minute = 60000ms) vi.advanceTimersByTime(60000) expect(app.isUnlocked).toBe(false) expect(app.encryptionKey).toBe(null) vi.useRealTimers() }) it('should lock on tab switch when lockOnTabSwitch is true', async () => { app.isUnlocked = true app.encryptionKey = { mock: 'key' } settings.lockOnTabSwitch = true startAutoLock() // Simulate visibility change Object.defineProperty(document, 'hidden', { value: true, writable: true, configurable: true }) document.dispatchEvent(new Event('visibilitychange')) expect(app.isUnlocked).toBe(false) expect(app.encryptionKey).toBe(null) }) it('should not lock on tab switch when lockOnTabSwitch is false', async () => { app.isUnlocked = true app.encryptionKey = { mock: 'key' } settings.lockOnTabSwitch = false startAutoLock() Object.defineProperty(document, 'hidden', { value: true, writable: true, configurable: true }) document.dispatchEvent(new Event('visibilitychange')) expect(app.isUnlocked).toBe(true) expect(app.encryptionKey).toEqual({ mock: 'key' }) }) it('should clear key on beforeunload', async () => { app.isUnlocked = true app.encryptionKey = { mock: 'key' } startAutoLock() window.dispatchEvent(new Event('beforeunload')) expect(app.encryptionKey).toBe(null) }) it('should reset timer on user activity', async () => { vi.useFakeTimers() settings.autoLockMinutes = 1 app.isUnlocked = true app.encryptionKey = { mock: 'key' } startAutoLock() // Tick 45 seconds — not enough to trigger vi.advanceTimersByTime(45000) expect(app.isUnlocked).toBe(true) // Simulate user activity (keydown resets timer) window.dispatchEvent(new Event('keydown')) // Tick another 45 seconds — should still be unlocked (timer was reset) vi.advanceTimersByTime(45000) expect(app.isUnlocked).toBe(true) // Tick the remaining 15+ seconds vi.advanceTimersByTime(20000) expect(app.isUnlocked).toBe(false) vi.useRealTimers() }) it('should not lock when already locked', async () => { app.isUnlocked = false settings.lockOnTabSwitch = true startAutoLock() Object.defineProperty(document, 'hidden', { value: true, writable: true, configurable: true }) document.dispatchEvent(new Event('visibilitychange')) expect(app.isUnlocked).toBe(false) }) })