Skip to content

Commit a4d2eb6

Browse files
committed
refactor: introduce tree table context
1 parent 958fe36 commit a4d2eb6

7 files changed

Lines changed: 205 additions & 139 deletions

File tree

src/taxonomy/tag-list/TagListTable.test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,8 +1248,8 @@ describe('<TagListTable /> pagination transition behavior', () => {
12481248
handleUpdateTag: jest.fn(),
12491249
validate: jest.fn(() => true),
12501250
});
1251-
jest.spyOn(treeTableModule, 'TableView').mockImplementation((props) => {
1252-
tableViewProps = props;
1251+
jest.spyOn(treeTableModule, 'TableView').mockImplementation(() => {
1252+
tableViewProps = React.useContext(treeTableModule.TreeTableContext);
12531253
return <div data-testid="mock-table-view" />;
12541254
});
12551255
});

src/taxonomy/tag-list/TagListTable.tsx

Lines changed: 66 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ import React, {
44
useEffect,
55
} from 'react';
66
import type { PaginationState } from '@tanstack/react-table';
7-
import { TableView } from '@src/taxonomy/tree-table';
87
import { useTagListData, useCreateTag, useUpdateTag } from '@src/taxonomy/data/apiHooks';
9-
import { TagTree } from './tagTree';
8+
import { TableView, TreeTableContext } from '@src/taxonomy/tree-table';
109
import type {
1110
RowId,
12-
TreeColumnDef,
1311
TreeRowData,
1412
} from '../tree-table/types';
13+
import { TagTree } from './tagTree';
1514
import {
1615
TABLE_MODES,
1716
} from './constants';
18-
import { getColumns } from './tagColumns';
1917
import { useTableModes, useEditActions } from './hooks';
18+
import { createTreeTableContextValue } from './createTreeTableContextValue';
2019

2120
interface TagListTableProps {
2221
taxonomyId: number;
@@ -102,81 +101,83 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
102101
setEditingRowId,
103102
});
104103

