perf: Improve performance of the carousel auto-height functionality (#2094)
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { MediaHeightController } from '../../src/components-lib/media-height-controller';
|
||||
import {
|
||||
callMutationHandler,
|
||||
MutationObserverMock,
|
||||
ResizeObserverMock,
|
||||
} from '../test-utils';
|
||||
import { callResizeHandler } from '../utils/embla/test-utils';
|
||||
|
||||
vi.mock('lodash-es', async () => ({
|
||||
...(await vi.importActual('lodash-es')),
|
||||
debounce: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('MediaHeightController', () => {
|
||||
beforeAll(() => {
|
||||
vi.stubGlobal('MutationObserver', MutationObserverMock);
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('should set height', () => {
|
||||
it('should set height on selection', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
const root = document.createElement('div');
|
||||
const child = document.createElement('div');
|
||||
child.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 600,
|
||||
});
|
||||
root.appendChild(child);
|
||||
|
||||
controller.setRoot(root);
|
||||
|
||||
// Calling a second time has no effect.
|
||||
controller.setRoot(root);
|
||||
|
||||
controller.setSelected(0);
|
||||
|
||||
expect(host.style.maxHeight).toBe(`600px`);
|
||||
});
|
||||
|
||||
it('should not set height without children', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
const root = document.createElement('div');
|
||||
|
||||
controller.setRoot(root);
|
||||
|
||||
controller.setSelected(10);
|
||||
|
||||
expect(host.style.maxHeight).toBe('');
|
||||
});
|
||||
|
||||
it('should respond to resize observer of selected child', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
const root = document.createElement('div');
|
||||
const child = document.createElement('div');
|
||||
root.appendChild(child);
|
||||
|
||||
controller.setRoot(root);
|
||||
controller.setSelected(0);
|
||||
|
||||
child.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 800,
|
||||
});
|
||||
|
||||
callResizeHandler([
|
||||
{
|
||||
target: child,
|
||||
height: 800,
|
||||
width: 400,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(host.style.maxHeight).toBe('800px');
|
||||
});
|
||||
|
||||
it('should not respond to resize observer without a selected child', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
const root = document.createElement('div');
|
||||
const child = document.createElement('div');
|
||||
root.appendChild(child);
|
||||
|
||||
controller.setRoot(root);
|
||||
|
||||
child.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 800,
|
||||
});
|
||||
|
||||
callResizeHandler([
|
||||
{
|
||||
target: child,
|
||||
height: 800,
|
||||
width: 400,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(host.style.maxHeight).toBe('');
|
||||
});
|
||||
|
||||
it('should respond to new children being added', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
const root = document.createElement('div');
|
||||
const child_0 = document.createElement('div');
|
||||
child_0.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 100,
|
||||
});
|
||||
root.appendChild(child_0);
|
||||
|
||||
controller.setRoot(root);
|
||||
|
||||
const child_1 = document.createElement('div');
|
||||
child_1.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 200,
|
||||
});
|
||||
root.appendChild(child_1);
|
||||
|
||||
callMutationHandler();
|
||||
|
||||
controller.setSelected(1);
|
||||
|
||||
expect(host.style.maxHeight).toBe('200px');
|
||||
});
|
||||
|
||||
it('should ignore new chil to new children being added', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
const root = document.createElement('div');
|
||||
const child_0 = document.createElement('div');
|
||||
child_0.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 100,
|
||||
});
|
||||
root.appendChild(child_0);
|
||||
|
||||
controller.setRoot(root);
|
||||
|
||||
const child_1 = document.createElement('div');
|
||||
child_1.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
height: 200,
|
||||
});
|
||||
root.appendChild(child_1);
|
||||
|
||||
callMutationHandler();
|
||||
|
||||
controller.setSelected(1);
|
||||
|
||||
expect(host.style.maxHeight).toBe('200px');
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy', () => {
|
||||
const host = document.createElement('div');
|
||||
const controller = new MediaHeightController(host, 'div');
|
||||
|
||||
controller.destroy();
|
||||
|
||||
// No observable effect.
|
||||
});
|
||||
});
|
||||
@@ -1,185 +0,0 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import AutoSize from '../../../../../src/utils/embla/plugins/auto-size/auto-size';
|
||||
import {
|
||||
IntersectionObserverMock,
|
||||
ResizeObserverMock,
|
||||
callIntersectionHandler,
|
||||
createParent,
|
||||
requestAnimationFrameMock,
|
||||
} from '../../../../test-utils';
|
||||
import {
|
||||
callEmblaHandler,
|
||||
callResizeHandler,
|
||||
createEmblaApiInstance,
|
||||
createTestEmblaOptionHandler,
|
||||
createTestSlideNodes,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('lodash-es', () => ({
|
||||
debounce: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('AutoSize', () => {
|
||||
beforeAll(() => {
|
||||
// Mock out requestAnimationFrame (used in the reinit controller).
|
||||
window.requestAnimationFrame = requestAnimationFrameMock;
|
||||
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should construct', () => {
|
||||
const plugin = AutoSize();
|
||||
expect(plugin.name).toBe('autoSize');
|
||||
});
|
||||
|
||||
it('should destroy', () => {
|
||||
const plugin = AutoSize();
|
||||
const emblaApi = createEmblaApiInstance();
|
||||
plugin.init(emblaApi, createTestEmblaOptionHandler());
|
||||
plugin.destroy();
|
||||
|
||||
expect(emblaApi.off).toBeCalledWith('settle', expect.anything());
|
||||
|
||||
expect(
|
||||
vi.mocked(IntersectionObserver).mock.results[0].value.disconnect,
|
||||
).toBeCalled();
|
||||
expect(vi.mocked(ResizeObserver).mock.results[0].value.disconnect).toBeCalled();
|
||||
});
|
||||
|
||||
it('should correctly handle intersection', () => {
|
||||
const plugin = AutoSize();
|
||||
const emblaApi = createEmblaApiInstance();
|
||||
plugin.init(emblaApi, createTestEmblaOptionHandler());
|
||||
|
||||
// First intersection handler call sets the state only.
|
||||
callIntersectionHandler(true);
|
||||
|
||||
// When not visible, will not re-init.
|
||||
callIntersectionHandler(false);
|
||||
callIntersectionHandler(false);
|
||||
callIntersectionHandler(false);
|
||||
|
||||
expect(emblaApi.reInit).not.toBeCalled();
|
||||
|
||||
// When visible, will re-initialize once.
|
||||
callIntersectionHandler(true);
|
||||
callIntersectionHandler(true);
|
||||
|
||||
expect(emblaApi.reInit).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should correctly handle resize', () => {
|
||||
const plugin = AutoSize();
|
||||
const parent = createParent();
|
||||
const children = createTestSlideNodes();
|
||||
const emblaApi = createEmblaApiInstance({
|
||||
containerNode: parent,
|
||||
selectedScrollSnap: 0,
|
||||
slideNodes: children,
|
||||
slideRegistry: [[0]],
|
||||
});
|
||||
|
||||
plugin.init(emblaApi, createTestEmblaOptionHandler());
|
||||
|
||||
children[0].getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
width: 200,
|
||||
height: 800,
|
||||
});
|
||||
|
||||
callResizeHandler([{ target: parent, width: 10, height: 20 }]);
|
||||
callResizeHandler([{ target: parent, width: 10, height: 20 }]);
|
||||
callResizeHandler([{ target: parent, width: 10, height: 20 }]);
|
||||
|
||||
expect(parent.style.maxHeight).toBe('800px');
|
||||
expect(emblaApi.reInit).toBeCalledTimes(1);
|
||||
|
||||
children[0].getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
width: 200,
|
||||
height: 600,
|
||||
});
|
||||
|
||||
callResizeHandler([{ target: parent, width: 20, height: 40 }]);
|
||||
|
||||
expect(parent.style.maxHeight).toBe('600px');
|
||||
expect(emblaApi.reInit).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should set container height on slide settle', () => {
|
||||
const plugin = AutoSize();
|
||||
const parent = createParent();
|
||||
const children = createTestSlideNodes();
|
||||
const emblaApi = createEmblaApiInstance({
|
||||
containerNode: parent,
|
||||
selectedScrollSnap: 0,
|
||||
slideNodes: children,
|
||||
// 0th scroll snap shows the 0th slide only.
|
||||
slideRegistry: [[0]],
|
||||
});
|
||||
plugin.init(emblaApi, createTestEmblaOptionHandler());
|
||||
|
||||
children[0].getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
width: 200,
|
||||
height: 800,
|
||||
});
|
||||
|
||||
// select should not do anything, we wait for it to have settled for
|
||||
// smoothness.
|
||||
callEmblaHandler(emblaApi, 'select');
|
||||
expect(parent.style.maxHeight).toBeFalsy();
|
||||
|
||||
callEmblaHandler(emblaApi, 'settle');
|
||||
expect(parent.style.maxHeight).toBe('800px');
|
||||
});
|
||||
|
||||
it('should not set container height on horizontal carousel', () => {
|
||||
const plugin = AutoSize();
|
||||
const parent = createParent();
|
||||
const children = createTestSlideNodes();
|
||||
const emblaApi = createEmblaApiInstance({
|
||||
containerNode: parent,
|
||||
selectedScrollSnap: 0,
|
||||
slideNodes: children,
|
||||
axis: 'y',
|
||||
// 0th scroll snap shows the 0th slide only.
|
||||
slideRegistry: [[0]],
|
||||
});
|
||||
plugin.init(emblaApi, createTestEmblaOptionHandler());
|
||||
|
||||
children[0].getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
width: 200,
|
||||
height: 800,
|
||||
});
|
||||
|
||||
callEmblaHandler(emblaApi, 'settle');
|
||||
|
||||
expect(parent.style.maxHeight).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not set container height when slide dimensions are invalid', () => {
|
||||
const plugin = AutoSize();
|
||||
const parent = createParent();
|
||||
const children = createTestSlideNodes();
|
||||
const emblaApi = createEmblaApiInstance({
|
||||
containerNode: parent,
|
||||
selectedScrollSnap: 0,
|
||||
slideNodes: children,
|
||||
axis: 'x',
|
||||
// 0th scroll snap shows the 0th slide only.
|
||||
slideRegistry: [[0]],
|
||||
});
|
||||
plugin.init(emblaApi, createTestEmblaOptionHandler());
|
||||
|
||||
children[0].getBoundingClientRect = vi.fn().mockReturnValue(NaN);
|
||||
callEmblaHandler(emblaApi, 'settle');
|
||||
|
||||
children[0].getBoundingClientRect = vi.fn().mockReturnValue(0);
|
||||
callEmblaHandler(emblaApi, 'settle');
|
||||
|
||||
expect(parent.style.maxHeight).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,59 +0,0 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { EmblaReInitController } from '../../../src/utils/embla/reinit-controller';
|
||||
import { requestAnimationFrameMock } from '../../test-utils';
|
||||
import { callEmblaHandler, createEmblaApiInstance } from './test-utils';
|
||||
|
||||
vi.mock('lodash-es', () => ({
|
||||
debounce: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('EmblaReInitController', () => {
|
||||
beforeAll(() => {
|
||||
window.requestAnimationFrame = requestAnimationFrameMock;
|
||||
});
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should construct', () => {
|
||||
const emblaApi = createEmblaApiInstance();
|
||||
new EmblaReInitController(emblaApi);
|
||||
expect(emblaApi.on).toBeCalledWith('scroll', expect.anything());
|
||||
expect(emblaApi.on).toBeCalledWith('settle', expect.anything());
|
||||
expect(emblaApi.on).toBeCalledWith('destroy', expect.anything());
|
||||
});
|
||||
|
||||
it('should destroy', () => {
|
||||
const emblaApi = createEmblaApiInstance();
|
||||
const controller = new EmblaReInitController(emblaApi);
|
||||
|
||||
controller.destroy();
|
||||
|
||||
expect(emblaApi.off).toBeCalledWith('scroll', expect.anything());
|
||||
expect(emblaApi.off).toBeCalledWith('settle', expect.anything());
|
||||
expect(emblaApi.off).toBeCalledWith('destroy', expect.anything());
|
||||
});
|
||||
|
||||
it('should reinit when not scrolling', () => {
|
||||
const emblaApi = createEmblaApiInstance();
|
||||
const controller = new EmblaReInitController(emblaApi);
|
||||
|
||||
controller.reinit();
|
||||
|
||||
expect(emblaApi.reInit).toBeCalled();
|
||||
});
|
||||
|
||||
it('should carefully reinit when scrolling', () => {
|
||||
const emblaApi = createEmblaApiInstance();
|
||||
const controller = new EmblaReInitController(emblaApi);
|
||||
|
||||
callEmblaHandler(emblaApi, 'scroll');
|
||||
|
||||
controller.reinit();
|
||||
expect(emblaApi.reInit).not.toBeCalled();
|
||||
|
||||
callEmblaHandler(emblaApi, 'settle');
|
||||
expect(emblaApi.reInit).toBeCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user