@thepalaceproject/circulation-admin 1.43.0-post.9 → 1.44.0-post.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +22 -10
- package/README.md +11 -5
- package/dist/circulation-admin.js +1 -1
- package/jest.config.js +11 -44
- package/package.json +1 -5
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +111 -0
- package/tests/jest/components/Announcement.test.tsx +76 -0
- package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
- package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
- package/tests/jest/components/Autocomplete.test.tsx +67 -0
- package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
- package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
- package/tests/jest/components/BookDetailsTabContainer.test.tsx +237 -0
- package/tests/jest/components/BookEditForm.test.tsx +352 -0
- package/tests/jest/components/BookEditor.test.tsx +20 -21
- package/tests/jest/components/CatalogPage.test.tsx +158 -0
- package/tests/jest/components/CatalogServices.test.tsx +38 -1
- package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
- package/tests/jest/components/Classifications.test.tsx +166 -0
- package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
- package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
- package/tests/jest/components/Collections.test.tsx +335 -7
- package/tests/jest/components/ColorPicker.test.tsx +58 -0
- package/tests/jest/components/ComplaintForm.test.tsx +171 -0
- package/tests/jest/components/Complaints.test.tsx +259 -0
- package/tests/jest/components/ConfigTabContainer.test.tsx +196 -0
- package/tests/jest/components/ContextProvider.test.tsx +138 -0
- package/tests/jest/components/Contributors.test.tsx +157 -0
- package/tests/jest/components/CustomListEditor.test.tsx +367 -0
- package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
- package/tests/jest/components/CustomLists.test.tsx +621 -4
- package/tests/jest/components/CustomListsForBook.test.tsx +353 -0
- package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
- package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
- package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
- package/tests/jest/components/EditableConfigList.test.tsx +598 -7
- package/tests/jest/components/EditableInput.test.tsx +250 -1
- package/tests/jest/components/EditorField.test.tsx +95 -0
- package/tests/jest/components/GenreForm.test.tsx +152 -0
- package/tests/jest/components/Header.test.tsx +421 -0
- package/tests/jest/components/IndividualAdminEditForm.test.tsx +506 -1
- package/tests/jest/components/IndividualAdmins.test.tsx +124 -8
- package/tests/jest/components/InputList.test.tsx +493 -0
- package/tests/jest/components/Lane.test.tsx +132 -10
- package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
- package/tests/jest/components/LaneEditor.test.tsx +377 -9
- package/tests/jest/components/Lanes.test.tsx +446 -0
- package/tests/jest/components/LanesSidebar.test.tsx +621 -0
- package/tests/jest/components/Libraries.test.tsx +48 -0
- package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
- package/tests/jest/components/LibraryRegistration.test.tsx +354 -0
- package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
- package/tests/jest/components/MetadataServices.test.tsx +37 -1
- package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
- package/tests/jest/components/PairedMenus.test.tsx +220 -0
- package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
- package/tests/jest/components/ProtocolFormField.test.tsx +443 -7
- package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
- package/tests/jest/components/SelfTests.test.tsx +601 -0
- package/tests/jest/components/SelfTestsTabContainer.test.tsx +216 -0
- package/tests/jest/components/ServiceEditForm.test.tsx +1092 -0
- package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
- package/tests/jest/components/Stats.test.tsx +0 -7
- package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
- package/tests/jest/components/ToolTip.test.tsx +46 -0
- package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
- package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
- package/tests/jest/features/book.test.ts +75 -80
- package/tests/jest/fixtures/classificationsData.ts +45 -0
- package/tests/jest/fixtures/genreData.ts +811 -0
- package/tests/jest/README.md +0 -6
- package/tests/jest/legacy-setup.ts +0 -13
- package/tests/jest/legacyJsdomEnvironment.js +0 -49
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen, within, act } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
// Capture the component's onDragStart/onDragEnd while still rendering the real
|
|
6
|
+
// render-prop DOM (so the button/column tests below are unaffected). A real drag
|
|
7
|
+
// can't run in jsdom, so the drag-handler tests call the captured callbacks
|
|
8
|
+
// directly.
|
|
9
|
+
let dndCallbacks: { onDragStart?: any; onDragEnd?: any } = {};
|
|
10
|
+
jest.mock("react-beautiful-dnd", () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
DragDropContext: ({ onDragStart, onDragEnd, children }: any) => {
|
|
13
|
+
dndCallbacks = { onDragStart, onDragEnd };
|
|
14
|
+
return children;
|
|
15
|
+
},
|
|
16
|
+
Droppable: ({ droppableId, isDropDisabled, children }: any) => (
|
|
17
|
+
<div
|
|
18
|
+
data-testid={`droppable-${droppableId}`}
|
|
19
|
+
data-drop-disabled={String(!!isDropDisabled)}
|
|
20
|
+
>
|
|
21
|
+
{children(
|
|
22
|
+
{ droppableProps: {}, innerRef: () => undefined, placeholder: null },
|
|
23
|
+
{ isDraggingOver: false }
|
|
24
|
+
)}
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
Draggable: ({ children }: any) =>
|
|
28
|
+
children(
|
|
29
|
+
{
|
|
30
|
+
draggableProps: {},
|
|
31
|
+
dragHandleProps: {},
|
|
32
|
+
innerRef: () => undefined,
|
|
33
|
+
draggableStyle: {},
|
|
34
|
+
placeholder: null,
|
|
35
|
+
},
|
|
36
|
+
{ isDragging: false }
|
|
37
|
+
),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
import LaneCustomListsEditor from "../../../src/components/LaneCustomListsEditor";
|
|
41
|
+
import { CustomListData } from "../../../src/interfaces";
|
|
42
|
+
|
|
43
|
+
// NOTE ON DRAG-AND-DROP COVERAGE
|
|
44
|
+
// ------------------------------
|
|
45
|
+
// This component uses `react-beautiful-dnd`. A real drag/drop cannot be
|
|
46
|
+
// simulated in jsdom, so the drag paths are exercised by invoking the captured
|
|
47
|
+
// onDragStart/onDragEnd callbacks and asserting the observable result:
|
|
48
|
+
// - Adding a list (drop from available to current) and removing one (drop from
|
|
49
|
+
// current to available) update the selection; both are also reachable via the
|
|
50
|
+
// "Add to lane" / "Remove from lane" buttons and are tested below.
|
|
51
|
+
// - onDragStart toggles each Droppable's `isDropDisabled` (surfaced by the test
|
|
52
|
+
// mock as `data-drop-disabled`) and, when dragging from the current lists,
|
|
53
|
+
// shows the "remove them from the lane" message over the available lists.
|
|
54
|
+
// - `reset()` / `getCustomListIds()` form the imperative API that LaneEditor
|
|
55
|
+
// drives through a ref in production; they are tested through a ref below.
|
|
56
|
+
|
|
57
|
+
const allCustomListsData: CustomListData[] = [
|
|
58
|
+
{ id: 1, name: "list 1", entry_count: 0, is_owner: true, is_shared: false },
|
|
59
|
+
{ id: 2, name: "list 2", entry_count: 2, is_owner: true, is_shared: false },
|
|
60
|
+
{ id: 3, name: "list 3", entry_count: 0, is_owner: true, is_shared: false },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const sharedList: CustomListData = {
|
|
64
|
+
id: 4,
|
|
65
|
+
name: "list 4",
|
|
66
|
+
entry_count: 0,
|
|
67
|
+
is_owner: false,
|
|
68
|
+
is_shared: true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const shareTitle = /shared by another library/i;
|
|
72
|
+
|
|
73
|
+
const availableLists = (container: HTMLElement) =>
|
|
74
|
+
Array.from(
|
|
75
|
+
container.querySelectorAll<HTMLElement>(".available-lists .available-list")
|
|
76
|
+
);
|
|
77
|
+
const currentLists = (container: HTMLElement) =>
|
|
78
|
+
Array.from(
|
|
79
|
+
container.querySelectorAll<HTMLElement>(".current-lists .current-list")
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
describe("LaneCustomListsEditor", () => {
|
|
83
|
+
it("renders available lists", () => {
|
|
84
|
+
const filteredCustomListsData = [
|
|
85
|
+
allCustomListsData[0],
|
|
86
|
+
allCustomListsData[2],
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const { container, rerender } = render(
|
|
90
|
+
<LaneCustomListsEditor
|
|
91
|
+
allCustomLists={allCustomListsData}
|
|
92
|
+
customListIds={[]}
|
|
93
|
+
filter="owned"
|
|
94
|
+
filteredCustomLists={filteredCustomListsData}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(container.querySelector(".available-lists")).toBeInTheDocument();
|
|
99
|
+
|
|
100
|
+
let lists = availableLists(container);
|
|
101
|
+
expect(lists).toHaveLength(2);
|
|
102
|
+
expect(lists[0]).toHaveTextContent("list 1");
|
|
103
|
+
expect(lists[0]).toHaveTextContent("Items in list: 0");
|
|
104
|
+
expect(lists[1]).toHaveTextContent("list 3");
|
|
105
|
+
expect(lists[1]).toHaveTextContent("Items in list: 0");
|
|
106
|
+
|
|
107
|
+
// With list 1 already in the lane, it drops out of the available column.
|
|
108
|
+
rerender(
|
|
109
|
+
<LaneCustomListsEditor
|
|
110
|
+
allCustomLists={allCustomListsData}
|
|
111
|
+
customListIds={[1]}
|
|
112
|
+
filter="owned"
|
|
113
|
+
filteredCustomLists={filteredCustomListsData}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
lists = availableLists(container);
|
|
118
|
+
expect(lists).toHaveLength(1);
|
|
119
|
+
expect(lists[0]).toHaveTextContent("list 3");
|
|
120
|
+
expect(lists[0]).toHaveTextContent("Items in list: 0");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("renders a share icon on available lists that are not owned by the current library", () => {
|
|
124
|
+
const sharedCustomListsData = [...allCustomListsData, sharedList];
|
|
125
|
+
|
|
126
|
+
const { container } = render(
|
|
127
|
+
<LaneCustomListsEditor
|
|
128
|
+
allCustomLists={sharedCustomListsData}
|
|
129
|
+
customListIds={[]}
|
|
130
|
+
filter=""
|
|
131
|
+
filteredCustomLists={sharedCustomListsData}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const lists = availableLists(container);
|
|
136
|
+
expect(lists).toHaveLength(4);
|
|
137
|
+
|
|
138
|
+
expect(within(lists[0]).queryByTitle(shareTitle)).toBeNull();
|
|
139
|
+
expect(within(lists[1]).queryByTitle(shareTitle)).toBeNull();
|
|
140
|
+
expect(within(lists[2]).queryByTitle(shareTitle)).toBeNull();
|
|
141
|
+
expect(within(lists[3]).queryByTitle(shareTitle)).not.toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("renders filter select", async () => {
|
|
145
|
+
const user = userEvent.setup();
|
|
146
|
+
const changeFilter = jest.fn();
|
|
147
|
+
|
|
148
|
+
const { container } = render(
|
|
149
|
+
<LaneCustomListsEditor
|
|
150
|
+
allCustomLists={allCustomListsData}
|
|
151
|
+
customListIds={[]}
|
|
152
|
+
filter="owned"
|
|
153
|
+
filteredCustomLists={allCustomListsData}
|
|
154
|
+
changeFilter={changeFilter}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const select = container.querySelector<HTMLSelectElement>(
|
|
159
|
+
'select[name="filter"]'
|
|
160
|
+
);
|
|
161
|
+
expect(select).toHaveValue("owned");
|
|
162
|
+
|
|
163
|
+
const options = within(select).getAllByRole("option");
|
|
164
|
+
expect(options).toHaveLength(3);
|
|
165
|
+
expect(options[0]).toHaveValue("");
|
|
166
|
+
expect(options[1]).toHaveValue("owned");
|
|
167
|
+
expect(options[2]).toHaveValue("shared-in");
|
|
168
|
+
|
|
169
|
+
await user.selectOptions(select, "shared-in");
|
|
170
|
+
|
|
171
|
+
expect(changeFilter).toHaveBeenCalledTimes(1);
|
|
172
|
+
expect(changeFilter).toHaveBeenCalledWith("shared-in");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("renders current lists", () => {
|
|
176
|
+
const { container, rerender } = render(
|
|
177
|
+
<LaneCustomListsEditor
|
|
178
|
+
allCustomLists={allCustomListsData}
|
|
179
|
+
customListIds={[]}
|
|
180
|
+
filter=""
|
|
181
|
+
filteredCustomLists={allCustomListsData}
|
|
182
|
+
/>
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(container.querySelector(".current-lists")).toBeInTheDocument();
|
|
186
|
+
expect(currentLists(container)).toHaveLength(0);
|
|
187
|
+
|
|
188
|
+
rerender(
|
|
189
|
+
<LaneCustomListsEditor
|
|
190
|
+
allCustomLists={allCustomListsData}
|
|
191
|
+
customListIds={[2, 3]}
|
|
192
|
+
filter=""
|
|
193
|
+
filteredCustomLists={allCustomListsData}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const lists = currentLists(container);
|
|
198
|
+
expect(lists).toHaveLength(2);
|
|
199
|
+
expect(lists[0]).toHaveTextContent("list 2");
|
|
200
|
+
expect(lists[0]).toHaveTextContent("Items in list: 2");
|
|
201
|
+
expect(lists[1]).toHaveTextContent("list 3");
|
|
202
|
+
expect(lists[1]).toHaveTextContent("Items in list: 0");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("renders a share icon on current lists that are not owned by the current library", () => {
|
|
206
|
+
const sharedCustomListsData = [...allCustomListsData, sharedList];
|
|
207
|
+
|
|
208
|
+
const { container } = render(
|
|
209
|
+
<LaneCustomListsEditor
|
|
210
|
+
allCustomLists={sharedCustomListsData}
|
|
211
|
+
customListIds={[3, 4]}
|
|
212
|
+
filter=""
|
|
213
|
+
filteredCustomLists={sharedCustomListsData}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const lists = currentLists(container);
|
|
218
|
+
expect(lists).toHaveLength(2);
|
|
219
|
+
|
|
220
|
+
expect(within(lists[0]).queryByTitle(shareTitle)).toBeNull();
|
|
221
|
+
expect(within(lists[1]).queryByTitle(shareTitle)).not.toBeNull();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("adds a list to the lane when the add button is clicked", async () => {
|
|
225
|
+
const user = userEvent.setup();
|
|
226
|
+
const onUpdate = jest.fn();
|
|
227
|
+
|
|
228
|
+
const { container } = render(
|
|
229
|
+
<LaneCustomListsEditor
|
|
230
|
+
allCustomLists={allCustomListsData}
|
|
231
|
+
customListIds={[2]}
|
|
232
|
+
filter=""
|
|
233
|
+
filteredCustomLists={allCustomListsData}
|
|
234
|
+
onUpdate={onUpdate}
|
|
235
|
+
/>
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Available column starts with list 1 and list 3 (list 2 is in the lane).
|
|
239
|
+
const available = container.querySelector<HTMLElement>(".available-lists");
|
|
240
|
+
const addButtons = within(available).getAllByRole("button", {
|
|
241
|
+
name: /add to lane/i,
|
|
242
|
+
});
|
|
243
|
+
expect(addButtons).toHaveLength(2);
|
|
244
|
+
|
|
245
|
+
// Add list 1 (the first available list).
|
|
246
|
+
await user.click(addButtons[0]);
|
|
247
|
+
|
|
248
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
249
|
+
expect(onUpdate.mock.calls[0][0]).toEqual(expect.arrayContaining([1, 2]));
|
|
250
|
+
|
|
251
|
+
// The item now appears in the current lists.
|
|
252
|
+
const nowCurrent = currentLists(container);
|
|
253
|
+
expect(nowCurrent).toHaveLength(2);
|
|
254
|
+
expect(nowCurrent[0]).toHaveTextContent("list 1");
|
|
255
|
+
expect(nowCurrent[1]).toHaveTextContent("list 2");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("removes a list from the lane when the remove button is clicked", async () => {
|
|
259
|
+
const user = userEvent.setup();
|
|
260
|
+
const onUpdate = jest.fn();
|
|
261
|
+
|
|
262
|
+
const { container, rerender } = render(
|
|
263
|
+
<LaneCustomListsEditor
|
|
264
|
+
allCustomLists={allCustomListsData}
|
|
265
|
+
customListIds={[1, 2]}
|
|
266
|
+
filter=""
|
|
267
|
+
filteredCustomLists={allCustomListsData}
|
|
268
|
+
onUpdate={onUpdate}
|
|
269
|
+
/>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const current = container.querySelector<HTMLElement>(".current-lists");
|
|
273
|
+
const removeButtons = within(current).getAllByRole("button", {
|
|
274
|
+
name: /remove from lane/i,
|
|
275
|
+
});
|
|
276
|
+
expect(removeButtons).toHaveLength(2);
|
|
277
|
+
|
|
278
|
+
// Remove list 1 (the first current list).
|
|
279
|
+
await user.click(removeButtons[0]);
|
|
280
|
+
|
|
281
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
282
|
+
expect(onUpdate.mock.calls[0][0]).toEqual([2]);
|
|
283
|
+
|
|
284
|
+
// Reflect the parent's updated ids; the removed list is gone.
|
|
285
|
+
rerender(
|
|
286
|
+
<LaneCustomListsEditor
|
|
287
|
+
allCustomLists={allCustomListsData}
|
|
288
|
+
customListIds={onUpdate.mock.calls[0][0]}
|
|
289
|
+
filter=""
|
|
290
|
+
filteredCustomLists={allCustomListsData}
|
|
291
|
+
onUpdate={onUpdate}
|
|
292
|
+
/>
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const nowCurrent = currentLists(container);
|
|
296
|
+
expect(nowCurrent).toHaveLength(1);
|
|
297
|
+
expect(nowCurrent[0]).toHaveTextContent("list 2");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("exposes reset()/getCustomListIds() as the imperative API LaneEditor drives via a ref", () => {
|
|
301
|
+
const onUpdate = jest.fn();
|
|
302
|
+
const ref = React.createRef<LaneCustomListsEditor>();
|
|
303
|
+
|
|
304
|
+
const { rerender } = render(
|
|
305
|
+
<LaneCustomListsEditor
|
|
306
|
+
ref={ref}
|
|
307
|
+
allCustomLists={allCustomListsData}
|
|
308
|
+
customListIds={[2]}
|
|
309
|
+
filter=""
|
|
310
|
+
filteredCustomLists={allCustomListsData}
|
|
311
|
+
onUpdate={onUpdate}
|
|
312
|
+
/>
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
expect(ref.current.getCustomListIds()).toEqual([2]);
|
|
316
|
+
|
|
317
|
+
// reset(ids) notifies the parent with the supplied original ids.
|
|
318
|
+
ref.current.reset([1]);
|
|
319
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
320
|
+
expect(onUpdate).toHaveBeenCalledWith([1]);
|
|
321
|
+
|
|
322
|
+
rerender(
|
|
323
|
+
<LaneCustomListsEditor
|
|
324
|
+
ref={ref}
|
|
325
|
+
allCustomLists={allCustomListsData}
|
|
326
|
+
customListIds={[1]}
|
|
327
|
+
filter=""
|
|
328
|
+
filteredCustomLists={allCustomListsData}
|
|
329
|
+
onUpdate={onUpdate}
|
|
330
|
+
/>
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
expect(ref.current.getCustomListIds()).toEqual([1]);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// The drag handlers (onDragStart/onDragEnd), exercised by calling the captured
|
|
338
|
+
// react-beautiful-dnd callbacks directly.
|
|
339
|
+
describe("LaneCustomListsEditor - drag handlers", () => {
|
|
340
|
+
// Factory rather than a shared const: the component's add()/remove() mutate
|
|
341
|
+
// customListIds in place, so each test needs its own fresh array.
|
|
342
|
+
const makeProps = () => ({
|
|
343
|
+
allCustomLists: allCustomListsData,
|
|
344
|
+
filteredCustomLists: allCustomListsData,
|
|
345
|
+
customListIds: [2],
|
|
346
|
+
onUpdate: undefined as any,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("adds a list when dragged from available to current", () => {
|
|
350
|
+
const onUpdate = jest.fn();
|
|
351
|
+
render(<LaneCustomListsEditor {...makeProps()} onUpdate={onUpdate} />);
|
|
352
|
+
|
|
353
|
+
act(() => {
|
|
354
|
+
dndCallbacks.onDragStart({ source: { droppableId: "available-lists" } });
|
|
355
|
+
dndCallbacks.onDragEnd({
|
|
356
|
+
draggableId: "1",
|
|
357
|
+
source: { droppableId: "available-lists" },
|
|
358
|
+
destination: { droppableId: "current-lists" },
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// list 2 was already selected; dragging list 1 in adds it.
|
|
363
|
+
expect(onUpdate).toHaveBeenCalledWith([2, 1]);
|
|
364
|
+
expect(document.body.classList.contains("dragging")).toBe(false);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("removes a list when dragged from current to available", () => {
|
|
368
|
+
const onUpdate = jest.fn();
|
|
369
|
+
render(<LaneCustomListsEditor {...makeProps()} onUpdate={onUpdate} />);
|
|
370
|
+
|
|
371
|
+
act(() => {
|
|
372
|
+
dndCallbacks.onDragEnd({
|
|
373
|
+
draggableId: 2,
|
|
374
|
+
source: { droppableId: "current-lists" },
|
|
375
|
+
destination: { droppableId: "available-lists" },
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// list 2 was the only selection; dragging it out clears the list.
|
|
380
|
+
expect(onUpdate).toHaveBeenCalledWith([]);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("does nothing when dropped outside a list (no destination)", () => {
|
|
384
|
+
const onUpdate = jest.fn();
|
|
385
|
+
render(<LaneCustomListsEditor {...makeProps()} onUpdate={onUpdate} />);
|
|
386
|
+
|
|
387
|
+
act(() => {
|
|
388
|
+
dndCallbacks.onDragEnd({
|
|
389
|
+
draggableId: "1",
|
|
390
|
+
source: { droppableId: "available-lists" },
|
|
391
|
+
destination: null,
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
expect(onUpdate).not.toHaveBeenCalled();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("disables dropping within the available lists while dragging from available", () => {
|
|
399
|
+
render(<LaneCustomListsEditor {...makeProps()} onUpdate={jest.fn()} />);
|
|
400
|
+
|
|
401
|
+
act(() => {
|
|
402
|
+
dndCallbacks.onDragStart({ source: { droppableId: "available-lists" } });
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
expect(screen.getByTestId("droppable-available-lists")).toHaveAttribute(
|
|
406
|
+
"data-drop-disabled",
|
|
407
|
+
"true"
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("disables dropping within the current lists while dragging from current", () => {
|
|
412
|
+
render(<LaneCustomListsEditor {...makeProps()} onUpdate={jest.fn()} />);
|
|
413
|
+
|
|
414
|
+
act(() => {
|
|
415
|
+
dndCallbacks.onDragStart({ source: { droppableId: "current-lists" } });
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
expect(screen.getByTestId("droppable-current-lists")).toHaveAttribute(
|
|
419
|
+
"data-drop-disabled",
|
|
420
|
+
"true"
|
|
421
|
+
);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("shows a removal message over the available lists while dragging from current, and clears it on drop", () => {
|
|
425
|
+
render(<LaneCustomListsEditor {...makeProps()} onUpdate={jest.fn()} />);
|
|
426
|
+
const available = () => screen.getByTestId("droppable-available-lists");
|
|
427
|
+
const removalMessage = () =>
|
|
428
|
+
screen.queryByText("Drag lists here to remove them from the lane.");
|
|
429
|
+
|
|
430
|
+
// Before any drag: the available lists reject drops and show no remove zone.
|
|
431
|
+
expect(available()).toHaveAttribute("data-drop-disabled", "true");
|
|
432
|
+
expect(removalMessage()).not.toBeInTheDocument();
|
|
433
|
+
|
|
434
|
+
act(() => {
|
|
435
|
+
dndCallbacks.onDragStart({ source: { droppableId: "current-lists" } });
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Dragging from the current lists turns the available column into a remove zone.
|
|
439
|
+
expect(available()).toHaveAttribute("data-drop-disabled", "false");
|
|
440
|
+
expect(removalMessage()).toBeInTheDocument();
|
|
441
|
+
|
|
442
|
+
act(() => {
|
|
443
|
+
dndCallbacks.onDragEnd({ source: { droppableId: "current-lists" } });
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Dropping (even outside a list) resets the remove zone.
|
|
447
|
+
expect(available()).toHaveAttribute("data-drop-disabled", "true");
|
|
448
|
+
expect(removalMessage()).not.toBeInTheDocument();
|
|
449
|
+
});
|
|
450
|
+
});
|