105-
const columns = useMemo<TreeColumnDef[]>(
106-
() =>
107-
getColumns({
108-
setIsCreatingTopTag,
109-
setCreatingParentId,
110-
handleUpdateTag,
111-
setEditingRowId,
112-
onStartDraft: enterDraftMode,
113-
setActiveActionMenuRowId,
114-
hasOpenDraft,
115-
canAddTag: tagList?.canAddTag !== false,
116-
draftError,
117-
setDraftError,
118-
isSavingDraft: createTagMutation.isPending,
119-
maxDepth,
120-
}),
104+
// RELOAD DATA IN VIEW MODE
105+
useEffect(() => {
106+
// Get row data in VIEW mode. Otherwise keep current data to avoid disrupting
107+
// users while they edit or create a tag.
108+
if (tableMode === TABLE_MODES.VIEW && tagList?.results) {
109+
const tree = new TagTree(tagList?.results);
110+
if (tree) {
111+
setTagTree(tree);
112+
}
113+
}
114+
}, [tagList?.results, tableMode]);
115+
116+
const contextValue = useMemo(
117+
() => createTreeTableContextValue({
118+
treeData,
119+
pageCount,
120+
pagination,
121+
handlePaginationChange,
122+
isLoading,
123+
isCreatingTopRow: isCreatingTopTag,
124+
draftError,
125+
createRowMutation: createTagMutation,
126+
updateRowMutation: updateTagMutation,
127+
toast,
128+
setToast,
129+
setIsCreatingTopRow: setIsCreatingTopTag,
130+
exitDraftWithoutSave,
131+
handleCreateRow: handleCreateTag,
132+
creatingParentId,
133+
setCreatingParentId,
134+
setDraftError,
135+
validate,
136+
handleUpdateRow: handleUpdateTag,
137+
editingRowId,
138+
setEditingRowId,
139+
onStartDraft: enterDraftMode,
140+
setActiveActionMenuRowId,
141+
hasOpenDraft,
142+
canAddTag: tagList?.canAddTag !== false,
143+
maxDepth,
144+
}),
121145
[
146+
treeData,
147+
pageCount,
148+
pagination,
149+
handlePaginationChange,
150+
isLoading,
122151
isCreatingTopTag,
123-
tableMode,
124-
activeActionMenuRowId,
125-
hasOpenDraft,
126-
creatingParentId,
127-
tagList?.canAddTag,
128152
draftError,
129-
createTagMutation.isPending,
130-
maxDepth,
153+
createTagMutation,
154+
updateTagMutation,
155+
toast,
156+
setToast,
131157
setIsCreatingTopTag,
158+
exitDraftWithoutSave,
159+
handleCreateTag,
160+
creatingParentId,
132161
setCreatingParentId,
162+
setDraftError,
163+
validate,
133164
handleUpdateTag,
165+
editingRowId,
134166
setEditingRowId,
135167
enterDraftMode,
136168
setActiveActionMenuRowId,
137-
setDraftError,
169+
hasOpenDraft,
170+
tagList?.canAddTag,
171+
maxDepth,
138172
],
139173
);
140174

141-
// RELOAD DATA IN VIEW MODE
142-
useEffect(() => {
143-
// Get row data in VIEW mode. Otherwise keep current data to avoid disrupting
144-
// users while they edit or create a tag.
145-
if (tableMode === TABLE_MODES.VIEW && tagList?.results) {
146-
const tree = new TagTree(tagList?.results);
147-
if (tree) {
148-
setTagTree(tree);
149-
}
150-
}
151-
}, [tagList?.results, tableMode]);
175+
152176

153177
return (
154-
<TableView
155-
{...{
156-
treeData,
157-
columns,
158-
pageCount,
159-
pagination,
160-
handlePaginationChange,
161-
isLoading,
162-
isCreatingTopRow: isCreatingTopTag,
163-
draftError,
164-
createRowMutation: createTagMutation,
165-
updateRowMutation: updateTagMutation,
166-
handleCreateRow: handleCreateTag,
167-
handleUpdateRow: handleUpdateTag,
168-
toast,
169-
setToast,
170-
setIsCreatingTopRow: setIsCreatingTopTag,
171-
exitDraftWithoutSave,
172-
creatingParentId,
173-
setCreatingParentId,
174-
setDraftError,
175-
validate,
176-
editingRowId,
177-
setEditingRowId,
178-
}}
179-
/>
178+
<TreeTableContext.Provider value={contextValue}>
179+
<TableView />
180+
</TreeTableContext.Provider>
180181
);
181182
};
182183

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { RowId } from '@src/taxonomy/tree-table/types';
2+
import type { TreeTableContextValue } from '@src/taxonomy/tree-table/TreeTableContext';
3+
4+
import { getColumns } from './tagColumns';
5+
6+
interface CreateTagListTreeTableContextValueArgs extends Omit<TreeTableContextValue, 'columns'> {
7+
onStartDraft: () => void;
8+
setActiveActionMenuRowId: (id: RowId | null) => void;
9+
hasOpenDraft: boolean;
10+
canAddTag: boolean;
11+
maxDepth: number;
12+
}
13+
14+
const createTreeTableContextValue = (
15+
args: CreateTagListTreeTableContextValueArgs,
16+
): TreeTableContextValue => ({
17+
...args,
18+
columns: getColumns(args),
19+
});
20+
21+
export { createTreeTableContextValue };

src/taxonomy/tree-table/TableView.test.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { IntlProvider } from '@edx/frontend-platform/i18n';
33
import { fireEvent, render, screen } from '@testing-library/react';
44

5+
import { TreeTableContext } from './TreeTableContext';
56
import { TableView } from './TableView';
67

