|
3 | 3 | */ |
4 | 4 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
5 | 5 |
|
6 | | -const { mockListActiveFolderRows } = vi.hoisted(() => ({ |
7 | | - mockListActiveFolderRows: vi.fn(), |
8 | | -})) |
| 6 | +const { mockDeleteFolder, mockListActiveFolderRows, mockNotifyFolderResourceChanged } = vi.hoisted( |
| 7 | + () => ({ |
| 8 | + mockDeleteFolder: vi.fn(), |
| 9 | + mockListActiveFolderRows: vi.fn(), |
| 10 | + mockNotifyFolderResourceChanged: vi.fn(), |
| 11 | + }) |
| 12 | +) |
9 | 13 |
|
10 | 14 | vi.mock('@/lib/folders/queries', () => ({ |
11 | 15 | listActiveFolderRows: mockListActiveFolderRows, |
12 | 16 | })) |
13 | 17 |
|
14 | 18 | vi.mock('@/lib/folders/orchestration', () => ({ |
15 | | - deleteFolder: vi.fn(), |
| 19 | + deleteFolder: mockDeleteFolder, |
16 | 20 | updateFolder: vi.fn(), |
17 | 21 | })) |
18 | 22 |
|
19 | 23 | vi.mock('@/lib/realtime/notify', () => ({ |
20 | | - notifyFolderResourceChanged: vi.fn(), |
| 24 | + notifyFolderResourceChanged: mockNotifyFolderResourceChanged, |
21 | 25 | })) |
22 | 26 |
|
23 | | -import { planFolderSelection } from '@/lib/folders/bulk' |
| 27 | +import { bulkDeleteFolders, planFolderSelection } from '@/lib/folders/bulk' |
24 | 28 |
|
25 | 29 | /** |
26 | 30 | * `a` holds `a1`, which holds `a1x`. `b` is a sibling with nothing inside it, so a plan can |
@@ -94,3 +98,78 @@ describe('planFolderSelection', () => { |
94 | 98 | expect(result.selected).toEqual([{ id: 'b', name: 'B' }]) |
95 | 99 | }) |
96 | 100 | }) |
| 101 | + |
| 102 | +describe('bulkDeleteFolders', () => { |
| 103 | + beforeEach(() => { |
| 104 | + vi.clearAllMocks() |
| 105 | + }) |
| 106 | + |
| 107 | + const deleteFolders = (folders: Array<{ id: string; name: string }>) => |
| 108 | + bulkDeleteFolders({ |
| 109 | + workspaceId: 'ws-1', |
| 110 | + resourceType: 'table', |
| 111 | + userId: 'user-1', |
| 112 | + folders, |
| 113 | + countKey: 'tables', |
| 114 | + referenceCheckCompleted: true, |
| 115 | + }) |
| 116 | + |
| 117 | + it('retries a target folder after a later referrer folder succeeds', async () => { |
| 118 | + mockDeleteFolder |
| 119 | + .mockResolvedValueOnce({ |
| 120 | + success: false, |
| 121 | + error: 'Target is referenced by Referrer', |
| 122 | + errorCode: 'conflict', |
| 123 | + }) |
| 124 | + .mockResolvedValueOnce({ |
| 125 | + success: true, |
| 126 | + deletedItems: { folders: 1, tables: 1 }, |
| 127 | + }) |
| 128 | + .mockResolvedValueOnce({ |
| 129 | + success: true, |
| 130 | + deletedItems: { folders: 1, tables: 1 }, |
| 131 | + }) |
| 132 | + |
| 133 | + const result = await deleteFolders([ |
| 134 | + { id: 'target-folder', name: 'Target' }, |
| 135 | + { id: 'referrer-folder', name: 'Referrer' }, |
| 136 | + ]) |
| 137 | + |
| 138 | + expect(result).toEqual({ |
| 139 | + succeeded: [ |
| 140 | + { id: 'referrer-folder', name: 'Referrer' }, |
| 141 | + { id: 'target-folder', name: 'Target' }, |
| 142 | + ], |
| 143 | + failed: [], |
| 144 | + folderCount: 2, |
| 145 | + resourceCount: 2, |
| 146 | + }) |
| 147 | + expect(mockDeleteFolder.mock.calls.map(([params]) => params.folderId)).toEqual([ |
| 148 | + 'target-folder', |
| 149 | + 'referrer-folder', |
| 150 | + 'target-folder', |
| 151 | + ]) |
| 152 | + expect(mockNotifyFolderResourceChanged).toHaveBeenCalledOnce() |
| 153 | + }) |
| 154 | + |
| 155 | + it('stops when a reference cycle makes no progress', async () => { |
| 156 | + mockDeleteFolder.mockResolvedValue({ |
| 157 | + success: false, |
| 158 | + error: 'Referenced by another selected folder', |
| 159 | + errorCode: 'conflict', |
| 160 | + }) |
| 161 | + |
| 162 | + const result = await deleteFolders([ |
| 163 | + { id: 'folder-a', name: 'A' }, |
| 164 | + { id: 'folder-b', name: 'B' }, |
| 165 | + ]) |
| 166 | + |
| 167 | + expect(result.succeeded).toEqual([]) |
| 168 | + expect(result.failed).toEqual([ |
| 169 | + { id: 'folder-a', name: 'A', reason: 'Referenced by another selected folder' }, |
| 170 | + { id: 'folder-b', name: 'B', reason: 'Referenced by another selected folder' }, |
| 171 | + ]) |
| 172 | + expect(mockDeleteFolder).toHaveBeenCalledTimes(2) |
| 173 | + expect(mockNotifyFolderResourceChanged).not.toHaveBeenCalled() |
| 174 | + }) |
| 175 | +}) |
0 commit comments