Repository navigation
full-calendar: actually stop the listener leak - #162
Merged
Merged
Conversation
Nothing ever called destroyCalendarEventListeners — no willDestroy, no
registerDestructor, no template reference — so a calendar on a route the user
navigated in and out of accumulated a listener per callback for the lifetime of
the page.
Wiring the destructor up is necessary but not sufficient, which is the whole
reason this one was flagged rather than fixed in passing. The method did:
this.calendar.off(eventName, this.triggerCalendarEvent.bind(this, callbackName));
.bind() returns a new function every call, so the reference handed to .off()
could never equal the one .on() was given and FullCalendar removed nothing. That
version looks fixed: the destructor runs, the method runs, nothing errors, and a
test asserting "destroyCalendarEventListeners was called" passes.
So: bind once, keep the function on the _listeners entry, hand that same
reference to both .on() and .off(), and empty _listeners afterwards.
The two tests assert the observable behaviour instead of the call — trigger the
event after the component is destroyed and require no callback to fire. Both
were confirmed to FAIL against the .bind() mismatch with willDestroy already
wired, i.e. against the version that looks fixed and is not.
full-calendar.js: 100% statements, branches, functions and lines (22 tests).
Left open in #17 and deliberately out of scope: the component never calls
this.calendar.destroy(), so the FullCalendar instance and the document-level
handlers it installs outlive the component. The test file already works around
that with an afterEach destroying the captured calendar. Fixing it changes what
a consumer's @onInit reference points at after teardown, so it wants its own
decision.
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.
Fixes DEFECTS #17. One component, one behaviour change, two tests.
The leak
Nothing ever called
destroyCalendarEventListeners— nowillDestroy, noregisterDestructor, no reference infull-calendar.hbs. Everyon<Event>callback a consumersupplies is registered with
calendar.on(...)and pushed onto_listeners, and nothing everunregisters it. A calendar on a route the user navigates in and out of accumulates a listener per
callback for the lifetime of the page.
Why this was flagged instead of fixed in passing
Wiring the destructor up is necessary but not sufficient. The method did:
.bind()returns a new function on every call, so the reference handed to.off()can never equalthe one
.on()was given, and FullCalendar removes nothing.That version looks fixed. The destructor runs, the method runs, nothing errors, and a test
asserting "destroyCalendarEventListeners was called" passes — while every listener stays
registered.
The fix
Bind once, keep the resulting function on the
_listenersentry, hand that same reference to both.on()and.off(), and empty_listenersafterwards.willDestroycalls it.The tests
Both assert the observable behaviour rather than the call: trigger the event after the component is
destroyed and require no callback to fire.
Both were confirmed to fail against the
.bind()mismatch withwillDestroyalready wired —that is, against the version that looks fixed and is not:
full-calendar.jsis now at 100% statements, branches, functions and lines (22 tests) — thefive gaps that remained in it were the leak.
Left open, deliberately
The component never calls
this.calendar.destroy(), so the FullCalendar instance and thedocument-level handlers it installs outlive the component. That is a separate and probably larger
leak than this one; the integration tests already work around it with an
afterEachthat destroysthe captured calendar. Fixing it changes what a consumer's
@onInitreference points at afterteardown, so it wants its own decision rather than being folded in here. Recorded in DEFECTS #17.
Verification
full-calendarfiltered run: 22 pass, 0 fail.rm -f .eslintcache && pnpm run lint→ exit 0.coverage-final.json, not from a passing run.