1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 const path = require('path');
7 describe('Collection panel tests', 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;
22 cy.getUser('collectionuser1', 'Collection', 'User', false, true)
23 .as('activeUser').then(function () {
24 activeUser = this.activeUser;
27 downloadsFolder = Cypress.config('downloadsFolder');
30 beforeEach(function () {
32 cy.clearLocalStorage();
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"
41 .as('testCollection').then(function (testCollection) {
42 cy.loginAs(activeUser);
43 cy.goToPath(`/collections/${testCollection.uuid}`);
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();
49 const filename = path.join(downloadsFolder, `${testCollection.name}.duck`);
51 cy.readFile(filename, { timeout: 15000 })
53 const childrenCollection = Array.prototype.slice.call(Cypress.$(body).find('dict')[0].children);
57 for (i=0; i < childrenCollection.length; i += j) {
58 map[childrenCollection[i].outerText] = childrenCollection[i + 1].outerText;
61 cy.get('#simple-tabpanel-0').find('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);
70 expect(map['Path']).to.equal(`/c=${testCollection.uuid}`);
74 .then(() => cy.task('clearDownload', { filename }));
78 it('uses the property editor with vocabulary terms', function () {
79 cy.createCollection(adminUser.token, {
80 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
81 owner_uuid: activeUser.user.uuid,
82 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
84 .as('testCollection').then(function () {
85 cy.loginAs(activeUser);
86 cy.goToPath(`/collections/${this.testCollection.uuid}`);
88 cy.get('[data-cy=collection-info-panel')
89 .should('contain', this.testCollection.name)
90 .and('not.contain', 'Color: Magenta')
91 .and('not.contain', 'Size: S');
92 cy.get('[data-cy=additional-info-icon]').click();
94 cy.get('[data-cy=details-panel]').within(() => {
95 cy.get('[data-cy=property-editor-btn]').click();
97 cy.get('[data-cy=resource-properties-dialog').contains('Edit properties');
99 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
100 cy.get('[data-cy=resource-properties-form]').within(() => {
101 cy.get('[data-cy=property-field-key]').within(() => {
102 cy.get('input').type('Color');
104 cy.get('[data-cy=property-field-value]').within(() => {
105 cy.get('input').type('Magenta');
109 // Confirm proper vocabulary labels are displayed on the UI.
110 cy.get('[data-cy=resource-properties-dialog]')
111 .should('contain', 'Color: Magenta');
112 // Confirm proper vocabulary IDs were saved on the backend.
113 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
114 .its('body').as('collection')
116 expect(this.collection.properties.IDTAGCOLORS).to.equal('IDVALCOLORS3');
119 // Case-insensitive on-blur auto-selection test
120 // Key: Size (IDTAGSIZES) - Value: Small (IDVALSIZES2)
121 cy.get('[data-cy=resource-properties-form]').within(() => {
122 cy.get('[data-cy=property-field-key]').within(() => {
123 cy.get('input').type('sIzE');
125 cy.get('[data-cy=property-field-value]').within(() => {
126 cy.get('input').type('sMaLL');
128 // Cannot "type()" TAB on Cypress so let's click another field
129 // to trigger the onBlur event.
130 cy.get('[data-cy=property-field-key]').click();
133 // Confirm proper vocabulary labels are displayed on the UI.
134 cy.get('[data-cy=resource-properties-dialog]')
135 .should('contain', 'Size: S');
136 // Confirm proper vocabulary IDs were saved on the backend.
137 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
138 .its('body').as('collection')
140 expect(this.collection.properties.IDTAGSIZES).to.equal('IDVALSIZES2');
143 // Close property editor & confirm properties display on the UI.
144 cy.get('[data-cy=resource-properties-dialog]').within(() => {
145 cy.get('[data-cy=close-dialog-btn]').click();
147 cy.get('[data-cy=collection-info-panel')
148 .should('contain', this.testCollection.name)
149 .and('contain', 'Color: Magenta')
150 .and('contain', 'Size: S');
154 it('shows collection by URL', function () {
155 cy.loginAs(activeUser);
156 [true, false].map(function (isWritable) {
157 // Using different file names to avoid test flakyness: the second iteration
158 // on this loop may pass an assertion from the first iteration by looking
159 // for the same file name.
160 const fileName = isWritable ? 'bar' : 'foo';
161 const subDirName = 'subdir';
162 cy.createGroup(adminUser.token, {
163 name: 'Shared project',
164 group_class: 'project',
165 }).as('sharedGroup').then(function () {
166 // Creates the collection using the admin token so we can set up
167 // a bogus manifest text without block signatures.
168 cy.doRequest('GET', '/arvados/v1/config', null, null)
169 .its('body').should((clusterConfig) => {
170 expect(clusterConfig.Collections, "clusterConfig").to.have.property("TrustAllContent", false);
171 expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAV").have.property("ExternalURL");
172 expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAVDownload").have.property("ExternalURL");
173 const inlineUrl = clusterConfig.Services.WebDAV.ExternalURL !== ""
174 ? clusterConfig.Services.WebDAV.ExternalURL
175 : clusterConfig.Services.WebDAVDownload.ExternalURL;
176 expect(inlineUrl).to.not.contain("*");
178 .createCollection(adminUser.token, {
179 name: 'Test collection',
180 owner_uuid: this.sharedGroup.uuid,
181 properties: { someKey: 'someValue' },
182 manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n./${subDirName} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`
184 .as('testCollection').then(function () {
185 // Share the group with active user.
186 cy.createLink(adminUser.token, {
187 name: isWritable ? 'can_write' : 'can_read',
188 link_class: 'permission',
189 head_uuid: this.sharedGroup.uuid,
190 tail_uuid: activeUser.user.uuid
192 cy.goToPath(`/collections/${this.testCollection.uuid}`);
194 // Check that name & uuid are correct.
195 cy.get('[data-cy=collection-info-panel]')
196 .should('contain', this.testCollection.name)
197 .and('contain', this.testCollection.uuid)
198 .and('not.contain', 'This is an old version');
199 // Check for the read-only icon
200 cy.get('[data-cy=read-only-icon]').should(`${isWritable ? 'not.' : ''}exist`);
201 // Check that both read and write operations are available on
202 // the 'More options' menu.
203 cy.get('[data-cy=collection-panel-options-btn]')
205 cy.get('[data-cy=context-menu]')
206 .should('contain', 'Add to favorites')
207 .and(`${isWritable ? '' : 'not.'}contain`, 'Edit collection');
208 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
209 cy.get('[data-cy=collection-info-panel]')
210 .should('contain', 'someKey: someValue')
211 .and('not.contain', 'anotherKey: anotherValue');
212 // Check that the file listing show both read & write operations
213 cy.get('[data-cy=collection-files-panel]').within(() => {
215 cy.root().should('contain', fileName);
217 cy.get('[data-cy=upload-button]')
218 .should(`${isWritable ? '' : 'not.'}contain`, 'Upload data');
221 // Test context menus
222 cy.get('[data-cy=collection-files-panel]')
223 .contains(fileName).rightclick({ force: true });
224 cy.get('[data-cy=context-menu]')
225 .should('contain', 'Download')
226 .and('not.contain', 'Open in new tab')
227 .and('contain', 'Copy to clipboard')
228 .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
229 .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
230 cy.get('body').click(); // Collapse the menu
231 cy.get('[data-cy=collection-files-panel]')
232 .contains(subDirName).rightclick({ force: true });
233 cy.get('[data-cy=context-menu]')
234 .should('not.contain', 'Download')
235 .and('not.contain', 'Open in new tab')
236 .and('contain', 'Copy to clipboard')
237 .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
238 .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
239 cy.get('body').click(); // Collapse the menu
240 // Hamburger 'more options' menu button
241 cy.get('[data-cy=collection-files-panel-options-btn]')
243 cy.get('[data-cy=context-menu]')
244 .should('contain', 'Select all')
246 cy.get('[data-cy=collection-files-panel-options-btn]')
248 cy.get('[data-cy=context-menu]')
249 .should(`${isWritable ? '' : 'not.'}contain`, 'Remove selected')
250 cy.get('body').click(); // Collapse the menu
256 it('renames a file using valid names', function () {
257 function eachPair(lst, func){
258 for(var i=0; i < lst.length - 1; i++){
259 func(lst[i], lst[i + 1])
262 // Creates the collection using the admin token so we can set up
263 // a bogus manifest text without block signatures.
264 cy.createCollection(adminUser.token, {
265 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
266 owner_uuid: activeUser.user.uuid,
267 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
269 .as('testCollection').then(function () {
270 cy.loginAs(activeUser);
271 cy.goToPath(`/collections/${this.testCollection.uuid}`);
274 'bar', // initial name already set
281 'some name with whitespaces',
283 'is this name legal? I hope it is',
291 "G%C3%BCnter's%20file.pdf",
293 'bar' // make sure we can go back to the original name as a last step
295 eachPair(names, (from, to) => {
296 cy.get('[data-cy=collection-files-panel]')
297 .contains(`${from}`).rightclick();
298 cy.get('[data-cy=context-menu]')
301 cy.get('[data-cy=form-dialog]')
302 .should('contain', 'Rename')
305 .type('{selectall}{backspace}')
306 .type(to, { parseSpecialCharSequences: false });
308 cy.get('[data-cy=form-submit-btn]').click();
309 cy.get('[data-cy=collection-files-panel]')
310 .should('not.contain', `${from}`)
311 .and('contain', `${to}`);
316 it('renames a file to a different directory', function () {
317 // Creates the collection using the admin token so we can set up
318 // a bogus manifest text without block signatures.
319 cy.createCollection(adminUser.token, {
320 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
321 owner_uuid: activeUser.user.uuid,
322 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
324 .as('testCollection').then(function () {
325 cy.loginAs(activeUser);
326 cy.goToPath(`/collections/${this.testCollection.uuid}`);
328 ['subdir', 'G%C3%BCnter\'s%20file', 'table%&?*2'].forEach((subdir) => {
329 cy.get('[data-cy=collection-files-panel]')
330 .contains('bar').rightclick({force: true});
331 cy.get('[data-cy=context-menu]')
334 cy.get('[data-cy=form-dialog]')
335 .should('contain', 'Rename')
337 cy.get('input').type(`{selectall}{backspace}${subdir}/foo`);
339 cy.get('[data-cy=form-submit-btn]').click();
340 cy.get('[data-cy=collection-files-panel]')
341 .should('not.contain', 'bar')
342 .and('contain', subdir);
344 cy.get('[data-cy=collection-files-panel]').contains(subdir).click();
345 // Rename 'subdir/foo' to 'foo'
347 cy.get('[data-cy=collection-files-panel]')
348 .contains('foo').rightclick();
349 cy.get('[data-cy=context-menu]')
352 cy.get('[data-cy=form-dialog]')
353 .should('contain', 'Rename')
356 .should('have.value', `${subdir}/foo`)
357 .type(`{selectall}{backspace}bar`);
359 cy.get('[data-cy=form-submit-btn]').click();
362 cy.get('[data-cy=collection-files-panel]')
367 cy.get('[data-cy=collection-files-panel]')
368 .should('contain', subdir) // empty dir kept
369 .and('contain', 'bar');
371 cy.get('[data-cy=collection-files-panel]')
372 .contains(subdir).rightclick();
373 cy.get('[data-cy=context-menu]')
376 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
381 it('tries to rename a file with illegal names', function () {
382 // Creates the collection using the admin token so we can set up
383 // a bogus manifest text without block signatures.
384 cy.createCollection(adminUser.token, {
385 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
386 owner_uuid: activeUser.user.uuid,
387 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
389 .as('testCollection').then(function () {
390 cy.loginAs(activeUser);
391 cy.goToPath(`/collections/${this.testCollection.uuid}`);
393 const illegalNamesFromUI = [
394 ['.', "Name cannot be '.' or '..'"],
395 ['..', "Name cannot be '.' or '..'"],
396 ['', 'This field is required'],
397 [' ', 'Leading/trailing whitespaces not allowed'],
398 [' foo', 'Leading/trailing whitespaces not allowed'],
399 ['foo ', 'Leading/trailing whitespaces not allowed'],
400 ['//foo', 'Empty dir name not allowed']
402 illegalNamesFromUI.forEach(([name, errMsg]) => {
403 cy.get('[data-cy=collection-files-panel]')
404 .contains('bar').rightclick();
405 cy.get('[data-cy=context-menu]')
408 cy.get('[data-cy=form-dialog]')
409 .should('contain', 'Rename')
411 cy.get('input').type(`{selectall}{backspace}${name}`);
413 cy.get('[data-cy=form-dialog]')
414 .should('contain', 'Rename')
416 cy.contains(`${errMsg}`);
418 cy.get('[data-cy=form-cancel-btn]').click();
423 it('can correctly display old versions', function () {
424 const colName = `Versioned Collection ${Math.floor(Math.random() * 999999)}`;
426 let oldVersionUuid = '';
427 // Make sure no other collections with this name exist
428 cy.doRequest('GET', '/arvados/v1/collections', null, {
429 filters: `[["name", "=", "${colName}"]]`,
430 include_old_versions: true
432 .its('body.items').as('collections')
434 expect(this.collections).to.be.empty;
436 // Creates the collection using the admin token so we can set up
437 // a bogus manifest text without block signatures.
438 cy.createCollection(adminUser.token, {
440 owner_uuid: activeUser.user.uuid,
441 preserve_version: true,
442 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
444 .as('originalVersion').then(function () {
445 // Change the file name to create a new version.
446 cy.updateCollection(adminUser.token, this.originalVersion.uuid, {
447 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n"
449 colUuid = this.originalVersion.uuid;
451 // Confirm that there are 2 versions of the collection
452 cy.doRequest('GET', '/arvados/v1/collections', null, {
453 filters: `[["name", "=", "${colName}"]]`,
454 include_old_versions: true
456 .its('body.items').as('collections')
458 expect(this.collections).to.have.lengthOf(2);
459 this.collections.map(function (aCollection) {
460 expect(aCollection.current_version_uuid).to.equal(colUuid);
461 if (aCollection.uuid !== aCollection.current_version_uuid) {
462 oldVersionUuid = aCollection.uuid;
465 // Check the old version displays as what it is.
466 cy.loginAs(activeUser)
467 cy.goToPath(`/collections/${oldVersionUuid}`);
469 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
470 cy.get('[data-cy=read-only-icon]').should('exist');
471 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
472 cy.get('[data-cy=collection-files-panel]').should('contain', 'bar');
476 it('views & edits storage classes data', function () {
477 const colName= `Test Collection ${Math.floor(Math.random() * 999999)}`;
478 cy.createCollection(adminUser.token, {
480 owner_uuid: activeUser.user.uuid,
481 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
482 }).as('collection').then(function () {
483 expect(this.collection.storage_classes_desired).to.deep.equal(['default'])
485 cy.loginAs(activeUser)
486 cy.goToPath(`/collections/${this.collection.uuid}`);
488 // Initial check: it should show the 'default' storage class
489 cy.get('[data-cy=collection-info-panel]')
490 .should('contain', 'Storage classes')
491 .and('contain', 'default')
492 .and('not.contain', 'foo')
493 .and('not.contain', 'bar');
494 // Edit collection: add storage class 'foo'
495 cy.get('[data-cy=collection-panel-options-btn]').click();
496 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
497 cy.get('[data-cy=form-dialog]')
498 .should('contain', 'Edit Collection')
499 .and('contain', 'Storage classes')
500 .and('contain', 'default')
501 .and('contain', 'foo')
502 .and('contain', 'bar')
504 cy.get('[data-cy=checkbox-foo]').click();
506 cy.get('[data-cy=form-submit-btn]').click();
507 cy.get('[data-cy=collection-info-panel]')
508 .should('contain', 'default')
509 .and('contain', 'foo')
510 .and('not.contain', 'bar');
511 cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
512 .its('body').as('updatedCollection')
514 expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['default', 'foo']);
516 // Edit collection: remove storage class 'default'
517 cy.get('[data-cy=collection-panel-options-btn]').click();
518 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
519 cy.get('[data-cy=form-dialog]')
520 .should('contain', 'Edit Collection')
521 .and('contain', 'Storage classes')
522 .and('contain', 'default')
523 .and('contain', 'foo')
524 .and('contain', 'bar')
526 cy.get('[data-cy=checkbox-default]').click();
528 cy.get('[data-cy=form-submit-btn]').click();
529 cy.get('[data-cy=collection-info-panel]')
530 .should('not.contain', 'default')
531 .and('contain', 'foo')
532 .and('not.contain', 'bar');
533 cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
534 .its('body').as('updatedCollection')
536 expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['foo']);
541 it('moves a collection to a different project', function () {
542 const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
543 const projName = `Test Project ${Math.floor(Math.random() * 999999)}`;
544 const fileName = `Test_File_${Math.floor(Math.random() * 999999)}`;
546 cy.createCollection(adminUser.token, {
548 owner_uuid: activeUser.user.uuid,
549 manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
550 }).as('testCollection');
551 cy.createGroup(adminUser.token, {
553 group_class: 'project',
554 owner_uuid: activeUser.user.uuid,
555 }).as('testProject');
557 cy.getAll('@testCollection', '@testProject')
558 .then(function ([testCollection, testProject]) {
559 cy.loginAs(activeUser);
560 cy.goToPath(`/collections/${testCollection.uuid}`);
561 cy.get('[data-cy=collection-files-panel]').should('contain', fileName);
562 cy.get('[data-cy=collection-info-panel]')
563 .should('not.contain', projName)
564 .and('not.contain', testProject.uuid);
565 cy.get('[data-cy=collection-panel-options-btn]').click();
566 cy.get('[data-cy=context-menu]').contains('Move to').click();
567 cy.get('[data-cy=form-dialog]')
568 .should('contain', 'Move to')
570 cy.get('[data-cy=projects-tree-home-tree-picker]')
573 cy.get('[data-cy=projects-tree-home-tree-picker]')
577 cy.get('[data-cy=form-submit-btn]').click();
578 cy.get('[data-cy=snackbar]')
579 .contains('Collection has been moved')
580 cy.get('[data-cy=collection-info-panel]')
581 .contains(projName).and('contain', testProject.uuid);
582 // Double check that the collection is in the project
583 cy.goToPath(`/projects/${testProject.uuid}`);
584 cy.get('[data-cy=project-panel]').should('contain', collName);
588 it('makes a copy of an existing collection', function() {
589 const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
590 const copyName = `Copy of: ${collName}`;
592 cy.createCollection(adminUser.token, {
594 owner_uuid: activeUser.user.uuid,
595 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
596 }).as('collection').then(function () {
597 cy.loginAs(activeUser)
598 cy.goToPath(`/collections/${this.collection.uuid}`);
599 cy.get('[data-cy=collection-files-panel]')
600 .should('contain', 'some-file');
601 cy.get('[data-cy=collection-panel-options-btn]').click();
602 cy.get('[data-cy=context-menu]').contains('Make a copy').click();
603 cy.get('[data-cy=form-dialog]')
604 .should('contain', 'Make a copy')
606 cy.get('[data-cy=projects-tree-home-tree-picker]')
607 .contains('Projects')
609 cy.get('[data-cy=form-submit-btn]').click();
611 cy.get('[data-cy=snackbar]')
612 .contains('Collection has been copied.')
613 cy.get('[data-cy=snackbar-goto-action]').click();
614 cy.get('[data-cy=project-panel]')
615 .contains(copyName).click();
616 cy.get('[data-cy=collection-files-panel]')
617 .should('contain', 'some-file');
621 it('uses the collection version browser to view a previous version', function () {
622 const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
624 // Creates the collection using the admin token so we can set up
625 // a bogus manifest text without block signatures.
626 cy.createCollection(adminUser.token, {
628 owner_uuid: activeUser.user.uuid,
629 preserve_version: true,
630 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
632 .as('collection').then(function () {
633 // Visit collection, check basic information
634 cy.loginAs(activeUser)
635 cy.goToPath(`/collections/${this.collection.uuid}`);
637 cy.get('[data-cy=collection-info-panel]').should('not.contain', 'This is an old version');
638 cy.get('[data-cy=read-only-icon]').should('not.exist');
639 cy.get('[data-cy=collection-version-number]').should('contain', '1');
640 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
641 cy.get('[data-cy=collection-files-panel]').should('contain', 'foo').and('contain', 'bar');
643 // Modify collection, expect version number change
644 cy.get('[data-cy=collection-files-panel]').contains('foo').rightclick();
645 cy.get('[data-cy=context-menu]').contains('Remove').click();
646 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Removing file');
647 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
648 cy.get('[data-cy=collection-version-number]').should('contain', '2');
649 cy.get('[data-cy=collection-files-panel]').should('not.contain', 'foo').and('contain', 'bar');
651 // Click on version number, check version browser. Click on past version.
652 cy.get('[data-cy=collection-version-browser]').should('not.exist');
653 cy.get('[data-cy=collection-version-number]').contains('2').click();
654 cy.get('[data-cy=collection-version-browser]')
655 .should('contain', 'Nr').and('contain', 'Size').and('contain', 'Date')
657 // Version 1: 6 bytes in size
658 cy.get('[data-cy=collection-version-browser-select-1]')
659 .should('contain', '1')
660 .and('contain', '6 B')
661 .and('contain', adminUser.user.uuid);
662 // Version 2: 3 bytes in size (one file removed)
663 cy.get('[data-cy=collection-version-browser-select-2]')
664 .should('contain', '2')
665 .and('contain', '3 B')
666 .and('contain', activeUser.user.full_name);
667 cy.get('[data-cy=collection-version-browser-select-3]')
668 .should('not.exist');
669 cy.get('[data-cy=collection-version-browser-select-1]')
672 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
673 cy.get('[data-cy=read-only-icon]').should('exist');
674 cy.get('[data-cy=collection-version-number]').should('contain', '1');
675 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
676 cy.get('[data-cy=collection-files-panel]')
677 .should('contain', 'foo').and('contain', 'bar');
679 // Check that only old collection action are available on context menu
680 cy.get('[data-cy=collection-panel-options-btn]').click();
681 cy.get('[data-cy=context-menu]')
682 .should('contain', 'Restore version')
683 .and('not.contain', 'Add to favorites');
684 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
686 // Click on "head version" link, confirm that it's the latest version.
687 cy.get('[data-cy=collection-info-panel]').contains('head version').click();
688 cy.get('[data-cy=collection-info-panel]')
689 .should('not.contain', 'This is an old version');
690 cy.get('[data-cy=read-only-icon]').should('not.exist');
691 cy.get('[data-cy=collection-version-number]').should('contain', '2');
692 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
693 cy.get('[data-cy=collection-files-panel]').
694 should('not.contain', 'foo').and('contain', 'bar');
696 // Check that old collection action isn't available on context menu
697 cy.get('[data-cy=collection-panel-options-btn]').click()
698 cy.get('[data-cy=context-menu]').should('not.contain', 'Restore version')
699 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
701 // Make another change, confirm new version.
702 cy.get('[data-cy=collection-panel-options-btn]').click();
703 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
704 cy.get('[data-cy=form-dialog]')
705 .should('contain', 'Edit Collection')
708 cy.get('input').first().type(' renamed');
710 cy.get('[data-cy=form-submit-btn]').click();
711 cy.get('[data-cy=collection-info-panel]')
712 .should('not.contain', 'This is an old version');
713 cy.get('[data-cy=read-only-icon]').should('not.exist');
714 cy.get('[data-cy=collection-version-number]').should('contain', '3');
715 cy.get('[data-cy=collection-info-panel]').should('contain', colName + ' renamed');
716 cy.get('[data-cy=collection-files-panel]')
717 .should('not.contain', 'foo').and('contain', 'bar');
718 cy.get('[data-cy=collection-version-browser-select-3]')
719 .should('contain', '3').and('contain', '3 B');
721 // Check context menus on version browser
722 cy.get('[data-cy=collection-version-browser-select-3]').rightclick()
723 cy.get('[data-cy=context-menu]')
724 .should('contain', 'Add to favorites')
725 .and('contain', 'Make a copy')
726 .and('contain', 'Edit collection');
727 cy.get('body').click();
728 // (and now an old version...)
729 cy.get('[data-cy=collection-version-browser-select-1]').rightclick()
730 cy.get('[data-cy=context-menu]')
731 .should('not.contain', 'Add to favorites')
732 .and('contain', 'Make a copy')
733 .and('not.contain', 'Edit collection');
734 cy.get('body').click();
736 // Restore first version
737 cy.get('[data-cy=collection-version-browser]').within(() => {
738 cy.get('[data-cy=collection-version-browser-select-1]').click();
740 cy.get('[data-cy=collection-panel-options-btn]').click()
741 cy.get('[data-cy=context-menu]').contains('Restore version').click();
742 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Restore version');
743 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
744 cy.get('[data-cy=collection-info-panel]')
745 .should('not.contain', 'This is an old version');
746 cy.get('[data-cy=collection-version-number]').should('contain', '4');
747 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
748 cy.get('[data-cy=collection-files-panel]')
749 .should('contain', 'foo').and('contain', 'bar');
753 it('creates new collection with properties on home project', function () {
754 cy.loginAs(activeUser);
755 cy.goToPath(`/projects/${activeUser.user.uuid}`);
756 cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
757 cy.get('[data-cy=breadcrumb-last]').should('not.exist');
758 // Create new collection
759 cy.get('[data-cy=side-panel-button]').click();
760 cy.get('[data-cy=side-panel-new-collection]').click();
761 // Name between brackets tests bugfix #17582
762 const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
764 // Select a storage class.
765 cy.get('[data-cy=form-dialog]')
766 .should('contain', 'New collection')
767 .and('contain', 'Storage classes')
768 .and('contain', 'default')
769 .and('contain', 'foo')
770 .and('contain', 'bar')
772 cy.get('[data-cy=parent-field]').within(() => {
773 cy.get('input').should('have.value', 'Home project');
775 cy.get('[data-cy=name-field]').within(() => {
776 cy.get('input').type(collName);
778 cy.get('[data-cy=checkbox-foo]').click();
782 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
783 cy.get('[data-cy=form-dialog]').should('not.contain', 'Color: Magenta');
784 cy.get('[data-cy=resource-properties-form]').within(() => {
785 cy.get('[data-cy=property-field-key]').within(() => {
786 cy.get('input').type('Color');
788 cy.get('[data-cy=property-field-value]').within(() => {
789 cy.get('input').type('Magenta');
793 // Confirm proper vocabulary labels are displayed on the UI.
794 cy.get('[data-cy=form-dialog]').should('contain', 'Color: Magenta');
796 cy.get('[data-cy=form-submit-btn]').click();
797 // Confirm that the user was taken to the newly created collection
798 cy.get('[data-cy=form-dialog]').should('not.exist');
799 cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
800 cy.get('[data-cy=breadcrumb-last]').should('contain', collName);
801 cy.get('[data-cy=collection-info-panel]')
802 .should('contain', 'default')
803 .and('contain', 'foo')
804 .and('contain', 'Color: Magenta')
805 .and('not.contain', 'bar');
806 // Confirm that the collection's properties has the real values.
807 cy.doRequest('GET', '/arvados/v1/collections', null, {
808 filters: `[["name", "=", "${collName}"]]`,
810 .its('body.items').as('collections')
812 expect(this.collections).to.have.lengthOf(1);
813 expect(this.collections[0].properties).to.have.property(
814 'IDTAGCOLORS', 'IDVALCOLORS3');
818 it('shows responsible person for collection if available', () => {
819 cy.createCollection(adminUser.token, {
820 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
821 owner_uuid: activeUser.user.uuid,
822 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
824 .as('testCollection1');
826 cy.createCollection(adminUser.token, {
827 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
828 owner_uuid: adminUser.user.uuid,
829 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
831 .as('testCollection2').then(function (testCollection2) {
832 cy.shareWith(adminUser.token, activeUser.user.uuid, testCollection2.uuid, 'can_write');
835 cy.getAll('@testCollection1', '@testCollection2')
836 .then(function ([testCollection1, testCollection2]) {
837 cy.loginAs(activeUser);
839 cy.goToPath(`/collections/${testCollection1.uuid}`);
840 cy.get('[data-cy=responsible-person-wrapper]')
841 .contains(activeUser.user.uuid);
843 cy.goToPath(`/collections/${testCollection2.uuid}`);
844 cy.get('[data-cy=responsible-person-wrapper]')
845 .contains(adminUser.user.uuid);
849 describe('file upload', () => {
851 cy.createCollection(adminUser.token, {
852 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
853 owner_uuid: activeUser.user.uuid,
854 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
856 .as('testCollection1');
859 it('allows to cancel running upload', () => {
860 cy.getAll('@testCollection1')
861 .then(function([testCollection1]) {
862 cy.loginAs(activeUser);
864 cy.goToPath(`/collections/${testCollection1.uuid}`);
866 cy.get('[data-cy=upload-button]').click();
868 cy.fixture('files/5mb.bin', 'base64').then(content => {
869 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
870 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
872 cy.get('[data-cy=form-submit-btn]').click();
874 cy.get('button').contains('Cancel').click();
876 cy.get('[data-cy=form-submit-btn]').should('not.exist');
881 it('allows to cancel single file from the running upload', () => {
882 cy.getAll('@testCollection1')
883 .then(function([testCollection1]) {
884 cy.loginAs(activeUser);
886 cy.goToPath(`/collections/${testCollection1.uuid}`);
888 cy.get('[data-cy=upload-button]').click();
890 cy.fixture('files/5mb.bin', 'base64').then(content => {
891 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
892 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
894 cy.get('[data-cy=form-submit-btn]').click();
896 cy.get('button[aria-label=Remove]').eq(1).click();
898 cy.get('[data-cy=form-submit-btn]').should('not.exist');
900 cy.get('[data-cy=collection-files-panel]').contains('5mb_a.bin').should('exist');
905 it('allows to cancel all files from the running upload', () => {
906 cy.getAll('@testCollection1')
907 .then(function([testCollection1]) {
908 cy.loginAs(activeUser);
910 cy.goToPath(`/collections/${testCollection1.uuid}`);
912 cy.get('[data-cy=upload-button]').click();
914 cy.fixture('files/5mb.bin', 'base64').then(content => {
915 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
916 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
918 cy.get('[data-cy=form-submit-btn]').click();
920 cy.get('button[aria-label=Remove]').should('exist');
921 cy.get('button[aria-label=Remove]').click({ multiple: true, force: true });
923 cy.get('[data-cy=form-submit-btn]').should('not.exist');