@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
350 lines • 11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const radix_1 = require("./radix");
const crypto_1 = require("crypto");
// generateUUID is used to generate a random UUID
function generateUUID() {
const buf = (0, crypto_1.randomBytes)(16);
return [
buf.subarray(0, 4).toString('hex'),
buf.subarray(4, 6).toString('hex'),
buf.subarray(6, 8).toString('hex'),
buf.subarray(8, 10).toString('hex'),
buf.subarray(10, 16).toString('hex')
].join('-');
}
describe('Radix Tree', () => {
test('TestRadix', async () => {
let min = '';
let max = '';
const inp = {};
for (let i = 0; i < 1000; i++) {
const gen = generateUUID();
inp[gen] = i;
if (gen < min || i === 0) {
min = gen;
}
if (gen > max || i === 0) {
max = gen;
}
}
const r = (0, radix_1.createTreeFromMap)(inp);
expect(r.len()).toBe(Object.keys(inp).length);
// Walk test (just to ensure it works, output not checked like in Go)
await r.walk(async (k, v) => {
// println equivalent would be console.log, but we don't need it for tests
return false;
});
for (const [k, v] of Object.entries(inp)) {
const [out, ok] = r.get(k);
expect(ok).toBe(true);
expect(out).toBe(v);
}
// Check min and max
const [outMin] = r.minimum();
expect(outMin).toBe(min);
const [outMax] = r.maximum();
expect(outMax).toBe(max);
for (const [k, v] of Object.entries(inp)) {
const [out, ok] = r.delete(k);
expect(ok).toBe(true);
expect(out).toBe(v);
}
expect(r.len()).toBe(0);
});
test('TestRoot', () => {
const r = (0, radix_1.createTree)();
let [, ok] = r.delete('');
expect(ok).toBe(false);
[, ok] = r.insert('', true);
expect(ok).toBe(false);
const [val, found] = r.get('');
expect(found).toBe(true);
expect(val).toBe(true);
const [deletedVal, deleted] = r.delete('');
expect(deleted).toBe(true);
expect(deletedVal).toBe(true);
});
test('TestDelete', () => {
const r = (0, radix_1.createTree)();
const s = ['', 'A', 'AB'];
for (const ss of s) {
r.insert(ss, true);
}
for (const ss of s) {
const [, ok] = r.delete(ss);
expect(ok).toBe(true);
}
});
test('TestDeletePrefix', async () => {
const cases = [
{
inp: ['', 'A', 'AB', 'ABC', 'R', 'S'],
prefix: 'A',
out: ['', 'R', 'S'],
numDeleted: 3
},
{
inp: ['', 'A', 'AB', 'ABC', 'R', 'S'],
prefix: 'ABC',
out: ['', 'A', 'AB', 'R', 'S'],
numDeleted: 1
},
{
inp: ['', 'A', 'AB', 'ABC', 'R', 'S'],
prefix: '',
out: [],
numDeleted: 6
},
{
inp: ['', 'A', 'AB', 'ABC', 'R', 'S'],
prefix: 'S',
out: ['', 'A', 'AB', 'ABC', 'R'],
numDeleted: 1
},
{
inp: ['', 'A', 'AB', 'ABC', 'R', 'S'],
prefix: 'SS',
out: ['', 'A', 'AB', 'ABC', 'R', 'S'],
numDeleted: 0
}
];
for (const testCase of cases) {
const r = (0, radix_1.createTree)();
for (const ss of testCase.inp) {
r.insert(ss, true);
}
const deleted = await r.deletePrefix(testCase.prefix);
expect(deleted).toBe(testCase.numDeleted);
const out = [];
const fn = async (s, v) => {
out.push(s);
return false;
};
await r.walk(fn);
expect(out.sort()).toEqual(testCase.out.sort());
}
});
test('TestLongestPrefix', () => {
const r = (0, radix_1.createTree)();
const keys = [
'',
'foo',
'foobar',
'foobarbaz',
'foobarbazzip',
'foozip'
];
for (const k of keys) {
r.insert(k, null);
}
expect(r.len()).toBe(keys.length);
const cases = [
{ inp: 'a', out: '' },
{ inp: 'abc', out: '' },
{ inp: 'fo', out: '' },
{ inp: 'foo', out: 'foo' },
{ inp: 'foob', out: 'foo' },
{ inp: 'foobar', out: 'foobar' },
{ inp: 'foobarba', out: 'foobar' },
{ inp: 'foobarbaz', out: 'foobarbaz' },
{ inp: 'foobarbazzi', out: 'foobarbaz' },
{ inp: 'foobarbazzip', out: 'foobarbazzip' },
{ inp: 'foozi', out: 'foo' },
{ inp: 'foozip', out: 'foozip' },
{ inp: 'foozipzap', out: 'foozip' }
];
for (const testCase of cases) {
const [m, , ok] = r.longestPrefix(testCase.inp);
expect(ok).toBe(true);
expect(m).toBe(testCase.out);
}
});
test('TestWalkPrefix', async () => {
const r = (0, radix_1.createTree)();
const keys = [
'foobar',
'foo/bar/baz',
'foo/baz/bar',
'foo/zip/zap',
'zipzap'
];
for (const k of keys) {
r.insert(k, null);
}
expect(r.len()).toBe(keys.length);
const cases = [
{
inp: 'f',
out: ['foobar', 'foo/bar/baz', 'foo/baz/bar', 'foo/zip/zap']
},
{
inp: 'foo',
out: ['foobar', 'foo/bar/baz', 'foo/baz/bar', 'foo/zip/zap']
},
{
inp: 'foob',
out: ['foobar']
},
{
inp: 'foo/',
out: ['foo/bar/baz', 'foo/baz/bar', 'foo/zip/zap']
},
{
inp: 'foo/b',
out: ['foo/bar/baz', 'foo/baz/bar']
},
{
inp: 'foo/ba',
out: ['foo/bar/baz', 'foo/baz/bar']
},
{
inp: 'foo/bar',
out: ['foo/bar/baz']
},
{
inp: 'foo/bar/baz',
out: ['foo/bar/baz']
},
{
inp: 'foo/bar/bazoo',
out: []
},
{
inp: 'z',
out: ['zipzap']
}
];
for (const testCase of cases) {
const out = [];
const fn = async (s, v) => {
out.push(s);
return false;
};
await r.walkPrefix(testCase.inp, fn);
out.sort();
const expectedOut = [...testCase.out].sort();
expect(out).toEqual(expectedOut);
}
});
test('TestWalkPath', async () => {
const r = (0, radix_1.createTree)();
const keys = [
'foo',
'foo/bar',
'foo/bar/baz',
'foo/baz/bar',
'foo/zip/zap',
'zipzap'
];
for (const k of keys) {
r.insert(k, null);
}
expect(r.len()).toBe(keys.length);
const cases = [
{
inp: 'f',
out: []
},
{
inp: 'foo',
out: ['foo']
},
{
inp: 'foo/',
out: ['foo']
},
{
inp: 'foo/ba',
out: ['foo']
},
{
inp: 'foo/bar',
out: ['foo', 'foo/bar']
},
{
inp: 'foo/bar/baz',
out: ['foo', 'foo/bar', 'foo/bar/baz']
},
{
inp: 'foo/bar/bazoo',
out: ['foo', 'foo/bar', 'foo/bar/baz']
},
{
inp: 'z',
out: []
}
];
for (const testCase of cases) {
const out = [];
const fn = async (s, v) => {
out.push(s);
return false;
};
await r.walkPath(testCase.inp, fn);
out.sort();
const expectedOut = [...testCase.out].sort();
expect(out).toEqual(expectedOut);
}
});
test('TestWalkDelete', async () => {
const r = (0, radix_1.createTree)();
r.insert('init0/0', null);
r.insert('init0/1', null);
r.insert('init0/2', null);
r.insert('init0/3', null);
r.insert('init1/0', null);
r.insert('init1/1', null);
r.insert('init1/2', null);
r.insert('init1/3', null);
r.insert('init2', null);
const deleteFn = async (s, v) => {
r.delete(s);
return false;
};
await r.walkPrefix('init1', deleteFn);
for (const s of ['init0/0', 'init0/1', 'init0/2', 'init0/3', 'init2']) {
const [, ok] = r.get(s);
expect(ok).toBe(true);
}
expect(r.len()).toBe(5);
await r.walk(deleteFn);
expect(r.len()).toBe(0);
});
test('ToMap functionality', async () => {
const r = (0, radix_1.createTree)();
const originalMap = {
'foo': 1,
'foobar': 2,
'baz': 3
};
for (const [k, v] of Object.entries(originalMap)) {
r.insert(k, v);
}
const resultMap = await r.toMap();
expect(resultMap).toEqual(originalMap);
});
});
// Performance test (similar to BenchmarkInsert but as a regular test)
describe('Performance Tests', () => {
test('Insert Performance', () => {
const r = (0, radix_1.createTree)();
// Pre-populate with some data
for (let i = 0; i < 1000; i++) {
r.insert(`init${i}`, true);
}
const startTime = process.hrtime.bigint();
// Insert 1000 more items
for (let n = 0; n < 1000; n++) {
const [, updated] = r.insert(n.toString(), true);
expect(updated).toBe(false);
}
const endTime = process.hrtime.bigint();
const durationMs = Number(endTime - startTime) / 1000000;
// This test mainly ensures the operations complete in reasonable time
// Adjust the threshold based on your performance requirements
expect(durationMs).toBeLessThan(1000); // Should complete within 1 second
expect(r.len()).toBe(2000);
});
});
//# sourceMappingURL=radix.test.js.map