Skip to content

Commit d544a07

Browse files
fix: implement disableTagActions logic
1 parent e5d0085 commit d544a07

6 files changed

Lines changed: 131 additions & 5 deletions

File tree

src/taxonomy/tag-list/Actions.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface ActionsHeaderProps {
2525
setEditingRowId: (id: RowId | null) => void;
2626
setActiveActionMenuRowId: (id: RowId | null) => void;
2727
hasOpenDraft: boolean;
28+
disableTagActions: boolean;
2829
draftInProgressHintId: string;
2930
canAddTag: boolean;
3031
}
@@ -36,6 +37,7 @@ const ActionsHeader = ({
3637
setEditingRowId,
3738
setActiveActionMenuRowId,
3839
hasOpenDraft,
40+
disableTagActions,
3941
canAddTag,
4042
draftInProgressHintId,
4143
}: ActionsHeaderProps) => {
@@ -55,7 +57,7 @@ const ActionsHeader = ({
5557
setEditingRowId(null);
5658
setActiveActionMenuRowId(null);
5759
}}
58-
disabled={hasOpenDraft || !canAddTag}
60+
disabled={disableTagActions || !canAddTag}
5961
aria-describedby={hasOpenDraft ? draftInProgressHintId : undefined}
6062
/>
6163
</div>

src/taxonomy/tag-list/TagListTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
8383
const createTagMutation = useCreateTag(taxonomyId);
8484
const updateTagMutation = useUpdateTag(taxonomyId);
8585
const deleteTagMutation = useDeleteTag(taxonomyId);
86+
const disableTagActions = hasOpenDraft || deleteTagMutation.isPending;
8687
const pageCount = tagList?.numPages ?? -1;
8788
const canAddTag = tagList?.canAddTag !== false;
8889

@@ -143,6 +144,7 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
143144
onStartDraft: enterDraftMode,
144145
setActiveActionMenuRowId,
145146
hasOpenDraft,
147+
disableTagActions,
146148
canAddTag,
147149
maxDepth,
148150
};

src/taxonomy/tag-list/hooks.test.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ describe('useTableModes', () => {
4242
});
4343

4444
describe('useEditActions', () => {
45+
afterEach(() => {
46+
jest.restoreAllMocks();
47+
});
48+
4549
const buildActions = (overrides = {}) => {
4650
const createTagMutation = { mutateAsync: jest.fn() };
4751
const updateTagMutation = { mutateAsync: jest.fn() };
@@ -177,4 +181,69 @@ describe('useEditActions', () => {
177181
message: 'Error creating tag: server failed',
178182
});
179183
});
184+
185+
it('enters view mode only after a delete request succeeds', async () => {
186+
const {
187+
actions,
188+
deleteTagMutation,
189+
enterViewMode,
190+
setToast,
191+
} = buildActions();
192+
deleteTagMutation.mutateAsync.mockResolvedValue(undefined);
193+
jest.spyOn(window, 'confirm').mockReturnValue(true);
194+
195+
actions.startDeleteRow({
196+
original: {
197+
id: 1,
198+
value: 'tag to delete',
199+
depth: 0,
200+
childCount: 0,
201+
},
202+
} as any);
203+
204+
await waitFor(() => {
205+
expect(enterViewMode).toHaveBeenCalled();
206+
});
207+
expect(deleteTagMutation.mutateAsync).toHaveBeenCalledWith({
208+
value: 'tag to delete',
209+
withSubtags: false,
210+
});
211+
expect(deleteTagMutation.mutateAsync.mock.invocationCallOrder[0]).toBeLessThan(
212+
enterViewMode.mock.invocationCallOrder[0],
213+
);
214+
expect(setToast).toHaveBeenCalledWith({
215+
show: true,
216+
message: '1 tag(s) deleted. This change will be applied across all tagged content.',
217+
});
218+
});
219+
220+
it('does not enter view mode when a delete request fails', async () => {
221+
const {
222+
actions,
223+
deleteTagMutation,
224+
enterViewMode,
225+
setDraftError,
226+
setToast,
227+
} = buildActions();
228+
deleteTagMutation.mutateAsync.mockRejectedValue(new Error('server failed'));
229+
jest.spyOn(window, 'confirm').mockReturnValue(true);
230+
231+
actions.startDeleteRow({
232+
original: {
233+
id: 1,
234+
value: 'tag to delete',
235+
depth: 0,
236+
childCount: 0,
237+
},
238+
} as any);
239+
240+
await waitFor(() => {
241+
expect(setDraftError).toHaveBeenCalledWith('server failed');
242+
});
243+
expect(enterViewMode).not.toHaveBeenCalled();
244+
expect(setToast).toHaveBeenCalledWith({
245+
show: true,
246+
message: 'Error deleting tag: server failed',
247+
});
248+
});
180249
});

