Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions __mocks__/css-element-queries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const mockDetach = jest.fn();
export const ResizeSensor = jest.fn(function MockSlider(el, cb) {
this.el = el;
this.cb = cb;

this.detach = mockDetach;
});
6 changes: 5 additions & 1 deletion src/components/Cards/CardHeightMatching.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CardHeightMatching extends React.Component {
this._resizeSensors.push(
new ResizeSensor(
elements,
debounce(this._matchHeights([selector]), 200)
debounce(() => this._matchHeights([selector]), 200)
)
);
});
Expand All @@ -45,6 +45,10 @@ class CardHeightMatching extends React.Component {
}

_matchHeights(selectors = this._selectors) {
if (!this._container) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just in case...it doesn't hurt...thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this was the source of the error @michaelkro mentioned before. Since _matchHeights is called inside of debounce, this can be called after the component has unmounted which will make _container null. I didn't see a good way to cancel the debounced call, unfortunately.

return;
}

const arrayMap = elements =>
Array.prototype.map
.call(elements, el => el.scrollHeight)
Expand Down
126 changes: 126 additions & 0 deletions src/components/Cards/CardHeightMatching.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from 'react';
import { mount } from 'enzyme';
import { ResizeSensor, mockDetach } from 'css-element-queries';
import CardHeightMatching from './CardHeightMatching';
import * as helpers from '../../common/helpers';

jest.mock('css-element-queries');

jest.spyOn(helpers, 'debounce').mockImplementation(v => v);
const mockQuerySelectorAll = jest.fn();
HTMLElement.prototype.querySelectorAll = mockQuerySelectorAll;

const mockElement = {
scrollHeight: 0,
style: {
height: 0
}
};

const props = {
selector: ['first', 'second'],
children: 'children'
};

beforeEach(() => {
mockDetach.mockClear();
ResizeSensor.mockClear();
helpers.debounce.mockClear();
mockQuerySelectorAll.mockReturnValue([mockElement]);
});

test('creates a ResizeSensor for each selector', () => {
mount(<CardHeightMatching {...props} />);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mount was used instead of shallow since refs are used inside of the component.

expect(ResizeSensor).toHaveBeenCalledTimes(props.selector.length);
expect(ResizeSensor).toBeCalledWith(
expect.arrayContaining([mockElement]),
expect.any(Function)
);
});

test('allows selector to be a string', () => {
mount(<CardHeightMatching {...props} selector="selector" />);
expect(ResizeSensor).toHaveBeenCalledTimes(1);
});

test('debounces resize events', () => {
mount(<CardHeightMatching {...props} />);
expect(helpers.debounce).toBeCalledWith(expect.any(Function), 200);
});

test('sets max height on mount', () => {
const firstElement = {
...mockElement,
scrollHeight: 0
};
const secondElement = {
...mockElement,
scrollHeight: 10
};
mockQuerySelectorAll.mockReturnValue([firstElement, secondElement]);
jest.useFakeTimers();
mount(<CardHeightMatching {...props} />);
jest.runAllTimers();
expect(firstElement.style.height).toBe(`${secondElement.scrollHeight}px`);
expect(secondElement.style.height).toBe(`${secondElement.scrollHeight}px`);
jest.useRealTimers();
});

test('sets max height on update', () => {
const firstElement = {
...mockElement,
scrollHeight: 0
};
const secondElement = {
...mockElement,
scrollHeight: 10
};
mockQuerySelectorAll.mockReturnValue([firstElement, secondElement]);
jest.useFakeTimers();
const view = mount(<CardHeightMatching {...props} />);
jest.runAllTimers();
secondElement.scrollHeight = 20;
view.setProps(props);
jest.runAllTimers();
expect(firstElement.style.height).toBe(`${secondElement.scrollHeight}px`);
expect(secondElement.style.height).toBe(`${secondElement.scrollHeight}px`);
jest.useRealTimers();
});

test('detaches on unmount', () => {
const view = mount(<CardHeightMatching {...props} />);
view.unmount();
expect(mockDetach).toHaveBeenCalledTimes(props.selector.length);
});

test('handles resize trigger without a container', () => {
let debounceHandler;
helpers.debounce.mockImplementationOnce(handler => {
debounceHandler = handler;
return handler;
});
jest.useFakeTimers();
const view = mount(<CardHeightMatching {...props} />);
jest.runAllTimers();
view.unmount();
mockQuerySelectorAll.mockClear();
debounceHandler();
expect(mockQuerySelectorAll).not.toBeCalled();
jest.useRealTimers();
});

test('sets height on update', () => {
let debounceHandler;
helpers.debounce.mockImplementationOnce(handler => {
debounceHandler = handler;
return handler;
});
jest.useFakeTimers();
const view = mount(<CardHeightMatching {...props} />);
jest.runAllTimers();
view.unmount();
mockQuerySelectorAll.mockClear();
debounceHandler();
expect(mockQuerySelectorAll).not.toBeCalled();
jest.useRealTimers();
});