Files
rplace/test/lib/pixel-buffer.test.js
T
tiennm99 33cfd3d7b3 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.
2026-04-16 22:37:12 +07:00

166 lines
5.0 KiB
JavaScript

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([]);
});
});
});