src/taxonomy/tag-list/hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,10 @@ const useEditActions = ({
296296
// the backend rejects the request instead of deleting the parent alone.
297297
const shouldDeleteSubtags = count > 1;
298298
try {
299+
await deleteTagMutation.mutateAsync({ value: rowData.value, withSubtags: shouldDeleteSubtags });
299300
// In view mode, the table reloads on change, reflecting the deletion
300301
// without needing to manually update the table state
301302
enterViewMode();
302-
await deleteTagMutation.mutateAsync({ value: rowData.value, withSubtags: shouldDeleteSubtags });
303303
setToast({
304304
show: true,
305305
message: intl.formatMessage(messages.tagsDeleteSuccessMessage, { count }),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
3+
import { getColumns } from './tagColumns';
4+
5+
const baseArgs = (overrides = {}) => ({
6+
setIsCreatingTopRow: jest.fn(),
7+
setEditingRowId: jest.fn(),
8+
onStartDraft: jest.fn(),
9+
setActiveActionMenuRowId: jest.fn(),
10+
hasOpenDraft: false,
11+
disableTagActions: false,
12+
canAddTag: true,
13+
setDraftError: jest.fn(),
14+
maxDepth: 3,
15+
startSubtagDraft: jest.fn(),
16+
startEditRow: jest.fn(),
17+
startDeleteRow: jest.fn(),
18+
...overrides,
19+
});
20+
21+
const getActionsColumn = (overrides = {}) => {
22+
const columns = getColumns(baseArgs(overrides));
23+
return columns.find(column => column.id === 'actions');
24+
};
25+
26+
describe('tagColumns', () => {
27+
it('disables create, edit, and delete actions when tag actions are disabled', () => {
28+
const actionsColumn = getActionsColumn({ disableTagActions: true });
29+
const headerElement = (actionsColumn?.header as any)();
30+
const cellElement = (actionsColumn?.cell as any)({
31+
row: {
32+
depth: 0,
33+
original: {
34+
id: 1,
35+
value: 'root',
36+
depth: 0,
37+
childCount: 0,
38+
canChangeTag: true,
39+
canDeleteTag: true,
40+
},
41+
},
42+
});
43+
const menuElement = React.Children.only(cellElement.props.children) as React.ReactElement;
44+
45+
expect(headerElement.props.disableTagActions).toBe(true);
46+
expect(menuElement.props.disableAddSubtag).toBe(true);
47+
expect(menuElement.props.disableEditRow).toBe(true);
48+
expect(menuElement.props.disableDeleteRow).toBe(true);
49+
});
50+
});

src/taxonomy/tag-list/tagColumns.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface GetColumnsArgs {
2020
onStartDraft: () => void;
2121
setActiveActionMenuRowId: (id: RowId | null) => void;
2222
hasOpenDraft: boolean;
23+
disableTagActions: boolean;
2324
canAddTag: boolean;
2425
setDraftError: (error: string) => void;
2526
maxDepth: number;
@@ -37,6 +38,7 @@ function getColumns({
3738
startDeleteRow,
3839
setActiveActionMenuRowId,
3940
hasOpenDraft,
41+
disableTagActions,
4042
canAddTag,
4143
setDraftError,
4244
maxDepth,
@@ -76,6 +78,7 @@ function getColumns({
7678
setEditingRowId={setEditingRowId}
7779
setActiveActionMenuRowId={setActiveActionMenuRowId}
7880
hasOpenDraft={hasOpenDraft}
81+
disableTagActions={disableTagActions}
7982
draftInProgressHintId={draftInProgressHintId}
8083
canAddTag={canAddTag}
8184
/>
@@ -87,9 +90,9 @@ function getColumns({
8790
return <div className="d-flex gap-2" />;
8891
}
8992

90-
const disableAddSubtag = hasOpenDraft || !canAddTag;
91-
const disableEditRow = hasOpenDraft || rowData.canChangeTag === false;
92-
const disableDeleteRow = hasOpenDraft || rowData.canDeleteTag === false;
93+
const disableAddSubtag = disableTagActions || !canAddTag;
94+
const disableEditRow = disableTagActions || rowData.canChangeTag === false;
95+
const disableDeleteRow = disableTagActions || rowData.canDeleteTag === false;
9396

9497
return (
9598
<div className="d-flex align-items-center justify-content-end gap-2">

0 commit comments

Comments
 (0)