78
jest.mock('./TableBody', () => {
@@ -19,7 +20,7 @@ const wrapper = ({ children }: { children: React.ReactNode; }) => (
1920
<IntlProvider locale="en" messages={{}}>{children}</IntlProvider>
2021
);
2122

22-
const baseProps = () => ({
23+
const baseContextValue = () => ({
2324
treeData: [{ id: 1, value: 'root' }],
2425
columns: [{ accessorKey: 'value', header: 'Tag name', cell: (info: any) => info.getValue() }],
2526
pageCount: 3,
@@ -44,52 +45,61 @@ const baseProps = () => ({
4445
setEditingRowId: jest.fn(),
4546
});
4647

48+
const renderTableView = (
49+
contextValue = baseContextValue(),
50+
props: React.ComponentProps<typeof TableView> = {},
51+
) => render(
52+
<TreeTableContext.Provider value={contextValue}>
53+
<TableView {...props} />
54+
</TreeTableContext.Provider>,
55+
{ wrapper },
56+
);
57+
4758
describe('TableView', () => {
4859
it('shows and dismisses save error banner', () => {
49-
const props = baseProps();
50-
props.createRowMutation = { isPending: false, isError: true };
51-
props.draftError = 'Request failed with status code 500';
60+
const contextValue = baseContextValue();
61+
contextValue.createRowMutation = { isPending: false, isError: true };
62+
contextValue.draftError = 'Request failed with status code 500';
5263

53-
render(<TableView {...props} />, { wrapper });
64+
renderTableView(contextValue);
5465

5566
expect(screen.getByText('Error saving changes')).toBeInTheDocument();
5667
fireEvent.click(screen.getByRole('button', { name: /dismiss/i }));
5768
expect(screen.queryByText('Error saving changes')).not.toBeInTheDocument();
5869
});
5970

6071
it('keeps pagination hidden by default even when multiple pages are reported', () => {
61-
const props = baseProps();
62-
render(<TableView {...props} />, { wrapper });
72+
renderTableView();
6373

6474
expect(screen.queryByRole('navigation', { name: /table pagination/i })).not.toBeInTheDocument();
6575
});
6676

6777
it('renders pagination and updates page selection when explicitly enabled', () => {
68-
const props = baseProps();
69-
render(<TableView {...props} enablePagination />, { wrapper });
78+
const contextValue = baseContextValue();
79+
renderTableView(contextValue, { enablePagination: true });
7080

7181
expect(screen.getByText('Page 1 of 3')).toBeInTheDocument();
7282
fireEvent.click(screen.getByRole('button', { name: /^page 2$/i }));
73-
expect(props.handlePaginationChange).toHaveBeenCalled();
83+
expect(contextValue.handlePaginationChange).toHaveBeenCalled();
7484
});
7585

7686
it('hides pagination when there is only one page', () => {
77-
const props = baseProps();
78-
props.pageCount = 1;
79-
render(<TableView {...props} />, { wrapper });
87+
const contextValue = baseContextValue();
88+
contextValue.pageCount = 1;
89+
renderTableView(contextValue);
8090

8191
expect(screen.queryByRole('navigation', { name: /table pagination/i })).not.toBeInTheDocument();
8292
});
8393

8494
it('closes toast by setting show to false', () => {
85-
const props = baseProps();
86-
props.toast = { show: true, message: 'created', variant: 'success' };
95+
const contextValue = baseContextValue();
96+
contextValue.toast = { show: true, message: 'created', variant: 'success' };
8797

88-
render(<TableView {...props} />, { wrapper });
98+
renderTableView(contextValue);
8999

90100
fireEvent.click(screen.getByRole('button', { name: /close/i }));
91-
expect(props.setToast).toHaveBeenCalled();
92-
const updater = props.setToast.mock.calls[0][0];
101+
expect(contextValue.setToast).toHaveBeenCalled();
102+
const updater = contextValue.setToast.mock.calls[0][0];
93103
expect(updater({ show: true, message: 'created', variant: 'success' })).toEqual({
94104
show: false,
95105
message: 'created',

0 commit comments

Comments
 (0)