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