Skip to content

Commit 5258d8b

Browse files
committed
add gridlist tests for loadmore
1 parent abfcde0 commit 5258d8b

File tree

1 file changed

+213
-7
lines changed

1 file changed

+213
-7
lines changed

packages/react-aria-components/test/GridList.test.js

Lines changed: 213 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {act, fireEvent, mockClickDefault, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
13+
import {act, fireEvent, mockClickDefault, pointerMap, render, setupIntersectionObserverMock, within} from '@react-spectrum/test-utils-internal';
1414
import {
1515
Button,
1616
Checkbox,
17+
Collection,
1718
Dialog,
1819
DialogTrigger,
1920
DropIndicator,
@@ -32,6 +33,7 @@ import {
3233
} from '../';
3334
import {getFocusableTreeWalker} from '@react-aria/focus';
3435
import React from 'react';
36+
import {UNSTABLE_GridListLoadingSentinel} from '../src/GridList';
3537
import {User} from '@react-aria/test-utils';
3638
import userEvent from '@testing-library/user-event';
3739

@@ -827,9 +829,9 @@ describe('GridList', () => {
827829
let {getAllByRole} = renderGridList({selectionMode: 'single', onSelectionChange});
828830
let items = getAllByRole('row');
829831

830-
await user.pointer({target: items[0], keys: '[MouseLeft>]'});
832+
await user.pointer({target: items[0], keys: '[MouseLeft>]'});
831833
expect(onSelectionChange).toBeCalledTimes(1);
832-
834+
833835
await user.pointer({target: items[0], keys: '[/MouseLeft]'});
834836
expect(onSelectionChange).toBeCalledTimes(1);
835837
});
@@ -839,9 +841,9 @@ describe('GridList', () => {
839841
let {getAllByRole} = renderGridList({selectionMode: 'single', onSelectionChange, shouldSelectOnPressUp: false});
840842
let items = getAllByRole('row');
841843

842-
await user.pointer({target: items[0], keys: '[MouseLeft>]'});
844+
await user.pointer({target: items[0], keys: '[MouseLeft>]'});
843845
expect(onSelectionChange).toBeCalledTimes(1);
844-
846+
845847
await user.pointer({target: items[0], keys: '[/MouseLeft]'});
846848
expect(onSelectionChange).toBeCalledTimes(1);
847849
});
@@ -851,11 +853,215 @@ describe('GridList', () => {
851853
let {getAllByRole} = renderGridList({selectionMode: 'single', onSelectionChange, shouldSelectOnPressUp: true});
852854
let items = getAllByRole('row');
853855

854-
await user.pointer({target: items[0], keys: '[MouseLeft>]'});
856+
await user.pointer({target: items[0], keys: '[MouseLeft>]'});
855857
expect(onSelectionChange).toBeCalledTimes(0);
856-
858+
857859
await user.pointer({target: items[0], keys: '[/MouseLeft]'});
858860
expect(onSelectionChange).toBeCalledTimes(1);
859861
});
860862
});
863+
864+
describe('async loading', () => {
865+
let items = [
866+
{name: 'Foo'},
867+
{name: 'Bar'},
868+
{name: 'Baz'}
869+
];
870+
let renderEmptyState = () => {
871+
return (
872+
<div>empty state</div>
873+
);
874+
};
875+
let AsyncGridList = (props) => {
876+
let {items, isLoading, onLoadMore, ...listBoxProps} = props;
877+
return (
878+
<GridList
879+
{...listBoxProps}
880+
aria-label="async gridlist"
881+
renderEmptyState={() => renderEmptyState()}>
882+
<Collection items={items}>
883+
{(item) => (
884+
<GridListItem id={item.name}>{item.name}</GridListItem>
885+
)}
886+
</Collection>
887+
<UNSTABLE_GridListLoadingSentinel isLoading={isLoading} onLoadMore={onLoadMore}>
888+
Loading...
889+
</UNSTABLE_GridListLoadingSentinel>
890+
</GridList>
891+
);
892+
};
893+
894+
let onLoadMore = jest.fn();
895+
let observe = jest.fn();
896+
afterEach(() => {
897+
jest.clearAllMocks();
898+
});
899+
900+
it('should render the loading element when loading', async () => {
901+
let tree = render(<AsyncGridList isLoading items={items} />);
902+
903+
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
904+
let rows = gridListTester.rows;
905+
expect(rows).toHaveLength(4);
906+
let loaderRow = rows[3];
907+
expect(loaderRow).toHaveTextContent('Loading...');
908+
909+
let sentinel = tree.getByTestId('loadMoreSentinel');
910+
expect(sentinel.parentElement).toHaveAttribute('inert');
911+
});
912+
913+
it('should render the sentinel but not the loading indicator when not loading', async () => {
914+
let tree = render(<AsyncGridList items={items} />);
915+
916+
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
917+
let rows = gridListTester.rows;
918+
expect(rows).toHaveLength(3);
919+
expect(tree.queryByText('Loading...')).toBeFalsy();
920+
expect(tree.getByTestId('loadMoreSentinel')).toBeInTheDocument();
921+
});
922+
923+
it('should properly render the renderEmptyState if gridlist is empty, even when loading', async () => {
924+
let tree = render(<AsyncGridList items={[]} />);
925+
926+
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
927+
let rows = gridListTester.rows;
928+
expect(rows).toHaveLength(1);
929+
expect(rows[0]).toHaveTextContent('empty state');
930+
expect(tree.queryByText('Loading...')).toBeFalsy();
931+
expect(tree.getByTestId('loadMoreSentinel')).toBeInTheDocument();
932+
933+
tree.rerender(<AsyncGridList items={[]} isLoading />);
934+
rows = gridListTester.rows;
935+
expect(rows).toHaveLength(1);
936+
expect(rows[0]).toHaveTextContent('empty state');
937+
expect(tree.queryByText('Loading...')).toBeFalsy();
938+
expect(tree.getByTestId('loadMoreSentinel')).toBeInTheDocument();
939+
});
940+
941+
it('should only fire loadMore when not loading and intersection is detected', async () => {
942+
let observer = setupIntersectionObserverMock({
943+
observe
944+
});
945+
946+
let tree = render(<AsyncGridList items={items} onLoadMore={onLoadMore} isLoading />);
947+
let sentinel = tree.getByTestId('loadMoreSentinel');
948+
expect(observe).toHaveBeenCalledTimes(2);
949+
expect(observe).toHaveBeenLastCalledWith(sentinel);
950+
expect(onLoadMore).toHaveBeenCalledTimes(0);
951+
952+
act(() => {observer.instance.triggerCallback([{isIntersecting: true}]);});
953+
expect(onLoadMore).toHaveBeenCalledTimes(0);
954+
955+
tree.rerender(<AsyncGridList items={items} onLoadMore={onLoadMore} />);
956+
expect(observe).toHaveBeenCalledTimes(3);
957+
expect(observe).toHaveBeenLastCalledWith(sentinel);
958+
expect(onLoadMore).toHaveBeenCalledTimes(0);
959+
960+
act(() => {observer.instance.triggerCallback([{isIntersecting: true}]);});
961+
expect(onLoadMore).toHaveBeenCalledTimes(1);
962+
});
963+
964+
describe('virtualized', () => {
965+
let items = [];
966+
for (let i = 0; i < 50; i++) {
967+
items.push({name: 'Foo' + i});
968+
}
969+
let clientWidth, clientHeight;
970+
971+
beforeAll(() => {
972+
clientWidth = jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 100);
973+
clientHeight = jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 100);
974+
});
975+
976+
afterAll(function () {
977+
clientWidth.mockReset();
978+
clientHeight.mockReset();
979+
});
980+
981+
let VirtualizedAsyncGridList = (props) => {
982+
let {items, isLoading, onLoadMore, ...listBoxProps} = props;
983+
return (
984+
<Virtualizer
985+
layout={ListLayout}
986+
layoutOptions={{
987+
rowHeight: 25,
988+
loaderHeight: 30
989+
}}>
990+
<GridList
991+
{...listBoxProps}
992+
aria-label="async virtualized gridlist"
993+
renderEmptyState={() => renderEmptyState()}>
994+
<Collection items={items}>
995+
{(item) => (
996+
<GridListItem id={item.name}>{item.name}</GridListItem>
997+
)}
998+
</Collection>
999+
<UNSTABLE_GridListLoadingSentinel isLoading={isLoading} onLoadMore={onLoadMore}>
1000+
Loading...
1001+
</UNSTABLE_GridListLoadingSentinel>
1002+
</GridList>
1003+
</Virtualizer>
1004+
);
1005+
};
1006+
1007+
it('should always render the sentinel even when virtualized', async () => {
1008+
let tree = render(<VirtualizedAsyncGridList isLoading items={items} />);
1009+
1010+
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
1011+
let rows = gridListTester.rows;
1012+
expect(rows).toHaveLength(8);
1013+
let loaderRow = rows[7];
1014+
expect(loaderRow).toHaveTextContent('Loading...');
1015+
expect(loaderRow).toHaveAttribute('aria-rowindex', '51');
1016+
let loaderParentStyles = loaderRow.parentElement.style;
1017+
1018+
// 50 items * 25px = 1250
1019+
expect(loaderParentStyles.top).toBe('1250px');
1020+
expect(loaderParentStyles.height).toBe('30px');
1021+
1022+
let sentinel = within(loaderRow.parentElement).getByTestId('loadMoreSentinel');
1023+
expect(sentinel.parentElement).toHaveAttribute('inert');
1024+
});
1025+
1026+
it('should not reserve room for the loader if isLoading is false or if gridlist is empty', async () => {
1027+
let tree = render(<VirtualizedAsyncGridList items={items} />);
1028+
1029+
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
1030+
let rows = gridListTester.rows;
1031+
expect(rows).toHaveLength(7);
1032+
expect(within(gridListTester.gridlist).queryByText('Loading...')).toBeFalsy();
1033+
1034+
let sentinel = within(gridListTester.gridlist).getByTestId('loadMoreSentinel');
1035+
let sentinelParentStyles = sentinel.parentElement.parentElement.style;
1036+
expect(sentinelParentStyles.top).toBe('1250px');
1037+
expect(sentinelParentStyles.height).toBe('0px');
1038+
expect(sentinel.parentElement).toHaveAttribute('inert');
1039+
1040+
tree.rerender(<VirtualizedAsyncGridList items={[]} />);
1041+
rows = gridListTester.rows;
1042+
expect(rows).toHaveLength(1);
1043+
let emptyStateRow = rows[0];
1044+
expect(emptyStateRow).toHaveTextContent('empty state');
1045+
expect(within(gridListTester.gridlist).queryByText('Loading...')).toBeFalsy();
1046+
1047+
sentinel = within(gridListTester.gridlist).getByTestId('loadMoreSentinel');
1048+
sentinelParentStyles = sentinel.parentElement.parentElement.style;
1049+
expect(sentinelParentStyles.top).toBe('0px');
1050+
expect(sentinelParentStyles.height).toBe('0px');
1051+
1052+
// Same as above, setting isLoading when gridlist is empty shouldnt change the layout
1053+
tree.rerender(<VirtualizedAsyncGridList items={[]} isLoading />);
1054+
rows = gridListTester.rows;
1055+
expect(rows).toHaveLength(1);
1056+
emptyStateRow = rows[0];
1057+
expect(emptyStateRow).toHaveTextContent('empty state');
1058+
expect(within(gridListTester.gridlist).queryByText('Loading...')).toBeFalsy();
1059+
1060+
sentinel = within(gridListTester.gridlist).getByTestId('loadMoreSentinel');
1061+
sentinelParentStyles = sentinel.parentElement.parentElement.style;
1062+
expect(sentinelParentStyles.top).toBe('0px');
1063+
expect(sentinelParentStyles.height).toBe('0px');
1064+
});
1065+
});
1066+
});
8611067
});

0 commit comments

Comments
 (0)