Add some tests
This commit is contained in:
parent
f228f78915
commit
f5406130df
|
|
@ -5,7 +5,8 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun --hot build/server.ts",
|
"start": "bun --hot build/server.ts",
|
||||||
"bake": "bun build/build.ts"
|
"bake": "bun build/build.ts",
|
||||||
|
"test": "bun test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@huggingface/gguf": "0.3.4",
|
"@huggingface/gguf": "0.3.4",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { describe, it, expect } from 'bun:test';
|
||||||
|
import { formatError, formatErrorMessage } from '@common/errors';
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
describe('formatErrorMessage', () => {
|
||||||
|
it('should extract message from Error objects', () => {
|
||||||
|
const error = new Error('Test error message');
|
||||||
|
expect(formatErrorMessage(error)).toBe('Test error message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle objects with message property', () => {
|
||||||
|
const obj = { message: 'Custom error' };
|
||||||
|
expect(formatErrorMessage(obj)).toBe('Custom error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert other values to string', () => {
|
||||||
|
expect(formatErrorMessage('string error')).toBe('string error');
|
||||||
|
expect(formatErrorMessage(123)).toBe('123');
|
||||||
|
expect(formatErrorMessage(null)).toBe('Unknown error');
|
||||||
|
expect(formatErrorMessage(undefined)).toBe('Unknown error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle plain objects without message', () => {
|
||||||
|
expect(formatErrorMessage({ foo: 'bar' })).toBe('[object Object]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatError', () => {
|
||||||
|
it('should format error with custom message', () => {
|
||||||
|
const error = new Error('Test error');
|
||||||
|
const result = formatError(error, 'Context');
|
||||||
|
expect(result).toContain('Context: Test error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format error without custom message', () => {
|
||||||
|
const error = new Error('Test error');
|
||||||
|
const result = formatError(error);
|
||||||
|
expect(result).toContain('Test error');
|
||||||
|
expect(result).toContain('Error: Test error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle string errors', () => {
|
||||||
|
const result = formatError('error string', 'Context');
|
||||||
|
expect(result).toBe('Context: error string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null/undefined errors', () => {
|
||||||
|
expect(formatError(null, 'Context')).toBe('Context: Unknown error');
|
||||||
|
expect(formatError(undefined, 'Context')).toBe('Context: Unknown error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle number errors', () => {
|
||||||
|
expect(formatError(42, 'Context')).toBe('Context: 42');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { describe, it, expect } from 'bun:test';
|
||||||
|
import Lock from '@common/lock';
|
||||||
|
|
||||||
|
describe('Lock', () => {
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create an unlocked lock', () => {
|
||||||
|
const lock = new Lock();
|
||||||
|
expect(lock.locked).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('locked', () => {
|
||||||
|
it('should return false when lock is not acquired', () => {
|
||||||
|
const lock = new Lock();
|
||||||
|
expect(lock.locked).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true after wait is called', () => {
|
||||||
|
const lock = new Lock();
|
||||||
|
lock.wait();
|
||||||
|
expect(lock.locked).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('wait', () => {
|
||||||
|
it('should return a promise', () => {
|
||||||
|
const lock = new Lock();
|
||||||
|
const result = lock.wait();
|
||||||
|
expect(result).toBeInstanceOf(Promise);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('release', () => {
|
||||||
|
it('should not throw when releasing an unlocked lock', () => {
|
||||||
|
const lock = new Lock();
|
||||||
|
expect(() => lock.release()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unlock the lock after wait', () => {
|
||||||
|
const lock = new Lock();
|
||||||
|
lock.wait();
|
||||||
|
lock.release();
|
||||||
|
expect(lock.locked).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,303 @@
|
||||||
|
import { describe, it, expect } from 'bun:test';
|
||||||
|
import {
|
||||||
|
randInt,
|
||||||
|
randBool,
|
||||||
|
choice,
|
||||||
|
weightedChoice,
|
||||||
|
shuffle,
|
||||||
|
mapNumber,
|
||||||
|
zip,
|
||||||
|
enumerate,
|
||||||
|
range,
|
||||||
|
clamp,
|
||||||
|
lerp,
|
||||||
|
prevent,
|
||||||
|
intHash,
|
||||||
|
sinHash,
|
||||||
|
throttle,
|
||||||
|
callUpdater,
|
||||||
|
} from '@common/utils';
|
||||||
|
|
||||||
|
describe('utils', () => {
|
||||||
|
describe('randInt', () => {
|
||||||
|
it('should return a number within the specified range', () => {
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const result = randInt(1, 10);
|
||||||
|
expect(result).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(result).toBeLessThanOrEqual(10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return integers only (no floats)', () => {
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const result = randInt(0, 100);
|
||||||
|
expect(Number.isInteger(result)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle single value range', () => {
|
||||||
|
expect(randInt(5, 6)).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('randBool', () => {
|
||||||
|
it('should return boolean values', () => {
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const result = randBool();
|
||||||
|
expect(typeof result).toBe('boolean');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return both true and false over multiple calls', () => {
|
||||||
|
const results = new Set();
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
results.add(randBool());
|
||||||
|
}
|
||||||
|
expect(results.has(true)).toBe(true);
|
||||||
|
expect(results.has(false)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('choice', () => {
|
||||||
|
it('should return an element from the array', () => {
|
||||||
|
const array = [1, 2, 3, 4, 5];
|
||||||
|
const result = choice(array);
|
||||||
|
expect(array).toContain(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with string arrays', () => {
|
||||||
|
const array = ['a', 'b', 'c'];
|
||||||
|
const result = choice(array);
|
||||||
|
expect(array).toContain(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with object arrays', () => {
|
||||||
|
const array = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
||||||
|
const result = choice(array);
|
||||||
|
expect(array).toContain(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('weightedChoice', () => {
|
||||||
|
it('should return an item based on weights (array input)', () => {
|
||||||
|
const options: [number, number][] = [[1, 10], [2, 1], [3, 1]];
|
||||||
|
const results = new Set<number>();
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const result = weightedChoice(options);
|
||||||
|
if (result !== null) results.add(result);
|
||||||
|
}
|
||||||
|
// Item 1 has much higher weight, should appear most often
|
||||||
|
expect(results.has(1)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an item based on weights (object input)', () => {
|
||||||
|
const options = { a: 10, b: 1, c: 1 };
|
||||||
|
const results = new Set<string>();
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const result = weightedChoice(options);
|
||||||
|
if (result !== null) results.add(result);
|
||||||
|
}
|
||||||
|
expect(results.has('a')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for empty options', () => {
|
||||||
|
expect(weightedChoice([])).toBe(null);
|
||||||
|
expect(weightedChoice({})).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shuffle', () => {
|
||||||
|
it('should return an array of the same length', () => {
|
||||||
|
const array = [1, 2, 3, 4, 5];
|
||||||
|
const shuffled = shuffle(array);
|
||||||
|
expect(shuffled.length).toBe(array.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the same elements', () => {
|
||||||
|
const array = [1, 2, 3, 4, 5];
|
||||||
|
const shuffled = shuffle(array);
|
||||||
|
expect(shuffled.sort((a, b) => a - b)).toEqual(array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify the original array', () => {
|
||||||
|
const array = [1, 2, 3, 4, 5];
|
||||||
|
const original = [...array];
|
||||||
|
shuffle(array);
|
||||||
|
expect(array).toEqual(original);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty arrays', () => {
|
||||||
|
expect(shuffle([])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle single element arrays', () => {
|
||||||
|
expect(shuffle([1])).toEqual([1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mapNumber', () => {
|
||||||
|
it('should map a number from one range to another', () => {
|
||||||
|
expect(mapNumber(5, 0, 10, 0, 100)).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle reverse ranges', () => {
|
||||||
|
expect(mapNumber(5, 0, 10, 100, 0)).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle negative ranges', () => {
|
||||||
|
expect(mapNumber(0, -10, 10, 0, 100)).toBe(50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('zip', () => {
|
||||||
|
it('should zip two arrays together', () => {
|
||||||
|
const result = [...zip([1, 2, 3], ['a', 'b', 'c'])];
|
||||||
|
expect(result).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should stop at the shortest array', () => {
|
||||||
|
const result = [...zip([1, 2], ['a', 'b', 'c', 'd'])];
|
||||||
|
expect(result).toEqual([[1, 'a'], [2, 'b']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should zip three arrays', () => {
|
||||||
|
const result = [...zip([1, 2], ['a', 'b'], [true, false])];
|
||||||
|
expect(result).toEqual([[1, 'a', true], [2, 'b', false]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty arrays', () => {
|
||||||
|
expect([...zip([], [])]).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('enumerate', () => {
|
||||||
|
it('should enumerate an array with indices', () => {
|
||||||
|
const result = [...enumerate(['a', 'b', 'c'])];
|
||||||
|
expect(result).toEqual([[0, 'a'], [1, 'b'], [2, 'c']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty arrays', () => {
|
||||||
|
expect([...enumerate([])]).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('range', () => {
|
||||||
|
it('should create a range from a number', () => {
|
||||||
|
expect(range(5)).toEqual([0, 1, 2, 3, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a range from a string number', () => {
|
||||||
|
expect(range('5')).toEqual([0, 1, 2, 3, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array for 0', () => {
|
||||||
|
expect(range(0)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clamp', () => {
|
||||||
|
it('should clamp a value within range', () => {
|
||||||
|
expect(clamp(5, 0, 10)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clamp values below minimum', () => {
|
||||||
|
expect(clamp(-5, 0, 10)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clamp values above maximum', () => {
|
||||||
|
expect(clamp(15, 0, 10)).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('lerp', () => {
|
||||||
|
it('should linearly interpolate between two values', () => {
|
||||||
|
expect(lerp(0, 100, 0.5)).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return start value at t=0', () => {
|
||||||
|
expect(lerp(0, 100, 0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return end value at t=1', () => {
|
||||||
|
expect(lerp(0, 100, 1)).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle values outside 0-1 range', () => {
|
||||||
|
expect(lerp(0, 100, -0.5)).toBe(-50);
|
||||||
|
expect(lerp(0, 100, 1.5)).toBe(150);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('prevent', () => {
|
||||||
|
it('should call preventDefault and return false', () => {
|
||||||
|
let preventDefaultCalled = false;
|
||||||
|
const mockEvent = {
|
||||||
|
preventDefault: () => { preventDefaultCalled = true; }
|
||||||
|
} as Event;
|
||||||
|
const result = prevent(mockEvent);
|
||||||
|
expect(preventDefaultCalled).toBe(true);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('intHash', () => {
|
||||||
|
it('should return a number', () => {
|
||||||
|
expect(typeof intHash(1, 2, 3)).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be deterministic', () => {
|
||||||
|
const hash1 = intHash(1, 2, 3);
|
||||||
|
const hash2 = intHash(1, 2, 3);
|
||||||
|
expect(hash1).toBe(hash2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should produce different hashes for different inputs', () => {
|
||||||
|
const hash1 = intHash(1);
|
||||||
|
const hash2 = intHash(2);
|
||||||
|
expect(hash1).not.toBe(hash2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sinHash', () => {
|
||||||
|
it('should return a number between 0 and 1', () => {
|
||||||
|
const result = sinHash(1, 2, 3);
|
||||||
|
expect(result).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(result).toBeLessThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be deterministic', () => {
|
||||||
|
const hash1 = sinHash(1, 2, 3);
|
||||||
|
const hash2 = sinHash(1, 2, 3);
|
||||||
|
expect(hash1).toBe(hash2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('throttle', () => {
|
||||||
|
it('should call the function immediately', () => {
|
||||||
|
let callCount = 0;
|
||||||
|
const fn = throttle(() => { callCount++; }, 1000);
|
||||||
|
fn();
|
||||||
|
expect(callCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throttle subsequent calls', () => {
|
||||||
|
let callCount = 0;
|
||||||
|
const fn = throttle(() => { callCount++; }, 1000);
|
||||||
|
fn();
|
||||||
|
fn();
|
||||||
|
fn();
|
||||||
|
expect(callCount).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('callUpdater', () => {
|
||||||
|
it('should call a function updater', () => {
|
||||||
|
const updater = (prev: number) => prev + 1;
|
||||||
|
expect(callUpdater(updater, 5)).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a direct value updater', () => {
|
||||||
|
expect(callUpdater(10, 5)).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue