MutationObserver is a useful mechanism to watch for changes in the DOM and respond to them.
The MutationObserver interface provides the ability to watch for changes being made to the DOM tree.
Here is an example that watches for a theme class change.
function setUpThemeClassObservers() { const observer = new MutationObserver(() => { const themeClass = this.getThemeClass(); this.fireStylesChangedEvent('themeChanged'); }); observer.observe(this.eGridDiv, { attributes: true, attributeFilter: ['class'], }); // we must disconnect otherwise "this" will not be GC'd // causing a memory leak return () => observer.disconnect(); }
However, if you forget to disconnect you could be exposing yourself to memory leaks depending on what is accessed from within the MutationObserver functions.
Wouldn't it be great to have a test that can validate that we disconnect our observers?
It turns out that it is possible to validate that every MutationObserver that is observing the DOM is also disconnected. (You may need more than one test if you have to exercise different code paths in order to setup the MutationObservers)
The idea is to Mock the global MutationObserver with sub mocks for its observe and disconnect methods. Before the mock is returned we record it in an array so that we can validate all instances at the end of the test run.
describe('Mutation Observers Disconnected', () => { let originalMutationObserver: typeof MutationObserver; const allMockedObservers: any = []; const mutationObserverMock = jest.fn().mockImplementation(() => { const mock = { observe: jest.fn(), disconnect: jest.fn(), takeRecords: jest.fn(), }; allMockedObservers.push(mock); return mock; }); beforeEach(() => { // Ensure we can restore the real MutationObserver after the test originalMutationObserver = global.MutationObserver; global.MutationObserver = mutationObserverMock; }); afterEach(() => { global.MutationObserver = originalMutationObserver; }); test('observer always disconnected after destroy', async () => { const api = createGrid(); // Perform some actions if required to exercise the code paths api.destroy(); expect(allMockedObservers.length).toBeGreaterThan(0); for (const mock of allMockedObservers) { expect(mock.observe).toHaveBeenCalled(); expect(mock.disconnect).toHaveBeenCalled(); } }); });
In this way we can validate that each MutationObserver, setup during the test, is also disconnected at the end of the test.
Stephen Cooper - Senior Developer at AG Grid
Follow me on X @ScooperDev
The above is the detailed content of Test that every MutationObserver is disconnected to avoid memory leaks. For more information, please follow other related articles on the PHP Chinese website!