import { describe, it, expect, vi } from 'vitest'; import { createOfflineQueue } from './index.js'; function createMemoryStorage() { const store: Record = {}; return { getItem: (k: string) => store[k] ?? null, setItem: (k: string, v: string) => { store[k] = v; }, _store: store, }; } describe('createOfflineQueue', () => { it('should start with zero length', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); expect(queue.length()).toBe(0); }); it('should enqueue items', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: { name: 'test' } }); expect(queue.length()).toBe(1); queue.enqueue({ id: '2', action: 'update', path: '/sessions/2', payload: { name: 'test2' } }); expect(queue.length()).toBe(2); }); it('should replace existing entry with same id + action', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: { v: 1 } }); queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: { v: 2 } }); expect(queue.length()).toBe(1); }); it('should not replace entries with different action', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: {} }); queue.enqueue({ id: '1', action: 'update', path: '/sessions/1', payload: {} }); expect(queue.length()).toBe(2); }); it('should flush all items successfully', async () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); queue.enqueue({ id: '1', action: 'create', path: '/a', payload: { n: 1 } }); queue.enqueue({ id: '2', action: 'create', path: '/b', payload: { n: 2 } }); const executor = vi.fn().mockResolvedValue(undefined); const result = await queue.flush(executor); expect(result.flushed).toBe(2); expect(result.failed).toBe(0); expect(queue.length()).toBe(0); expect(executor).toHaveBeenCalledTimes(2); }); it('should retry failed items on next flush', async () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage, maxRetries: 3 }); queue.enqueue({ id: '1', action: 'create', path: '/a', payload: {} }); const failingExecutor = vi.fn().mockRejectedValue(new Error('offline')); const result = await queue.flush(failingExecutor); expect(result.flushed).toBe(0); expect(result.failed).toBe(1); expect(queue.length()).toBe(1); }); it('should drop items after max retries', async () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage, maxRetries: 2 }); queue.enqueue({ id: '1', action: 'create', path: '/a', payload: {} }); const failingExecutor = vi.fn().mockRejectedValue(new Error('offline')); // Retry 1 await queue.flush(failingExecutor); expect(queue.length()).toBe(1); // Retry 2 — should be dropped await queue.flush(failingExecutor); expect(queue.length()).toBe(0); }); it('should cap queue at maxQueueSize', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage, maxQueueSize: 3 }); for (let i = 0; i < 5; i++) { queue.enqueue({ id: `${i}`, action: 'create', path: `/item/${i}`, payload: {} }); } expect(queue.length()).toBe(3); }); it('should persist to storage', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); queue.enqueue({ id: '1', action: 'create', path: '/a', payload: { x: 1 } }); const stored = JSON.parse(storage._store['test-q']); expect(stored).toHaveLength(1); expect(stored[0].id).toBe('1'); }); it('should clear the queue', () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); queue.enqueue({ id: '1', action: 'create', path: '/a', payload: {} }); queue.enqueue({ id: '2', action: 'create', path: '/b', payload: {} }); expect(queue.length()).toBe(2); queue.clear(); expect(queue.length()).toBe(0); }); it('should return empty result when flushing empty queue', async () => { const storage = createMemoryStorage(); const queue = createOfflineQueue({ storageKey: 'test-q', storage }); const executor = vi.fn(); const result = await queue.flush(executor); expect(result.flushed).toBe(0); expect(result.failed).toBe(0); expect(executor).not.toHaveBeenCalled(); }); });