fix: Lazy unloading should not leave dangling connections (#2004)
- Closes #1992
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import {
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import { LazyLoadController } from '../../src/components-lib/lazy-load-controller';
|
||||
import { LazyUnloadCondition } from '../../src/config/schema/common/media-actions';
|
||||
import {
|
||||
callIntersectionHandler,
|
||||
callVisibilityHandler,
|
||||
createLitElement,
|
||||
getMockIntersectionObserver,
|
||||
IntersectionObserverMock,
|
||||
} from '../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('LazyLoadController', () => {
|
||||
beforeAll(() => {
|
||||
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(global.document, 'addEventListener');
|
||||
vi.spyOn(global.document, 'removeEventListener');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should be loaded by default', () => {
|
||||
const controller = new LazyLoadController(createLitElement());
|
||||
expect(controller.isLoaded()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not be loaded by default when lazy load is set to true', () => {
|
||||
const controller = new LazyLoadController(createLitElement(), true);
|
||||
expect(controller.isLoaded()).toBe(false);
|
||||
});
|
||||
|
||||
it('should add controller to host', () => {
|
||||
const host = createLitElement();
|
||||
const controller = new LazyLoadController(host);
|
||||
expect(host.addController).toBeCalledWith(controller);
|
||||
});
|
||||
|
||||
it('should remove controller from host', () => {
|
||||
const host = createLitElement();
|
||||
const controller = new LazyLoadController(host);
|
||||
controller.removeController();
|
||||
expect(host.removeController).toBeCalledWith(controller);
|
||||
});
|
||||
|
||||
it('should remove handlers and listeners on destroy', () => {
|
||||
const controller = new LazyLoadController(createLitElement(), true, [
|
||||
'unselected',
|
||||
'hidden',
|
||||
]);
|
||||
controller.hostConnected();
|
||||
|
||||
const listener = vi.fn();
|
||||
controller.addListener(listener);
|
||||
|
||||
controller.destroy();
|
||||
|
||||
expect(getMockIntersectionObserver()?.disconnect).toBeCalled();
|
||||
expect(global.document.removeEventListener).toBeCalledWith(
|
||||
'visibilitychange',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(controller.isLoaded()).toBe(false);
|
||||
|
||||
callVisibilityHandler(true);
|
||||
callIntersectionHandler(true);
|
||||
expect(listener).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('should lazy load', () => {
|
||||
it('should load when both visible and intersecting', () => {
|
||||
const controller = new LazyLoadController(createLitElement(), true);
|
||||
controller.hostConnected();
|
||||
|
||||
expect(controller.isLoaded()).toBe(false);
|
||||
|
||||
callVisibilityHandler(true);
|
||||
expect(controller.isLoaded()).toBe(false);
|
||||
|
||||
callIntersectionHandler(true);
|
||||
expect(controller.isLoaded()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should lazy unload', () => {
|
||||
it('should unload on DOM disconnection', () => {
|
||||
const controller = new LazyLoadController(createLitElement());
|
||||
controller.hostConnected();
|
||||
|
||||
expect(controller.isLoaded()).toBe(true);
|
||||
|
||||
controller.hostDisconnected();
|
||||
|
||||
expect(controller.isLoaded()).toBe(false);
|
||||
|
||||
// Should also stop observing.
|
||||
expect(getMockIntersectionObserver()?.disconnect).toBeCalled();
|
||||
expect(global.document.removeEventListener).toBeCalledWith(
|
||||
'visibilitychange',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
describe('should lazy unload when not visible', () => {
|
||||
it.each([
|
||||
[[], true],
|
||||
[['unselected' as const], true],
|
||||
[['hidden' as const], false],
|
||||
[['unselected' as const, 'hidden' as const], false],
|
||||
])(
|
||||
'when unload conditions are: %s',
|
||||
(unloadConditions: LazyUnloadCondition[], shouldBeLoaded: boolean) => {
|
||||
const controller = new LazyLoadController(
|
||||
createLitElement(),
|
||||
true,
|
||||
unloadConditions,
|
||||
);
|
||||
controller.hostConnected();
|
||||
|
||||
callIntersectionHandler(true);
|
||||
callVisibilityHandler(true);
|
||||
expect(controller.isLoaded()).toBe(true);
|
||||
|
||||
callVisibilityHandler(false);
|
||||
expect(controller.isLoaded()).toBe(shouldBeLoaded);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('should lazy unload when not intersecting', () => {
|
||||
it.each([
|
||||
[[], true],
|
||||
[['unselected' as const], false],
|
||||
[['hidden' as const], true],
|
||||
[['unselected' as const, 'hidden' as const], false],
|
||||
])(
|
||||
'when unload conditions are: %s',
|
||||
(unloadConditions: LazyUnloadCondition[], shouldBeLoaded: boolean) => {
|
||||
const controller = new LazyLoadController(
|
||||
createLitElement(),
|
||||
true,
|
||||
unloadConditions,
|
||||
);
|
||||
controller.hostConnected();
|
||||
|
||||
callIntersectionHandler(true);
|
||||
callVisibilityHandler(true);
|
||||
expect(controller.isLoaded()).toBe(true);
|
||||
|
||||
callIntersectionHandler(false);
|
||||
expect(controller.isLoaded()).toBe(shouldBeLoaded);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call listeners', () => {
|
||||
const listener = vi.fn();
|
||||
const controller = new LazyLoadController(createLitElement(), true, [
|
||||
'unselected',
|
||||
'hidden',
|
||||
]);
|
||||
controller.hostConnected();
|
||||
controller.addListener(listener);
|
||||
|
||||
expect(controller.isLoaded()).toBe(false);
|
||||
|
||||
callIntersectionHandler(true);
|
||||
callVisibilityHandler(true);
|
||||
expect(listener).toHaveBeenLastCalledWith(true);
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
callIntersectionHandler(false);
|
||||
expect(listener).toHaveBeenLastCalledWith(false);
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
callIntersectionHandler(true);
|
||||
expect(listener).toHaveBeenLastCalledWith(true);
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
|
||||
controller.removeListener(listener);
|
||||
|
||||
callIntersectionHandler(false);
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
});
|
||||
});
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
MutationObserverMock,
|
||||
callIntersectionHandler,
|
||||
callMutationHandler,
|
||||
callVisibilityHandler,
|
||||
createParent,
|
||||
flushPromises,
|
||||
} from '../test-utils';
|
||||
import { callVisibilityHandler, createTestSlideNodes } from '../utils/embla/test-utils';
|
||||
import { createTestSlideNodes } from '../utils/embla/test-utils';
|
||||
|
||||
const getPlayer = (
|
||||
element: HTMLElement,
|
||||
@@ -47,7 +48,7 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -455,11 +456,7 @@ describe('MediaActionsController', () => {
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).not.toBeCalled();
|
||||
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
value: 'visible',
|
||||
writable: true,
|
||||
});
|
||||
await callVisibilityHandler();
|
||||
await callVisibilityHandler(true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(
|
||||
@@ -502,11 +499,7 @@ describe('MediaActionsController', () => {
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).not.toBeCalled();
|
||||
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
value: 'hidden',
|
||||
writable: true,
|
||||
});
|
||||
await callVisibilityHandler();
|
||||
await callVisibilityHandler(false);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user