efde53e5e87f1762695cb8bfe6a6192222a4091e
[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                     cy.get('[data-cy=collection-files-panel]')
459                         .contains('Home')
460                         .click();
461
462                     cy.wait(2000);
463                     cy.get('[data-cy=collection-files-panel]')
464                         .should('contain', subdir) // empty dir kept
465                         .and('contain', 'bar');
466
467                     cy.get('[data-cy=collection-files-panel]')
468                         .contains(subdir).rightclick();
469                     cy.get('[data-cy=context-menu]')
470                         .contains('Remove')
471                         .click();
472                     cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
473                 });
474             });
475     });
476
477     it('shows collection owner', () => {
478         cy.createCollection(adminUser.token, {
479             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
480             owner_uuid: activeUser.user.uuid,
481             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
482         })
483             .as('testCollection').then((testCollection) => {
484                 cy.loginAs(activeUser);
485                 cy.goToPath(`/collections/${testCollection.uuid}`);
486                 cy.wait(5000);
487                 cy.get('[data-cy=collection-info-panel]').contains(`Collection User`);
488             });
489     });
490
491     it('tries to rename a file with illegal names', function () {
492         // Creates the collection using the admin token so we can set up
493         // a bogus manifest text without block signatures.
494         cy.createCollection(adminUser.token, {
495             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
496             owner_uuid: activeUser.user.uuid,
497             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
498         })
499             .as('testCollection').then(function () {
500                 cy.loginAs(activeUser);
501                 cy.goToPath(`/collections/${this.testCollection.uuid}`);
502
503                 const illegalNamesFromUI = [
504                     ['.', "Name cannot be '.' or '..'"],
505                     ['..', "Name cannot be '.' or '..'"],
506                     ['', 'This field is required'],
507                     [' ', 'Leading/trailing whitespaces not allowed'],
508                     [' foo', 'Leading/trailing whitespaces not allowed'],
509                     ['foo ', 'Leading/trailing whitespaces not allowed'],
510                     ['//foo', 'Empty dir name not allowed']
511                 ]
512                 illegalNamesFromUI.forEach(([name, errMsg]) => {
513                     cy.get('[data-cy=collection-files-panel]')
514                         .contains('bar').rightclick();
515                     cy.get('[data-cy=context-menu]')
516                         .contains('Rename')
517                         .click();
518                     cy.get('[data-cy=form-dialog]')
519                         .should('contain', 'Rename')
520                         .within(() => {
521                             cy.get('input').type(`{selectall}{backspace}${name}`);
522                         });
523                     cy.get('[data-cy=form-dialog]')
524                         .should('contain', 'Rename')
525                         .within(() => {
526                             cy.contains(`${errMsg}`);
527                         });
528                     cy.get('[data-cy=form-cancel-btn]').click();
529                 })
530             });
531     });
532
533     it('can correctly display old versions', function () {
534         const colName = `Versioned Collection ${Math.floor(Math.random() * 999999)}`;
535         let colUuid = '';
536         let oldVersionUuid = '';
537         // Make sure no other collections with this name exist
538         cy.doRequest('GET', '/arvados/v1/collections', null, {
539             filters: `[["name", "=", "${colName}"]]`,
540             include_old_versions: true
541         })
542             .its('body.items').as('collections')
543             .then(function () {
544                 expect(this.collections).to.be.empty;
545             });
546         // Creates the collection using the admin token so we can set up
547         // a bogus manifest text without block signatures.
548         cy.createCollection(adminUser.token, {
549             name: colName,
550             owner_uuid: activeUser.user.uuid,
551             preserve_version: true,
552             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
553         })
554             .as('originalVersion').then(function () {
555                 // Change the file name to create a new version.
556                 cy.updateCollection(adminUser.token, this.originalVersion.uuid, {
557                     manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n"
558                 })
559                 colUuid = this.originalVersion.uuid;
560             });
561         // Confirm that there are 2 versions of the collection
562         cy.doRequest('GET', '/arvados/v1/collections', null, {
563             filters: `[["name", "=", "${colName}"]]`,
564             include_old_versions: true
565         })
566             .its('body.items').as('collections')
567             .then(function () {
568                 expect(this.collections).to.have.lengthOf(2);
569                 this.collections.map(function (aCollection) {
570                     expect(aCollection.current_version_uuid).to.equal(colUuid);
571                     if (aCollection.uuid !== aCollection.current_version_uuid) {
572                         oldVersionUuid = aCollection.uuid;
573                     }
574                 });
575                 // Check the old version displays as what it is.
576                 cy.loginAs(activeUser)
577                 cy.goToPath(`/collections/${oldVersionUuid}`);
578
579                 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
580                 cy.get('[data-cy=read-only-icon]').should('exist');
581                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
582                 cy.get('[data-cy=collection-files-panel]').should('contain', 'bar');
583             });
584     });
585
586     it('views & edits storage classes data', function () {
587         const colName= `Test Collection ${Math.floor(Math.random() * 999999)}`;
588         cy.createCollection(adminUser.token, {
589             name: colName,
590             owner_uuid: activeUser.user.uuid,
591             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
592         }).as('collection').then(function () {
593             expect(this.collection.storage_classes_desired).to.deep.equal(['default'])
594
595             cy.loginAs(activeUser)
596             cy.goToPath(`/collections/${this.collection.uuid}`);
597
598             // Initial check: it should show the 'default' storage class
599             cy.get('[data-cy=collection-info-panel]')
600                 .should('contain', 'Storage classes')
601                 .and('contain', 'default')
602                 .and('not.contain', 'foo')
603                 .and('not.contain', 'bar');
604             // Edit collection: add storage class 'foo'
605             cy.get('[data-cy=collection-panel-options-btn]').click();
606             cy.get('[data-cy=context-menu]').contains('Edit collection').click();
607             cy.get('[data-cy=form-dialog]')
608                 .should('contain', 'Edit Collection')
609                 .and('contain', 'Storage classes')
610                 .and('contain', 'default')
611                 .and('contain', 'foo')
612                 .and('contain', 'bar')
613                 .within(() => {
614                     cy.get('[data-cy=checkbox-foo]').click();
615                 });
616             cy.get('[data-cy=form-submit-btn]').click();
617             cy.get('[data-cy=collection-info-panel]')
618                 .should('contain', 'default')
619                 .and('contain', 'foo')
620                 .and('not.contain', 'bar');
621             cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
622                 .its('body').as('updatedCollection')
623                 .then(function () {
624                     expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['default', 'foo']);
625                 });
626             // Edit collection: remove storage class 'default'
627             cy.get('[data-cy=collection-panel-options-btn]').click();
628             cy.get('[data-cy=context-menu]').contains('Edit collection').click();
629             cy.get('[data-cy=form-dialog]')
630                 .should('contain', 'Edit Collection')
631                 .and('contain', 'Storage classes')
632                 .and('contain', 'default')
633                 .and('contain', 'foo')
634                 .and('contain', 'bar')
635                 .within(() => {
636                     cy.get('[data-cy=checkbox-default]').click();
637                 });
638             cy.get('[data-cy=form-submit-btn]').click();
639             cy.get('[data-cy=collection-info-panel]')
640                 .should('not.contain', 'default')
641                 .and('contain', 'foo')
642                 .and('not.contain', 'bar');
643             cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
644                 .its('body').as('updatedCollection')
645                 .then(function () {
646                     expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['foo']);
647                 });
648         })
649     });
650
651     it('moves a collection to a different project', function () {
652         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
653         const projName = `Test Project ${Math.floor(Math.random() * 999999)}`;
654         const fileName = `Test_File_${Math.floor(Math.random() * 999999)}`;
655
656         cy.createCollection(adminUser.token, {
657             name: collName,
658             owner_uuid: activeUser.user.uuid,
659             manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
660         }).as('testCollection');
661         cy.createGroup(adminUser.token, {
662             name: projName,
663             group_class: 'project',
664             owner_uuid: activeUser.user.uuid,
665         }).as('testProject');
666
667         cy.getAll('@testCollection', '@testProject')
668             .then(function ([testCollection, testProject]) {
669                 cy.loginAs(activeUser);
670                 cy.goToPath(`/collections/${testCollection.uuid}`);
671                 cy.get('[data-cy=collection-files-panel]').should('contain', fileName);
672                 cy.get('[data-cy=collection-info-panel]')
673                     .should('not.contain', projName)
674                     .and('not.contain', testProject.uuid);
675                 cy.get('[data-cy=collection-panel-options-btn]').click();
676                 cy.get('[data-cy=context-menu]').contains('Move to').click();
677                 cy.get('[data-cy=form-dialog]')
678                     .should('contain', 'Move to')
679                     .within(() => {
680                         // must use .then to avoid selecting instead of expanding https://github.com/cypress-io/cypress/issues/5529
681                         cy.get('[data-cy=projects-tree-home-tree-picker]')
682                             .find('i')
683                             .then(el => el.click());
684                         cy.get('[data-cy=projects-tree-home-tree-picker]')
685                             .contains(projName)
686                             .click();
687                     });
688                 cy.get('[data-cy=form-submit-btn]').click();
689                 cy.get('[data-cy=snackbar]')
690                     .contains('Collection has been moved')
691                 cy.get('[data-cy=collection-info-panel]')
692                     .contains(projName).and('contain', testProject.uuid);
693                 // Double check that the collection is in the project
694                 cy.goToPath(`/projects/${testProject.uuid}`);
695                 cy.waitForDom().get('[data-cy=project-panel]').should('contain', collName);
696             });
697     });
698
699     it('automatically updates the collection UI contents without using the Refresh button', function () {
700         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
701
702         cy.createCollection(adminUser.token, {
703             name: collName,
704             owner_uuid: activeUser.user.uuid,
705         }).as('testCollection');
706
707         cy.getAll('@testCollection').then(function ([testCollection]) {
708             cy.loginAs(activeUser);
709
710             const files = [
711                 "foobar",
712                 "anotherFile",
713                 "",
714                 "finalName",
715             ];
716
717             cy.goToPath(`/collections/${testCollection.uuid}`);
718             cy.get('[data-cy=collection-files-panel]').should('contain', 'This collection is empty');
719             cy.get('[data-cy=collection-files-panel]').should('not.contain', files[0]);
720             cy.get('[data-cy=collection-info-panel]').should('contain', collName);
721
722             files.map((fileName, i, files) => {
723                 cy.updateCollection(adminUser.token, testCollection.uuid, {
724                     name: `${collName + ' updated'}`,
725                     manifest_text: fileName ? `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n` : "",
726                 }).as('updatedCollection');
727                 cy.getAll('@updatedCollection').then(function ([updatedCollection]) {
728                     expect(updatedCollection.name).to.equal(`${collName + ' updated'}`);
729                     cy.get('[data-cy=collection-info-panel]').should('contain', updatedCollection.name);
730                     fileName
731                         ? cy.get('[data-cy=collection-files-panel]').should('contain', fileName)
732                         : cy.get('[data-cy=collection-files-panel]').should('not.contain', files[i-1]);;
733                 });
734             });
735
736         });
737     });
738
739     it('makes a copy of an existing collection', function() {
740         const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
741         const copyName = `Copy of: ${collName}`;
742
743         cy.createCollection(adminUser.token, {
744             name: collName,
745             owner_uuid: activeUser.user.uuid,
746             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
747         }).as('collection').then(function () {
748             cy.loginAs(activeUser)
749             cy.goToPath(`/collections/${this.collection.uuid}`);
750             cy.get('[data-cy=collection-files-panel]')
751                 .should('contain', 'some-file');
752             cy.get('[data-cy=collection-panel-options-btn]').click();
753             cy.get('[data-cy=context-menu]').contains('Make a copy').click();
754             cy.get('[data-cy=form-dialog]')
755                 .should('contain', 'Make a copy')
756                 .within(() => {
757                     cy.get('[data-cy=projects-tree-home-tree-picker]')
758                         .contains('Projects')
759                         .click();
760                     cy.get('[data-cy=form-submit-btn]').click();
761                 });
762             cy.get('[data-cy=snackbar]')
763                 .contains('Collection has been copied.')
764             cy.get('[data-cy=snackbar-goto-action]').click();
765             cy.get('[data-cy=project-panel]')
766                 .contains(copyName).click();
767             cy.get('[data-cy=collection-files-panel]')
768                 .should('contain', 'some-file');
769         });
770     });
771
772     it('uses the collection version browser to view a previous version', function () {
773         const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
774
775         // Creates the collection using the admin token so we can set up
776         // a bogus manifest text without block signatures.
777         cy.createCollection(adminUser.token, {
778             name: colName,
779             owner_uuid: activeUser.user.uuid,
780             preserve_version: true,
781             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
782         })
783             .as('collection').then(function () {
784                 // Visit collection, check basic information
785                 cy.loginAs(activeUser)
786                 cy.goToPath(`/collections/${this.collection.uuid}`);
787
788                 cy.get('[data-cy=collection-info-panel]').should('not.contain', 'This is an old version');
789                 cy.get('[data-cy=read-only-icon]').should('not.exist');
790                 cy.get('[data-cy=collection-version-number]').should('contain', '1');
791                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
792                 cy.get('[data-cy=collection-files-panel]').should('contain', 'foo').and('contain', 'bar');
793
794                 // Modify collection, expect version number change
795                 cy.get('[data-cy=collection-files-panel]').contains('foo').rightclick();
796                 cy.get('[data-cy=context-menu]').contains('Remove').click();
797                 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Removing file');
798                 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
799                 cy.get('[data-cy=collection-version-number]').should('contain', '2');
800                 cy.get('[data-cy=collection-files-panel]').should('not.contain', 'foo').and('contain', 'bar');
801
802                 // Click on version number, check version browser. Click on past version.
803                 cy.get('[data-cy=collection-version-browser]').should('not.exist');
804                 cy.get('[data-cy=collection-version-number]').contains('2').click();
805                 cy.get('[data-cy=collection-version-browser]')
806                     .should('contain', 'Nr').and('contain', 'Size').and('contain', 'Date')
807                     .within(() => {
808                         // Version 1: 6 bytes in size
809                         cy.get('[data-cy=collection-version-browser-select-1]')
810                             .should('contain', '1')
811                             .and('contain', '6 B')
812                             .and('contain', adminUser.user.uuid);
813                         // Version 2: 3 bytes in size (one file removed)
814                         cy.get('[data-cy=collection-version-browser-select-2]')
815                             .should('contain', '2')
816                             .and('contain', '3 B')
817                             .and('contain', activeUser.user.full_name);
818                         cy.get('[data-cy=collection-version-browser-select-3]')
819                             .should('not.exist');
820                         cy.get('[data-cy=collection-version-browser-select-1]')
821                             .click();
822                     });
823                 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
824                 cy.get('[data-cy=read-only-icon]').should('exist');
825                 cy.get('[data-cy=collection-version-number]').should('contain', '1');
826                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
827                 cy.get('[data-cy=collection-files-panel]')
828                     .should('contain', 'foo').and('contain', 'bar');
829
830                 // Check that only old collection action are available on context menu
831                 cy.get('[data-cy=collection-panel-options-btn]').click();
832                 cy.get('[data-cy=context-menu]')
833                     .should('contain', 'Restore version')
834                     .and('not.contain', 'Add to favorites');
835                 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
836
837                 // Click on "head version" link, confirm that it's the latest version.
838                 cy.get('[data-cy=collection-info-panel]').contains('head version').click();
839                 cy.get('[data-cy=collection-info-panel]')
840                     .should('not.contain', 'This is an old version');
841                 cy.get('[data-cy=read-only-icon]').should('not.exist');
842                 cy.get('[data-cy=collection-version-number]').should('contain', '2');
843                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
844                 cy.get('[data-cy=collection-files-panel]').
845                     should('not.contain', 'foo').and('contain', 'bar');
846
847                 // Check that old collection action isn't available on context menu
848                 cy.get('[data-cy=collection-panel-options-btn]').click()
849                 cy.get('[data-cy=context-menu]').should('not.contain', 'Restore version')
850                 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
851
852                 // Make another change, confirm new version.
853                 cy.get('[data-cy=collection-panel-options-btn]').click();
854                 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
855                 cy.get('[data-cy=form-dialog]')
856                     .should('contain', 'Edit Collection')
857                     .within(() => {
858                         // appends some text
859                         cy.get('input').first().type(' renamed');
860                     });
861                 cy.get('[data-cy=form-submit-btn]').click();
862                 cy.get('[data-cy=collection-info-panel]')
863                     .should('not.contain', 'This is an old version');
864                 cy.get('[data-cy=read-only-icon]').should('not.exist');
865                 cy.get('[data-cy=collection-version-number]').should('contain', '3');
866                 cy.get('[data-cy=collection-info-panel]').should('contain', colName + ' renamed');
867                 cy.get('[data-cy=collection-files-panel]')
868                     .should('not.contain', 'foo').and('contain', 'bar');
869                 cy.get('[data-cy=collection-version-browser-select-3]')
870                     .should('contain', '3').and('contain', '3 B');
871
872                 // Check context menus on version browser
873                 cy.get('[data-cy=collection-version-browser-select-3]').rightclick()
874                 cy.get('[data-cy=context-menu]')
875                     .should('contain', 'Add to favorites')
876                     .and('contain', 'Make a copy')
877                     .and('contain', 'Edit collection');
878                 cy.get('body').click();
879                 // (and now an old version...)
880                 cy.get('[data-cy=collection-version-browser-select-1]').rightclick()
881                 cy.get('[data-cy=context-menu]')
882                     .should('not.contain', 'Add to favorites')
883                     .and('contain', 'Make a copy')
884                     .and('not.contain', 'Edit collection');
885                 cy.get('body').click();
886
887                 // Restore first version
888                 cy.get('[data-cy=collection-version-browser]').within(() => {
889                     cy.get('[data-cy=collection-version-browser-select-1]').click();
890                 });
891                 cy.get('[data-cy=collection-panel-options-btn]').click()
892                 cy.get('[data-cy=context-menu]').contains('Restore version').click();
893                 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Restore version');
894                 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
895                 cy.get('[data-cy=collection-info-panel]')
896                     .should('not.contain', 'This is an old version');
897                 cy.get('[data-cy=collection-version-number]').should('contain', '4');
898                 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
899                 cy.get('[data-cy=collection-files-panel]')
900                     .should('contain', 'foo').and('contain', 'bar');
901             });
902     });
903
904     it('creates collection from selected files of another collection', () => {
905         cy.createCollection(adminUser.token, {
906             name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
907             owner_uuid: activeUser.user.uuid,
908             preserve_version: true,
909             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
910         })
911             .as('collection').then(function () {
912                 // Visit collection, check basic information
913                 cy.loginAs(activeUser)
914                 cy.goToPath(`/collections/${this.collection.uuid}`);
915
916                 cy.get('[data-cy=collection-files-panel]').within(() => {
917                     cy.get('input[type=checkbox]').first().click();
918                 });
919
920                 cy.get('[data-cy=collection-files-panel-options-btn]').click();
921                 cy.get('[data-cy=context-menu]').contains('Create a new collection with selected').click();
922
923                 cy.get('[data-cy=form-dialog]').contains('Projects').click();
924
925                 cy.get('[data-cy=form-submit-btn]').click();
926
927                 cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
928
929                 cy.get('main').contains(`Files extracted from: ${this.collection.name}`).should('exist');
930             });
931     });
932
933     it('creates new collection with properties on home project', function () {
934         cy.loginAs(activeUser);
935         cy.goToPath(`/projects/${activeUser.user.uuid}`);
936         cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
937         cy.get('[data-cy=breadcrumb-last]').should('not.exist');
938         // Create new collection
939         cy.get('[data-cy=side-panel-button]').click();
940         cy.get('[data-cy=side-panel-new-collection]').click();
941         // Name between brackets tests bugfix #17582
942         const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
943
944         // Select a storage class.
945         cy.get('[data-cy=form-dialog]')
946             .should('contain', 'New collection')
947             .and('contain', 'Storage classes')
948             .and('contain', 'default')
949             .and('contain', 'foo')
950             .and('contain', 'bar')
951             .within(() => {
952                 cy.get('[data-cy=parent-field]').within(() => {
953                     cy.get('input').should('have.value', 'Home project');
954                 });
955                 cy.get('[data-cy=name-field]').within(() => {
956                     cy.get('input').type(collName);
957                 });
958                 cy.get('[data-cy=checkbox-foo]').click();
959             })
960
961         // Add a property.
962         // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
963         cy.get('[data-cy=form-dialog]').should('not.contain', 'Color: Magenta');
964         cy.get('[data-cy=resource-properties-form]').within(() => {
965             cy.get('[data-cy=property-field-key]').within(() => {
966                 cy.get('input').type('Color');
967             });
968             cy.get('[data-cy=property-field-value]').within(() => {
969                 cy.get('input').type('Magenta');
970             });
971             cy.root().submit();
972         });
973         // Confirm proper vocabulary labels are displayed on the UI.
974         cy.get('[data-cy=form-dialog]').should('contain', 'Color: Magenta');
975
976         // Value field should not complain about being required just after
977         // adding a new property. See #19732
978         cy.get('[data-cy=form-dialog]').should('not.contain', 'This field is required');
979
980         cy.get('[data-cy=form-submit-btn]').click();
981         // Confirm that the user was taken to the newly created collection
982         cy.get('[data-cy=form-dialog]').should('not.exist');
983         cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
984         cy.get('[data-cy=breadcrumb-last]').should('contain', collName);
985         cy.get('[data-cy=collection-info-panel]')
986             .should('contain', 'default')
987             .and('contain', 'foo')
988             .and('contain', 'Color: Magenta')
989             .and('not.contain', 'bar');
990         // Confirm that the collection's properties has the real values.
991         cy.doRequest('GET', '/arvados/v1/collections', null, {
992             filters: `[["name", "=", "${collName}"]]`,
993         })
994         .its('body.items').as('collections')
995         .then(function() {
996             expect(this.collections).to.have.lengthOf(1);
997             expect(this.collections[0].properties).to.have.property(
998                 'IDTAGCOLORS', 'IDVALCOLORS3');
999         });
1000     });
1001
1002     it('shows responsible person for collection if available', () => {
1003         cy.createCollection(adminUser.token, {
1004             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
1005             owner_uuid: activeUser.user.uuid,
1006             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
1007         }).as('testCollection1');
1008
1009         cy.createCollection(adminUser.token, {
1010             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
1011             owner_uuid: adminUser.user.uuid,
1012             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
1013         }).as('testCollection2').then(function (testCollection2) {
1014             cy.shareWith(adminUser.token, activeUser.user.uuid, testCollection2.uuid, 'can_write');
1015         });
1016
1017         cy.getAll('@testCollection1', '@testCollection2')
1018             .then(function ([testCollection1, testCollection2]) {
1019                 cy.loginAs(activeUser);
1020
1021                 cy.goToPath(`/collections/${testCollection1.uuid}`);
1022                 cy.get('[data-cy=responsible-person-wrapper]')
1023                     .contains(activeUser.user.uuid);
1024
1025                 cy.goToPath(`/collections/${testCollection2.uuid}`);
1026                 cy.get('[data-cy=responsible-person-wrapper]')
1027                     .contains(adminUser.user.uuid);
1028             });
1029     });
1030
1031     describe('file upload', () => {
1032         beforeEach(() => {
1033             cy.createCollection(adminUser.token, {
1034                 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
1035                 owner_uuid: activeUser.user.uuid,
1036                 manifest_text: "./subdir 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
1037             }).as('testCollection1');
1038         });
1039
1040         it('uploads a file and checks the collection UI to be fresh', () => {
1041             cy.getAll('@testCollection1')
1042                 .then(function([testCollection1]) {
1043                     cy.loginAs(activeUser);
1044                     cy.goToPath(`/collections/${testCollection1.uuid}`);
1045                     cy.get('[data-cy=upload-button]').click();
1046                     cy.get('[data-cy=collection-files-panel]')
1047                         .contains('5mb_a.bin').should('not.exist');
1048                     cy.get('[data-cy=collection-file-count]').should('contain', '2');
1049                     cy.fixture('files/5mb.bin', 'base64').then(content => {
1050                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
1051                         cy.get('[data-cy=form-submit-btn]').click();
1052                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
1053                         cy.get('[data-cy=collection-files-panel]')
1054                             .contains('5mb_a.bin').should('exist');
1055                         cy.get('[data-cy=collection-file-count]').should('contain', '3');
1056
1057                         cy.get('[data-cy=collection-files-panel]').contains('subdir').click();
1058                         cy.get('[data-cy=upload-button]').click();
1059                         cy.fixture('files/5mb.bin', 'base64').then(content => {
1060                             cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
1061                             cy.get('[data-cy=form-submit-btn]').click();
1062                             cy.get('[data-cy=form-submit-btn]').should('not.exist');
1063                             cy.get('[data-cy=collection-files-right-panel]')
1064                                  .contains('5mb_b.bin').should('exist');
1065                         });
1066                     });
1067                 });
1068         });
1069
1070         it('allows to cancel running upload', () => {
1071             cy.getAll('@testCollection1')
1072                 .then(function([testCollection1]) {
1073                     cy.loginAs(activeUser);
1074
1075                     cy.goToPath(`/collections/${testCollection1.uuid}`);
1076
1077                     cy.get('[data-cy=upload-button]').click();
1078
1079                     cy.fixture('files/5mb.bin', 'base64').then(content => {
1080                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
1081                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
1082
1083                         cy.get('[data-cy=form-submit-btn]').click();
1084
1085                         cy.get('button').contains('Cancel').click();
1086
1087                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
1088                     });
1089                 });
1090         });
1091
1092         it('allows to cancel single file from the running upload', () => {
1093             cy.getAll('@testCollection1')
1094                 .then(function([testCollection1]) {
1095                     cy.loginAs(activeUser);
1096
1097                     cy.goToPath(`/collections/${testCollection1.uuid}`);
1098
1099                     cy.get('[data-cy=upload-button]').click();
1100
1101                     cy.fixture('files/5mb.bin', 'base64').then(content => {
1102                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
1103                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
1104
1105                         cy.get('[data-cy=form-submit-btn]').click();
1106
1107                         cy.get('button[aria-label=Remove]').eq(1).click();
1108
1109                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
1110
1111                         cy.get('[data-cy=collection-files-panel]').contains('5mb_a.bin').should('exist');
1112                     });
1113                 });
1114         });
1115
1116         it('allows to cancel all files from the running upload', () => {
1117             cy.getAll('@testCollection1')
1118                 .then(function([testCollection1]) {
1119                     cy.loginAs(activeUser);
1120
1121                     cy.goToPath(`/collections/${testCollection1.uuid}`);
1122
1123                     // Confirm initial collection state.
1124                     cy.get('[data-cy=collection-files-panel]')
1125                         .contains('bar').should('exist');
1126                     cy.get('[data-cy=collection-files-panel]')
1127                         .contains('5mb_a.bin').should('not.exist');
1128                     cy.get('[data-cy=collection-files-panel]')
1129                         .contains('5mb_b.bin').should('not.exist');
1130
1131                     cy.get('[data-cy=upload-button]').click();
1132
1133                     cy.fixture('files/5mb.bin', 'base64').then(content => {
1134                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
1135                         cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
1136
1137                         cy.get('[data-cy=form-submit-btn]').click();
1138
1139                         cy.get('button[aria-label=Remove]').should('exist');
1140                         cy.get('button[aria-label=Remove]')
1141                             .click({ multiple: true, force: true });
1142
1143                         cy.get('[data-cy=form-submit-btn]').should('not.exist');
1144
1145                         // Confirm final collection state.
1146                         cy.get('[data-cy=collection-files-panel]')
1147                             .contains('bar').should('exist');
1148                         // The following fails, but doesn't seem to happen
1149                         // in the real world. Maybe there's a race between
1150                         // the PUT request finishing and the 'Remove' button
1151                         // dissapearing, because sometimes just one of the 2
1152                         // files gets uploaded.
1153                         // Maybe this will be needed to simulate a slow network:
1154                         // https://docs.cypress.io/api/commands/intercept#Convenience-functions-1
1155                         // cy.get('[data-cy=collection-files-panel]')
1156                         //     .contains('5mb_a.bin').should('not.exist');
1157                         // cy.get('[data-cy=collection-files-panel]')
1158                         //     .contains('5mb_b.bin').should('not.exist');
1159                     });
1160                 });
1161         });
1162     });
1163 })