test: add comprehensive unit tests with Vitest

65 tests covering canvas decoder, pixel buffer, user ID hashing,
canvas storage (with binary encoding regression test), Redis client
API shape, and worker endpoint validation.
This commit is contained in:
tiennm99 committed 2026-04-16 22:37:12 +07:00
1 parent b35769cc73
commit 33cfd3d7b3
8 files changed
+692 -1

No files matched your search

+165
View File
@@ -0,0 +1,165 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createPixelBuffer } from '../../src/lib/pixel-buffer.js';
describe('createPixelBuffer', () => {
let buffer;
beforeEach(() => {
buffer = createPixelBuffer();
});
describe('initial state', () => {
it('starts empty', () => {
expect(buffer.canUndo).toBe(false);
expect(buffer.canRedo).toBe(false);
expect(buffer.pixelCount).toBe(0);
expect(buffer.getAllPixels()).toEqual([]);
});
});
describe('addStroke', () => {
it('adds pixels to buffer', () => {
buffer.addStroke([{ x: 0, y: 0, color: 5 }]);
expect(buffer.pixelCount).toBe(1);
expect(buffer.canUndo).toBe(true);
});
it('ignores empty strokes', () => {
buffer.addStroke([]);
expect(buffer.pixelCount).toBe(0);
expect(buffer.canUndo).toBe(false);
});
it('clears redo stack on new stroke', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }]);
buffer.undo();
expect(buffer.canRedo).toBe(true);
buffer.addStroke([{ x: 1, y: 1, color: 2 }]);
expect(buffer.canRedo).toBe(false);
});
it('copies input array (does not hold reference)', () => {
const stroke = [{ x: 0, y: 0, color: 1 }];
buffer.addStroke(stroke);
stroke.push({ x: 1, y: 1, color: 2 });
expect(buffer.pixelCount).toBe(1);
});
});
describe('undo / redo', () => {
it('undo returns removed stroke', () => {
const stroke = [{ x: 5, y: 10, color: 3 }];
buffer.addStroke(stroke);
const removed = buffer.undo();
expect(removed).toEqual(stroke);
expect(buffer.pixelCount).toBe(0);
});
it('undo on empty returns null', () => {
expect(buffer.undo()).toBeNull();
});
it('redo returns re-applied stroke', () => {
const stroke = [{ x: 5, y: 10, color: 3 }];
buffer.addStroke(stroke);
buffer.undo();
const redone = buffer.redo();
expect(redone).toEqual(stroke);
expect(buffer.pixelCount).toBe(1);
});
it('redo on empty returns null', () => {
expect(buffer.redo()).toBeNull();
});
it('supports multiple undo/redo cycles', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }]);
buffer.addStroke([{ x: 1, y: 1, color: 2 }]);
buffer.addStroke([{ x: 2, y: 2, color: 3 }]);
expect(buffer.pixelCount).toBe(3);
buffer.undo();
buffer.undo();
expect(buffer.pixelCount).toBe(1);
expect(buffer.canRedo).toBe(true);
buffer.redo();
expect(buffer.pixelCount).toBe(2);
});
});
describe('getAllPixels (deduplication)', () => {
it('last stroke wins for same coordinate', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }]);
buffer.addStroke([{ x: 0, y: 0, color: 5 }]);
const pixels = buffer.getAllPixels();
expect(pixels.length).toBe(1);
expect(pixels[0].color).toBe(5);
});
it('merges pixels from multiple strokes', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }, { x: 1, y: 0, color: 2 }]);
buffer.addStroke([{ x: 2, y: 0, color: 3 }]);
expect(buffer.getAllPixels().length).toBe(3);
});
});
describe('getColorAt', () => {
it('returns -1 for non-pending pixel', () => {
expect(buffer.getColorAt(0, 0)).toBe(-1);
});
it('returns latest color for pending pixel', () => {
buffer.addStroke([{ x: 0, y: 0, color: 10 }]);
buffer.addStroke([{ x: 0, y: 0, color: 20 }]);
expect(buffer.getColorAt(0, 0)).toBe(20);
});
it('returns -1 after undo removes the pixel', () => {
buffer.addStroke([{ x: 0, y: 0, color: 10 }]);
buffer.undo();
expect(buffer.getColorAt(0, 0)).toBe(-1);
});
it('reveals earlier stroke after undo', () => {
buffer.addStroke([{ x: 0, y: 0, color: 10 }]);
buffer.addStroke([{ x: 0, y: 0, color: 20 }]);
buffer.undo();
expect(buffer.getColorAt(0, 0)).toBe(10);
});
});
describe('getAffectedKeys', () => {
it('returns empty set for empty buffer', () => {
expect(buffer.getAffectedKeys().size).toBe(0);
});
it('returns unique coordinate keys', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }, { x: 1, y: 0, color: 2 }]);
buffer.addStroke([{ x: 0, y: 0, color: 3 }]);
const keys = buffer.getAffectedKeys();
expect(keys.size).toBe(2);
});
});
describe('pixelCount', () => {
it('counts unique coordinates across strokes', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }]);
buffer.addStroke([{ x: 0, y: 0, color: 2 }, { x: 1, y: 0, color: 3 }]);
// (0,0) counted once + (1,0) = 2 unique pixels
expect(buffer.pixelCount).toBe(2);
});
});
describe('clear', () => {
it('resets all state', () => {
buffer.addStroke([{ x: 0, y: 0, color: 1 }]);
buffer.undo();
buffer.clear();
expect(buffer.canUndo).toBe(false);
expect(buffer.canRedo).toBe(false);
expect(buffer.pixelCount).toBe(0);
expect(buffer.getAllPixels()).toEqual([]);
});
});
});