UNPKG

@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
"use strict"; 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