03ec1b58e25934ab5e754f8d046795e15eea0711
[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('uses the property editor with vocabulary terms', function () {
79         cy.createCollection(adminUser.token, {
80             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
81             owner_uuid: activeUser.user.uuid,
82             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
83         })
84             .as('testCollection').then(function () {
85                 cy.loginAs(activeUser);
86                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
87
88                 cy.get('[data-cy=collection-info-panel')
89                     .should('contain', this.testCollection.name)
90                     .and('not.contain', 'Color: Magenta')
91                     .and('not.contain', 'Size: S');
92                 cy.get('[data-cy=additional-info-icon]').click();
93
94                 cy.get('[data-cy=details-panel]').within(() => {
95                     cy.get('[data-cy=property-editor-btn]').click();
96                 });
97                 cy.get('[data-cy=resource-properties-dialog').contains('Edit properties');
98
99                 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
100                 cy.get('[data-cy=resource-properties-form]').within(() => {
101                     cy.get('[data-cy=property-field-key]').within(() => {
102                         cy.get('input').type('Color');
103                     });
104                     cy.get('[data-cy=property-field-value]').within(() => {
105                         cy.get('input').type('Magenta');
106                     });
107                     cy.root().submit();
108                 });
109                 // Confirm proper vocabulary labels are displayed on the UI.
110                 cy.get('[data-cy=resource-properties-dialog]')
111                     .should('contain', 'Color: Magenta');
112                 // Confirm proper vocabulary IDs were saved on the backend.
113                 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
114                     .its('body').as('collection')
115                     .then(function () {
116                         expect(this.collection.properties.IDTAGCOLORS).to.equal('IDVALCOLORS3');
117                     });
118
119                 // Case-insensitive on-blur auto-selection test
120                 // Key: Size (IDTAGSIZES) - Value: Small (IDVALSIZES2)
121                 cy.get('[data-cy=resource-properties-form]').within(() => {
122                     cy.get('[data-cy=property-field-key]').within(() => {
123                         cy.get('input').type('sIzE');
124                     });
125                     cy.get('[data-cy=property-field-value]').within(() => {
126                         cy.get('input').type('sMaLL');
127                     });
128                     // Cannot "type()" TAB on Cypress so let's click another field
129                     // to trigger the onBlur event.
130                     cy.get('[data-cy=property-field-key]').click();
131                     cy.root().submit();
132                 });
133                 // Confirm proper vocabulary labels are displayed on the UI.
134                 cy.get('[data-cy=resource-properties-dialog]')
135                     .should('contain', 'Size: S');
136                 // Confirm proper vocabulary IDs were saved on the backend.
137                 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
138                     .its('body').as('collection')
139                     .then(function () {
140                         expect(this.collection.properties.IDTAGSIZES).to.equal('IDVALSIZES2');
141                     });
142
143                 // Close property editor & confirm properties display on the UI.
144                 cy.get('[data-cy=resource-properties-dialog]').within(() => {
145                     cy.get('[data-cy=close-dialog-btn]').click();
146                 });
147                 cy.get('[data-cy=collection-info-panel')
148                     .should('contain', this.testCollection.name)
149                     .and('contain', 'Color: Magenta')
150                     .and('contain', 'Size: S');
151             });
152     });
153
154     it('shows collection by URL', function () {
155         cy.loginAs(activeUser);
156         [true, false].map(function (isWritable) {
157             // Using different file names to avoid test flakyness: the second iteration
158             // on this loop may pass an assertion from the first iteration by looking
159             // for the same file name.
160             const fileName = isWritable ? 'bar' : 'foo';
161             const subDirName = 'subdir';
162             cy.createGroup(adminUser.token, {
163                 name: 'Shared project',
164                 group_class: 'project',
165             }).as('sharedGroup').then(function () {
166                 // Creates the collection using the admin token so we can set up
167                 // a bogus manifest text without block signatures.
168                 cy.doRequest('GET', '/arvados/v1/config', null, null)
169                     .its('body').should((clusterConfig) => {
170                       expect(clusterConfig.Collections, "clusterConfig").to.have.property("TrustAllContent", false);
171                       expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAV").have.property("ExternalURL");
172                       expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAVDownload").have.property("ExternalURL");
173                       const inlineUrl = clusterConfig.Services.WebDAV.ExternalURL !== ""
174                           ? clusterConfig.Services.WebDAV.ExternalURL
175                           : clusterConfig.Services.WebDAVDownload.ExternalURL;
176                       expect(inlineUrl).to.not.contain("*");
177                     })
178                     .createCollection(adminUser.token, {
179                       name: 'Test collection',
180                       owner_uuid: this.sharedGroup.uuid,
181                       properties: { someKey: 'someValue' },
182                       manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n./${subDirName} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`
183                     })
184                     .as('testCollection').then(function () {
185                         // Share the group with active user.
186                         cy.createLink(adminUser.token, {
187                             name: isWritable ? 'can_write' : 'can_read',
188                             link_class: 'permission',
189                             head_uuid: this.sharedGroup.uuid,
190                             tail_uuid: activeUser.user.uuid
191                         })
192                         cy.goToPath(`/collections/${this.testCollection.uuid}`);
193
194                         // Check that name & uuid are correct.
195                         cy.get('[data-cy=collection-info-panel]')
196                             .should('contain', this.testCollection.name)
197                             .and('contain', this.testCollection.uuid)
198                             .and('not.contain', 'This is an old version');
199                         // Check for the read-only icon
200                         cy.get('[data-cy=read-only-icon]').should(`${isWritable ? 'not.' : ''}exist`);
201                         // Check that both read and write operations are available on
202                         // the 'More options' menu.
203                         cy.get('[data-cy=collection-panel-options-btn]')
204                             .click()
205                         cy.get('[data-cy=context-menu]')
206                             .should('contain', 'Add to favorites')
207                             .and(`${isWritable ? '' : 'not.'}contain`, 'Edit collection');
208                         cy.get('body').click(); // Collapse the menu avoiding details panel expansion
209                         cy.get('[data-cy=collection-info-panel]')
210                             .should('contain', 'someKey: someValue')
211                             .and('not.contain', 'anotherKey: anotherValue');
212                         // Check that the file listing show both read & write operations
213                         cy.get('[data-cy=collection-files-panel]').within(() => {
214                             cy.wait(1000);
215                             cy.root().should('contain', fileName);
216                             if (isWritable) {
217                                 cy.get('[data-cy=upload-button]')
218                                     .should(`${isWritable ? '' : 'not.'}contain`, 'Upload data');
219                             }
220                         });
221                         // Test context menus
222                         cy.get('[data-cy=collection-files-panel]')
223                             .contains(fileName).rightclick({ force: true });
224                         cy.get('[data-cy=context-menu]')
225                             .should('contain', 'Download')
226                             .and('not.contain', 'Open in new tab')
227                             .and('contain', 'Copy to clipboard')
228                             .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
229                             .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
230                         cy.get('body').click(); // Collapse the menu
231                         cy.get('[data-cy=collection-files-panel]')
232                             .contains(subDirName).rightclick({ force: true });
233                         cy.get('[data-cy=context-menu]')
234                             .should('not.contain', 'Download')
235                             .and('not.contain', 'Open in new tab')
236                             .and('contain', 'Copy to clipboard')
237                             .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
238                             .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
239                         cy.get('body').click(); // Collapse the menu
240                         // Hamburger 'more options' menu button
241                         cy.get('[data-cy=collection-files-panel-options-btn]')
242                             .click()
243                         cy.get('[data-cy=context-menu]')
244                             .should('contain', 'Select all')
245                             .click()
246                         cy.get('[data-cy=collection-files-panel-options-btn]')
247                             .click()
248                         cy.get('[data-cy=context-menu]')
249                             .should(`${isWritable ? '' : 'not.'}contain`, 'Remove selected')
250                         cy.get('body').click(); // Collapse the menu
251                     })
252             })
253         })
254     })
255
256     it('renames a file using valid names', function () {
257         function eachPair(lst, func){
258             for(var i=0; i < lst.length - 1; i++){
259                 func(lst[i], lst[i + 1])
260             }
261         }
262         // Creates the collection using the admin token so we can set up
263         // a bogus manifest text without block signatures.
264         cy.createCollection(adminUser.token, {
265             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
266             owner_uuid: activeUser.user.uuid,
267             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
268         })
269             .as('testCollection').then(function () {
270                 cy.loginAs(activeUser);
271                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
272
273                 const names = [
274                     'bar', // initial name already set
275                     '&',
276                     'foo',
277                     '&amp;',
278                     'I ❤️ ⛵️',
279                     '...',
280                     '#..',
281                     'some name with whitespaces',
282                     'some name with #2',
283                     'is this name legal? I hope it is',
284                     'some_file.pdf#',
285                     'some_file.pdf?',
286                     '?some_file.pdf',
287                     'some%file.pdf',
288                     'some%2Ffile.pdf',
289                     'some%22file.pdf',
290                     'some%20file.pdf',
291                     "G%C3%BCnter's%20file.pdf",
292                     'table%&?*2',
293                     'bar' // make sure we can go back to the original name as a last step
294                 ];
295                 eachPair(names, (from, to) => {
296                     cy.get('[data-cy=collection-files-panel]')
297                         .contains(`${from}`).rightclick();
298                     cy.get('[data-cy=context-menu]')
299                         .contains('Rename')
300                         .click();
301                     cy.get('[data-cy=form-dialog]')
302                         .should('contain', 'Rename')
303                         .within(() => {
304                             cy.get('input')
305                                 .type('{selectall}{backspace}')
306                                 .type(to, { parseSpecialCharSequences: false });
307                         });
308                     cy.get('[data-cy=form-submit-btn]').click();
309                     cy.get('[data-cy=collection-files-panel]')
310                         .should('not.contain', `${from}`)
311                         .and('contain', `${to}`);
312                 })
313             });
314     });
315
316     it('renames a file to a different directory', function () {
317         // Creates the collection using the admin token so we can set up
318         // a bogus manifest text without block signatures.
319         cy.createCollection(adminUser.token, {
320             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
321             owner_uuid: activeUser.user.uuid,
322             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
323         })
324             .as('testCollection').then(function () {
325                 cy.loginAs(activeUser);
326                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
327
328                 ['subdir', 'G%C3%BCnter\'s%20file', 'table%&?*2'].forEach((subdir) => {
329                     cy.get('[data-cy=collection-files-panel]')
330                         .contains('bar').rightclick({force: true});
331                     cy.get('[data-cy=context-menu]')
332                         .contains('Rename')
333                         .click();
334                     cy.get('[data-cy=form-dialog]')
335                         .should('contain', 'Rename')
336                         .within(() => {
337                             cy.get('input').type(`{selectall}{backspace}${subdir}/foo`);
338                         });
339                     cy.get('[data-cy=form-submit-btn]').click();
340                     cy.get('[data-cy=collection-files-panel]')
341                         .should('not.contain', 'bar')
342                         .and('contain', subdir);
343                     cy.wait(1000);
344                     cy.get('[data-cy=collection-files-panel]').contains(subdir).click();
345                     // Rename 'subdir/foo' to 'foo'
346                     cy.wait(1000);
347                     cy.get('[data-cy=collection-files-panel]')
348                         .contains('foo').rightclick();
349                     cy.get('[data-cy=context-menu]')
350                         .contains('Rename')
351                         .click();
352                     cy.get('[data-cy=form-dialog]')
353                         .should('contain', 'Rename')
354                         .within(() => {
355                             cy.get('input')
356                                 .should('have.value', `${subdir}/foo`)
357                                 .type(`{selectall}{backspace}bar`);
358                         });
359                     cy.get('[data-cy=form-submit-btn]').click();
360
361                     cy.wait(1000);
362                     cy.get('[data-cy=collection-files-panel]')
363                         .contains('Home')
364                         .click();
365
366                     cy.wait(2000);
367                     cy.get('[data-cy=collection-files-panel]')
368                         .should('contain', subdir) // empty dir kept
369                         .and('contain', 'bar');
370
371                     cy.get('[data-cy=collection-files-panel]')
372                         .contains(subdir).rightclick();
373                     cy.get('[data-cy=context-menu]')
374                         .contains('Remove')
375                         .click();
376                     cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
377                 });
378             });
379     });
380
381     it('tries to rename a file with illegal names', function () {
382         // Creates the collection using the admin token so we can set up
383         // a bogus manifest text without block signatures.
384         cy.createCollection(adminUser.token, {
385             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
386             owner_uuid: activeUser.user.uuid,
387             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
388         })
389             .as('testCollection').then(function () {
390                 cy.loginAs(activeUser);
391                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
392
393                 const illegalNamesFromUI = [
394                     ['.', "Name cannot be '.' or '..'"],
395                     ['..', "Name cannot be '.' or '..'"],
396                     ['', 'This field is required'],
397                     [' ', 'Leading/trailing whitespaces not allowed'],
398                     [' foo', 'Leading/trailing whitespaces not allowed'],
399                     ['foo ', 'Leading/trailing whitespaces not allowed'],
400                     ['//foo', 'Empty dir name not allowed']
401                 ]
402                 illegalNamesFromUI.forEach(([name, errMsg]) => {
403                     cy.get('[data-cy=collection-files-panel]')
404                         .contains('bar').rightclick();
405                     cy.get('[data-cy=context-menu]')
406                         .contains('Rename')
407                         .click();
408                     cy.get('[data-cy=form-dialog]')
409                         .should('contain', 'Rename')
410                         .within(() => {
411                             cy.get('input').type(`{selectall}{backspace}${name}`);
412                         });
413                     cy.get('[data-cy=form-dialog]')
414                         .should('contain', 'Rename')
415                         .within(() => {
416                             cy.contains(`${errMsg}`);
417                         });
418                     cy.get('[data-cy=form-cancel-btn]').click();
419                 })
420             });
421     });
422
423     it('can correctly display old versions', function () {
424         const colName = `Versioned Collection ${Math.floor(Math.random() * 999999)}`;
425         let colUuid = '';
426         let oldVersionUuid = '';
427         // Make sure no other collections with this name exist
428         cy.doRequest('GET', '/arvados/v1/collections', null, {
429             filters: `[["name", "=", "${colName}"]]`,
430             include_old_versions: true
431         })
432             .its('body.items').as('collections')
433             .then(function () {
434                 expect(this.collections).to.be.empty;
435             });
436         // Creates the collection using the admin token so we can set up
437         // a bogus manifest text without block signatures.
438         cy.createCollection(adminUser.token, {
439             name: colName,
440             owner_uuid: activeUser.user.uuid,
441             preserve_version: true,
442             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
443         })
444             .as('originalVersion').then(function () {
445                 // Change the file name to create a new version.
446                 cy.updateCollection(adminUser.token, this.originalVersion.uuid, {
447                     manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n"
448                 })
449                 colUuid = this.originalVersion.uuid;
450             });
451         // Confirm that there are 2 versions of the collection
452         cy.doRequest('GET', '/arvados/v1/collections', null, {
453             filters: `[["name", "=", "${colName}"]]`,
454             include_old_versions: true
455         })
456             .its('body.items').as('collections')
457             .then(function () {
458                 expect(this.collections).to.have.lengthOf(2);
459                 this.collections.map(function (aCollection) {
460                     expect(aCollection.current_version_uuid).to.equal(colUuid);
461                     if (aCollection.uuid !== aCollection.current_version_uuid) {
462                         oldVersionUuid = aCollection.uuid;
463                     }
464                 });
465                 // Check the old version displays as what it is.
466                 cy.loginAs(activeUser)
467                 cy.goToPath(`/collections/${oldVersionUuid}`);
468
469                 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
470                 cy.get('[data-cy=read-only-icon]').should('exist');
471                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
472                 cy.get('[data-cy=collection-files-panel]').should('contain', 'bar');
473             });
474     });
475
476     it('views & edits storage classes data', function () {
477         const colName= `Test Collection ${Math.floor(Math.random() * 999999)}`;
478         cy.createCollection(adminUser.token, {
479             name: colName,
480             owner_uuid: activeUser.user.uuid,
481             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
482         }).as('collection').then(function () {
483             expect(this.collection.storage_classes_desired).to.deep.equal(['default'])
484
485             cy.loginAs(activeUser)
486             cy.goToPath(`/collections/${this.collection.uuid}`);
487
488             // Initial check: it should show the 'default' storage class
489             cy.get('[data-cy=collection-info-panel]')
490                 .should('contain', 'Storage classes')
491                 .and('contain', 'default')
492                 .and('not.contain', 'foo')
493                 .and('not.contain', 'bar');
494             // Edit collection: add storage class 'foo'
495             cy.get('[data-cy=collection-panel-options-btn]').click();
496             cy.get('[data-cy=context-menu]').contains('Edit collection').click();
497             cy.get('[data-cy=form-dialog]')
498                 .should('contain', 'Edit Collection')
499                 .and('contain', 'Storage classes')
500                 .and('contain', 'default')
501                 .and('contain', 'foo')
502                 .and('contain', 'bar')
503                 .within(() => {
504                     cy.get('[data-cy=checkbox-foo]').click();
505                 });
506             cy.get('[data-cy=form-submit-btn]').click();
507             cy.get('[data-cy=collection-info-panel]')
508                 .should('contain', 'default')
509                 .and('contain', 'foo')
510                 .and('not.contain', 'bar');
511             cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
512                 .its('body').as('updatedCollection')
513                 .then(function () {
514                     expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['default', 'foo']);
515                 });
516             // Edit collection: remove storage class 'default'
517             cy.get('[data-cy=collection-panel-options-btn]').click();
518             cy.get('[data-cy=context-menu]').contains('Edit collection').click();
519             cy.get('[data-cy=form-dialog]')
520                 .should('contain', 'Edit Collection')
521                 .and('contain', 'Storage classes')
522                 .and('contain', 'default')
523                 .and('contain', 'foo')
524                 .and('contain', 'bar')
525                 .within(() => {
526                     cy.get('[data-cy=checkbox-default]').click();
527                 });
528             cy.get('[data-cy=form-submit-btn]').click();
529             cy.get('[data-cy=collection-info-panel]')
530                 .should('not.contain', 'default')
531                 .and('contain', 'foo')
532                 .and('not.contain', 'bar');
533             cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
534                 .its('body').as('updatedCollection')
535                 .then(function () {
536                     expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['foo']);
537                 });
538         })
539     });
540
541     it('moves a collection to a different project', function () {
542         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
543         const projName = `Test Project ${Math.floor(Math.random() * 999999)}`;
544         const fileName = `Test_File_${Math.floor(Math.random() * 999999)}`;
545
546         cy.createCollection(adminUser.token, {
547             name: collName,
548             owner_uuid: activeUser.user.uuid,
549             manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
550         }).as('testCollection');
551         cy.createGroup(adminUser.token, {
552             name: projName,
553             group_class: 'project',
554             owner_uuid: activeUser.user.uuid,
555         }).as('testProject');
556
557         cy.getAll('@testCollection', '@testProject')
558             .then(function ([testCollection, testProject]) {
559                 cy.loginAs(activeUser);
560                 cy.goToPath(`/collections/${testCollection.uuid}`);
561                 cy.get('[data-cy=collection-files-panel]').should('contain', fileName);
562                 cy.get('[data-cy=collection-info-panel]')
563                     .should('not.contain', projName)
564                     .and('not.contain', testProject.uuid);
565                 cy.get('[data-cy=collection-panel-options-btn]').click();
566                 cy.get('[data-cy=context-menu]').contains('Move to').click();
567                 cy.get('[data-cy=form-dialog]')
568                     .should('contain', 'Move to')
569                     .within(() => {
570                         cy.get('[data-cy=projects-tree-home-tree-picker]')
571                             .find('i')
572                             .click();
573                         cy.get('[data-cy=projects-tree-home-tree-picker]')
574                             .contains(projName)
575                             .click();
576                     });
577                 cy.get('[data-cy=form-submit-btn]').click();
578                 cy.get('[data-cy=snackbar]')
579                     .contains('Collection has been moved')
580                 cy.get('[data-cy=collection-info-panel]')
581                     .contains(projName).and('contain', testProject.uuid);
582                 // Double check that the collection is in the project
583                 cy.goToPath(`/projects/${testProject.uuid}`);
584                 cy.get('[data-cy=project-panel]').should('contain', collName);
585             });
586     });
587
588     it('makes a copy of an existing collection', function() {
589         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
590         const copyName = `Copy of: ${collName}`;
591
592         cy.createCollection(adminUser.token, {
593             name: collName,
594             owner_uuid: activeUser.user.uuid,
595             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
596         }).as('collection').then(function () {
597             cy.loginAs(activeUser)
598             cy.goToPath(`/collections/${this.collection.uuid}`);
599             cy.get('[data-cy=collection-files-panel]')
600                 .should('contain', 'some-file');
601             cy.get('[data-cy=collection-panel-options-btn]').click();
602             cy.get('[data-cy=context-menu]').contains('Make a copy').click();
603             cy.get('[data-cy=form-dialog]')
604                 .should('contain', 'Make a copy')
605                 .within(() => {
606                     cy.get('[data-cy=projects-tree-home-tree-picker]')
607                         .contains('Projects')
608                         .click();
609                     cy.get('[data-cy=form-submit-btn]').click();
610                 });
611             cy.get('[data-cy=snackbar]')
612                 .contains('Collection has been copied.')
613             cy.get('[data-cy=snackbar-goto-action]').click();
614             cy.get('[data-cy=project-panel]')
615                 .contains(copyName).click();
616             cy.get('[data-cy=collection-files-panel]')
617                 .should('contain', 'some-file');
618         });
619     });
620
621     it('uses the collection version browser to view a previous version', function () {
622         const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
623
624         // Creates the collection using the admin token so we can set up
625         // a bogus manifest text without block signatures.
626         cy.createCollection(adminUser.token, {
627             name: colName,
628             owner_uuid: activeUser.user.uuid,
629             preserve_version: true,
630             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
631         })
632             .as('collection').then(function () {
633                 // Visit collection, check basic information
634                 cy.loginAs(activeUser)
635                 cy.goToPath(`/collections/${this.collection.uuid}`);
636
637                 cy.get('[data-cy=collection-info-panel]').should('not.contain', 'This is an old version');
638                 cy.get('[data-cy=read-only-icon]').should('not.exist');
639                 cy.get('[data-cy=collection-version-number]').should('contain', '1');
640                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
641                 cy.get('[data-cy=collection-files-panel]').should('contain', 'foo').and('contain', 'bar');
642
643                 // Modify collection, expect version number change
644                 cy.get('[data-cy=collection-files-panel]').contains('foo').rightclick();
645                 cy.get('[data-cy=context-menu]').contains('Remove').click();
646                 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Removing file');
647                 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
648                 cy.get('[data-cy=collection-version-number]').should('contain', '2');
649                 cy.get('[data-cy=collection-files-panel]').should('not.contain', 'foo').and('contain', 'bar');
650
651                 // Click on version number, check version browser. Click on past version.
652                 cy.get('[data-cy=collection-version-browser]').should('not.exist');
653                 cy.get('[data-cy=collection-version-number]').contains('2').click();
654                 cy.get('[data-cy=collection-version-browser]')
655                     .should('contain', 'Nr').and('contain', 'Size').and('contain', 'Date')
656                     .within(() => {
657                         // Version 1: 6 bytes in size
658                         cy.get('[data-cy=collection-version-browser-select-1]')
659                             .should('contain', '1')
660                             .and('contain', '6 B')
661                             .and('contain', adminUser.user.uuid);
662                         // Version 2: 3 bytes in size (one file removed)
663                         cy.get('[data-cy=collection-version-browser-select-2]')
664                             .should('contain', '2')
665                             .and('contain', '3 B')
666                             .and('contain', activeUser.user.full_name);
667                         cy.get('[data-cy=collection-version-browser-select-3]')
668                             .should('not.exist');
669                         cy.get('[data-cy=collection-version-browser-select-1]')
670                             .click();
671                     });
672                 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
673                 cy.get('[data-cy=read-only-icon]').should('exist');
674                 cy.get('[data-cy=collection-version-number]').should('contain', '1');
675                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
676                 cy.get('[data-cy=collection-files-panel]')
677                     .should('contain', 'foo').and('contain', 'bar');
678
679                 // Check that only old collection action are available on context menu
680                 cy.get('[data-cy=collection-panel-options-btn]').click();
681                 cy.get('[data-cy=context-menu]')
682                     .should('contain', 'Restore version')
683                     .and('not.contain', 'Add to favorites');
684                 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
685
686                 // Click on "head version" link, confirm that it's the latest version.
687                 cy.get('[data-cy=collection-info-panel]').contains('head version').click();
688                 cy.get('[data-cy=collection-info-panel]')
689                     .should('not.contain', 'This is an old version');
690                 cy.get('[data-cy=read-only-icon]').should('not.exist');
691                 cy.get('[data-cy=collection-version-number]').should('contain', '2');
692                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
693                 cy.get('[data-cy=collection-files-panel]').
694                     should('not.contain', 'foo').and('contain', 'bar');
695
696                 // Check that old collection action isn't available on context menu
697                 cy.get('[data-cy=collection-panel-options-btn]').click()
698                 cy.get('[data-cy=context-menu]').should('not.contain', 'Restore version')
699                 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
700
701                 // Make another change, confirm new version.
702                 cy.get('[data-cy=collection-panel-options-btn]').click();
703                 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
704                 cy.get('[data-cy=form-dialog]')
705                     .should('contain', 'Edit Collection')
706                     .within(() => {
707                         // appends some text
708                         cy.get('input').first().type(' renamed');
709                     });
710                 cy.get('[data-cy=form-submit-btn]').click();
711                 cy.get('[data-cy=collection-info-panel]')
712                     .should('not.contain', 'This is an old version');
713                 cy.get('[data-cy=read-only-icon]').should('not.exist');
714                 cy.get('[data-cy=collection-version-number]').should('contain', '3');
715                 cy.get('[data-cy=collection-info-panel]').should('contain', colName + ' renamed');
716                 cy.get('[data-cy=collection-files-panel]')
717                     .should('not.contain', 'foo').and('contain', 'bar');
718                 cy.get('[data-cy=collection-version-browser-select-3]')
719                     .should('contain', '3').and('contain', '3 B');
720
721                 // Check context menus on version browser
722                 cy.get('[data-cy=collection-version-browser-select-3]').rightclick()
723                 cy.get('[data-cy=context-menu]')
724                     .should('contain', 'Add to favorites')
725                     .and('contain', 'Make a copy')
726                     .and('contain', 'Edit collection');
727                 cy.get('body').click();
728                 // (and now an old version...)
729                 cy.get('[data-cy=collection-version-browser-select-1]').rightclick()
730                 cy.get('[data-cy=context-menu]')
731                     .should('not.contain', 'Add to favorites')
732                     .and('contain', 'Make a copy')
733                     .and('not.contain', 'Edit collection');
734                 cy.get('body').click();
735
736                 // Restore first version
737                 cy.get('[data-cy=collection-version-browser]').within(() => {
738                     cy.get('[data-cy=collection-version-browser-select-1]').click();
739                 });
740                 cy.get('[data-cy=collection-panel-options-btn]').click()
741                 cy.get('[data-cy=context-menu]').contains('Restore version').click();
742                 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Restore version');
743                 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
744                 cy.get('[data-cy=collection-info-panel]')
745                     .should('not.contain', 'This is an old version');
746                 cy.get('[data-cy=collection-version-number]').should('contain', '4');
747                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
748                 cy.get('[data-cy=collection-files-panel]')
749                     .should('contain', 'foo').and('contain', 'bar');
750             });
751     });
752
753     it('creates new collection on home project', function () {
754         cy.loginAs(activeUser);
755         cy.goToPath(`/projects/${activeUser.user.uuid}`);
756         cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
757         cy.get('[data-cy=breadcrumb-last]').should('not.exist');
758         // Create new collection
759         cy.get('[data-cy=side-panel-button]').click();
760         cy.get('[data-cy=side-panel-new-collection]').click();
761         // Name between brackets tests bugfix #17582
762         const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
763         cy.get('[data-cy=form-dialog]')
764             .should('contain', 'New collection')
765             .and('contain', 'Storage classes')
766             .and('contain', 'default')
767             .and('contain', 'foo')
768             .and('contain', 'bar')
769             .within(() => {
770                 cy.get('[data-cy=parent-field]').within(() => {
771                     cy.get('input').should('have.value', 'Home project');
772                 });
773                 cy.get('[data-cy=name-field]').within(() => {
774                     cy.get('input').type(collName);
775                 });
776                 cy.get('[data-cy=checkbox-foo]').click();
777             })
778         cy.get('[data-cy=form-submit-btn]').click();
779         // Confirm that the user was taken to the newly created thing
780         cy.get('[data-cy=form-dialog]').should('not.exist');
781         cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
782         cy.get('[data-cy=breadcrumb-last]').should('contain', collName);
783         cy.get('[data-cy=collection-info-panel]')
784             .should('contain', 'default')
785             .and('contain', 'foo')
786             .and('not.contain', 'bar');
787     });
788
789     it('shows responsible person for collection if available', () => {
790         cy.createCollection(adminUser.token, {
791             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
792             owner_uuid: activeUser.user.uuid,
793             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
794         })
795             .as('testCollection1');
796
797         cy.createCollection(adminUser.token, {
798             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
799             owner_uuid: adminUser.user.uuid,
800             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
801         })
802             .as('testCollection2').then(function (testCollection2) {
803                 cy.shareWith(adminUser.token, activeUser.user.uuid, testCollection2.uuid, 'can_write');
804             });
805
806         cy.getAll('@testCollection1', '@testCollection2')
807             .then(function ([testCollection1, testCollection2]) {
808                 cy.loginAs(activeUser);
809
810                 cy.goToPath(`/collections/${testCollection1.uuid}`);
811                 cy.get('[data-cy=responsible-person-wrapper]')
812                     .contains(activeUser.user.uuid);
813
814                 cy.goToPath(`/collections/${testCollection2.uuid}`);
815                 cy.get('[data-cy=responsible-person-wrapper]')
816                     .contains(adminUser.user.uuid);
817             });
818     });
819
820     describe('file upload', () => {
821         beforeEach(() => {
822             cy.createCollection(adminUser.token, {
823                 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
824                 owner_uuid: activeUser.user.uuid,
825                 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
826             })
827                 .as('testCollection1');
828         });
829
830         it('allows to cancel running upload', () => {
831             cy.getAll('@testCollection1')
832                 .then(function([testCollection1]) {
833                     cy.loginAs(activeUser);
834
835                     cy.goToPath(`/collections/${testCollection1.uuid}`);
836
837                     cy.get('[data-cy=upload-button]').click();
838
839                     cy.fixture('files/5mb.bin', 'base64').then(content => {
840                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
841                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
842
843                         cy.get('[data-cy=form-submit-btn]').click();
844
845                         cy.get('button').contains('Cancel').click();
846
847                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
848                     });
849                 });
850         });
851
852         it('allows to cancel single file from the running upload', () => {
853             cy.getAll('@testCollection1')
854                 .then(function([testCollection1]) {
855                     cy.loginAs(activeUser);
856
857                     cy.goToPath(`/collections/${testCollection1.uuid}`);
858
859                     cy.get('[data-cy=upload-button]').click();
860
861                     cy.fixture('files/5mb.bin', 'base64').then(content => {
862                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
863                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
864
865                         cy.get('[data-cy=form-submit-btn]').click();
866
867                         cy.get('button[aria-label=Remove]').eq(1).click();
868
869                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
870
871                         cy.get('[data-cy=collection-files-panel]').contains('5mb_a.bin').should('exist');
872                     });
873                 });
874         });
875
876         it('allows to cancel all files from the running upload', () => {
877             cy.getAll('@testCollection1')
878                 .then(function([testCollection1]) {
879                     cy.loginAs(activeUser);
880
881                     cy.goToPath(`/collections/${testCollection1.uuid}`);
882
883                     cy.get('[data-cy=upload-button]').click();
884
885                     cy.fixture('files/5mb.bin', 'base64').then(content => {
886                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
887                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
888
889                         cy.get('[data-cy=form-submit-btn]').click();
890
891                         cy.get('button[aria-label=Remove]').should('exist');
892                         cy.get('button[aria-label=Remove]').click({ multiple: true, force: true });
893
894                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
895                     });
896                 });
897         });
898     });
899 })