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