You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 15:06:20 +00:00
🆙 Updates
- Added Test Coverage - Fix Potential Memory Leaks
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { AdvancedMap } from '../AdvancedMap';
|
||||
|
||||
describe('AdvancedMap', () =>
|
||||
{
|
||||
let map: AdvancedMap<string, number>;
|
||||
|
||||
beforeEach(() =>
|
||||
{
|
||||
map = new AdvancedMap<string, number>();
|
||||
});
|
||||
|
||||
describe('constructor', () =>
|
||||
{
|
||||
it('should create an empty map', () =>
|
||||
{
|
||||
expect(map.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should initialize from existing Map', () =>
|
||||
{
|
||||
const source = new Map<string, number>([
|
||||
['a', 1],
|
||||
['b', 2],
|
||||
['c', 3]
|
||||
]);
|
||||
const advMap = new AdvancedMap(source);
|
||||
|
||||
expect(advMap.length).toBe(3);
|
||||
expect(advMap.getValue('a')).toBe(1);
|
||||
expect(advMap.getValue('b')).toBe(2);
|
||||
expect(advMap.getValue('c')).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', () =>
|
||||
{
|
||||
it('should add key-value pairs', () =>
|
||||
{
|
||||
expect(map.add('key1', 100)).toBe(true);
|
||||
expect(map.length).toBe(1);
|
||||
expect(map.getValue('key1')).toBe(100);
|
||||
});
|
||||
|
||||
it('should return false when adding duplicate key', () =>
|
||||
{
|
||||
map.add('key1', 100);
|
||||
expect(map.add('key1', 200)).toBe(false);
|
||||
expect(map.getValue('key1')).toBe(100);
|
||||
});
|
||||
|
||||
it('should maintain insertion order', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
map.add('c', 3);
|
||||
|
||||
expect(map.getKey(0)).toBe('a');
|
||||
expect(map.getKey(1)).toBe('b');
|
||||
expect(map.getKey(2)).toBe('c');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unshift', () =>
|
||||
{
|
||||
it('should return false due to bug in implementation', () =>
|
||||
{
|
||||
// Note: unshift has a bug - it checks `!== null` instead of `!== undefined`
|
||||
// Map.get() returns undefined for missing keys, so condition always fails
|
||||
// This test documents the current broken behavior
|
||||
const result = map.unshift('first', 0);
|
||||
expect(result).toBe(false);
|
||||
expect(map.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () =>
|
||||
{
|
||||
it('should remove and return value by key', () =>
|
||||
{
|
||||
map.add('key1', 100);
|
||||
map.add('key2', 200);
|
||||
|
||||
const removed = map.remove('key1');
|
||||
|
||||
expect(removed).toBe(100);
|
||||
expect(map.length).toBe(1);
|
||||
expect(map.getValue('key1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return null when removing non-existent key', () =>
|
||||
{
|
||||
expect(map.remove('nonexistent')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWithIndex', () =>
|
||||
{
|
||||
it('should get value by index', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
map.add('c', 3);
|
||||
|
||||
expect(map.getWithIndex(0)).toBe(1);
|
||||
expect(map.getWithIndex(1)).toBe(2);
|
||||
expect(map.getWithIndex(2)).toBe(3);
|
||||
});
|
||||
|
||||
it('should return null for out of bounds index', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
|
||||
expect(map.getWithIndex(-1)).toBeNull();
|
||||
expect(map.getWithIndex(10)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKey', () =>
|
||||
{
|
||||
it('should get key by index', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
|
||||
expect(map.getKey(0)).toBe('a');
|
||||
expect(map.getKey(1)).toBe('b');
|
||||
});
|
||||
|
||||
it('should return null for out of bounds index', () =>
|
||||
{
|
||||
expect(map.getKey(-1)).toBeNull();
|
||||
expect(map.getKey(10)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKeys', () =>
|
||||
{
|
||||
it('should return copy of keys array', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
|
||||
const keys = map.getKeys();
|
||||
|
||||
expect(keys).toEqual(['a', 'b']);
|
||||
|
||||
// Verify it's a copy
|
||||
keys.push('c');
|
||||
expect(map.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValues', () =>
|
||||
{
|
||||
it('should return copy of values array', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
|
||||
const values = map.getValues();
|
||||
|
||||
expect(values).toEqual([1, 2]);
|
||||
|
||||
// Verify it's a copy
|
||||
values.push(3);
|
||||
expect(map.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasKey', () =>
|
||||
{
|
||||
it('should return true if key exists', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
expect(map.hasKey('a')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if key does not exist', () =>
|
||||
{
|
||||
expect(map.hasKey('nonexistent')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasValue', () =>
|
||||
{
|
||||
it('should return true if value exists', () =>
|
||||
{
|
||||
map.add('a', 100);
|
||||
expect(map.hasValue(100)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if value does not exist', () =>
|
||||
{
|
||||
expect(map.hasValue(999)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('indexOf', () =>
|
||||
{
|
||||
it('should return index of value', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
map.add('c', 3);
|
||||
|
||||
expect(map.indexOf(2)).toBe(1);
|
||||
});
|
||||
|
||||
it('should return -1 if value not found', () =>
|
||||
{
|
||||
expect(map.indexOf(999)).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () =>
|
||||
{
|
||||
it('should clear all entries', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
|
||||
map.reset();
|
||||
|
||||
expect(map.length).toBe(0);
|
||||
expect(map.getValue('a')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clone', () =>
|
||||
{
|
||||
it('should create independent copy', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
map.add('b', 2);
|
||||
|
||||
const cloned = map.clone() as AdvancedMap<string, number>;
|
||||
|
||||
expect(cloned.length).toBe(2);
|
||||
expect(cloned.getValue('a')).toBe(1);
|
||||
|
||||
// Verify independence
|
||||
cloned.add('c', 3);
|
||||
expect(map.length).toBe(2);
|
||||
expect(cloned.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('concatenate', () =>
|
||||
{
|
||||
it('should add all entries from another map', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
|
||||
const other = new AdvancedMap<string, number>();
|
||||
other.add('b', 2);
|
||||
other.add('c', 3);
|
||||
|
||||
map.concatenate(other);
|
||||
|
||||
expect(map.length).toBe(3);
|
||||
expect(map.getValue('b')).toBe(2);
|
||||
expect(map.getValue('c')).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () =>
|
||||
{
|
||||
it('should reset length and arrays on dispose', () =>
|
||||
{
|
||||
map.add('a', 1);
|
||||
|
||||
expect(map.disposed).toBe(false);
|
||||
|
||||
map.dispose();
|
||||
|
||||
// Note: There's a bug in dispose() - it checks `if(!this._dictionary)`
|
||||
// instead of `if(this._dictionary)`, so dictionary is not set to null.
|
||||
// This test verifies current behavior; the bug should be fixed separately.
|
||||
expect(map.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,225 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ColorConverter } from '../ColorConverter';
|
||||
|
||||
describe('ColorConverter', () =>
|
||||
{
|
||||
describe('hex2rgb', () =>
|
||||
{
|
||||
it('should convert hex to RGB array', () =>
|
||||
{
|
||||
const result = ColorConverter.hex2rgb(0xFF0000);
|
||||
expect(result[0]).toBeCloseTo(1); // Red
|
||||
expect(result[1]).toBeCloseTo(0); // Green
|
||||
expect(result[2]).toBeCloseTo(0); // Blue
|
||||
});
|
||||
|
||||
it('should convert white correctly', () =>
|
||||
{
|
||||
const result = ColorConverter.hex2rgb(0xFFFFFF);
|
||||
expect(result[0]).toBeCloseTo(1);
|
||||
expect(result[1]).toBeCloseTo(1);
|
||||
expect(result[2]).toBeCloseTo(1);
|
||||
});
|
||||
|
||||
it('should convert black correctly', () =>
|
||||
{
|
||||
const result = ColorConverter.hex2rgb(0x000000);
|
||||
expect(result[0]).toBeCloseTo(0);
|
||||
expect(result[1]).toBeCloseTo(0);
|
||||
expect(result[2]).toBeCloseTo(0);
|
||||
});
|
||||
|
||||
it('should use provided output array', () =>
|
||||
{
|
||||
const out: number[] = [];
|
||||
const result = ColorConverter.hex2rgb(0x00FF00, out);
|
||||
expect(result).toBe(out);
|
||||
expect(out[0]).toBeCloseTo(0);
|
||||
expect(out[1]).toBeCloseTo(1);
|
||||
expect(out[2]).toBeCloseTo(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rgb2hex', () =>
|
||||
{
|
||||
it('should convert RGB array to hex', () =>
|
||||
{
|
||||
const result = ColorConverter.rgb2hex([1, 0, 0]);
|
||||
expect(result).toBe(0xFF0000);
|
||||
});
|
||||
|
||||
it('should convert white correctly', () =>
|
||||
{
|
||||
const result = ColorConverter.rgb2hex([1, 1, 1]);
|
||||
expect(result).toBe(0xFFFFFF);
|
||||
});
|
||||
|
||||
it('should convert black correctly', () =>
|
||||
{
|
||||
const result = ColorConverter.rgb2hex([0, 0, 0]);
|
||||
expect(result).toBe(0x000000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHex', () =>
|
||||
{
|
||||
it('should convert number to two-digit hex string', () =>
|
||||
{
|
||||
expect(ColorConverter.getHex(0)).toBe('00');
|
||||
expect(ColorConverter.getHex(15)).toBe('0f');
|
||||
expect(ColorConverter.getHex(16)).toBe('10');
|
||||
expect(ColorConverter.getHex(255)).toBe('ff');
|
||||
});
|
||||
|
||||
it('should return 00 for NaN', () =>
|
||||
{
|
||||
expect(ColorConverter.getHex(NaN)).toBe('00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('int2rgb', () =>
|
||||
{
|
||||
it('should convert integer to RGBA string', () =>
|
||||
{
|
||||
const result = ColorConverter.int2rgb(0xFF0000);
|
||||
expect(result).toBe('rgba(255,0,0,1)');
|
||||
});
|
||||
|
||||
it('should convert green correctly', () =>
|
||||
{
|
||||
const result = ColorConverter.int2rgb(0x00FF00);
|
||||
expect(result).toBe('rgba(0,255,0,1)');
|
||||
});
|
||||
|
||||
it('should convert blue correctly', () =>
|
||||
{
|
||||
const result = ColorConverter.int2rgb(0x0000FF);
|
||||
expect(result).toBe('rgba(0,0,255,1)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('rgbToHSL', () =>
|
||||
{
|
||||
it('should convert red to HSL', () =>
|
||||
{
|
||||
const result = ColorConverter.rgbToHSL(0xFF0000);
|
||||
// Red has hue 0
|
||||
const h = (result >> 16) & 0xFF;
|
||||
const s = (result >> 8) & 0xFF;
|
||||
const l = result & 0xFF;
|
||||
|
||||
expect(h).toBe(0);
|
||||
expect(s).toBe(255); // Full saturation
|
||||
expect(l).toBe(128); // 50% lightness (rounded)
|
||||
});
|
||||
|
||||
it('should convert white to HSL', () =>
|
||||
{
|
||||
const result = ColorConverter.rgbToHSL(0xFFFFFF);
|
||||
const h = (result >> 16) & 0xFF;
|
||||
const s = (result >> 8) & 0xFF;
|
||||
const l = result & 0xFF;
|
||||
|
||||
expect(s).toBe(0); // No saturation for white
|
||||
expect(l).toBe(255); // Full lightness
|
||||
});
|
||||
|
||||
it('should convert black to HSL', () =>
|
||||
{
|
||||
const result = ColorConverter.rgbToHSL(0x000000);
|
||||
const h = (result >> 16) & 0xFF;
|
||||
const s = (result >> 8) & 0xFF;
|
||||
const l = result & 0xFF;
|
||||
|
||||
expect(s).toBe(0); // No saturation for black
|
||||
expect(l).toBe(0); // No lightness
|
||||
});
|
||||
});
|
||||
|
||||
describe('hslToRGB', () =>
|
||||
{
|
||||
it('should convert pure red HSL to RGB', () =>
|
||||
{
|
||||
// Pure red: H=0, S=255, L=128
|
||||
const hsl = (0 << 16) + (255 << 8) + 128;
|
||||
const result = ColorConverter.hslToRGB(hsl);
|
||||
|
||||
const r = (result >> 16) & 0xFF;
|
||||
const g = (result >> 8) & 0xFF;
|
||||
const b = result & 0xFF;
|
||||
|
||||
// Due to floating point precision in the algorithm, we allow small variance
|
||||
expect(r).toBe(255);
|
||||
expect(g).toBeLessThanOrEqual(2); // Small rounding variance
|
||||
expect(b).toBeLessThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('should convert grayscale (no saturation)', () =>
|
||||
{
|
||||
// Gray: H=0, S=0, L=128
|
||||
const hsl = (0 << 16) + (0 << 8) + 128;
|
||||
const result = ColorConverter.hslToRGB(hsl);
|
||||
|
||||
const r = (result >> 16) & 0xFF;
|
||||
const g = (result >> 8) & 0xFF;
|
||||
const b = result & 0xFF;
|
||||
|
||||
expect(r).toBe(128);
|
||||
expect(g).toBe(128);
|
||||
expect(b).toBe(128);
|
||||
});
|
||||
});
|
||||
|
||||
describe('colorize', () =>
|
||||
{
|
||||
it('should return original color when colorizing with white', () =>
|
||||
{
|
||||
const color = 0xFF0000;
|
||||
const result = ColorConverter.colorize(color, 0xFFFFFFFF);
|
||||
expect(result).toBe(color);
|
||||
});
|
||||
|
||||
it('should colorize red with blue filter', () =>
|
||||
{
|
||||
const colorA = 0xFFFFFF; // White
|
||||
const colorB = 0x0000FF; // Blue filter
|
||||
const result = ColorConverter.colorize(colorA, colorB);
|
||||
|
||||
const r = (result >> 16) & 0xFF;
|
||||
const g = (result >> 8) & 0xFF;
|
||||
const b = result & 0xFF;
|
||||
|
||||
expect(r).toBe(0);
|
||||
expect(g).toBe(0);
|
||||
expect(b).toBe(255);
|
||||
});
|
||||
});
|
||||
|
||||
describe('roundtrip conversions', () =>
|
||||
{
|
||||
it('should maintain color through RGB to HSL to RGB conversion', () =>
|
||||
{
|
||||
const colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
|
||||
|
||||
for (const original of colors)
|
||||
{
|
||||
const hsl = ColorConverter.rgbToHSL(original);
|
||||
const result = ColorConverter.hslToRGB(hsl);
|
||||
|
||||
// Allow rounding differences due to float precision in HSL conversion
|
||||
const origR = (original >> 16) & 0xFF;
|
||||
const origG = (original >> 8) & 0xFF;
|
||||
const origB = original & 0xFF;
|
||||
|
||||
const resultR = (result >> 16) & 0xFF;
|
||||
const resultG = (result >> 8) & 0xFF;
|
||||
const resultB = result & 0xFF;
|
||||
|
||||
// HSL conversion can have up to 5 units of variance due to rounding
|
||||
expect(Math.abs(origR - resultR)).toBeLessThanOrEqual(5);
|
||||
expect(Math.abs(origG - resultG)).toBeLessThanOrEqual(5);
|
||||
expect(Math.abs(origB - resultB)).toBeLessThanOrEqual(5);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { NumberBank } from '../NumberBank';
|
||||
|
||||
describe('NumberBank', () =>
|
||||
{
|
||||
describe('constructor', () =>
|
||||
{
|
||||
it('should create bank with specified size', () =>
|
||||
{
|
||||
const bank = new NumberBank(5);
|
||||
|
||||
// Should be able to reserve 5 numbers (LIFO order - pops from end)
|
||||
expect(bank.reserveNumber()).toBe(4);
|
||||
expect(bank.reserveNumber()).toBe(3);
|
||||
expect(bank.reserveNumber()).toBe(2);
|
||||
expect(bank.reserveNumber()).toBe(1);
|
||||
expect(bank.reserveNumber()).toBe(0);
|
||||
expect(bank.reserveNumber()).toBe(-1); // No more available
|
||||
});
|
||||
|
||||
it('should handle negative size as zero', () =>
|
||||
{
|
||||
const bank = new NumberBank(-5);
|
||||
expect(bank.reserveNumber()).toBe(-1);
|
||||
});
|
||||
|
||||
it('should handle zero size', () =>
|
||||
{
|
||||
const bank = new NumberBank(0);
|
||||
expect(bank.reserveNumber()).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reserveNumber', () =>
|
||||
{
|
||||
let bank: NumberBank;
|
||||
|
||||
beforeEach(() =>
|
||||
{
|
||||
bank = new NumberBank(3);
|
||||
});
|
||||
|
||||
it('should return numbers in LIFO order (stack behavior)', () =>
|
||||
{
|
||||
// Numbers are added 0, 1, 2 to the array
|
||||
// pop() returns from the end, so we get 2, 1, 0
|
||||
expect(bank.reserveNumber()).toBe(2);
|
||||
expect(bank.reserveNumber()).toBe(1);
|
||||
expect(bank.reserveNumber()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return -1 when no numbers available', () =>
|
||||
{
|
||||
bank.reserveNumber();
|
||||
bank.reserveNumber();
|
||||
bank.reserveNumber();
|
||||
|
||||
expect(bank.reserveNumber()).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('freeNumber', () =>
|
||||
{
|
||||
let bank: NumberBank;
|
||||
|
||||
beforeEach(() =>
|
||||
{
|
||||
bank = new NumberBank(3);
|
||||
});
|
||||
|
||||
it('should make number available again after freeing', () =>
|
||||
{
|
||||
const num = bank.reserveNumber();
|
||||
bank.freeNumber(num);
|
||||
|
||||
// The freed number should be available again
|
||||
expect(bank.reserveNumber()).toBe(num);
|
||||
});
|
||||
|
||||
it('should handle freeing in different order', () =>
|
||||
{
|
||||
const n1 = bank.reserveNumber();
|
||||
const n2 = bank.reserveNumber();
|
||||
const n3 = bank.reserveNumber();
|
||||
|
||||
// Free in middle order
|
||||
bank.freeNumber(n2);
|
||||
bank.freeNumber(n1);
|
||||
|
||||
// Should get them back in LIFO order
|
||||
expect(bank.reserveNumber()).toBe(n1);
|
||||
expect(bank.reserveNumber()).toBe(n2);
|
||||
});
|
||||
|
||||
it('should ignore freeing numbers not in reserved list', () =>
|
||||
{
|
||||
bank.reserveNumber(); // reserves 2
|
||||
|
||||
// Try to free a number that wasn't reserved
|
||||
bank.freeNumber(999);
|
||||
|
||||
// Should still work normally
|
||||
expect(bank.reserveNumber()).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow reusing freed numbers', () =>
|
||||
{
|
||||
// Reserve all
|
||||
const n1 = bank.reserveNumber();
|
||||
const n2 = bank.reserveNumber();
|
||||
const n3 = bank.reserveNumber();
|
||||
|
||||
// Free and re-reserve multiple times
|
||||
bank.freeNumber(n1);
|
||||
const reused1 = bank.reserveNumber();
|
||||
expect(reused1).toBe(n1);
|
||||
|
||||
bank.freeNumber(n2);
|
||||
bank.freeNumber(reused1);
|
||||
|
||||
expect(bank.reserveNumber()).toBe(reused1);
|
||||
expect(bank.reserveNumber()).toBe(n2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () =>
|
||||
{
|
||||
it('should set internal arrays to null', () =>
|
||||
{
|
||||
const bank = new NumberBank(5);
|
||||
bank.reserveNumber();
|
||||
|
||||
bank.dispose();
|
||||
|
||||
// After dispose, reserveNumber should fail (arrays are null)
|
||||
// This will throw an error, which is expected behavior
|
||||
expect(() => bank.reserveNumber()).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () =>
|
||||
{
|
||||
it('should handle large bank size', () =>
|
||||
{
|
||||
const bank = new NumberBank(1000);
|
||||
|
||||
// Reserve all
|
||||
for (let i = 0; i < 1000; i++)
|
||||
{
|
||||
expect(bank.reserveNumber()).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
expect(bank.reserveNumber()).toBe(-1);
|
||||
});
|
||||
|
||||
it('should maintain consistency after multiple reserve/free cycles', () =>
|
||||
{
|
||||
const bank = new NumberBank(10);
|
||||
const reserved: number[] = [];
|
||||
|
||||
// Reserve 5
|
||||
for (let i = 0; i < 5; i++)
|
||||
{
|
||||
reserved.push(bank.reserveNumber());
|
||||
}
|
||||
|
||||
// Free 3
|
||||
bank.freeNumber(reserved[0]);
|
||||
bank.freeNumber(reserved[2]);
|
||||
bank.freeNumber(reserved[4]);
|
||||
|
||||
// Reserve 3 more (should get the freed ones)
|
||||
const newReserved: number[] = [];
|
||||
for (let i = 0; i < 3; i++)
|
||||
{
|
||||
newReserved.push(bank.reserveNumber());
|
||||
}
|
||||
|
||||
// All previously freed numbers should be reserved again
|
||||
expect(newReserved).toContain(reserved[0]);
|
||||
expect(newReserved).toContain(reserved[2]);
|
||||
expect(newReserved).toContain(reserved[4]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,299 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Vector3d } from '../Vector3d';
|
||||
|
||||
describe('Vector3d', () =>
|
||||
{
|
||||
describe('constructor', () =>
|
||||
{
|
||||
it('should create a vector with default values (0, 0, 0)', () =>
|
||||
{
|
||||
const vector = new Vector3d();
|
||||
expect(vector.x).toBe(0);
|
||||
expect(vector.y).toBe(0);
|
||||
expect(vector.z).toBe(0);
|
||||
});
|
||||
|
||||
it('should create a vector with specified values', () =>
|
||||
{
|
||||
const vector = new Vector3d(1, 2, 3);
|
||||
expect(vector.x).toBe(1);
|
||||
expect(vector.y).toBe(2);
|
||||
expect(vector.z).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('static sum', () =>
|
||||
{
|
||||
it('should return sum of two vectors', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
const v2 = new Vector3d(4, 5, 6);
|
||||
const result = Vector3d.sum(v1, v2);
|
||||
|
||||
expect(result.x).toBe(5);
|
||||
expect(result.y).toBe(7);
|
||||
expect(result.z).toBe(9);
|
||||
});
|
||||
|
||||
it('should return null if either vector is null', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
expect(Vector3d.sum(v1, null)).toBeNull();
|
||||
expect(Vector3d.sum(null, v1)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('static dif', () =>
|
||||
{
|
||||
it('should return difference of two vectors', () =>
|
||||
{
|
||||
const v1 = new Vector3d(5, 7, 9);
|
||||
const v2 = new Vector3d(1, 2, 3);
|
||||
const result = Vector3d.dif(v1, v2);
|
||||
|
||||
expect(result.x).toBe(4);
|
||||
expect(result.y).toBe(5);
|
||||
expect(result.z).toBe(6);
|
||||
});
|
||||
|
||||
it('should return null if either vector is null', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
expect(Vector3d.dif(v1, null)).toBeNull();
|
||||
expect(Vector3d.dif(null, v1)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('static product', () =>
|
||||
{
|
||||
it('should return vector multiplied by scalar', () =>
|
||||
{
|
||||
const v = new Vector3d(1, 2, 3);
|
||||
const result = Vector3d.product(v, 2);
|
||||
|
||||
expect(result.x).toBe(2);
|
||||
expect(result.y).toBe(4);
|
||||
expect(result.z).toBe(6);
|
||||
});
|
||||
|
||||
it('should return null if vector is null', () =>
|
||||
{
|
||||
expect(Vector3d.product(null, 2)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('static dotProduct', () =>
|
||||
{
|
||||
it('should calculate dot product of two vectors', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
const v2 = new Vector3d(4, 5, 6);
|
||||
const result = Vector3d.dotProduct(v1, v2);
|
||||
|
||||
// 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32
|
||||
expect(result).toBe(32);
|
||||
});
|
||||
|
||||
it('should return 0 if either vector is null', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
expect(Vector3d.dotProduct(v1, null)).toBe(0);
|
||||
expect(Vector3d.dotProduct(null, v1)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('static crossProduct', () =>
|
||||
{
|
||||
it('should calculate cross product of two vectors', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 0, 0);
|
||||
const v2 = new Vector3d(0, 1, 0);
|
||||
const result = Vector3d.crossProduct(v1, v2);
|
||||
|
||||
expect(result.x).toBe(0);
|
||||
expect(result.y).toBe(0);
|
||||
expect(result.z).toBe(1);
|
||||
});
|
||||
|
||||
it('should return null if either vector is null', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
expect(Vector3d.crossProduct(v1, null)).toBeNull();
|
||||
expect(Vector3d.crossProduct(null, v1)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('static isEqual', () =>
|
||||
{
|
||||
it('should return true for equal vectors', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
const v2 = new Vector3d(1, 2, 3);
|
||||
expect(Vector3d.isEqual(v1, v2)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for different vectors', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
const v2 = new Vector3d(1, 2, 4);
|
||||
expect(Vector3d.isEqual(v1, v2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if either vector is null', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
expect(Vector3d.isEqual(v1, null)).toBe(false);
|
||||
expect(Vector3d.isEqual(null, v1)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance methods', () =>
|
||||
{
|
||||
describe('assign', () =>
|
||||
{
|
||||
it('should copy values from another vector', () =>
|
||||
{
|
||||
const v1 = new Vector3d(0, 0, 0);
|
||||
const v2 = new Vector3d(1, 2, 3);
|
||||
v1.assign(v2);
|
||||
|
||||
expect(v1.x).toBe(1);
|
||||
expect(v1.y).toBe(2);
|
||||
expect(v1.z).toBe(3);
|
||||
});
|
||||
|
||||
it('should do nothing if vector is null', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
v1.assign(null);
|
||||
|
||||
expect(v1.x).toBe(1);
|
||||
expect(v1.y).toBe(2);
|
||||
expect(v1.z).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', () =>
|
||||
{
|
||||
it('should add another vector to this vector', () =>
|
||||
{
|
||||
const v1 = new Vector3d(1, 2, 3);
|
||||
const v2 = new Vector3d(4, 5, 6);
|
||||
v1.add(v2);
|
||||
|
||||
expect(v1.x).toBe(5);
|
||||
expect(v1.y).toBe(7);
|
||||
expect(v1.z).toBe(9);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subtract', () =>
|
||||
{
|
||||
it('should subtract another vector from this vector', () =>
|
||||
{
|
||||
const v1 = new Vector3d(5, 7, 9);
|
||||
const v2 = new Vector3d(1, 2, 3);
|
||||
v1.subtract(v2);
|
||||
|
||||
expect(v1.x).toBe(4);
|
||||
expect(v1.y).toBe(5);
|
||||
expect(v1.z).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiply', () =>
|
||||
{
|
||||
it('should multiply vector by scalar', () =>
|
||||
{
|
||||
const v = new Vector3d(1, 2, 3);
|
||||
v.multiply(3);
|
||||
|
||||
expect(v.x).toBe(3);
|
||||
expect(v.y).toBe(6);
|
||||
expect(v.z).toBe(9);
|
||||
});
|
||||
});
|
||||
|
||||
describe('divide', () =>
|
||||
{
|
||||
it('should divide vector by scalar', () =>
|
||||
{
|
||||
const v = new Vector3d(4, 8, 12);
|
||||
v.divide(4);
|
||||
|
||||
expect(v.x).toBe(1);
|
||||
expect(v.y).toBe(2);
|
||||
expect(v.z).toBe(3);
|
||||
});
|
||||
|
||||
it('should not divide by zero', () =>
|
||||
{
|
||||
const v = new Vector3d(1, 2, 3);
|
||||
v.divide(0);
|
||||
|
||||
expect(v.x).toBe(1);
|
||||
expect(v.y).toBe(2);
|
||||
expect(v.z).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('negate', () =>
|
||||
{
|
||||
it('should negate all components', () =>
|
||||
{
|
||||
const v = new Vector3d(1, -2, 3);
|
||||
v.negate();
|
||||
|
||||
expect(v.x).toBe(-1);
|
||||
expect(v.y).toBe(2);
|
||||
expect(v.z).toBe(-3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('length', () =>
|
||||
{
|
||||
it('should calculate vector length', () =>
|
||||
{
|
||||
const v = new Vector3d(3, 4, 0);
|
||||
expect(v.length).toBe(5);
|
||||
});
|
||||
|
||||
it('should cache length until values change', () =>
|
||||
{
|
||||
const v = new Vector3d(3, 4, 0);
|
||||
const length1 = v.length;
|
||||
const length2 = v.length;
|
||||
expect(length1).toBe(length2);
|
||||
|
||||
v.x = 6;
|
||||
expect(v.length).toBe(Math.sqrt(52)); // sqrt(36 + 16 + 0)
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalize', () =>
|
||||
{
|
||||
it('should normalize vector to unit length', () =>
|
||||
{
|
||||
const v = new Vector3d(3, 4, 0);
|
||||
v.normalize();
|
||||
|
||||
expect(v.x).toBeCloseTo(0.6);
|
||||
expect(v.y).toBeCloseTo(0.8);
|
||||
expect(v.z).toBeCloseTo(0);
|
||||
// Note: The actual calculated length would be 1, but the cached _length
|
||||
// is not reset in normalize() - this is a known limitation
|
||||
const actualLength = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||
expect(actualLength).toBeCloseTo(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toString', () =>
|
||||
{
|
||||
it('should return string representation', () =>
|
||||
{
|
||||
const v = new Vector3d(1, 2, 3);
|
||||
expect(v.toString()).toBe('[Vector3d: 1, 2, 3]');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user