15768: all collection specs pass Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox...
[arvados-workbench2.git] / cypress / integration / collection.spec.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 const path = require("path");
6
7 describe("Collection panel tests", function () {
8     let activeUser;
9     let adminUser;
10     let downloadsFolder;
11
12     before(function () {
13         // Only set up common users once. These aren't set up as aliases because
14         // aliases are cleaned up after every test. Also it doesn't make sense
15         // to set the same users on beforeEach() over and over again, so we
16         // separate a little from Cypress' 'Best Practices' here.
17         cy.getUser("admin", "Admin", "User", true, true)
18             .as("adminUser")
19             .then(function () {
20                 adminUser = this.adminUser;
21             });
22         cy.getUser("collectionuser1", "Collection", "User", false, true)
23             .as("activeUser")
24             .then(function () {
25                 activeUser = this.activeUser;
26             });
27         downloadsFolder = Cypress.config("downloadsFolder");
28     });
29
30     beforeEach(function () {
31         cy.clearCookies();
32         cy.clearLocalStorage();
33     });
34
35     it("allows to download mountain duck config for a collection", () => {
36         cy.createCollection(adminUser.token, {
37             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
38             owner_uuid: activeUser.user.uuid,
39             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
40         })
41             .as("testCollection")
42             .then(function (testCollection) {
43                 cy.loginAs(activeUser);
44                 cy.goToPath(`/collections/${testCollection.uuid}`);
45
46                 cy.get("[data-cy=collection-panel-options-btn]").click();
47                 cy.get("[data-cy=context-menu]").contains("Open with 3rd party client").click();
48                 cy.get("[data-cy=download-button").click();
49
50                 const filename = path.join(downloadsFolder, `${testCollection.name}.duck`);
51
52                 cy.readFile(filename, { timeout: 15000 })
53                     .then(body => {
54                         const childrenCollection = Array.prototype.slice.call(Cypress.$(body).find("dict")[0].children);
55                         const map = {};
56                         let i,
57                             j = 2;
58
59                         for (i = 0; i < childrenCollection.length; i += j) {
60                             map[childrenCollection[i].outerText] = childrenCollection[i + 1].outerText;
61                         }
62
63                         cy.get("#simple-tabpanel-0")
64                             .find("a")
65                             .then(a => {
66                                 const [host, port] = a.text().split("@")[1].split("/")[0].split(":");
67                                 expect(map["Protocol"]).to.equal("davs");
68                                 expect(map["UUID"]).to.equal(testCollection.uuid);
69                                 expect(map["Username"]).to.equal(activeUser.user.username);
70                                 expect(map["Port"]).to.equal(port);
71                                 expect(map["Hostname"]).to.equal(host);
72                                 if (map["Path"]) {
73                                     expect(map["Path"]).to.equal(`/c=${testCollection.uuid}`);
74                                 }
75                             });
76                     })
77                     .then(() => cy.task("clearDownload", { filename }));
78             });
79     });
80
81     it("attempts to use a preexisting name creating or updating a collection", function () {
82         const name = `Test collection ${Math.floor(Math.random() * 999999)}`;
83         cy.createCollection(adminUser.token, {
84             name: name,
85             owner_uuid: activeUser.user.uuid,
86             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
87         });
88         cy.loginAs(activeUser);
89         cy.goToPath(`/projects/${activeUser.user.uuid}`);
90         cy.get("[data-cy=breadcrumb-first]").should("contain", "Projects");
91         cy.get("[data-cy=breadcrumb-last]").should("not.exist");
92         // Attempt to create new collection with a duplicate name
93         cy.get("[data-cy=side-panel-button]").click();
94         cy.get("[data-cy=side-panel-new-collection]").click();
95         cy.get("[data-cy=form-dialog]")
96             .should("contain", "New collection")
97             .within(() => {
98                 cy.get("[data-cy=name-field]").within(() => {
99                     cy.get("input").type(name);
100                 });
101                 cy.get("[data-cy=form-submit-btn]").click();
102             });
103         // Error message should display, allowing editing the name
104         cy.get("[data-cy=form-dialog]")
105             .should("exist")
106             .and("contain", "Collection with the same name already exists")
107             .within(() => {
108                 cy.get("[data-cy=name-field]").within(() => {
109                     cy.get("input").type(" renamed");
110                 });
111                 cy.get("[data-cy=form-submit-btn]").click();
112             });
113         cy.get("[data-cy=form-dialog]").should("not.exist");
114         // Attempt to rename the collection with the duplicate name
115         cy.get("[data-cy=collection-panel-options-btn]").click();
116         cy.get("[data-cy=context-menu]").contains("Edit collection").click();
117         cy.get("[data-cy=form-dialog]")
118             .should("contain", "Edit Collection")
119             .within(() => {
120                 cy.get("[data-cy=name-field]").within(() => {
121                     cy.get("input").type("{selectall}{backspace}").type(name);
122                 });
123                 cy.get("[data-cy=form-submit-btn]").click();
124             });
125         cy.get("[data-cy=form-dialog]").should("exist").and("contain", "Collection with the same name already exists");
126     });
127
128     it("uses the property editor (from edit dialog) with vocabulary terms", function () {
129         cy.createCollection(adminUser.token, {
130             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
131             owner_uuid: activeUser.user.uuid,
132             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
133         })
134             .as("testCollection")
135             .then(function () {
136                 cy.loginAs(activeUser);
137                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
138
139                 cy.get("[data-cy=collection-info-panel").should("contain", this.testCollection.name).and("not.contain", "Color: Magenta");
140
141                 cy.get("[data-cy=collection-panel-options-btn]").click();
142                 cy.get("[data-cy=context-menu]").contains("Edit collection").click();
143                 cy.get("[data-cy=form-dialog]").should("contain", "Properties");
144
145                 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
146                 cy.get("[data-cy=resource-properties-form]").within(() => {
147                     cy.get("[data-cy=property-field-key]").within(() => {
148                         cy.get("input").type("Color");
149                     });
150                     cy.get("[data-cy=property-field-value]").within(() => {
151                         cy.get("input").type("Magenta");
152                     });
153                     cy.root().submit();
154                 });
155                 // Confirm proper vocabulary labels are displayed on the UI.
156                 cy.get("[data-cy=form-dialog]").should("contain", "Color: Magenta");
157                 cy.get("[data-cy=form-dialog]").contains("Save").click();
158                 cy.get("[data-cy=form-dialog]").should("not.exist");
159                 // Confirm proper vocabulary IDs were saved on the backend.
160                 cy.doRequest("GET", `/arvados/v1/collections/${this.testCollection.uuid}`)
161                     .its("body")
162                     .as("collection")
163                     .then(function () {
164                         expect(this.collection.properties.IDTAGCOLORS).to.equal("IDVALCOLORS3");
165                     });
166                 // Confirm the property is displayed on the UI.
167                 cy.get("[data-cy=collection-info-panel").should("contain", this.testCollection.name).and("contain", "Color: Magenta");
168             });
169     });
170
171     it("uses the editor (from details panel) with vocabulary terms", function () {
172         cy.createCollection(adminUser.token, {
173             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
174             owner_uuid: activeUser.user.uuid,
175             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
176         })
177             .as("testCollection")
178             .then(function () {
179                 cy.loginAs(activeUser);
180                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
181
182                 cy.get("[data-cy=collection-info-panel")
183                     .should("contain", this.testCollection.name)
184                     .and("not.contain", "Color: Magenta")
185                     .and("not.contain", "Size: S");
186                 cy.get("[data-cy=additional-info-icon]").click();
187
188                 cy.get("[data-cy=details-panel]").within(() => {
189                     cy.get("[data-cy=details-panel-edit-btn]").click();
190                 });
191                 cy.get("[data-cy=form-dialog").contains("Edit Collection");
192
193                 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
194                 cy.get("[data-cy=resource-properties-form]").within(() => {
195                     cy.get("[data-cy=property-field-key]").within(() => {
196                         cy.get("input").type("Color");
197                     });
198                     cy.get("[data-cy=property-field-value]").within(() => {
199                         cy.get("input").type("Magenta");
200                     });
201                     cy.root().submit();
202                 });
203                 // Confirm proper vocabulary labels are displayed on the UI.
204                 cy.get("[data-cy=form-dialog]").should("contain", "Color: Magenta");
205
206                 // Case-insensitive on-blur auto-selection test
207                 // Key: Size (IDTAGSIZES) - Value: Small (IDVALSIZES2)
208                 cy.get("[data-cy=resource-properties-form]").within(() => {
209                     cy.get("[data-cy=property-field-key]").within(() => {
210                         cy.get("input").type("sIzE");
211                     });
212                     cy.get("[data-cy=property-field-value]").within(() => {
213                         cy.get("input").type("sMaLL");
214                     });
215                     // Cannot "type()" TAB on Cypress so let's click another field
216                     // to trigger the onBlur event.
217                     cy.get("[data-cy=property-field-key]").click();
218                     cy.root().submit();
219                 });
220                 // Confirm proper vocabulary labels are displayed on the UI.
221                 cy.get("[data-cy=form-dialog]").should("contain", "Size: S");
222
223                 cy.get("[data-cy=form-dialog]").contains("Save").click();
224                 cy.get("[data-cy=form-dialog]").should("not.exist");
225
226                 // Confirm proper vocabulary IDs were saved on the backend.
227                 cy.doRequest("GET", `/arvados/v1/collections/${this.testCollection.uuid}`)
228                     .its("body")
229                     .as("collection")
230                     .then(function () {
231                         expect(this.collection.properties.IDTAGCOLORS).to.equal("IDVALCOLORS3");
232                         expect(this.collection.properties.IDTAGSIZES).to.equal("IDVALSIZES2");
233                     });
234
235                 // Confirm properties display on the UI.
236                 cy.get("[data-cy=collection-info-panel")
237                     .should("contain", this.testCollection.name)
238                     .and("contain", "Color: Magenta")
239                     .and("contain", "Size: S");
240             });
241     });
242
243     it("shows collection by URL", function () {
244         cy.loginAs(activeUser);
245         [true, false].map(function (isWritable) {
246             // Using different file names to avoid test flakyness: the second iteration
247             // on this loop may pass an assertion from the first iteration by looking
248             // for the same file name.
249             const fileName = isWritable ? "bar" : "foo";
250             const subDirName = "subdir";
251             cy.createGroup(adminUser.token, {
252                 name: "Shared project",
253                 group_class: "project",
254             })
255                 .as("sharedGroup")
256                 .then(function () {
257                     // Creates the collection using the admin token so we can set up
258                     // a bogus manifest text without block signatures.
259                     cy.doRequest("GET", "/arvados/v1/config", null, null)
260                         .its("body")
261                         .should(clusterConfig => {
262                             expect(clusterConfig.Collections, "clusterConfig").to.have.property("TrustAllContent", true);
263                             expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAV").have.property("ExternalURL");
264                             expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAVDownload").have.property("ExternalURL");
265                             const inlineUrl =
266                                 clusterConfig.Services.WebDAV.ExternalURL !== ""
267                                     ? clusterConfig.Services.WebDAV.ExternalURL
268                                     : clusterConfig.Services.WebDAVDownload.ExternalURL;
269                             expect(inlineUrl).to.not.contain("*");
270                         })
271                         .createCollection(adminUser.token, {
272                             name: "Test collection",
273                             owner_uuid: this.sharedGroup.uuid,
274                             properties: { someKey: "someValue" },
275                             manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n./${subDirName} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
276                         })
277                         .as("testCollection")
278                         .then(function () {
279                             // Share the group with active user.
280                             cy.createLink(adminUser.token, {
281                                 name: isWritable ? "can_write" : "can_read",
282                                 link_class: "permission",
283                                 head_uuid: this.sharedGroup.uuid,
284                                 tail_uuid: activeUser.user.uuid,
285                             });
286                             cy.goToPath(`/collections/${this.testCollection.uuid}`);
287
288                             // Check that name & uuid are correct.
289                             cy.get("[data-cy=collection-info-panel]")
290                                 .should("contain", this.testCollection.name)
291                                 .and("contain", this.testCollection.uuid)
292                                 .and("not.contain", "This is an old version");
293                             // Check for the read-only icon
294                             cy.get("[data-cy=read-only-icon]").should(`${isWritable ? "not." : ""}exist`);
295                             // Check that both read and write operations are available on
296                             // the 'More options' menu.
297                             cy.get("[data-cy=collection-panel-options-btn]").click();
298                             cy.get("[data-cy=context-menu]")
299                                 .should("contain", "Add to favorites")
300                                 .and(`${isWritable ? "" : "not."}contain`, "Edit collection");
301                             cy.get("body").click(); // Collapse the menu avoiding details panel expansion
302                             cy.get("[data-cy=collection-info-panel]")
303                                 .should("contain", "someKey: someValue")
304                                 .and("not.contain", "anotherKey: anotherValue");
305                             // Check that the file listing show both read & write operations
306                             cy.waitForDom()
307                                 .get("[data-cy=collection-files-panel]")
308                                 .within(() => {
309                                     cy.get("[data-cy=collection-files-right-panel]", { timeout: 5000 }).should("contain", fileName);
310                                     if (isWritable) {
311                                         cy.get("[data-cy=upload-button]").should(`${isWritable ? "" : "not."}contain`, "Upload data");
312                                     }
313                                 });
314                             // Test context menus
315                             cy.get("[data-cy=collection-files-panel]").contains(fileName).rightclick();
316                             cy.get("[data-cy=context-menu]")
317                                 .should("contain", "Download")
318                                 .and("contain", "Open in new tab")
319                                 .and("contain", "Copy to clipboard")
320                                 .and(`${isWritable ? "" : "not."}contain`, "Rename")
321                                 .and(`${isWritable ? "" : "not."}contain`, "Remove");
322                             cy.get("body").click(); // Collapse the menu
323                             cy.get("[data-cy=collection-files-panel]").contains(subDirName).rightclick();
324                             cy.get("[data-cy=context-menu]")
325                                 .should("not.contain", "Download")
326                                 .and("contain", "Open in new tab")
327                                 .and("contain", "Copy to clipboard")
328                                 .and(`${isWritable ? "" : "not."}contain`, "Rename")
329                                 .and(`${isWritable ? "" : "not."}contain`, "Remove");
330                             cy.get("body").click(); // Collapse the menu
331                             // File/dir item 'more options' button
332                             cy.get("[data-cy=file-item-options-btn").first().click();
333                             cy.get("[data-cy=context-menu]").should(`${isWritable ? "" : "not."}contain`, "Remove");
334                             cy.get("body").click(); // Collapse the menu
335                             // Hamburger 'more options' menu button
336                             cy.get("[data-cy=collection-files-panel-options-btn]").click();
337                             cy.get("[data-cy=context-menu]").should("contain", "Select all").click();
338                             cy.get("[data-cy=collection-files-panel-options-btn]").click();
339                             cy.get("[data-cy=context-menu]").should(`${isWritable ? "" : "not."}contain`, "Remove selected");
340                             cy.get("body").click(); // Collapse the menu
341                         });
342                 });
343         });
344     });
345
346     it("renames a file using valid names", function () {
347         function eachPair(lst, func) {
348             for (var i = 0; i < lst.length - 1; i++) {
349                 func(lst[i], lst[i + 1]);
350             }
351         }
352         // Creates the collection using the admin token so we can set up
353         // a bogus manifest text without block signatures.
354         cy.createCollection(adminUser.token, {
355             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
356             owner_uuid: activeUser.user.uuid,
357             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
358         })
359             .as("testCollection")
360             .then(function () {
361                 cy.loginAs(activeUser);
362                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
363
364                 const names = [
365                     "bar", // initial name already set
366                     "&",
367                     "foo",
368                     "&amp;",
369                     "I ❤️ ⛵️",
370                     "...",
371                     "#..",
372                     "some name with whitespaces",
373                     "some name with #2",
374                     "is this name legal? I hope it is",
375                     "some_file.pdf#",
376                     "some_file.pdf?",
377                     "?some_file.pdf",
378                     "some%file.pdf",
379                     "some%2Ffile.pdf",
380                     "some%22file.pdf",
381                     "some%20file.pdf",
382                     "G%C3%BCnter's%20file.pdf",
383                     "table%&?*2",
384                     "bar", // make sure we can go back to the original name as a last step
385                 ];
386                 eachPair(names, (from, to) => {
387                     cy.waitForDom().get("[data-cy=collection-files-panel]").contains(`${from}`).rightclick();
388                     cy.get("[data-cy=context-menu]").contains("Rename").click();
389                     cy.get("[data-cy=form-dialog]")
390                         .should("contain", "Rename")
391                         .within(() => {
392                             cy.get("input").type("{selectall}{backspace}").type(to, { parseSpecialCharSequences: false });
393                         });
394                     cy.get("[data-cy=form-submit-btn]").click();
395                     cy.get("[data-cy=collection-files-panel]").should("not.contain", `${from}`).and("contain", `${to}`);
396                 });
397             });
398     });
399
400     it("renames a file to a different directory", function () {
401         // Creates the collection using the admin token so we can set up
402         // a bogus manifest text without block signatures.
403         cy.createCollection(adminUser.token, {
404             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
405             owner_uuid: activeUser.user.uuid,
406             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
407         })
408             .as("testCollection")
409             .then(function () {
410                 cy.loginAs(activeUser);
411                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
412
413                 ["subdir", "G%C3%BCnter's%20file", "table%&?*2"].forEach(subdir => {
414                     cy.waitForDom().get("[data-cy=collection-files-panel]").contains("bar").rightclick();
415                     cy.get("[data-cy=context-menu]").contains("Rename").click();
416                     cy.get("[data-cy=form-dialog]")
417                         .should("contain", "Rename")
418                         .within(() => {
419                             cy.get("input").type(`{selectall}{backspace}${subdir}/foo`);
420                         });
421                     cy.get("[data-cy=form-submit-btn]").click();
422                     cy.get("[data-cy=collection-files-panel]").should("not.contain", "bar").and("contain", subdir);
423                     cy.get("[data-cy=collection-files-panel]").contains(subdir).click();
424
425                     // Rename 'subdir/foo' to 'bar'
426                     cy.wait(1000);
427                     cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
428                     cy.get("[data-cy=context-menu]").contains("Rename").click();
429                     cy.get("[data-cy=form-dialog]")
430                         .should("contain", "Rename")
431                         .within(() => {
432                             cy.get("input").should("have.value", `${subdir}/foo`).type(`{selectall}{backspace}bar`);
433                         });
434                     cy.get("[data-cy=form-submit-btn]").click();
435
436                     // need to wait for dialog to dismiss
437                     cy.get("[data-cy=form-dialog]").should("not.exist");
438
439                     cy.waitForDom().get("[data-cy=collection-files-panel]").contains("Home").click();
440
441                     cy.wait(2000);
442                     cy.get("[data-cy=collection-files-panel]")
443                         .should("contain", subdir) // empty dir kept
444                         .and("contain", "bar");
445
446                     cy.get("[data-cy=collection-files-panel]").contains(subdir).rightclick();
447                     cy.get("[data-cy=context-menu]").contains("Remove").click();
448                     cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
449                     cy.get("[data-cy=form-dialog]").should("not.exist");
450                 });
451             });
452     });
453
454     it("shows collection owner", () => {
455         cy.createCollection(adminUser.token, {
456             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
457             owner_uuid: activeUser.user.uuid,
458             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
459         })
460             .as("testCollection")
461             .then(testCollection => {
462                 cy.loginAs(activeUser);
463                 cy.goToPath(`/collections/${testCollection.uuid}`);
464                 cy.wait(5000);
465                 cy.get("[data-cy=collection-info-panel]").contains(`Collection User`);
466             });
467     });
468
469     it("tries to rename a file with illegal names", function () {
470         // Creates the collection using the admin token so we can set up
471         // a bogus manifest text without block signatures.
472         cy.createCollection(adminUser.token, {
473             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
474             owner_uuid: activeUser.user.uuid,
475             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
476         })
477             .as("testCollection")
478             .then(function () {
479                 cy.loginAs(activeUser);
480                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
481
482                 const illegalNamesFromUI = [
483                     [".", "Name cannot be '.' or '..'"],
484                     ["..", "Name cannot be '.' or '..'"],
485                     ["", "This field is required"],
486                     [" ", "Leading/trailing whitespaces not allowed"],
487                     [" foo", "Leading/trailing whitespaces not allowed"],
488                     ["foo ", "Leading/trailing whitespaces not allowed"],
489                     ["//foo", "Empty dir name not allowed"],
490                 ];
491                 illegalNamesFromUI.forEach(([name, errMsg]) => {
492                     cy.get("[data-cy=collection-files-panel]").contains("bar").rightclick();
493                     cy.get("[data-cy=context-menu]").contains("Rename").click();
494                     cy.get("[data-cy=form-dialog]")
495                         .should("contain", "Rename")
496                         .within(() => {
497                             cy.get("input").type(`{selectall}{backspace}${name}`);
498                         });
499                     cy.get("[data-cy=form-dialog]")
500                         .should("contain", "Rename")
501                         .within(() => {
502                             cy.contains(`${errMsg}`);
503                         });
504                     cy.get("[data-cy=form-cancel-btn]").click();
505                 });
506             });
507     });
508
509     it("can correctly display old versions", function () {
510         const colName = `Versioned Collection ${Math.floor(Math.random() * 999999)}`;
511         let colUuid = "";
512         let oldVersionUuid = "";
513         // Make sure no other collections with this name exist
514         cy.doRequest("GET", "/arvados/v1/collections", null, {
515             filters: `[["name", "=", "${colName}"]]`,
516             include_old_versions: true,
517         })
518             .its("body.items")
519             .as("collections")
520             .then(function () {
521                 expect(this.collections).to.be.empty;
522             });
523         // Creates the collection using the admin token so we can set up
524         // a bogus manifest text without block signatures.
525         cy.createCollection(adminUser.token, {
526             name: colName,
527             owner_uuid: activeUser.user.uuid,
528             preserve_version: true,
529             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
530         })
531             .as("originalVersion")
532             .then(function () {
533                 // Change the file name to create a new version.
534                 cy.updateCollection(adminUser.token, this.originalVersion.uuid, {
535                     manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n",
536                 });
537                 colUuid = this.originalVersion.uuid;
538             });
539         // Confirm that there are 2 versions of the collection
540         cy.doRequest("GET", "/arvados/v1/collections", null, {
541             filters: `[["name", "=", "${colName}"]]`,
542             include_old_versions: true,
543         })
544             .its("body.items")
545             .as("collections")
546             .then(function () {
547                 expect(this.collections).to.have.lengthOf(2);
548                 this.collections.map(function (aCollection) {
549                     expect(aCollection.current_version_uuid).to.equal(colUuid);
550                     if (aCollection.uuid !== aCollection.current_version_uuid) {
551                         oldVersionUuid = aCollection.uuid;
552                     }
553                 });
554                 // Check the old version displays as what it is.
555                 cy.loginAs(activeUser);
556                 cy.goToPath(`/collections/${oldVersionUuid}`);
557
558                 cy.get("[data-cy=collection-info-panel]").should("contain", "This is an old version");
559                 cy.get("[data-cy=read-only-icon]").should("exist");
560                 cy.get("[data-cy=collection-info-panel]").should("contain", colName);
561                 cy.get("[data-cy=collection-files-panel]").should("contain", "bar");
562             });
563     });
564
565     it("views & edits storage classes data", function () {
566         const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
567         cy.createCollection(adminUser.token, {
568             name: colName,
569             owner_uuid: activeUser.user.uuid,
570             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
571         })
572             .as("collection")
573             .then(function () {
574                 expect(this.collection.storage_classes_desired).to.deep.equal(["default"]);
575
576                 cy.loginAs(activeUser);
577                 cy.goToPath(`/collections/${this.collection.uuid}`);
578
579                 // Initial check: it should show the 'default' storage class
580                 cy.get("[data-cy=collection-info-panel]")
581                     .should("contain", "Storage classes")
582                     .and("contain", "default")
583                     .and("not.contain", "foo")
584                     .and("not.contain", "bar");
585                 // Edit collection: add storage class 'foo'
586                 cy.get("[data-cy=collection-panel-options-btn]").click();
587                 cy.get("[data-cy=context-menu]").contains("Edit collection").click();
588                 cy.get("[data-cy=form-dialog]")
589                     .should("contain", "Edit Collection")
590                     .and("contain", "Storage classes")
591                     .and("contain", "default")
592                     .and("contain", "foo")
593                     .and("contain", "bar")
594                     .within(() => {
595                         cy.get("[data-cy=checkbox-foo]").click();
596                     });
597                 cy.get("[data-cy=form-submit-btn]").click();
598                 cy.get("[data-cy=collection-info-panel]").should("contain", "default").and("contain", "foo").and("not.contain", "bar");
599                 cy.doRequest("GET", `/arvados/v1/collections/${this.collection.uuid}`)
600                     .its("body")
601                     .as("updatedCollection")
602                     .then(function () {
603                         expect(this.updatedCollection.storage_classes_desired).to.deep.equal(["default", "foo"]);
604                     });
605                 // Edit collection: remove storage class 'default'
606                 cy.get("[data-cy=collection-panel-options-btn]").click();
607                 cy.get("[data-cy=context-menu]").contains("Edit collection").click();
608                 cy.get("[data-cy=form-dialog]")
609                     .should("contain", "Edit Collection")
610                     .and("contain", "Storage classes")
611                     .and("contain", "default")
612                     .and("contain", "foo")
613                     .and("contain", "bar")
614                     .within(() => {
615                         cy.get("[data-cy=checkbox-default]").click();
616                     });
617                 cy.get("[data-cy=form-submit-btn]").click();
618                 cy.get("[data-cy=collection-info-panel]").should("not.contain", "default").and("contain", "foo").and("not.contain", "bar");
619                 cy.doRequest("GET", `/arvados/v1/collections/${this.collection.uuid}`)
620                     .its("body")
621                     .as("updatedCollection")
622                     .then(function () {
623                         expect(this.updatedCollection.storage_classes_desired).to.deep.equal(["foo"]);
624                     });
625             });
626     });
627
628     it("moves a collection to a different project", function () {
629         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
630         const projName = `Test Project ${Math.floor(Math.random() * 999999)}`;
631         const fileName = `Test_File_${Math.floor(Math.random() * 999999)}`;
632
633         cy.createCollection(adminUser.token, {
634             name: collName,
635             owner_uuid: activeUser.user.uuid,
636             manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
637         }).as("testCollection");
638         cy.createGroup(adminUser.token, {
639             name: projName,
640             group_class: "project",
641             owner_uuid: activeUser.user.uuid,
642         }).as("testProject");
643
644         cy.getAll("@testCollection", "@testProject").then(function ([testCollection, testProject]) {
645             cy.loginAs(activeUser);
646             cy.goToPath(`/collections/${testCollection.uuid}`);
647             cy.get("[data-cy=collection-files-panel]").should("contain", fileName);
648             cy.get("[data-cy=collection-info-panel]").should("not.contain", projName).and("not.contain", testProject.uuid);
649             cy.get("[data-cy=collection-panel-options-btn]").click();
650             cy.get("[data-cy=context-menu]").contains("Move to").click();
651             cy.get("[data-cy=form-dialog]")
652                 .should("contain", "Move to")
653                 .within(() => {
654                     // must use .then to avoid selecting instead of expanding https://github.com/cypress-io/cypress/issues/5529
655                     cy.get("[data-cy=projects-tree-home-tree-picker]")
656                         .find("i")
657                         .then(el => el.click());
658                     cy.get("[data-cy=projects-tree-home-tree-picker]").contains(projName).click();
659                 });
660             cy.get("[data-cy=form-submit-btn]").click();
661             cy.get("[data-cy=snackbar]").contains("Collection has been moved");
662             cy.get("[data-cy=collection-info-panel]").contains(projName).and("contain", testProject.uuid);
663             // Double check that the collection is in the project
664             cy.goToPath(`/projects/${testProject.uuid}`);
665             cy.waitForDom().get("[data-cy=project-panel]").should("contain", collName);
666         });
667     });
668
669     it("automatically updates the collection UI contents without using the Refresh button", function () {
670         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
671
672         cy.createCollection(adminUser.token, {
673             name: collName,
674             owner_uuid: activeUser.user.uuid,
675         }).as("testCollection");
676
677         cy.getAll("@testCollection").then(function ([testCollection]) {
678             cy.loginAs(activeUser);
679
680             const files = ["foobar", "anotherFile", "", "finalName"];
681
682             cy.goToPath(`/collections/${testCollection.uuid}`);
683             cy.get("[data-cy=collection-files-panel]").should("contain", "This collection is empty");
684             cy.get("[data-cy=collection-files-panel]").should("not.contain", files[0]);
685             cy.get("[data-cy=collection-info-panel]").should("contain", collName);
686
687             files.map((fileName, i, files) => {
688                 cy.updateCollection(adminUser.token, testCollection.uuid, {
689                     name: `${collName + " updated"}`,
690                     manifest_text: fileName ? `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n` : "",
691                 }).as("updatedCollection");
692                 cy.getAll("@updatedCollection").then(function ([updatedCollection]) {
693                     expect(updatedCollection.name).to.equal(`${collName + " updated"}`);
694                     cy.get("[data-cy=collection-info-panel]").should("contain", updatedCollection.name);
695                     fileName
696                         ? cy.get("[data-cy=collection-files-panel]").should("contain", fileName)
697                         : cy.get("[data-cy=collection-files-panel]").should("not.contain", files[i - 1]);
698                 });
699             });
700         });
701     });
702
703     it("makes a copy of an existing collection", function () {
704         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
705         const copyName = `Copy of: ${collName}`;
706
707         cy.createCollection(adminUser.token, {
708             name: collName,
709             owner_uuid: activeUser.user.uuid,
710             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
711         })
712             .as("collection")
713             .then(function () {
714                 cy.loginAs(activeUser);
715                 cy.goToPath(`/collections/${this.collection.uuid}`);
716                 cy.get("[data-cy=collection-files-panel]").should("contain", "some-file");
717                 cy.get("[data-cy=collection-panel-options-btn]").click();
718                 cy.get("[data-cy=context-menu]").contains("Make a copy").click();
719                 cy.get("[data-cy=form-dialog]")
720                     .should("contain", "Make a copy")
721                     .within(() => {
722                         cy.get("[data-cy=projects-tree-home-tree-picker]").contains("Projects").click();
723                         cy.get("[data-cy=form-submit-btn]").click();
724                     });
725                 cy.get("[data-cy=snackbar]").contains("Collection has been copied.");
726                 cy.get("[data-cy=snackbar-goto-action]").click();
727                 cy.get("[data-cy=project-panel]").contains(copyName).click();
728                 cy.get("[data-cy=collection-files-panel]").should("contain", "some-file");
729             });
730     });
731
732     it("uses the collection version browser to view a previous version", function () {
733         const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
734
735         // Creates the collection using the admin token so we can set up
736         // a bogus manifest text without block signatures.
737         cy.createCollection(adminUser.token, {
738             name: colName,
739             owner_uuid: activeUser.user.uuid,
740             preserve_version: true,
741             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
742         })
743             .as("collection")
744             .then(function () {
745                 // Visit collection, check basic information
746                 cy.loginAs(activeUser);
747                 cy.goToPath(`/collections/${this.collection.uuid}`);
748
749                 cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
750                 cy.get("[data-cy=read-only-icon]").should("not.exist");
751                 cy.get("[data-cy=collection-version-number]").should("contain", "1");
752                 cy.get("[data-cy=collection-info-panel]").should("contain", colName);
753                 cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
754
755                 // Modify collection, expect version number change
756                 cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
757                 cy.get("[data-cy=context-menu]").contains("Remove").click();
758                 cy.get("[data-cy=confirmation-dialog]").should("contain", "Removing file");
759                 cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
760                 cy.get("[data-cy=collection-version-number]").should("contain", "2");
761                 cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
762
763                 // Click on version number, check version browser. Click on past version.
764                 cy.get("[data-cy=collection-version-browser]").should("not.exist");
765                 cy.get("[data-cy=collection-version-number]").contains("2").click();
766                 cy.get("[data-cy=collection-version-browser]")
767                     .should("contain", "Nr")
768                     .and("contain", "Size")
769                     .and("contain", "Date")
770                     .within(() => {
771                         // Version 1: 6 bytes in size
772                         cy.get("[data-cy=collection-version-browser-select-1]")
773                             .should("contain", "1")
774                             .and("contain", "6 B")
775                             .and("contain", adminUser.user.full_name);
776                         // Version 2: 3 bytes in size (one file removed)
777                         cy.get("[data-cy=collection-version-browser-select-2]")
778                             .should("contain", "2")
779                             .and("contain", "3 B")
780                             .and("contain", activeUser.user.full_name);
781                         cy.get("[data-cy=collection-version-browser-select-3]").should("not.exist");
782                         cy.get("[data-cy=collection-version-browser-select-1]").click();
783                     });
784                 cy.get("[data-cy=collection-info-panel]").should("contain", "This is an old version");
785                 cy.get("[data-cy=read-only-icon]").should("exist");
786                 cy.get("[data-cy=collection-version-number]").should("contain", "1");
787                 cy.get("[data-cy=collection-info-panel]").should("contain", colName);
788                 cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
789
790                 // Check that only old collection action are available on context menu
791                 cy.get("[data-cy=collection-panel-options-btn]").click();
792                 cy.get("[data-cy=context-menu]").should("contain", "Restore version").and("not.contain", "Add to favorites");
793                 cy.get("body").click(); // Collapse the menu avoiding details panel expansion
794
795                 // Click on "head version" link, confirm that it's the latest version.
796                 cy.get("[data-cy=collection-info-panel]").contains("head version").click();
797                 cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
798                 cy.get("[data-cy=read-only-icon]").should("not.exist");
799                 cy.get("[data-cy=collection-version-number]").should("contain", "2");
800                 cy.get("[data-cy=collection-info-panel]").should("contain", colName);
801                 cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
802
803                 // Check that old collection action isn't available on context menu
804                 cy.get("[data-cy=collection-panel-options-btn]").click();
805                 cy.get("[data-cy=context-menu]").should("not.contain", "Restore version");
806                 cy.get("body").click(); // Collapse the menu avoiding details panel expansion
807
808                 // Make another change, confirm new version.
809                 cy.get("[data-cy=collection-panel-options-btn]").click();
810                 cy.get("[data-cy=context-menu]").contains("Edit collection").click();
811                 cy.get("[data-cy=form-dialog]")
812                     .should("contain", "Edit Collection")
813                     .within(() => {
814                         // appends some text
815                         cy.get("input").first().type(" renamed");
816                     });
817                 cy.get("[data-cy=form-submit-btn]").click();
818                 cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
819                 cy.get("[data-cy=read-only-icon]").should("not.exist");
820                 cy.get("[data-cy=collection-version-number]").should("contain", "3");
821                 cy.get("[data-cy=collection-info-panel]").should("contain", colName + " renamed");
822                 cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
823                 cy.get("[data-cy=collection-version-browser-select-3]").should("contain", "3").and("contain", "3 B");
824
825                 // Check context menus on version browser
826                 cy.get("[data-cy=collection-version-browser-select-3]").rightclick({ force: true });
827                 cy.get("[data-cy=context-menu]")
828                     .should("contain", "Add to favorites")
829                     .and("contain", "Make a copy")
830                     .and("contain", "Edit collection");
831                 cy.get("body").click();
832                 // (and now an old version...)
833                 cy.get("[data-cy=collection-version-browser-select-1]").rightclick();
834                 cy.get("[data-cy=context-menu]")
835                     .should("not.contain", "Add to favorites")
836                     .and("contain", "Make a copy")
837                     .and("not.contain", "Edit collection");
838                 cy.get("body").click();
839
840                 // Restore first version
841                 cy.get("[data-cy=collection-version-browser]").within(() => {
842                     cy.get("[data-cy=collection-version-browser-select-1]").click();
843                 });
844                 cy.get("[data-cy=collection-panel-options-btn]").click();
845                 cy.get("[data-cy=context-menu]").contains("Restore version").click();
846                 cy.get("[data-cy=confirmation-dialog]").should("contain", "Restore version");
847                 cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
848                 cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
849                 cy.get("[data-cy=collection-version-number]").should("contain", "4");
850                 cy.get("[data-cy=collection-info-panel]").should("contain", colName);
851                 cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
852             });
853     });
854
855     it("creates collection from selected files of another collection", () => {
856         cy.createCollection(adminUser.token, {
857             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
858             owner_uuid: activeUser.user.uuid,
859             preserve_version: true,
860             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
861         })
862             .as("collection")
863             .then(function () {
864                 // Visit collection, check basic information
865                 cy.loginAs(activeUser);
866                 cy.goToPath(`/collections/${this.collection.uuid}`);
867
868                 cy.get("[data-cy=collection-files-panel]").within(() => {
869                     cy.get("input[type=checkbox]").first().click();
870                 });
871
872                 cy.get("[data-cy=collection-files-panel-options-btn]").click();
873                 cy.get("[data-cy=context-menu]").contains("Create a new collection with selected").click();
874
875                 cy.get("[data-cy=form-dialog]").contains("Projects").click();
876
877                 cy.get("[data-cy=form-submit-btn]").click();
878
879                 cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
880
881                 cy.get("main").contains(`Files extracted from: ${this.collection.name}`).should("exist");
882             });
883     });
884
885     it("creates new collection with properties on home project", function () {
886         cy.loginAs(activeUser);
887         cy.goToPath(`/projects/${activeUser.user.uuid}`);
888         cy.get("[data-cy=breadcrumb-first]").should("contain", "Projects");
889         cy.get("[data-cy=breadcrumb-last]").should("not.exist");
890         // Create new collection
891         cy.get("[data-cy=side-panel-button]").click();
892         cy.get("[data-cy=side-panel-new-collection]").click();
893         // Name between brackets tests bugfix #17582
894         const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
895
896         // Select a storage class.
897         cy.get("[data-cy=form-dialog]")
898             .should("contain", "New collection")
899             .and("contain", "Storage classes")
900             .and("contain", "default")
901             .and("contain", "foo")
902             .and("contain", "bar")
903             .within(() => {
904                 cy.get("[data-cy=parent-field]").within(() => {
905                     cy.get("input").should("have.value", "Home project");
906                 });
907                 cy.get("[data-cy=name-field]").within(() => {
908                     cy.get("input").type(collName);
909                 });
910                 cy.get("[data-cy=checkbox-foo]").click();
911             });
912
913         // Add a property.
914         // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
915         cy.get("[data-cy=form-dialog]").should("not.contain", "Color: Magenta");
916         cy.get("[data-cy=resource-properties-form]").within(() => {
917             cy.get("[data-cy=property-field-key]").within(() => {
918                 cy.get("input").type("Color");
919             });
920             cy.get("[data-cy=property-field-value]").within(() => {
921                 cy.get("input").type("Magenta");
922             });
923             cy.root().submit();
924         });
925         // Confirm proper vocabulary labels are displayed on the UI.
926         cy.get("[data-cy=form-dialog]").should("contain", "Color: Magenta");
927
928         // Value field should not complain about being required just after
929         // adding a new property. See #19732
930         cy.get("[data-cy=form-dialog]").should("not.contain", "This field is required");
931
932         cy.get("[data-cy=form-submit-btn]").click();
933         // Confirm that the user was taken to the newly created collection
934         cy.get("[data-cy=form-dialog]").should("not.exist");
935         cy.get("[data-cy=breadcrumb-first]").should("contain", "Projects");
936         cy.get("[data-cy=breadcrumb-last]").should("contain", collName);
937         cy.get("[data-cy=collection-info-panel]")
938             .should("contain", "default")
939             .and("contain", "foo")
940             .and("contain", "Color: Magenta")
941             .and("not.contain", "bar");
942         // Confirm that the collection's properties has the real values.
943         cy.doRequest("GET", "/arvados/v1/collections", null, {
944             filters: `[["name", "=", "${collName}"]]`,
945         })
946             .its("body.items")
947             .as("collections")
948             .then(function () {
949                 expect(this.collections).to.have.lengthOf(1);
950                 expect(this.collections[0].properties).to.have.property("IDTAGCOLORS", "IDVALCOLORS3");
951             });
952     });
953
954     it("shows responsible person for collection if available", () => {
955         cy.createCollection(adminUser.token, {
956             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
957             owner_uuid: activeUser.user.uuid,
958             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
959         }).as("testCollection1");
960
961         cy.createCollection(adminUser.token, {
962             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
963             owner_uuid: adminUser.user.uuid,
964             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
965         })
966             .as("testCollection2")
967             .then(function (testCollection2) {
968                 cy.shareWith(adminUser.token, activeUser.user.uuid, testCollection2.uuid, "can_write");
969             });
970
971         cy.getAll("@testCollection1", "@testCollection2").then(function ([testCollection1, testCollection2]) {
972             cy.loginAs(activeUser);
973
974             cy.goToPath(`/collections/${testCollection1.uuid}`);
975             cy.get("[data-cy=responsible-person-wrapper]").contains(activeUser.user.uuid);
976
977             cy.goToPath(`/collections/${testCollection2.uuid}`);
978             cy.get("[data-cy=responsible-person-wrapper]").contains(adminUser.user.uuid);
979         });
980     });
981
982     describe("file upload", () => {
983         beforeEach(() => {
984             cy.createCollection(adminUser.token, {
985                 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
986                 owner_uuid: activeUser.user.uuid,
987                 manifest_text: "./subdir 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
988             }).as("testCollection1");
989         });
990
991         it("uploads a file and checks the collection UI to be fresh", () => {
992             cy.getAll("@testCollection1").then(function ([testCollection1]) {
993                 cy.loginAs(activeUser);
994                 cy.goToPath(`/collections/${testCollection1.uuid}`);
995                 cy.get("[data-cy=upload-button]").click();
996                 cy.get("[data-cy=collection-files-panel]").contains("5mb_a.bin").should("not.exist");
997                 cy.get("[data-cy=collection-file-count]").should("contain", "2");
998                 cy.fixture("files/5mb.bin", "base64").then(content => {
999                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_a.bin");
1000                     cy.get("[data-cy=form-submit-btn]").click();
1001                     cy.get("[data-cy=form-submit-btn]").should("not.exist");
1002                     cy.get("[data-cy=collection-files-panel]").contains("5mb_a.bin").should("exist");
1003                     cy.get("[data-cy=collection-file-count]").should("contain", "3");
1004
1005                     cy.get("[data-cy=collection-files-panel]").contains("subdir").click();
1006                     cy.get("[data-cy=upload-button]").click();
1007                     cy.fixture("files/5mb.bin", "base64").then(content => {
1008                         cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_b.bin");
1009                         cy.get("[data-cy=form-submit-btn]").click();
1010                         cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
1011                         // subdir gets unselected, I think this is a bug but
1012                         // for the time being let's just make sure the test works.
1013                         cy.get("[data-cy=collection-files-panel]").contains("subdir").click();
1014                         cy.waitForDom({ ficre: true }).get("[data-cy=collection-files-right-panel]").contains("5mb_b.bin").should("exist");
1015                     });
1016                 });
1017             });
1018         });
1019
1020         it("allows to cancel running upload", () => {
1021             cy.getAll("@testCollection1").then(function ([testCollection1]) {
1022                 cy.loginAs(activeUser);
1023
1024                 cy.goToPath(`/collections/${testCollection1.uuid}`);
1025
1026                 cy.get("[data-cy=upload-button]").click();
1027
1028                 cy.fixture("files/5mb.bin", "base64").then(content => {
1029                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_a.bin");
1030                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_b.bin");
1031
1032                     cy.get("[data-cy=form-submit-btn]").click();
1033
1034                     cy.get("button").contains("Cancel").click();
1035
1036                     cy.get("[data-cy=form-submit-btn]").should("not.exist");
1037                 });
1038             });
1039         });
1040
1041         it("allows to cancel single file from the running upload", () => {
1042             cy.getAll("@testCollection1").then(function ([testCollection1]) {
1043                 cy.loginAs(activeUser);
1044
1045                 cy.goToPath(`/collections/${testCollection1.uuid}`);
1046
1047                 cy.get("[data-cy=upload-button]").click();
1048
1049                 cy.fixture("files/5mb.bin", "base64").then(content => {
1050                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_a.bin");
1051                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_b.bin");
1052
1053                     cy.get("[data-cy=form-submit-btn]").click();
1054
1055                     cy.get("button[aria-label=Remove]").eq(1).click();
1056
1057                     cy.get("[data-cy=form-submit-btn]").should("not.exist");
1058
1059                     cy.get("[data-cy=collection-files-panel]").contains("5mb_a.bin").should("exist");
1060                 });
1061             });
1062         });
1063
1064         it("allows to cancel all files from the running upload", () => {
1065             cy.getAll("@testCollection1").then(function ([testCollection1]) {
1066                 cy.loginAs(activeUser);
1067
1068                 cy.goToPath(`/collections/${testCollection1.uuid}`);
1069
1070                 // Confirm initial collection state.
1071                 cy.get("[data-cy=collection-files-panel]").contains("bar").should("exist");
1072                 cy.get("[data-cy=collection-files-panel]").contains("5mb_a.bin").should("not.exist");
1073                 cy.get("[data-cy=collection-files-panel]").contains("5mb_b.bin").should("not.exist");
1074
1075                 cy.get("[data-cy=upload-button]").click();
1076
1077                 cy.fixture("files/5mb.bin", "base64").then(content => {
1078                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_a.bin");
1079                     cy.get("[data-cy=drag-and-drop]").upload(content, "5mb_b.bin");
1080
1081                     cy.get("[data-cy=form-submit-btn]").click();
1082
1083                     cy.get("button[aria-label=Remove]").should("exist");
1084                     cy.get("button[aria-label=Remove]").click({ multiple: true, force: true });
1085
1086                     cy.get("[data-cy=form-submit-btn]").should("not.exist");
1087
1088                     // Confirm final collection state.
1089                     cy.get("[data-cy=collection-files-panel]").contains("bar").should("exist");
1090                     // The following fails, but doesn't seem to happen
1091                     // in the real world. Maybe there's a race between
1092                     // the PUT request finishing and the 'Remove' button
1093                     // dissapearing, because sometimes just one of the 2
1094                     // files gets uploaded.
1095                     // Maybe this will be needed to simulate a slow network:
1096                     // https://docs.cypress.io/api/commands/intercept#Convenience-functions-1
1097                     // cy.get('[data-cy=collection-files-panel]')
1098                     //     .contains('5mb_a.bin').should('not.exist');
1099                     // cy.get('[data-cy=collection-files-panel]')
1100                     //     .contains('5mb_b.bin').should('not.exist');
1101                 });
1102             });
1103         });
1104     });
1105 });