Skip to content

Commit b8f8d1d

Browse files
fix: fix TableView from crashing when another collection is rendered as empty state (#9557)
* nested collections * added test * lint fix * fix: wrap renderEmptyState in CollectionRendererContext.Provider in TableView component * remove story, simplify test and add interactions --------- Co-authored-by: Robert Snow <rsnow@adobe.com> Co-authored-by: Robert Snow <snowystinger@gmail.com>
1 parent bb38442 commit b8f8d1d

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

packages/@react-spectrum/s2/src/TableView.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import {
1717
ButtonContext,
1818
CellRenderProps,
1919
Collection,
20+
CollectionRendererContext,
2021
ColumnRenderProps,
2122
ColumnResizer,
2223
ContextValue,
2324
DEFAULT_SLOT,
25+
DefaultCollectionRenderer,
2426
Form,
2527
Key,
2628
OverlayTriggerStateContext,
@@ -439,7 +441,9 @@ export const TableBody = /*#__PURE__*/ (forwardRef as forwardRefType)(function T
439441
if (renderEmptyState != null && !isLoading) {
440442
emptyRender = (props: TableBodyRenderProps) => (
441443
<div className={centeredWrapper}>
442-
{renderEmptyState(props)}
444+
<CollectionRendererContext.Provider value={DefaultCollectionRenderer}>
445+
{renderEmptyState(props)}
446+
</CollectionRendererContext.Provider>
443447
</div>
444448
);
445449
} else if (loadingState === 'loading') {

packages/@react-spectrum/s2/stories/TableView.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
Picker,
2727
PickerItem,
2828
Row,
29+
SortDescriptor,
2930
StatusLight,
3031
TableBody,
3132
TableHeader,
@@ -41,7 +42,6 @@ import FolderOpen from '../spectrum-illustrations/linear/FolderOpen';
4142
import {Key} from '@react-types/shared';
4243
import type {Meta, StoryObj} from '@storybook/react';
4344
import React, {ReactElement, useCallback, useEffect, useRef, useState} from 'react';
44-
import {SortDescriptor} from 'react-aria-components';
4545
import {style} from '../style/spectrum-theme' with {type: 'macro'};
4646
import {useAsyncList, useListData} from '@react-stately/data';
4747
import {useEffectEvent} from '@react-aria/utils';

packages/@react-spectrum/s2/test/TableView.test.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ import {
1919
MenuItem,
2020
MenuSection,
2121
Row,
22+
Tab,
2223
TableBody,
2324
TableHeader,
2425
TableView,
26+
TabList,
27+
Tabs,
2528
Text
2629
} from '../src';
2730
import {DisabledBehavior} from '@react-types/shared';
@@ -41,6 +44,21 @@ describe('TableView', () => {
4144
user = userEvent.setup({delay: null, pointerMap});
4245
offsetWidth = jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 400);
4346
offsetHeight = jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 200);
47+
window.CSSTransition = jest.fn(({children}) => children);
48+
49+
// Mock the getAnimations method
50+
Element.prototype.getAnimations = jest.fn().mockImplementation(() => {
51+
// Return an array of mock Animation objects
52+
return [
53+
{
54+
// Mock the properties and methods you need
55+
finished: Promise.resolve(), // Useful for waiting for animations to "finish"
56+
play: jest.fn(),
57+
pause: jest.fn(),
58+
cancel: jest.fn()
59+
}
60+
];
61+
});
4462
jest.useFakeTimers();
4563
});
4664

@@ -148,4 +166,44 @@ describe('TableView', () => {
148166
await user.tab();
149167
expect(document.activeElement).toBe(cells[3]);
150168
});
169+
170+
it('should render empty state + nested collection without crashing', async () => {
171+
const tabItems = [
172+
{id: 'general', label: 'General'},
173+
{id: 'advanced', label: 'Advanced'}
174+
];
175+
const renderEmptyState = () => (
176+
<Tabs aria-label="Settings">
177+
<TabList items={tabItems}>
178+
{(item) => (
179+
<Tab>
180+
{item.label}
181+
</Tab>
182+
)}
183+
</TabList>
184+
</Tabs>
185+
);
186+
187+
let {getAllByRole} = render(
188+
<TableView aria-label="Debug table" selectionMode="none">
189+
<TableHeader columns={columns}>
190+
{(column) => (
191+
<Column>
192+
{column.name}
193+
</Column>
194+
)}
195+
</TableHeader>
196+
<TableBody items={[]} renderEmptyState={renderEmptyState} />
197+
</TableView>
198+
);
199+
await act(() => Promise.resolve());
200+
201+
let tabs = getAllByRole('tab');
202+
expect(tabs).toHaveLength(tabs.length);
203+
expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
204+
expect(tabs[1]).toHaveAttribute('aria-selected', 'false');
205+
await user.click(tabs[1]);
206+
expect(tabs[0]).toHaveAttribute('aria-selected', 'false');
207+
expect(tabs[1]).toHaveAttribute('aria-selected', 'true');
208+
});
151209
});

0 commit comments

Comments
 (0)