91 lines
3.1 KiB
TypeScript
91 lines
3.1 KiB
TypeScript
|
|
import { test } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { ASCIILexicalTable, orderKeys } from './ASCIILexicalTable';
|
||
|
|
|
||
|
|
const a = 'a'.charCodeAt(0);
|
||
|
|
const z = 'z'.charCodeAt(0);
|
||
|
|
|
||
|
|
const makeLex = (maxWidth = 4) => new ASCIILexicalTable(a, z, maxWidth);
|
||
|
|
|
||
|
|
const isStrictlyIncreasing = (keys: string[]): boolean =>
|
||
|
|
keys.every((key, i) => i === 0 || keys[i - 1] < key);
|
||
|
|
|
||
|
|
test('orderKeys returns an empty array for empty input', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
assert.deepEqual(orderKeys(lex, []), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys preserves all existing keys when none are missing', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
assert.deepEqual(orderKeys(lex, ['a', 'b', 'c']), ['a', 'b', 'c']);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys keeps existing keys and fills a single interior gap', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const result = orderKeys(lex, ['b', undefined, 'd']);
|
||
|
|
assert.deepEqual(result, ['b', 'c', 'd']);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys output length always matches input length', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const inputs: Array<Array<string | undefined>> = [
|
||
|
|
[undefined],
|
||
|
|
[undefined, undefined],
|
||
|
|
[undefined, undefined, undefined],
|
||
|
|
['b', undefined, undefined, 'y'],
|
||
|
|
];
|
||
|
|
inputs.forEach((input) => {
|
||
|
|
const result = orderKeys(lex, input);
|
||
|
|
assert.ok(result, 'expected a defined result');
|
||
|
|
assert.equal(result?.length, input.length);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys produces strictly increasing, valid keys', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const result = orderKeys(lex, [undefined, undefined, undefined, undefined]);
|
||
|
|
assert.ok(result);
|
||
|
|
assert.equal(result?.length, 4);
|
||
|
|
assert.ok(isStrictlyIncreasing(result as string[]));
|
||
|
|
assert.ok((result as string[]).every((key) => lex.has(key)));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys keeps fixed keys at their positions when filling gaps', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const result = orderKeys(lex, ['b', undefined, undefined, 'y']) as string[];
|
||
|
|
assert.equal(result[0], 'b');
|
||
|
|
assert.equal(result[3], 'y');
|
||
|
|
assert.ok(isStrictlyIncreasing(result));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys is deterministic for the same input', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const first = orderKeys(lex, [undefined, undefined, undefined]);
|
||
|
|
const second = orderKeys(lex, [undefined, undefined, undefined]);
|
||
|
|
assert.deepEqual(first, second);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys handles a leading gap before an existing key', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const result = orderKeys(lex, [undefined, 'm']) as string[];
|
||
|
|
assert.equal(result.length, 2);
|
||
|
|
assert.equal(result[1], 'm');
|
||
|
|
assert.ok(result[0] < 'm');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys handles a trailing gap after an existing key', () => {
|
||
|
|
const lex = makeLex();
|
||
|
|
const result = orderKeys(lex, ['m', undefined]) as string[];
|
||
|
|
assert.equal(result.length, 2);
|
||
|
|
assert.equal(result[0], 'm');
|
||
|
|
assert.ok(result[1] > 'm');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('orderKeys works with a tiny table', () => {
|
||
|
|
const tiny = new ASCIILexicalTable('a'.charCodeAt(0), 'b'.charCodeAt(0), 2);
|
||
|
|
const result = orderKeys(tiny, [undefined, undefined]) as string[];
|
||
|
|
assert.equal(result.length, 2);
|
||
|
|
assert.ok(isStrictlyIncreasing(result));
|
||
|
|
assert.ok(result.every((key) => tiny.has(key)));
|
||
|
|
});
|