-
-
Notifications
You must be signed in to change notification settings - Fork 734
Guard against invalid removeChild #1707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdded defensive guards and error handling around DOM removals: optional chaining for removeChild calls, try/catch around marker removal, a corrected forEach callback signature, and a guarded zeroElement removal; tests added to validate destroy behavior when nodes are already removed. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Add expect.assertions(2) to ensure tests verify expected behavior - Add assertion that tether.enabled is true after enable() - Wrap destroy() calls in expect().not.toThrow() for proper error handling - Fix whitespace and formatting for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/js/utils/bounds.js (1)
145-150: DefensivezeroElementremoval looks correct; consider when to clear the referenceThe new
zeroElement && zeroElement?.parentNode === bodyguard is a good defensive fix and should prevent DOM exceptions when the cached node has been detached or belongs to a differentbody.One minor refinement: in the case where
zeroElementexists but is not a child ofbody, you still null out the reference without removing the node, which can leave a stray helper<div>in the DOM. Not a big deal, but you could optionally only clearzeroElementafter a successful removal, or fall back to removing it from its actual parent if you want stronger cleanup guarantees.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/js/tether.js(3 hunks)src/js/utils/bounds.js(1 hunks)test/unit/tether.spec.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/unit/tether.spec.js (1)
src/js/tether.js (1)
Tether(775-775)
🪛 GitHub Actions: CI Build
test/unit/tether.spec.js
[error] 178-178: 'originalTarget' is assigned a value but never used no-unused-vars
src/js/utils/bounds.js
[warning] 46-46: Function 'getScrollHandleBounds' has a complexity of 9. Maximum allowed is 6 complexity
src/js/tether.js
[warning] 140-140: Method 'setOptions' has a complexity of 8. Maximum allowed is 6 complexity
[warning] 296-296: Method 'updateAttachClasses' has a complexity of 10. Maximum allowed is 6 complexity
[warning] 312-312: File has too many lines (672). Maximum allowed is 250 max-lines
[warning] 367-367: Method 'position' has a complexity of 22. Maximum allowed is 6 complexity
[warning] 570-570: Method 'move' has a complexity of 35. Maximum allowed is 6 complexity
[warning] 602-602: Arrow function has a complexity of 10. Maximum allowed is 6 complexity
[warning] 811-811: Method 'position' has a complexity of 8. Maximum allowed is 6 complexity
🪛 GitHub Check: Tests
test/unit/tether.spec.js
[failure] 178-178:
'originalTarget' is assigned a value but never used
🔇 Additional comments (3)
src/js/tether.js (2)
680-724: Safe reparenting inmove()matches the defensive-removal goalSwitching the raw
parentNode.removeChild(this.element)calls tothis.element?.parentNode?.removeChild(this.element)in the deferred reparenting logic reduces the chance of DOM exceptions when the element or its parent is removed between scheduling and execution. The subsequentappendChildcalls preserve the original behavior for the common case wherethis.elementis still a live node.This is a good, minimal change aligned with the PR’s objective.
796-808: Marker cleanup is now robust to missing parents and prior removalsThe updated
destroy()handler for markers:
- Guards against
this.markersorthis.markers[type]being absent.- Uses
el?.parentNode?.removeChild(el)inside atry/catchto tolerate parents that have already been removed or other DOM quirks.Given this is destroy-time cleanup, swallowing these errors is reasonable and prevents teardown from throwing in dynamic DOM environments.
test/unit/tether.spec.js (1)
163-260: New defensive-destroy tests exercise the right edge casesThe added
describe('defensive DOM removal', ...)suite appropriately covers:
- Target removed before
destroy().- Element removed before
destroy().- Both removed before
destroy().Disabling the tether before manual removals and then recreating/resetting
element/targetso the sharedafterEachcan run cleanly keeps these tests robust without overcomplicating the setup/teardown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
test/unit/tether.spec.js (1)
182-204: Optionally align target removal with the guarded pattern used for elementHere you call
document.body.removeChild(target)directly, whereas in the later tests you use aparentNodeguard before removing nodes. For resilience iftarget’s parent ever stops beingdocument.body, you could match that pattern:- // Remove target from DOM before destroying tether - document.body.removeChild(target); + // Remove target from DOM before destroying tether + if (target.parentNode) { + target.parentNode.removeChild(target); + }Not critical, but it keeps your tests robust if Tether’s DOM placement strategy evolves.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
test/unit/tether.spec.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/unit/tether.spec.js (1)
src/js/tether.js (1)
Tether(775-775)
🔇 Additional comments (1)
test/unit/tether.spec.js (1)
163-280: New defensive DOM removal tests correctly exercise destroy() behavior and manage shared DOM stateThe three tests under
describe('defensive DOM removal')do a good job of:
- Verifying
destroy()doesn’t throw when the target, element, or both are removed from the DOM first.- Explicitly disabling before teardown to avoid incidental positioning work during the scenario you want to test.
- Re-establishing fresh
element/targetinstances so the sharedafterEachcan run without hittingremoveChildon detached nodes.The interplay with the outer
beforeEach/afterEachlooks sound and shouldn’t leak DOM state across tests.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.