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