Skip to content

Commit a2049ab

Browse files
committed
clean up and add row index tests for GridList and Table
1 parent 69b7db1 commit a2049ab

File tree

10 files changed

+112
-44
lines changed

10 files changed

+112
-44
lines changed

packages/@react-spectrum/picker/test/Picker.test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,9 +1025,6 @@ describe('Picker', function () {
10251025
expect(document.activeElement).toBe(picker);
10261026
expect(picker).toHaveTextContent('Empty');
10271027

1028-
// TODO: this test (along with others in this suite) fails because we seem to be detecting that the button is being focused after the
1029-
// dropdown is opened, resulting in the dropdown closing due to useOverlay interactOutside logic
1030-
// Seems to specifically happen if the Picker has a selected item and the user tries to open the Picker
10311028
await selectTester.selectOption({option: 'Zero'});
10321029
expect(onSelectionChange).toHaveBeenCalledTimes(2);
10331030
expect(onSelectionChange).toHaveBeenLastCalledWith('0');

packages/@react-spectrum/s2/chromatic/Combobox.stories.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ export const WithEmptyState = {
8787
export const WithInitialLoading = {
8888
...EmptyCombobox,
8989
args: {
90-
loadingState: 'loading'
90+
loadingState: 'loading',
91+
label: 'Initial loading'
9192
},
9293
play: async ({canvasElement}) => {
9394
await userEvent.tab();
@@ -101,7 +102,8 @@ export const WithInitialLoading = {
101102
export const WithLoadMore = {
102103
...Example,
103104
args: {
104-
loadingState: 'loadingMore'
105+
loadingState: 'loadingMore',
106+
label: 'Loading more'
105107
},
106108
play: async ({canvasElement}) => {
107109
await userEvent.tab();
@@ -114,6 +116,10 @@ export const WithLoadMore = {
114116

115117
export const AsyncResults = {
116118
...AsyncComboBoxStory,
119+
args: {
120+
...AsyncComboBoxStory.args,
121+
delay: 2000
122+
},
117123
play: async ({canvasElement}) => {
118124
await userEvent.tab();
119125
await userEvent.keyboard('{ArrowDown}');
@@ -127,6 +133,10 @@ export const AsyncResults = {
127133

128134
export const Filtering = {
129135
...AsyncComboBoxStory,
136+
args: {
137+
...AsyncComboBoxStory.args,
138+
delay: 2000
139+
},
130140
play: async ({canvasElement}) => {
131141
await userEvent.tab();
132142
await userEvent.keyboard('{ArrowDown}');

packages/@react-spectrum/s2/chromatic/Picker.stories.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {userEvent, waitFor, within} from '@storybook/testing-library';
1919
const meta: Meta<typeof Picker<any>> = {
2020
component: Picker,
2121
parameters: {
22-
chromaticProvider: {colorSchemes: ['light'], backgrounds: ['base'], locales: ['en-US'], disableAnimations: true}
22+
chromaticProvider: {colorSchemes: ['light'], backgrounds: ['base'], locales: ['en-US'], disableAnimations: true},
23+
chromatic: {ignoreSelectors: ['[role="progressbar"]']}
2324
},
2425
tags: ['autodocs'],
2526
title: 'S2 Chromatic/Picker'
@@ -71,7 +72,7 @@ export const ContextualHelp = {
7172

7273
export const EmptyAndLoading = {
7374
render: () => (
74-
<Picker isLoading>
75+
<Picker label="loading" isLoading>
7576
{[]}
7677
</Picker>
7778
),
@@ -88,6 +89,10 @@ export const EmptyAndLoading = {
8889

8990
export const AsyncResults = {
9091
...AsyncPickerStory,
92+
args: {
93+
...AsyncPickerStory.args,
94+
delay: 2000
95+
},
9196
play: async ({canvasElement}) => {
9297
let body = canvasElement.ownerDocument.body;
9398
await waitFor(() => {

packages/@react-spectrum/s2/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@
137137
"@react-aria/interactions": "^3.25.0",
138138
"@react-aria/live-announcer": "^3.4.2",
139139
"@react-aria/overlays": "^3.27.0",
140-
"@react-aria/separator": "^3.4.8",
141140
"@react-aria/utils": "^3.28.2",
142141
"@react-spectrum/utils": "^3.12.4",
143142
"@react-stately/layout": "^4.2.2",

packages/react-aria-components/src/GridList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ function GridListInner<T extends object>({props, collection, gridListRef: ref}:
217217
if (isEmpty && props.renderEmptyState) {
218218
let content = props.renderEmptyState(renderValues);
219219
emptyState = (
220-
<div role="row" style={{display: 'contents'}}>
220+
<div role="row" aria-rowindex={1} style={{display: 'contents'}}>
221221
<div role="gridcell" style={{display: 'contents'}}>
222222
{content}
223223
</div>
@@ -532,7 +532,7 @@ export const UNSTABLE_GridListLoadingSentinel = createLeafComponent('loader', fu
532532

533533
return (
534534
<>
535-
{/* TODO: Alway render the sentinel. Might need to style it as visually hidden? */}
535+
{/* TODO: Alway render the sentinel. Will need to figure out how best to position this in cases where the user is using flex + gap (this would introduce a gap even though it doesn't take room) */}
536536
{/* @ts-ignore - compatibility with React < 19 */}
537537
<div style={{position: 'relative', width: 0, height: 0}} inert={inertValue(true)} >
538538
<div data-testid="loadMoreSentinel" ref={sentinelRef} style={{position: 'absolute', height: 1, width: 1}} />

packages/react-aria-components/src/ListBox.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,6 @@ function ListBoxInner<T extends object>({state: inputState, props, listBoxRef}:
230230
);
231231
}
232232

233-
// TODO: Think about if completely empty state. Do we leave it up to the user to setup the two states for empty and empty + loading?
234-
// Do we add a data attibute/prop/renderprop to ListBox for isLoading
235233
return (
236234
<FocusScope>
237235
<div
@@ -511,7 +509,7 @@ export const UNSTABLE_ListBoxLoadingSentinel = createLeafComponent('loader', fun
511509

512510
return (
513511
<>
514-
{/* TODO: Alway render the sentinel. Might need to style it as visually hidden? */}
512+
{/* TODO: Alway render the sentinel. Will need to figure out how best to position this in cases where the user is using flex + gap (this would introduce a gap even though it doesn't take room) */}
515513
{/* @ts-ignore - compatibility with React < 19 */}
516514
<div style={{position: 'relative', width: 0, height: 0}} inert={inertValue(true)} >
517515
<div data-testid="loadMoreSentinel" ref={sentinelRef} style={{position: 'absolute', height: 1, width: 1}} />

packages/react-aria-components/src/Table.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,7 @@ export const UNSTABLE_TableLoadingSentinel = createLeafComponent('loader', funct
13981398
return (
13991399
<>
14001400
{/* TODO weird structure? Renders a extra row but we hide via inert so maybe ok? */}
1401+
{/* TODO: Will need to figure out how best to position this in cases where the user is using flex + gap (this would introduce a gap even though it doesn't take room) */}
14011402
{/* @ts-ignore - compatibility with React < 19 */}
14021403
<TR style={{position: 'relative', width: 0, height: 0}} inert={inertValue(true)} >
14031404
<TD data-testid="loadMoreSentinel" ref={sentinelRef} style={{position: 'absolute', height: 1, width: 1}} />

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

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -867,9 +867,9 @@ describe('GridList', () => {
867867
{name: 'Bar'},
868868
{name: 'Baz'}
869869
];
870-
let renderEmptyState = () => {
870+
let renderEmptyState = (loadingState) => {
871871
return (
872-
<div>empty state</div>
872+
loadingState === 'loading' ? <div>loading</div> : <div>empty state</div>
873873
);
874874
};
875875
let AsyncGridList = (props) => {
@@ -979,7 +979,7 @@ describe('GridList', () => {
979979
});
980980

981981
let VirtualizedAsyncGridList = (props) => {
982-
let {items, isLoading, onLoadMore, ...listBoxProps} = props;
982+
let {items, loadingState, onLoadMore, ...listBoxProps} = props;
983983
return (
984984
<Virtualizer
985985
layout={ListLayout}
@@ -990,13 +990,13 @@ describe('GridList', () => {
990990
<GridList
991991
{...listBoxProps}
992992
aria-label="async virtualized gridlist"
993-
renderEmptyState={() => renderEmptyState()}>
993+
renderEmptyState={() => renderEmptyState(loadingState)}>
994994
<Collection items={items}>
995995
{(item) => (
996996
<GridListItem id={item.name}>{item.name}</GridListItem>
997997
)}
998998
</Collection>
999-
<UNSTABLE_GridListLoadingSentinel isLoading={isLoading} onLoadMore={onLoadMore}>
999+
<UNSTABLE_GridListLoadingSentinel isLoading={loadingState === 'loadingMore'} onLoadMore={onLoadMore}>
10001000
Loading...
10011001
</UNSTABLE_GridListLoadingSentinel>
10021002
</GridList>
@@ -1005,7 +1005,7 @@ describe('GridList', () => {
10051005
};
10061006

10071007
it('should always render the sentinel even when virtualized', async () => {
1008-
let tree = render(<VirtualizedAsyncGridList isLoading items={items} />);
1008+
let tree = render(<VirtualizedAsyncGridList loadingState="loadingMore" items={items} />);
10091009

10101010
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
10111011
let rows = gridListTester.rows;
@@ -1049,20 +1049,52 @@ describe('GridList', () => {
10491049
expect(sentinelParentStyles.top).toBe('0px');
10501050
expect(sentinelParentStyles.height).toBe('0px');
10511051

1052-
// Setting isLoading will render the loader even if the list is empty.
1053-
tree.rerender(<VirtualizedAsyncGridList items={[]} isLoading />);
1052+
tree.rerender(<VirtualizedAsyncGridList items={[]} loadingState="loading" />);
10541053
rows = gridListTester.rows;
1055-
expect(rows).toHaveLength(2);
1056-
emptyStateRow = rows[1];
1057-
expect(emptyStateRow).toHaveTextContent('empty state');
1058-
1059-
let loadingRow = rows[0];
1060-
expect(loadingRow).toHaveTextContent('Loading...');
1054+
expect(rows).toHaveLength(1);
1055+
emptyStateRow = rows[0];
1056+
expect(emptyStateRow).toHaveTextContent('loading');
10611057

10621058
sentinel = within(gridListTester.gridlist).getByTestId('loadMoreSentinel');
10631059
sentinelParentStyles = sentinel.parentElement.parentElement.style;
10641060
expect(sentinelParentStyles.top).toBe('0px');
1065-
expect(sentinelParentStyles.height).toBe('30px');
1061+
expect(sentinelParentStyles.height).toBe('0px');
1062+
});
1063+
1064+
it('should have the correct row indicies after loading more items', async () => {
1065+
let tree = render(<VirtualizedAsyncGridList items={[]} loadingState="loading" />);
1066+
1067+
let gridListTester = testUtilUser.createTester('GridList', {root: tree.getByRole('grid')});
1068+
let rows = gridListTester.rows;
1069+
expect(rows).toHaveLength(1);
1070+
1071+
let loaderRow = rows[0];
1072+
expect(loaderRow).toHaveAttribute('aria-rowindex', '1');
1073+
expect(loaderRow).toHaveTextContent('loading');
1074+
for (let [index, row] of rows.entries()) {
1075+
expect(row).toHaveAttribute('aria-rowindex', `${index + 1}`);
1076+
}
1077+
1078+
tree.rerender(<VirtualizedAsyncGridList items={items} />);
1079+
rows = gridListTester.rows;
1080+
expect(rows).toHaveLength(7);
1081+
expect(within(gridListTester.gridlist).queryByText('loading')).toBeFalsy();
1082+
for (let [index, row] of rows.entries()) {
1083+
expect(row).toHaveAttribute('aria-rowindex', `${index + 1}`);
1084+
}
1085+
1086+
tree.rerender(<VirtualizedAsyncGridList items={items} loadingState="loadingMore" />);
1087+
rows = gridListTester.rows;
1088+
expect(rows).toHaveLength(8);
1089+
loaderRow = rows[7];
1090+
expect(loaderRow).toHaveAttribute('aria-rowindex', '51');
1091+
for (let [index, row] of rows.entries()) {
1092+
if (index === 7) {
1093+
continue;
1094+
} else {
1095+
expect(row).toHaveAttribute('aria-rowindex', `${index + 1}`);
1096+
}
1097+
}
10661098
});
10671099
});
10681100
});

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

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,7 +2042,7 @@ describe('Table', () => {
20422042
});
20432043

20442044
let VirtualizedTableLoad = (props) => {
2045-
let {items, isLoading, onLoadMore} = props;
2045+
let {items, loadingState, onLoadMore} = props;
20462046

20472047
return (
20482048
<Virtualizer layout={TableLayout} layoutOptions={{rowHeight: 25, loaderHeight: 30}}>
@@ -2051,7 +2051,7 @@ describe('Table', () => {
20512051
<Column isRowHeader>Foo</Column>
20522052
<Column>Bar</Column>
20532053
</TableHeader>
2054-
<TableBody renderEmptyState={() => 'No results'}>
2054+
<TableBody renderEmptyState={() => loadingState === 'loading' ? 'loading' : 'No results'}>
20552055
<Collection items={items}>
20562056
{(item) => (
20572057
<Row>
@@ -2060,7 +2060,7 @@ describe('Table', () => {
20602060
</Row>
20612061
)}
20622062
</Collection>
2063-
<UNSTABLE_TableLoadingSentinel isLoading={isLoading} onLoadMore={onLoadMore}>
2063+
<UNSTABLE_TableLoadingSentinel isLoading={loadingState === 'loadingMore'} onLoadMore={onLoadMore}>
20642064
<div>spinner</div>
20652065
</UNSTABLE_TableLoadingSentinel>
20662066
</TableBody>
@@ -2070,7 +2070,7 @@ describe('Table', () => {
20702070
};
20712071

20722072
it('should always render the sentinel even when virtualized', () => {
2073-
let tree = render(<VirtualizedTableLoad isLoading items={items} />);
2073+
let tree = render(<VirtualizedTableLoad loadingState="loadingMore" items={items} />);
20742074
let tableTester = testUtilUser.createTester('Table', {root: tree.getByRole('grid')});
20752075
let rows = tableTester.rows;
20762076
expect(rows).toHaveLength(7);
@@ -2111,25 +2111,52 @@ describe('Table', () => {
21112111
expect(sentinelParentStyles.top).toBe('0px');
21122112
expect(sentinelParentStyles.height).toBe('0px');
21132113

2114-
// Setting isLoading will render the loader even if the table is empty.
2115-
tree.rerender(<VirtualizedTableLoad items={[]} isLoading />);
2114+
tree.rerender(<VirtualizedTableLoad items={[]} loadingState="loading" />);
21162115
rows = tableTester.rows;
2117-
expect(rows).toHaveLength(2);
2118-
emptyStateRow = rows[1];
2119-
expect(emptyStateRow).toHaveTextContent('No results');
2120-
2121-
let loadingRow = rows[0];
2122-
expect(loadingRow).toHaveTextContent('spinner');
2116+
expect(rows).toHaveLength(1);
2117+
emptyStateRow = rows[0];
2118+
expect(emptyStateRow).toHaveTextContent('loading');
21232119

21242120
sentinel = within(tableTester.table).getByTestId('loadMoreSentinel', {hidden: true});
21252121
sentinelParentStyles = sentinel.parentElement.parentElement.style;
21262122
expect(sentinelParentStyles.top).toBe('0px');
2127-
expect(sentinelParentStyles.height).toBe('30px');
2123+
expect(sentinelParentStyles.height).toBe('0px');
21282124
});
21292125

21302126
it('should have the correct row indicies after loading more items', async () => {
2131-
// TODO: first render without items and is loading
2132-
// then render with items and double check
2127+
let tree = render(<VirtualizedTableLoad items={[]} loadingState="loading" />);
2128+
let tableTester = testUtilUser.createTester('Table', {root: tree.getByRole('grid')});
2129+
let rows = tableTester.rows;
2130+
expect(rows).toHaveLength(1);
2131+
2132+
let loaderRow = rows[0];
2133+
expect(loaderRow).toHaveAttribute('aria-rowindex', '2');
2134+
expect(loaderRow).toHaveTextContent('loading');
2135+
for (let [index, row] of rows.entries()) {
2136+
// the header row is the first row but isn't included in "rows" so add +2
2137+
expect(row).toHaveAttribute('aria-rowindex', `${index + 2}`);
2138+
}
2139+
2140+
tree.rerender(<VirtualizedTableLoad items={items} />);
2141+
rows = tableTester.rows;
2142+
expect(rows).toHaveLength(6);
2143+
expect(within(tableTester.table).queryByText('spinner')).toBeFalsy();
2144+
for (let [index, row] of rows.entries()) {
2145+
expect(row).toHaveAttribute('aria-rowindex', `${index + 2}`);
2146+
}
2147+
2148+
tree.rerender(<VirtualizedTableLoad items={items} loadingState="loadingMore" />);
2149+
rows = tableTester.rows;
2150+
expect(rows).toHaveLength(7);
2151+
loaderRow = rows[6];
2152+
expect(loaderRow).toHaveAttribute('aria-rowindex', '52');
2153+
for (let [index, row] of rows.entries()) {
2154+
if (index === 6) {
2155+
continue;
2156+
} else {
2157+
expect(row).toHaveAttribute('aria-rowindex', `${index + 2}`);
2158+
}
2159+
}
21332160
});
21342161
});
21352162
});

yarn.lock

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7946,7 +7946,6 @@ __metadata:
79467946
"@react-aria/interactions": "npm:^3.25.0"
79477947
"@react-aria/live-announcer": "npm:^3.4.2"
79487948
"@react-aria/overlays": "npm:^3.27.0"
7949-
"@react-aria/separator": "npm:^3.4.8"
79507949
"@react-aria/test-utils": "npm:^1.0.0-alpha.6"
79517950
"@react-aria/utils": "npm:^3.28.2"
79527951
"@react-spectrum/utils": "npm:^3.12.4"

0 commit comments

Comments
 (0)