data-structure-typed
Version:
Standard data structure
1,528 lines (1,320 loc) • 58.6 kB
text/typescript
import { BinaryTree, BinaryTreeNode, BTNEntry } from '../../../../src';
import { getRandomIntArray } from '../../../utils';
// import { isDebugTest } from '../../../config';
// const isDebug = isDebugTest;
describe('BinaryTreeNode', () => {
it('should create an instance of BinaryTreeNode', () => {
const node = new BinaryTreeNode<number>(1);
expect(node).toBeInstanceOf(BinaryTreeNode);
});
it('should set and get the ID correctly', () => {
const node = new BinaryTreeNode<number>(1);
expect(node.key).toBe(1);
node.key = 2;
expect(node.key).toBe(2);
});
it('should set and get the value correctly', () => {
const node: BinaryTreeNode<number> = new BinaryTreeNode<number>(1, 42);
expect(node.key).toBe(1);
expect(node.value).toBe(42);
node.value = 55;
expect(node.value).toBe(55);
});
it('should set and get the left child correctly', () => {
const node1 = new BinaryTreeNode<number>(1);
const node2 = new BinaryTreeNode<number>(2);
node1.left = node2;
expect(node1.left).toBe(node2);
expect(node2.parent).toBe(node1);
});
it('should set and get the right child correctly', () => {
const node1 = new BinaryTreeNode<number>(1);
const node2 = new BinaryTreeNode<number>(2);
node1.right = node2;
expect(node1.right).toBe(node2);
expect(node2.parent).toBe(node1);
});
it('should set and get the parent correctly', () => {
const node1 = new BinaryTreeNode<number>(1);
const node2 = new BinaryTreeNode<number>(2);
node1.left = node2;
expect(node2.parent).toBe(node1);
expect(node1.left).toBe(node2);
});
it('should determine family position correctly', () => {
const root = new BinaryTreeNode<number>(1);
const leftChild = new BinaryTreeNode<number>(2);
const rightChild = new BinaryTreeNode<number>(3);
root.left = leftChild;
root.right = rightChild;
expect(leftChild.familyPosition).toBe('LEFT');
leftChild.right = new BinaryTreeNode<number>(4);
expect(rightChild.familyPosition).toBe('RIGHT');
expect(root.familyPosition).toBe('ROOT');
expect(leftChild.familyPosition).toBe('ROOT_LEFT');
rightChild.left = new BinaryTreeNode<number>(5);
expect(rightChild.familyPosition).toBe('ROOT_RIGHT');
});
it('should determine only right child family position correctly', () => {
const root = new BinaryTreeNode<number>(1);
const rightChild = new BinaryTreeNode<number>(3);
const isolated = new BinaryTreeNode<number>(2);
root.right = rightChild;
expect(rightChild.familyPosition).toBe('RIGHT');
expect(isolated.familyPosition).toBe('ISOLATED');
expect(root.familyPosition).toBe('ROOT');
});
});
describe('BinaryTree.addMany', () => {
it('addMany(): adds entries via toEntryFn and values override', () => {
const binTree = new BinaryTree<number, number, { id: number; name: number }>([], {
toEntryFn: ({ id, name }) => [id, name]
});
binTree.addMany(
[
{ id: 1, name: 1 },
{ id: 2, name: 2 },
{ id: 4, name: 4 },
{ id: 3, name: 3 }
],
[undefined, 22, 44, 33]
);
expect(binTree.get(2)).toBe(22);
expect(binTree.get(binTree.getNode(3))).toBe(33);
expect(binTree.get(binTree.getNode(4))).toBe(44);
expect(binTree.get(binTree.getNode(1))).toBe(1);
});
it('addMany(): handles undefined and null keys', () => {
const binaryTree = new BinaryTree<number, string>();
const addManyWithUndefined = binaryTree.addMany([1, undefined, 3]);
expect(addManyWithUndefined).toEqual([true, false, true]);
expect(binaryTree.get(undefined)).toBe(undefined);
const addManyWithNull = binaryTree.addMany([1, null, 3, 4]);
expect(addManyWithNull).toEqual([true, true, true, true]);
const addManyEntriesWithNull = binaryTree.addMany([
[1, '1'],
[null, 'null'],
[3, '3'],
[4, '4']
]);
expect(addManyEntriesWithNull).toEqual([true, true, true, true]);
expect(binaryTree.get(null)).toBe(undefined);
expect(binaryTree.getNode(null)).toBe(undefined);
// // TODO should be null instead of undefined
// expect(binaryTree.getNode(null)).toBe(null);
const node0 = binaryTree.add(0, '0');
expect(node0).toBe(true);
expect(binaryTree.get(0)).toBe('0');
});
});
describe('BinaryTree', () => {
let binTree: BinaryTree<number>;
beforeEach(() => {
binTree = new BinaryTree<number>();
});
afterEach(() => {
binTree.clear();
});
it('add(): inserts a node and updates size', () => {
const node = binTree.add(1);
expect(node).not.toBeNull();
expect(binTree.size).toBe(1);
});
it('delete(): leaf/one-child/two-children/missing; updates size and minHeight', () => {
expect(binTree.getHeight(binTree.root, 'ITERATIVE')).toBe(-1);
expect(binTree.getMinHeight()).toBe(-1);
const node1 = binTree._createNode(1);
binTree.add(node1);
expect(binTree.size).toBe(1);
const leftChild = new BinaryTreeNode<number>(2);
const rightChild = new BinaryTreeNode<number>(3);
binTree.add(leftChild);
binTree.add(rightChild);
const root = binTree.root;
expect(leftChild.familyPosition).toBe('LEFT');
binTree.add(null);
binTree.add(new BinaryTreeNode<number>(4));
expect(rightChild.familyPosition).toBe('RIGHT');
expect(root?.familyPosition).toBe('ROOT');
expect(leftChild.familyPosition).toBe('ROOT_LEFT');
binTree.add(new BinaryTreeNode<number>(5));
expect(rightChild.familyPosition).toBe('ROOT_RIGHT');
binTree.delete(new BinaryTreeNode<number>(200));
binTree.delete(rightChild);
if (node1) {
const result = binTree.delete(node1);
expect(result).toHaveLength(1);
expect(binTree.size).toBe(4);
expect(binTree.getMinHeight(binTree.root, 'RECURSIVE')).toBe(1);
}
});
it('add()/has()/getNode(): find nodes by key and predicate', () => {
binTree.add([1, 1]);
binTree.add(undefined);
binTree.add([2, 2]);
binTree.add([3, 3]);
expect(binTree.has(1)).toBe(true);
expect(binTree.has(2)).toBe(true);
expect(binTree.has(3)).toBe(true);
expect(binTree.has(4)).toBe(false);
const node4 = binTree.getNode(4);
expect(binTree.has(node4)).toBe(false);
expect(binTree.has(node => node === node4)).toBe(false);
expect(binTree.has(node => node.key?.toString() === '3')).toBe(true);
});
it('clone(): structural copy; subtree dfs with includeNull permutations', () => {
expect(binTree.isEmpty()).toBe(true);
binTree.addMany([4, 2, 6, null, 1, 3, null, 5, null, 7]);
expect(binTree.root?.key).toBe(4);
expect(binTree.root?.left?.key).toBe(2);
expect(binTree.root?.left?.left).toBe(null);
expect(binTree.root?.left?.right?.key).toBe(1);
expect(binTree.root?.right?.key).toBe(6);
expect(binTree.root?.right?.left?.key).toBe(3);
expect(binTree.root?.right?.right).toBe(null);
const cloned = binTree.clone();
expect(cloned.root?.key).toBe(4);
expect(cloned.root?.left?.key).toBe(2);
expect(cloned.root?.left?.left).toBe(null);
expect(cloned.root?.left?.right?.key).toBe(1);
expect(cloned.root?.right?.key).toBe(6);
expect(cloned.root?.right?.left?.key).toBe(3);
expect(cloned.root?.right?.right).toBe(null);
expect(cloned.dfs(node => node.key, 'PRE', false, cloned.getNode(6), 'ITERATIVE')).toEqual([6, 3, 7]);
expect(cloned.dfs(node => (node ? node.key : null), 'PRE', false, cloned.getNode(6), 'ITERATIVE', true)).toEqual([
6,
3,
7,
null
]);
expect(cloned.dfs(node => (node ? node.key : node), 'PRE', false, cloned.getNode(6), 'ITERATIVE', true)).toEqual([
6,
3,
7,
null
]);
expect(cloned.dfs(node => (node ? node.key : null), 'PRE', false, cloned.getNode(6), 'RECURSIVE', true)).toEqual([
6,
3,
7,
null
]);
cloned.delete(6);
cloned.delete(3);
cloned.delete(7);
cloned.delete(1);
cloned.delete(5);
cloned.delete(4);
cloned.delete(2);
// cloned.delete(null);
// cloned.delete(null);
// cloned.delete(null);
expect(binTree.size).toBe(10);
expect(cloned.size).toBe(3);
// expect(cloned.size).toBe(0);
// expect(cloned.isEmpty()).toBe(true);
});
it('isPerfectlyBalanced(): toggles with pointer tampering and skewed levels', () => {
binTree.add(3);
binTree.add(12);
binTree.addMany(getRandomIntArray(100, 1, 100));
binTree.add(10);
expect(binTree.isPerfectlyBalanced()).toBe(true);
const node3 = binTree.getNode(3);
if (node3) node3.right = binTree._createNode(1);
expect(binTree.isPerfectlyBalanced()).toBe(false);
binTree.clear();
binTree.addMany([1, null, 2, null, 3, null, 4, null, 5, null, 6, null]);
expect(binTree.isPerfectlyBalanced()).toBe(false);
});
it('getDepth(): returns correct depth with/without root parameter', () => {
binTree.add(1);
expect(binTree.getDepth(1)).toBe(0);
binTree.add(2);
expect(binTree.getDepth(2)).toBe(1);
binTree.add(3);
expect(binTree.getDepth(3, 1)).toBe(1);
binTree.add(4);
expect(binTree.getDepth(4, 1)).toBe(2);
expect(binTree.getDepth(4)).toBe(2);
expect(binTree.getDepth(4, 2)).toBe(1);
});
it('dfs(IN): returns in-order; height respects iterationType', () => {
binTree.add(null);
binTree.delete(1);
expect(binTree.getHeight()).toBe(-1);
binTree.add(4);
binTree.add(2);
expect(binTree.getHeight()).toBe(1);
binTree.iterationType = 'RECURSIVE';
expect(binTree.getHeight()).toBe(1);
binTree.iterationType = 'ITERATIVE';
binTree.add(6);
binTree.add(1);
binTree.add(new BinaryTreeNode(3));
binTree.add(5);
binTree.add(7);
const inOrder = binTree.dfs(node => node.key);
expect(inOrder).toEqual([1, 2, 3, 4, 5, 6, 7]);
});
it('isBST(): returns true for subtree (iterative & recursive)', () => {
binTree.addMany([
new BinaryTreeNode(4, 4),
new BinaryTreeNode(2, 2),
new BinaryTreeNode(6, 6),
new BinaryTreeNode(1, 1),
new BinaryTreeNode(3, 3),
new BinaryTreeNode(5, 5),
new BinaryTreeNode(7, 7),
new BinaryTreeNode(4, 4)
]);
expect(binTree.isBST(binTree.getNode(4), 'RECURSIVE')).toBe(true);
expect(binTree.isBST(binTree.getNode(4), 'ITERATIVE')).toBe(true);
});
it('isBST(): returns true for subtree (iterative & recursive)', () => {
expect(binTree.toVisual()).toBe('');
binTree.addMany([4, 2, 6, 1, 3, 5, 7, 4]);
expect(binTree.toVisual()).toBe(
'N for null\n' +
' ___4___ \n' +
' / \\ \n' +
' _2_ _6_ \n' +
' / \\ / \\ \n' +
' 1 3 5 7 \n' +
' \n'
);
const visualized = binTree.toVisual(undefined, {
isShowUndefined: true,
isShowNull: true,
isShowRedBlackNIL: true
});
expect(visualized).toBe(
'U for undefined\n' +
'N for null\n' +
'S for Sentinel Node(NIL)\n' +
' _______4_______ \n' +
' / \\ \n' +
' ___2___ ___6___ \n' +
' / \\ / \\ \n' +
' _1_ _3_ _5_ _7_ \n' +
' / \\ / \\ / \\ / \\ \n' +
' U U U U U U U U \n' +
' \n'
);
expect(binTree.isBST(binTree.getNode(4), 'RECURSIVE')).toBe(true);
expect(binTree.isBST(binTree.getNode(4), 'ITERATIVE')).toBe(true);
expect(binTree.getNodes(2, false, null)).toEqual([]);
expect(binTree.getNodes(undefined)).toEqual([]);
expect(binTree.getNodes(binTree.getNode(2), false, binTree.root)).toEqual([binTree.getNode(2)]);
});
describe('isValidKey', () => {
describe('primitive types', () => {
it('isValidKey(): numbers are valid keys', () => {
expect(binTree.isValidKey(42)).toBe(true);
expect(binTree.isValidKey(0)).toBe(true);
expect(binTree.isValidKey(-1)).toBe(true);
expect(binTree.isValidKey(Infinity)).toBe(true);
expect(binTree.isValidKey(-Infinity)).toBe(true);
});
// it('isValidKey(): NaN is not a valid key', () => {
// expect(binTree.isValidKey(NaN)).toBe(false);
// });
it('isValidKey(): strings are valid keys', () => {
expect(binTree.isValidKey('hello')).toBe(true);
expect(binTree.isValidKey('')).toBe(true);
expect(binTree.isValidKey('123')).toBe(true);
});
it('isValidKey(): BigInt is a valid key', () => {
expect(binTree.isValidKey(BigInt(42))).toBe(true);
expect(binTree.isValidKey(BigInt(0))).toBe(true);
expect(binTree.isValidKey(BigInt(-1))).toBe(true);
});
it('isValidKey(): booleans are valid keys', () => {
expect(binTree.isValidKey(true)).toBe(true);
expect(binTree.isValidKey(false)).toBe(true);
});
it('isValidKey(): null is valid, undefined is not', () => {
expect(binTree.isValidKey(null)).toBe(true);
expect(binTree.isValidKey(undefined)).toBe(false);
});
it('isValidKey(): symbols are not valid keys', () => {
expect(binTree.isValidKey(Symbol('test'))).toBe(false);
expect(binTree.isValidKey(Symbol.for('test'))).toBe(false);
});
});
describe('Date objects', () => {
it('isValidKey(): valid Date objects are valid keys', () => {
expect(binTree.isValidKey(new Date())).toBe(true);
expect(binTree.isValidKey(new Date('2024-01-01'))).toBe(true);
});
// it('invalid Date objects should not be a key', () => {
// expect(binTree.isValidKey(new Date('invalid'))).toBe(false);
// });
});
describe('arrays', () => {
it('isValidKey(): arrays are valid (stringified) keys', () => {
expect(binTree.isValidKey([])).toBe(true);
expect(binTree.isValidKey([1, 2, 3])).toBe(true);
expect(binTree.isValidKey(['a', 'b', 'c'])).toBe(true);
});
});
describe('plain objects', () => {
it('isValidKey(): plain objects are not valid keys', () => {
expect(binTree.isValidKey({})).toBe(false);
expect(binTree.isValidKey({ a: 1 })).toBe(false);
});
});
describe('custom objects', () => {
it('isValidKey(): objects with numeric valueOf are valid keys', () => {
expect(binTree.isValidKey({ valueOf: () => 42 })).toBe(true);
});
it('isValidKey(): objects with string valueOf are valid keys', () => {
expect(binTree.isValidKey({ valueOf: () => 'test' })).toBe(true);
});
it('isValidKey(): objects with boolean valueOf are treated as valid', () => {
expect(binTree.isValidKey({ valueOf: () => true })).toBe(true);
});
it('isValidKey(): nested valueOf/toString results in valid key', () => {
expect(
binTree.isValidKey({
valueOf: () => ({ toString: () => '42' })
})
).toBe(true);
});
});
describe('deeply nested objects', () => {
it('isValidKey(): deeply nested valueOf is valid', () => {
const deeplyNested = {
valueOf: () => ({
valueOf: () => 42
})
};
expect(binTree.isValidKey(deeplyNested)).toBe(true);
});
it('isValidKey(): very deep conversion to string is valid', () => {
const veryDeeplyNested = {
valueOf: () => ({
valueOf: () => ({
toString: () => '42'
})
})
};
expect(binTree.isValidKey(veryDeeplyNested)).toBe(true);
});
it('isValidKey(): circular reference is invalid', () => {
const circular: any = {
valueOf: () => circular
};
expect(binTree.isValidKey(circular)).toBe(false);
});
});
describe('edge cases', () => {
it('isValidKey(): non-primitive ultimate value is invalid', () => {
const complexObject = {
valueOf: () => ({
toString: () => ({
valueOf: () => 'valid'
})
})
};
expect(binTree.isValidKey(complexObject)).toBe(false);
});
it('isValidKey(): ultimately primitive conversion is valid', () => {
const complexObject = {
valueOf: () => ({
valueOf: () => ({
valueOf: () => ({
valueOf: () => ({
toString: () => `{
valueOf: () => 'valid'
}`
})
})
})
})
};
expect(binTree.isValidKey(complexObject)).toBe(true);
});
});
describe('type checking', () => {
it('isValidKey(): works as a type guard in array methods', () => {
const values: unknown[] = [42, 'test', true, null, undefined, new Date()];
const comparableValues = values.filter(item => binTree.isValidKey(item));
expect(comparableValues.length).toBe(5);
});
});
});
it('isLeaf(): detects leaves; null is treated as leaf', () => {
expect(binTree.getLeftMost()).toBe(undefined);
expect(binTree.getRightMost()).toBe(undefined);
binTree.addMany([4, 2, 6, 1, 3, 5, 7, 4]);
const leftMost = binTree.getLeftMost();
expect(binTree.isLeaf(leftMost)).toBe(true);
expect(binTree.isLeaf(null)).toBe(true);
const rightMost = binTree.getRightMost();
expect(binTree.isLeaf(rightMost)).toBe(true);
expect(binTree.isLeaf(null)).toBe(true);
});
it('dfs/bfs on mixed-null level-order tree: expected orders (includeNull on/off)', () => {
expect(binTree.dfs()).toEqual([]);
expect([...binTree.values()]).toEqual([]);
binTree.addMany([4, 2, 6, null, 1, 3, null, 5, null, 7]);
expect(binTree.dfs(node => node.key, 'PRE', false, undefined, 'ITERATIVE')).toEqual([4, 2, 1, 5, 6, 3, 7]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, undefined, 'ITERATIVE', false)).toEqual(
[4, 2, 1, 5, 6, 3, 7]
);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, undefined, 'ITERATIVE', true)).toEqual([
4,
2,
null,
1,
5,
null,
6,
3,
7,
null
]);
expect(binTree.dfs(node => node.key, 'PRE', false, undefined, 'RECURSIVE')).toEqual([4, 2, 1, 5, 6, 3, 7]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, undefined, 'RECURSIVE', false)).toEqual(
[4, 2, 1, 5, 6, 3, 7]
);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, undefined, 'RECURSIVE', true)).toEqual([
4,
2,
null,
1,
5,
null,
6,
3,
7,
null
]);
expect(binTree.dfs(node => node.key, 'IN', false, undefined, 'ITERATIVE')).toEqual([2, 5, 1, 4, 7, 3, 6]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, undefined, 'ITERATIVE', false)).toEqual([
2, 5, 1, 4, 7, 3, 6
]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, undefined, 'ITERATIVE', true)).toEqual([
null,
2,
5,
1,
null,
4,
7,
3,
6,
null
]);
expect(binTree.dfs(node => node.key, 'IN', false, undefined, 'RECURSIVE')).toEqual([2, 5, 1, 4, 7, 3, 6]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, undefined, 'RECURSIVE', false)).toEqual([
2, 5, 1, 4, 7, 3, 6
]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, undefined, 'RECURSIVE', true)).toEqual([
null,
2,
5,
1,
null,
4,
7,
3,
6,
null
]);
expect(binTree.dfs(node => node.key, 'POST', false, undefined, 'ITERATIVE')).toEqual([5, 1, 2, 7, 3, 6, 4]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, undefined, 'ITERATIVE', false)
).toEqual([5, 1, 2, 7, 3, 6, 4]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, undefined, 'ITERATIVE', true)).toEqual(
[null, 5, null, 1, 2, 7, 3, null, 6, 4]
);
expect(binTree.dfs(node => node.key, 'POST', false, undefined, 'RECURSIVE')).toEqual([5, 1, 2, 7, 3, 6, 4]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, undefined, 'RECURSIVE', false)
).toEqual([5, 1, 2, 7, 3, 6, 4]);
expect(binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, undefined, 'RECURSIVE', true)).toEqual(
[null, 5, null, 1, 2, 7, 3, null, 6, 4]
);
});
it('dfs on subtree (startNode): expected orders (includeNull on/off)', () => {
binTree.addMany([4, 2, 6, null, 1, 3, null, 5, null, 7]);
expect(binTree.dfs(node => node.key, 'PRE', false, binTree.getNode(6), 'ITERATIVE')).toEqual([6, 3, 7]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, binTree.getNode(6), 'ITERATIVE', false)
).toEqual([6, 3, 7]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, binTree.getNode(6), 'ITERATIVE', true)
).toEqual([6, 3, 7, null]);
expect(binTree.dfs(node => node.key, 'PRE', false, binTree.getNode(6), 'RECURSIVE')).toEqual([6, 3, 7]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, binTree.getNode(6), 'RECURSIVE', false)
).toEqual([6, 3, 7]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'PRE', false, binTree.getNode(6), 'RECURSIVE', true)
).toEqual([6, 3, 7, null]);
expect(binTree.dfs(node => node.key, 'IN', false, binTree.getNode(6), 'ITERATIVE')).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, binTree.getNode(6), 'ITERATIVE', false)
).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, binTree.getNode(6), 'ITERATIVE', true)
).toEqual([7, 3, 6, null]);
expect(binTree.dfs(node => node.key, 'IN', false, binTree.getNode(6), 'RECURSIVE')).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, binTree.getNode(6), 'RECURSIVE', false)
).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'IN', false, binTree.getNode(6), 'RECURSIVE', true)
).toEqual([7, 3, 6, null]);
expect(binTree.dfs(node => node.key, 'POST', false, binTree.getNode(6), 'ITERATIVE')).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, binTree.getNode(6), 'ITERATIVE', false)
).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, binTree.getNode(6), 'ITERATIVE', true)
).toEqual([7, 3, null, 6]);
expect(binTree.dfs(node => node.key, 'POST', false, binTree.getNode(6), 'RECURSIVE')).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, binTree.getNode(6), 'RECURSIVE', false)
).toEqual([7, 3, 6]);
expect(
binTree.dfs(node => (node !== null ? node.key : null), 'POST', false, binTree.getNode(6), 'RECURSIVE', true)
).toEqual([7, 3, null, 6]);
});
it('clear(): empties tree and resets root', () => {
binTree.add(1);
binTree.add(2);
expect(binTree.size).toBe(2);
binTree.clear();
expect(binTree.size).toBe(0);
expect(binTree.root).toBeUndefined();
});
it('duplicate keys: replace existing value; bfs includeNull snapshot', function () {
binTree.clear();
expect(binTree.bfs()).toEqual([]);
binTree.addMany([-10, -10, -10, 9, 9, 20, null, null, 15, 7, 8, null, 2, null, 6, null, null, 8, 8, 8]);
expect(binTree.bfs(node => (node ? node.key : null), undefined, undefined, true)).toEqual([
-10,
9,
20,
null,
null,
15,
7,
8,
null,
2,
null,
6,
null,
null
]);
});
// it('should keyValueNodeEntryRawToNodeAndValue', () => {
// const binTree = new BinaryTree<number>();
// const node0 = binTree.keyValueNodeEntryRawToNodeAndValue(0);
// expect(node0).toEqual([
// {
// _left: undefined,
// _right: undefined,
// key: 0,
// parent: undefined,
// value: undefined
// },
// undefined
// ]);
//
// const nodeUndefined = binTree.keyValueNodeEntryRawToNodeAndValue(undefined);
// expect(nodeUndefined).toEqual([undefined, undefined]);
//
// const nodeNull = binTree.keyValueNodeEntryRawToNodeAndValue(null);
// expect(nodeNull).toEqual([null, undefined]);
//
// const [, nodeWithSeparateValue] = binTree.keyValueNodeEntryRawToNodeAndValue(7, 77);
// expect(nodeWithSeparateValue).toBe(77);
//
// expect(binTree.keyValueNodeEntryRawToNodeAndValue([undefined, 2])).toEqual([undefined, undefined]);
//
// expect(binTree.keyValueNodeEntryRawToNodeAndValue(Symbol('test') as unknown as number)).toEqual([
// undefined,
// undefined
// ]);
//
// const bTree = new BinaryTree<number, number, { obj: { id: number } }>([], {
// toEntryFn: (ele: { obj: { id: number } }) => [Symbol('test') as unknown as number, ele.obj.id]
// });
// expect(bTree.keyValueNodeEntryRawToNodeAndValue({ obj: { id: 1 } })).toEqual([undefined, undefined]);
// });
it('add(): duplicate key updates value (Map vs non-Map behavior)', () => {
const binTree = new BinaryTree<number, string>([4, 5, [1, '1'], 2, 3], { isMapMode: false });
expect(binTree.get(1)).toBe('1');
expect(binTree.getNode(1)?.value).toBe('1');
binTree.add(1, 'a');
expect(binTree.get(1)).toBe('a');
binTree.add([1, 'b']);
expect(binTree.getNode(1)?.value).toBe('b');
expect(binTree.get(1)).toBe('b');
const treeMap = new BinaryTree<number>([4, 5, [1, '1'], 2, 3]);
expect(treeMap.get(1)).toBe('1');
expect(treeMap.getNode(1)?.value).toBe(undefined);
treeMap.add(1, 'a');
expect(treeMap.get(1)).toBe('a');
treeMap.add([1, 'b']);
expect(treeMap.getNode(1)?.value).toBe(undefined);
expect(treeMap.get(1)).toBe('b');
});
});
describe('BinaryTree.ensureNode', () => {
it('ensureNode(): with toEntryFn returns existing node; handles null/undefined/Symbol', () => {
const binTree = new BinaryTree<
number,
string,
{
id: number;
name: string;
}
>([], { toEntryFn: rawElement => [rawElement.id, rawElement.name] });
binTree.add([1, 'Pablo']);
const node = binTree.getNode(1);
// expect(binTree.ensureNode({ id: 1, name: 'Pablo' })).toBe(node);
expect(binTree.ensureNode([1, 'Pablo'])).toBe(node);
expect(binTree.ensureNode([null, 'Pablo'])).toBe(null);
expect(binTree.ensureNode([undefined, 'Pablo'])).toBe(undefined);
expect(binTree.ensureNode(Symbol('test') as unknown as number)).toBe(undefined);
});
});
describe('BinaryTree - Morris traversal', () => {
// Create a binary binTree
const binTree = new BinaryTree<number>();
binTree.add(1);
binTree.add(2);
binTree.add(3);
binTree.add(4);
binTree.add(5);
it('morris(IN): equals dfs(IN) (iterative/recursive)', () => {
// Perform in-order Morris traversal
const result = binTree.morris(node => node.key, 'IN');
// Expected in-order traversal result
const expected = [4, 2, 5, 1, 3];
expect(result).toEqual(expected);
expect(binTree.dfs(node => node.key, 'IN')).toEqual(expected);
expect(binTree.dfs(node => node.key, 'IN', false, binTree.root, 'RECURSIVE')).toEqual(expected);
});
it('morris(PRE): equals dfs(PRE)', () => {
// Perform pre-order Morris traversal
const result = binTree.morris(node => node.key, 'PRE');
// Expected pre-order traversal result
const expected = [1, 2, 4, 5, 3];
expect(result).toEqual(expected);
expect(binTree.dfs(node => node.key, 'PRE')).toEqual(expected);
});
it('morris(POST): equals dfs(POST)', () => {
// Perform post-order Morris traversal
const result = binTree.morris(node => node.key, 'POST');
// Expected post-order traversal result
const expected = [4, 5, 2, 3, 1];
expect(result).toEqual([4, 5, 2, 3, 1]);
expect(binTree.dfs(node => node.key, 'POST')).toEqual(expected);
});
it('morris(): structure intact afterwards', () => {
const node1 = binTree.getNode(1);
const node2 = binTree.getNode(2);
const node3 = binTree.getNode(3);
expect(node1?.left).toBe(node2);
expect(node1?.right).toBe(node3);
});
});
describe('BinaryTree.toEntryFn', () => {
it('toEntryFn: non-function throws toEntryFn must be a function type', () => {
expect(() => {
new BinaryTree<number, number, { obj: { id: number } }>([], {
toEntryFn: `ele => [ele.obj.id, ele.obj.id]` as unknown as (rawElement: {
obj: { id: number };
}) => BTNEntry<number, number>
});
}).toThrow('toEntryFn must be a function type');
});
it('toEntryFn + addMany(): IN order equals dfs/Morris', () => {
const binTree = new BinaryTree<number, number, { obj: { id: number } }>([], {
toEntryFn: ele => [ele.obj.id, ele.obj.id]
});
binTree.addMany([
{ obj: { id: 1 } },
{ obj: { id: 2 } },
{ obj: { id: 3 } },
{ obj: { id: 4 } },
{ obj: { id: 5 } }
]);
const expected = [4, 2, 5, 1, 3];
expect(binTree.morris(node => node.key, 'IN')).toEqual(expected);
expect(binTree.dfs(node => node.key, 'IN')).toEqual(expected);
expect(binTree.dfs(node => node.key, 'IN', false, binTree.root, 'RECURSIVE')).toEqual(expected);
});
it('constructor toEntryFn (initial data): IN order equals dfs/Morris', () => {
const binTree = new BinaryTree<number, number, { obj: { id: number } }>(
[{ obj: { id: 1 } }, { obj: { id: 2 } }, { obj: { id: 3 } }, { obj: { id: 4 } }, { obj: { id: 5 } }],
{
toEntryFn: ele => [ele.obj.id, ele.obj.id]
}
);
const expected = [4, 2, 5, 1, 3];
expect(binTree.morris(node => node.key, 'IN')).toEqual(expected);
expect(binTree.dfs(node => node.key, 'IN')).toEqual(expected);
expect(binTree.dfs(node => node.key, 'IN', false, binTree.root, 'RECURSIVE')).toEqual(expected);
});
it('without toEntryFn (valueOf-based): dfs/Morris behaviors', () => {
const data = [
{ obj: { id: 4 }, valueOf: () => 4 },
{ obj: { id: 2 }, valueOf: () => 2 },
{ obj: { id: 5 }, valueOf: () => 5 },
{ obj: { id: 1 }, valueOf: () => 1 },
{ obj: { id: 3 }, valueOf: () => 3 }
];
const binTree = new BinaryTree<{ obj: { id: number }; valueOf: () => number }, number>(data);
expect(binTree.morris(node => node.key, 'IN')).toEqual(data.sort((a, b) => a.obj.id - b.obj.id));
expect(binTree.dfs(node => node.key, 'IN')).toEqual(data);
expect(binTree.dfs(node => node.key, 'IN', false, binTree.root, 'RECURSIVE')).toEqual(data);
});
});
describe('BinaryTree - traversal suites', () => {
it('bfs/dfs/listLevels: permutations return expected sequences', () => {
const binTree = new BinaryTree<number>();
const arr = [35, 20, 40, 15, 29, null, 50, null, 16, 28, 30, 45, 55];
binTree.refill(arr);
expect(binTree.bfs(node => node, binTree.root, 'ITERATIVE', true).map(node => (node ? node.key : null))).toEqual([
35,
20,
40,
15,
29,
null,
50,
null,
16,
28,
30,
45,
55
]);
expect(binTree.bfs(node => node, binTree.root, 'RECURSIVE', true).map(node => (node ? node.key : null))).toEqual([
35,
20,
40,
15,
29,
null,
50,
null,
16,
28,
30,
45,
55
]);
expect(binTree.bfs(node => node, binTree.root, 'ITERATIVE').map(node => (node === null ? null : node.key))).toEqual(
[35, 20, 40, 15, 29, 50, 16, 28, 30, 45, 55]
);
expect(binTree.bfs(node => node, binTree.root, 'RECURSIVE').map(node => (node === null ? null : node.key))).toEqual(
[35, 20, 40, 15, 29, 50, 16, 28, 30, 45, 55]
);
expect(binTree.dfs(node => node.key, 'PRE')).toEqual([35, 20, 15, 16, 29, 28, 30, 40, 50, 45, 55]);
expect(binTree.dfs(node => node.key, 'PRE', false, binTree.root, 'RECURSIVE')).toEqual([
35, 20, 15, 16, 29, 28, 30, 40, 50, 45, 55
]);
expect(
binTree
.dfs(node => node, 'PRE', false, binTree.root, 'ITERATIVE', true)
.map(node => (node === null ? null : node.key))
).toEqual([35, 20, 15, null, 16, 29, 28, 30, 40, null, 50, 45, 55]);
expect(
binTree.dfs(node => node, 'PRE', false, binTree.root, 'RECURSIVE', true).map(node => (node ? node.key : null))
).toEqual([35, 20, 15, null, 16, 29, 28, 30, 40, null, 50, 45, 55]);
expect(binTree.dfs(node => node.key, 'IN')).toEqual([15, 16, 20, 28, 29, 30, 35, 40, 45, 50, 55]);
expect(binTree.dfs(node => node.key, 'POST')).toEqual([16, 15, 28, 30, 29, 20, 45, 55, 50, 40, 35]);
expect(binTree.dfs(node => node.key, 'POST', false, binTree.root, 'RECURSIVE')).toEqual([
16, 15, 28, 30, 29, 20, 45, 55, 50, 40, 35
]);
expect(binTree.bfs(node => node.key, binTree.root, 'RECURSIVE')).toEqual([
35, 20, 40, 15, 29, 50, 16, 28, 30, 45, 55
]);
expect(binTree.bfs(node => node.key, binTree.root, 'ITERATIVE')).toEqual([
35, 20, 40, 15, 29, 50, 16, 28, 30, 45, 55
]);
expect(binTree.listLevels(node => node.key)).toEqual([[35], [20, 40], [15, 29, 50], [16, 28, 30, 45, 55]]);
expect(binTree.listLevels(node => node.key, binTree.root, 'RECURSIVE')).toEqual([
[35],
[20, 40],
[15, 29, 50],
[16, 28, 30, 45, 55]
]);
expect(binTree.listLevels(node => (node ? node.key : null), binTree.root, 'ITERATIVE', true)).toEqual([
[35],
[20, 40],
[15, 29, null, 50],
[null, 16, 28, 30, 45, 55]
]);
expect(binTree.listLevels(node => (node ? node.key : null), binTree.root, 'RECURSIVE', true)).toEqual([
[35],
[20, 40],
[15, 29, null, 50],
[null, 16, 28, 30, 45, 55]
]);
binTree.clear();
expect(binTree.listLevels()).toEqual([]);
});
});
describe('BinaryTree', () => {
let binTree: BinaryTree<number, string>;
beforeEach(() => {
binTree = new BinaryTree<number, string>([], {
iterationType: 'RECURSIVE'
});
});
afterEach(() => {
binTree.clear();
});
it('constructor: creates an empty BinaryTree', () => {
expect(binTree.size).toBe(0);
expect(binTree.isEmpty()).toBe(true);
expect(binTree.root).toBe(undefined);
});
it('add(): inserts nodes and sets root', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
expect(binTree.size).toBe(3);
expect(binTree.isEmpty()).toBe(false);
expect(binTree.root?.key).toBe(5);
});
it('should clear the BinaryTree', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
binTree.clear();
expect(binTree.size).toBe(0);
expect(binTree.isEmpty()).toBe(true);
expect(binTree.root).toBe(undefined);
});
it('getNode()/get(): resolve nodes and values by key', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
const nodeA = binTree.getNode(5);
const nodeB = binTree.getNode(3);
expect(nodeA?.key).toBe(5);
expect(nodeA?.value).toBe(undefined);
expect(nodeB?.key).toBe(3);
expect(binTree.get(nodeB)).toBe('B');
});
it('getNode(): returns undefined for missing key', () => {
binTree.add([5, 'A']);
const node = binTree.getNode(3);
expect(node).toBe(undefined);
});
it('should get the depth of a node', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
expect(binTree.getDepth(7)).toBe(1);
expect(binTree.getDepth(3)).toBe(1);
});
it('getHeight()/getMinHeight(): expected heights', () => {
expect(binTree.getMinHeight()).toBe(-1);
binTree.add([5, 'A']);
binTree.add(3, 'B');
binTree.add([7, 'C']);
expect(binTree.getHeight()).toBe(1);
expect(binTree.getHeight(undefined, 'RECURSIVE')).toBe(1);
expect(binTree.getMinHeight(undefined, 'RECURSIVE')).toBe(1);
});
it('isBST(): returns true for valid tree', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
expect(binTree.isBST()).toBe(true);
});
it('dfs(default IN): returns expected order', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
const result = binTree.dfs();
expect(result).toEqual([3, 5, 7]);
// Add assertions for the result of depth-first traversal
});
it('bfs(): returns expected level-order', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
const result = binTree.bfs(node => node.key);
expect(result).toEqual([5, 3, 7]);
// Add assertions for the result of breadth-first traversal
});
it('listLevels(): returns keys by level', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
const levels = binTree.listLevels();
expect(levels).toEqual([[5], [3, 7]]);
// Add assertions for the levels of the binTree
});
it('delete(): removes nodes and updates size', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
binTree.delete(3);
expect(binTree.size).toBe(2);
expect(binTree.getNode(3)).toBe(undefined);
});
it('getPathToRoot(): path from key to root; [] for missing', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
expect(binTree.getPathToRoot(7)).toEqual([7, 5]);
expect(binTree.getPathToRoot(1)).toEqual([]);
});
it('isPerfectlyBalanced(): true for balanced tree', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
expect(binTree.isPerfectlyBalanced()).toBe(true);
});
it('getNodes(predicate): returns matches (iterative & recursive)', () => {
binTree.add([5, 'E']);
binTree.add([4, 'D']);
binTree.add([3, 'C']);
binTree.add([7, 'G']);
binTree.add([null, 'null']);
binTree.add([1, 'A']);
binTree.add([6, 'F']);
binTree.add([null, 'null']);
binTree.add([2, 'B']);
binTree.add([null, 'null']);
const nodes = binTree.getNodes(node => node.key === 2);
expect(nodes.length).toBe(1);
expect(nodes[0].key).toBe(2);
const nodesRec = binTree.getNodes(node => node.key === 2, false, binTree.root, 'RECURSIVE');
expect(nodesRec.length).toBe(1);
expect(nodesRec[0].key).toBe(2);
const nodesItr = binTree.getNodes(node => node.key === 2, false, binTree.root, 'ITERATIVE');
expect(nodesItr.length).toBe(1);
expect(nodesItr[0].key).toBe(2);
expect(nodesItr).toEqual(nodesRec);
});
it('morris(IN): equals dfs(IN); clear() => []', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
binTree.iterationType = 'ITERATIVE';
expect([...binTree]).toEqual([
[3, 'B'],
[5, 'A'],
[7, 'C']
]);
binTree.iterationType = 'RECURSIVE';
expect([...binTree]).toEqual([
[3, 'B'],
[5, 'A'],
[7, 'C']
]);
binTree.iterationType = 'ITERATIVE';
const result = binTree.morris();
expect(result).toEqual([3, 5, 7]);
binTree.clear();
expect(binTree.morris()).toEqual([]);
});
it('delete(): removes all nodes; height == -1', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
binTree.delete(5);
binTree.delete(7);
binTree.delete(3);
expect(binTree.root).toBe(undefined);
expect(binTree.getHeight()).toBe(-1);
});
});
describe('BinaryTree (non-Map mode)', () => {
let binTree: BinaryTree<number, string>;
beforeEach(() => {
binTree = new BinaryTree<number, string>([], {
iterationType: 'RECURSIVE',
isMapMode: false
});
});
afterEach(() => {
binTree.clear();
});
it('add()/has()/getNode(): find nodes by key and predicate', () => {
binTree.add([1, '1']);
binTree.add(undefined);
binTree.add([2, '2']);
binTree.add([3, '3']);
expect(binTree.has(1)).toBe(true);
expect(binTree.has(2)).toBe(true);
expect(binTree.has(3)).toBe(true);
expect(binTree.has(4)).toBe(false);
const node4 = binTree.getNode(4);
expect(binTree.has(node4)).toBe(false);
expect(binTree.has(node => node === node4)).toBe(false);
expect(binTree.has(node => node.value?.toString() === '3')).toBe(true);
});
it('isBST(): returns true for subtree (iterative & recursive)', () => {
binTree.addMany([
new BinaryTreeNode(4),
new BinaryTreeNode(2),
new BinaryTreeNode(6),
new BinaryTreeNode(1),
new BinaryTreeNode(3),
new BinaryTreeNode(5),
new BinaryTreeNode(7),
new BinaryTreeNode(4)
]);
expect(binTree.isBST(binTree.getNode(4), 'RECURSIVE')).toBe(true);
expect(binTree.isBST(binTree.getNode(4), 'ITERATIVE')).toBe(true);
});
it('getNode()/get(): resolve nodes and values by key', () => {
binTree.add([5, 'A']);
binTree.add([3, 'B']);
binTree.add([7, 'C']);
const nodeA = binTree.getNode(5);
const nodeB = binTree.getNode(3);
expect(nodeA?.key).toBe(5);
expect(binTree.get(nodeA)).toBe('A');
expect(nodeB?.key).toBe(3);
expect(binTree.get(nodeB)).toBe('B');
});
it('getNodes(predicate): returns matches (iterative & recursive)', () => {
binTree.add([5, 'E']);
binTree.add([4, 'D']);
binTree.add([3, 'C']);
binTree.add([7, 'G']);
binTree.add([null, 'null']);
binTree.add([1, 'A']);
binTree.add([6, 'F']);
binTree.add([null, 'null']);
binTree.add([2, 'B']);
binTree.add([null, 'null']);
const nodes = binTree.getNodes(node => node.key === 2);
expect(nodes.length).toBe(1);
expect(nodes[0].key).toBe(2);
const nodesRec = binTree.getNodes(node => node.key === 2, false, binTree.root, 'RECURSIVE');
expect(nodesRec.length).toBe(1);
expect(nodesRec[0].key).toBe(2);
const nodesItr = binTree.getNodes(node => node.key === 2, false, binTree.root, 'ITERATIVE');
expect(nodesItr.length).toBe(1);
expect(nodesItr[0].key).toBe(2);
expect(nodesItr).toEqual(nodesRec);
});
});
describe('BinaryTree (map mode) - higher-order & iteration', () => {
let binaryTree: BinaryTree<number, string>;
beforeEach(() => {
binaryTree = new BinaryTree();
binaryTree.add([1, 'a']);
binaryTree.add(2, 'b');
binaryTree.add([3, 'c']);
});
it('getNode(): returns BinaryTreeNode instance', () => {
const node3 = binaryTree.getNode(3);
expect(node3).toBeInstanceOf(BinaryTreeNode);
});
it('forEach(): iterates all entries', () => {
const mockCallback = jest.fn();
binaryTree.forEach((key, value) => {
mockCallback(key, value);
});
expect(mockCallback.mock.calls.length).toBe(3);
expect(mockCallback.mock.calls[0]).toEqual([2, 'b']);
expect(mockCallback.mock.calls[1]).toEqual([1, 'a']);
expect(mockCallback.mock.calls[2]).toEqual([3, 'c']);
});
it('filter(): returns new tree with filtered entries', () => {
const filteredTree = binaryTree.filter(key => key > 1);
expect(filteredTree.size).toBe(2);
expect([...filteredTree]).toEqual([
[3, 'c'],
[2, 'b']
]);
});
it('map(): returns new tree with transformed keys/values', () => {
const mappedTree = binaryTree.map((key, value) => [(key * 2).toString(), value]);
expect(mappedTree.size).toBe(3);
expect([...mappedTree]).toEqual([
['2', 'a'],
['4', 'b'],
['6', 'c']
]);
});
it('reduce(): aggregates over entries', () => {
const sum = binaryTree.reduce((acc, currentValue, currentKey) => acc + currentKey, 0);
expect(sum).toBe(6);
});
it('[Symbol.iterator]: yields entries', () => {
const entries = [];
for (const entry of binaryTree) {
entries.push(entry);
}
expect(entries.length).toBe(3);
expect(entries).toEqual([
[2, 'b'],
[1, 'a'],
[3, 'c']
]);
});
it('clone(): preserves structure and allows get() by node', () => {
const cloned = binaryTree.clone();
expect(cloned.root?.left?.key).toBe(2);
expect(cloned.root?.right?.value).toBe(undefined);
expect(cloned.get(cloned.root?.right)).toBe('c');
});
it('keys(): iterator yields keys in-order', () => {
const keys = binaryTree.keys();
expect([...keys]).toEqual([2, 1, 3]);
});
it('values(): iterator yields values in-order', () => {
const values = binaryTree.values();
expect([...values]).toEqual(['b', 'a', 'c']);
});
it('leaves(): returns leaf keys; clear() => []', () => {
const leaves = binaryTree.leaves();
expect(leaves).toEqual([2, 3]);
binaryTree.clear();
expect(binaryTree.leaves()).toEqual([]);
});
it('bfs(includeNull=true, no callback): yields undefined placeholders', () => {
const binTree = new BinaryTree();
binTree.addMany([-10, -10, -10, 9, 9, 20, null, null, 15, 7, 8, null, 2, null, 6, null, null, 8, 8, 8]);
const bfsResult = binTree.bfs(undefined, undefined, undefined, true);
expect(bfsResult).toEqual([
-10,
9,
20,
undefined,
undefined,
15,
7,
8,
undefined,
2,
undefined,
6,
undefined,
undefined
]);
});
});
describe('BinaryTree (non-Map mode) - higher-order & iteration', () => {
let binaryTree: BinaryTree<number, string>;
beforeEach(() => {
binaryTree = new BinaryTree<number, string>([], { isMapMode: false });
binaryTree.add([1, 'a']);
binaryTree.add(2, 'b');
binaryTree.add([3, 'c']);
});
it('clone(): preserves structure and allows get() by node', () => {
const cloned = binaryTree.clone();
expect(cloned.root?.left?.key).toBe(2);
expect(cloned.get(cloned.root?.right)).toBe('c');
});
});
describe('Coverage boosters - merge/print/iterator/startNode/addMany-mismatch/delete-miss', () => {
it('merge: merge another tree (Map mode)', () => {
const a = new BinaryTree<number, string>(
[
[2, 'b'],
[1, 'a']
],
{ isMapMode: true }
);
const b = new BinaryTree<number, string>(
[
[3, 'c'],
[4, 'd']
],
{ isMapMode: true }
);
a.merge(b);
expect(a.size).toBe(4);
expect(a.get(3)).toBe('c');
expect(a.get(4)).toBe('d');
});
it('print: cover console.log branch (invokes toVisual)', () => {
const t = new BinaryTree<number>([4, 2, 6, 1, 3]);
const spy = jest.spyOn(console, 'log').mockImplementation(() => void 0);
t.print({ isShowNull: true } as any, t.root);
expect(spy).toHaveBeenCalledTimes(1);
const out: string = (spy.mock.calls[0]?.[0] as string) ?? '';
expect(out.includes('N for null')).toBe(true);
spy.mockRestore();
});
it('for...of: trigger [Symbol.iterator] path (IterableEntryBase)', () => {
const t = new BinaryTree<number, string>(
[
[1, 'x'],
[2, 'y'],
[3, 'z']
],
{ isMapMode: true } as any
);
const arr = Array.from(t); // like Array<[K, V | undefined]>
const keys = arr.map(e => e?.[0]);
const vals = arr.map(e => e?.[1]);
expect(new Set(keys)).toEqual(new Set([1, 2, 3]));
expect(new Set(vals)).toEqual(new Set(['x', 'y', 'z']));
});
it('startNode restricted to subtree: hits only within subtree', () => {
const t = new BinaryTree<number, string>([
[4, 'd'],
[2, 'b'],
[6, 'f'],
[1, 'a'],
[3, 'c']
]);
const sub = t.getNode(2)!; // subtree {2,1,3}
// search 6 in subtree rooted at 2 -> miss
expect(t.search(6, true, n => (n ? n.key : undefined), sub)).toEqual([]);
// search 3 -> hit
expect(t.search(3, true, n => (n ? n.key : undefined), sub)).toEqual([3]);
});
it('addMany: edge cases when values iterator shorter/longer than keys', () => {
const t = new BinaryTree<number, number>([], { isMapMode: true } as any);
// values has only 1 item, keys has 3
t.addMany([1, 2, 3], [10]);
expect(t.get(1)).toBe(10);
expect(t.get(2)).toBeUndefined(); // subsequent value not provided
expect(t.get(3)).toBeUndefined();
// reverse test: values longer (extra values should be ignored)
const t2 = new BinaryTree<number, number>([], { isMapMode: true } as any);
t2.addMany([7, 8], [70, 80, 90, 100]);
expect(t2.get(7)).toBe(70);
expect(t2.get(8)).toBe(80);
});
it('delete non-existent key: returns empty array and does not affect size', () => {
const t = new BinaryTree<number>([1, 2, 3]);
const before = t.size;
const res = t.delete(999);
expect(Array.isArray(res)).toBe(true);
expect(res.length).toBe(0);
expect(t.size).toBe(before);
});
it('toVisual additional combinations (S only / U only)', () => {
const t = new BinaryTree<number>([2, 1, 3]);
const sOnly = t.toVisual(t.root, { isShowRedBlackNIL: true } as any);
const uOnly = t.toVisual(t.root, { isShowUndefined: true } as any);
expect(sOnly.includes('S for Sentinel Node')).toBe(true);
expect(uOnly.startsWith('U for undefined')).toBe(true);
});
it('clone in Map mode shares storage: updates in original visible in clone', () => {
const t = new BinaryTree<number, string>(
[
[1, 'a'],
[2, 'b']
],
{ isMapMode: true } as any
);
const c = t.clone() as BinaryTree<number, string>;
// In the original tree, "replace" the value for the same key (Map mode triggers _store.set)
t.add([2, 'B']);
// Because clone shares M