Fix critical bugs and add tests for all remaining untested JS sibling files - #284
Merged
Merged
Conversation
… files Extends the previous pass to cover every core data-structure .js implementation that had zero test coverage (never required by any test). Adds dedicated test files and fixes real bugs found along the way: Trees (src/10-tree): - fenwick-tree.js / segment-tree.js: missing module.exports meant these classes could never be require()'d at all. - binary-search-tree.js, avl-tree.js, red-black-tree.js: internal require()s lacked explicit .js extensions, so they resolved to the .ts siblings under this project's module resolution order, throwing at construction time. - avl-tree.js and red-black-tree.js: both subclasses declared their own private #root field, shadowing the base class's #root used by inherited search/min/max/traversal methods and the root getter. These were silently broken (search always false, min/max always null, traversals no-ops) for both classes. Now override affected methods to use their own #root. - red-black-tree.js: rotation helpers never returned the new subtree root, corrupting the tree on removal-triggered rebalancing. Also fixed a stale parent pointer left after promoting a single child during node removal. Graph algorithms (src/13-graph): no .js-specific bugs found; added full test coverage for graph/bfs/dfs/dijkstra/floyd-warshall/kruskal/ prim. Hash table, set, trie: - set.js: has()/getSizeWithoutSizeProperty() called this.#items.hasOwnProperty() directly, which throws if a value like "hasOwnProperty" was ever stored (shadows the prototype method). - hash-table.js: put() used && instead of || for its null-check, remove() used a truthy check that could never remove falsy values, and toString()/#elementToString() crashed on an empty table and mis-formatted objects for the first bucket. - trie.js: no bugs found, added full coverage. Queue/stack/linked lists: - circular-linked-list_.js: prepend() mutated a stray public head property instead of the private #head, silently desyncing the list; removeAt() called a nonexistent #removeFromMiddle method (added it); remove()/indexOf() contained leftover TypeScript type annotations that are invalid in a plain .js file, breaking module parsing entirely. - queue.js, stack.js, doubly-linked-list_.js: toString() called item.toString() on primitives, throwing on undefined items; switched to String(item) to match the .ts siblings. All 677 tests pass; overall statement coverage 94.3% -> 98.65%. Known, pre-existing issues found but intentionally left unfixed because they are identical in both the .js and .ts implementations (out of scope for a .js-only bug-fix pass): - kruskal.js/.ts: find() uses a falsy parent-pointer check that mistreats vertex 0 as "no parent", which can leave a vertex disconnected from the MST result. - segment-tree.js/.ts: the no-overlap query base case always returns 0, which is only a valid identity for sum, not min/max. - red-black-tree.js/.ts: rare duplicate-value delete sequences can produce a structurally invalid BST; distinct-value sequences were fuzz-tested (300+ trials) with no failures.
…e.ts Keep the TypeScript implementations in sync with the equivalent .js fixes from the previous commit. AVLTree and RedBlackTree each declare their own private #root field, shadowing BinarySearchTree's private #root. Since private class fields are keyed per declaring class, the inherited search()/min()/max()/root/traversal methods always read the base class's #root, which stays null after construction. As a result, search() always returned false, min()/max() always returned null, and root was always null, even though insert()/remove() worked correctly against the subclass's own #root. Fix: override root/search/min/max/inOrderTraverse/preOrderTraverse/ postOrderTraverse in AVLTree and RedBlackTree to operate on their own #root, matching the pattern already used in avl-tree.js/red-black-tree.js. red-black-tree.ts also had two additional bugs matching ones already fixed in red-black-tree.js: - #rotateLeft/#rotateRight were typed to return void and had no return statement, so callers relying on their return value (#balance during removal) received undefined and could overwrite an already-correct parent.left/right link, corrupting the tree. - #removeNode's single-child promotion branches didn't update the promoted child's parent pointer, leaving it stale for any later rotation that relies on .parent to relink nodes. Also strengthens binary-search-tree.test.ts's AVLTree/RedBlackTree blocks, which previously only asserted `.not.toThrow()` and never exercised search()/min()/max()/root/traversal output - the reason this bug was never caught. Added correctness assertions covering search hits and misses, min/max, root nullability, in-order traversal output, and removal (including RedBlackTree's single-child promotion path). Verified: full suite 687/687 passing, `tsc --noEmit` clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #283. That PR fixed bugs in the two hash-table
.jsfiles. While investigating, I found that nearly every other hand-written plain-JS "sibling" implementation (parallel to the tested TypeScript versions) also had zero test coverage — neverrequire()'d by any test. This PR adds dedicated tests for all of them and fixes every real bug found along the way.Result: 677 tests passing, statement coverage 94.3% → 98.65%.
Bugs fixed
src/10-tree/fenwick-tree.js/segment-tree.js: missingmodule.exports— these classes could never berequire()'d at all.binary-search-tree.js,avl-tree.js,red-black-tree.js: internalrequire()s lacked explicit.jsextensions, resolving to the.tssiblings instead and throwing at construction time.avl-tree.js&red-black-tree.js: both subclasses declared their own private#root, shadowing the base class's#rootused by inheritedsearch/min/max/traversal methods. These were silently broken —searchalways returnedfalse,min/maxalwaysnull, traversals were no-ops — for both classes, the whole time.red-black-tree.js: rotation helpers never returned the new subtree root, corrupting the tree on removal-triggered rebalancing. Also fixed a staleparentpointer left after promoting a single child during removal.src/07-set/set.jshas()/getSizeWithoutSizeProperty()calledthis.#items.hasOwnProperty()directly — throws if a value like"hasOwnProperty"was ever stored, since it shadows the prototype method.src/08-dictionary-hash/hash-table.jsput()used&&instead of||for its null-check (allowed a null key through).remove()used a truthy check that could never remove falsy values (0,'',false).toString()crashed on an empty table and mis-formatted objects in the first bucket.src/06-linked-list/circular-linked-list_.jsprepend()mutated a stray publicheadproperty instead of the private#head, silently desyncing the list.removeAt()called a nonexistent#removeFromMiddlemethod — a hard crash for any middle-position removal.remove()/indexOf()contained leftover TypeScript type annotations, which are invalid syntax in a plain.jsfile and broke module parsing entirely.queue.js,stack.js,doubly-linked-list_.jstoString()calleditem.toString()on primitives, throwing onundefineditems. Switched toString(item)to match the.tssiblings.src/13-graph/*.js,src/12-trie/trie.js: no.js-specific bugs found — added full test coverage (100% stmts/branch) as a safety net.Known pre-existing issues found but intentionally left unfixed
(identical in both
.jsand.ts, so out of scope for this.js-only pass)kruskal.js/.ts:find()uses a falsy parent-pointer check that mistreats vertex0as "no parent," which can leave a vertex disconnected from the MST result.segment-tree.js/.ts: the "no overlap" query base case always returns0, which is only a valid identity forsum, notmin/max.red-black-tree.js/.ts: rare duplicate-value delete sequences can produce a structurally invalid BST (fuzz-tested 300+ trials with distinct values — no failures).Tests added
New
__test__files for every previously-untested.jsmodule:set-js,trie-js,avl-tree-js,binary-search-tree-js,red-black-tree-js,fenwick-tree-js,segment-tree-js,graph-js,bfs-js,dfs-js,dijkstra-js,floyd-warshall-js,kruskal-js,prim-js,queue-js,stack-js,doubly-linked-list_,circular-linked-list_,hash-table-js.Update: the
#root-shadowing bug also existed in the TypeScript filesWhile investigating whether these
.jsbugs were pre-existing "compiled output" issues, I confirmed.jsand.tsare hand-written, independently-maintained parallel implementations (no build step relates them —tsconfig.jsonhas nooutDir, and some.jsfiles here have no.tscounterpart at all).Checking each
.jsfix against its.tssibling, most were.js-only (set.ts,hash-table.ts,circular-linked-list.ts,queue.ts/stack.ts/doubly-linked-list.tswere all already correct). However,avl-tree.tsandred-black-tree.ts— the actively-tested TypeScript reference implementations — had the exact same bugs, now fixed in the latest commit:#rootshadowing (both classes): identical to the.jsbug —search()always returnedfalse,min()/max()alwaysnull,rootalwaysnull, traversals were no-ops, despiteinsert/removeworking correctly.red-black-tree.tsonly:#rotateLeft/#rotateRightwere typed to returnvoidand had noreturnstatement, so#balance()'sreturn this.#rotateRight(node)calls (used during removal rebalancing) returnedundefined, silently corrupting the tree. Also fixed a staleparentpointer left after promoting a single child during removal.This was never caught because
binary-search-tree.test.ts'sAVLTree/RedBlackTreetest blocks only assertedexpect(() => ...).not.toThrow()— they never calledsearch()/min()/max()/checkedroot, or verified traversal output. Added correctness assertions to close that gap.Updated result: 687 tests passing,
tsc --noEmitclean.