command-queue.test.js 3.41 KB
/**
 * Unit Tests — engine/command-queue.js
 */
import { describe, it, expect } from 'vitest';

const { CommandQueue, CommandLaneClearedError } = require('../../src/engine/command-queue');

describe('CommandQueue', () => {
  describe('basic enqueue', () => {
    it('should execute a simple task', async () => {
      const q = new CommandQueue();
      const result = await q.enqueue('main', async () => 42);
      expect(result).toBe(42);
    });

    it('should execute tasks in order within a lane', async () => {
      const q = new CommandQueue();
      const order = [];
      await Promise.all([
        q.enqueue('main', async () => { order.push(1); }),
        q.enqueue('main', async () => { order.push(2); }),
        q.enqueue('main', async () => { order.push(3); }),
      ]);
      expect(order).toEqual([1, 2, 3]);
    });

    it('should run different lanes in parallel', async () => {
      const q = new CommandQueue();
      const results = [];
      await Promise.all([
        q.enqueue('A', async () => { results.push('A'); }),
        q.enqueue('B', async () => { results.push('B'); }),
      ]);
      expect(results).toContain('A');
      expect(results).toContain('B');
    });
  });

  describe('clearAll', () => {
    it('should increment generation', () => {
      const q = new CommandQueue();
      const gen0 = q.generation;
      q.clearAll();
      expect(q.generation).toBe(gen0 + 1);
    });

    it('should reject queued tasks with CommandLaneClearedError', async () => {
      const q = new CommandQueue();
      // Enqueue a slow task to block the lane
      q.enqueue('main', () => new Promise(r => setTimeout(r, 200)));

      // Queue another behind it
      const p = q.enqueue('main', async () => 'should not run');

      // Clear immediately
      q.clearAll();

      await expect(p).rejects.toThrow(CommandLaneClearedError);
    });
  });

  describe('clearLane', () => {
    it('should clear a specific lane', () => {
      const q = new CommandQueue();
      q.enqueue('A', async () => {});
      q.clearLane('A');
      expect(q.size).toBe(0);
    });
  });

  describe('size', () => {
    it('should reflect active lane count', async () => {
      const q = new CommandQueue();
      expect(q.size).toBe(0);
      const p = q.enqueue('lane1', () => new Promise(r => setTimeout(r, 100)));
      expect(q.size).toBe(1);
      await p;
    });
  });

  describe('CommandLaneClearedError', () => {
    it('should be an Error subclass', () => {
      const err = new CommandLaneClearedError('test');
      expect(err).toBeInstanceOf(Error);
      expect(err.name).toBe('CommandLaneClearedError');
      expect(err.lane).toBe('test');
    });

    it('should work without lane name', () => {
      const err = new CommandLaneClearedError();
      expect(err.message).toContain('cleared');
    });
  });

  describe('error handling in tasks', () => {
    it('should propagate task errors', async () => {
      const q = new CommandQueue();
      await expect(
        q.enqueue('main', async () => { throw new Error('task failed'); })
      ).rejects.toThrow('task failed');
    });

    it('should continue lane after a failed task', async () => {
      const q = new CommandQueue();
      try {
        await q.enqueue('main', async () => { throw new Error('fail'); });
      } catch {}
      // Next task should still work
      const result = await q.enqueue('main', async () => 'ok');
      expect(result).toBe('ok');
    });
  });
});