From 01d98d68b116d44b42241928bc855d1b9b1067df Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Fri, 23 Jul 2021 08:20:51 +0100 Subject: [PATCH 1/8] added drag and drop test --- cypress/integration/07_drag_and_drop_spec.js | 209 +++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 cypress/integration/07_drag_and_drop_spec.js diff --git a/cypress/integration/07_drag_and_drop_spec.js b/cypress/integration/07_drag_and_drop_spec.js new file mode 100644 index 00000000..24ba154a --- /dev/null +++ b/cypress/integration/07_drag_and_drop_spec.js @@ -0,0 +1,209 @@ +/// +// TODO: add spies +import { FSApi } from '../../fusion-studio-extension/src/common/api'; +context('Fusion Studio', () => { + describe('Drag and drop', () => { + let dataTransfer; + const connection = { + server: Cypress.env('API_HOST'), + username: 'admin', + password: '', + }; + before(() => { + // prepare collections/documents used in the test + new Cypress.Promise(async resolve => { + await FSApi.remove(connection, '/db/test_col', true).catch(e => { }); + await FSApi.newCollection(connection, '/db/test_col'); + await FSApi.newCollection(connection, '/db/test_col/col1'); + await FSApi.newCollection(connection, '/db/test_col/col2'); + await FSApi.newCollection(connection, '/db/test_col/col3'); + await FSApi.save(connection, '/db/test_col/col1/test.txt', 'test text file'); + resolve(); + }) + cy.connect() + cy.visit('/') + cy.get('.fusion-view') + .should('be.visible') + .find('.fusion-item') + .click() + cy.get('.fusion-view') + .contains('db') + .click() + cy.get('.fusion-view') + .contains('test_col') + .click() + cy.get('.fusion-view') + .contains('col1') + .click() + cy.get('.fusion-view') + .contains('col2') + .click() + cy.get('.fusion-view') + .contains('col3') + .click() + cy.get('.fa-spinner') + .should('not.exist') + }) + beforeEach(() => { + cy.window().then(win => cy.spy(win, 'fetch').as('fetch')); + dataTransfer = new DataTransfer(); + }); + after(() => { + // clean the test colelction + new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test_col', true).then(resolve).catch(resolve)) + }) + + it('should move a document', () => { + cy.get('[node-id$="col1\\/test.txt"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=col2]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('[node-id$="col1\\/test.txt"]') + .should('not.exist') + cy.get('[node-id$="col2\\/test.txt"]') + .should('be.visible') + }) + + it('should copy a document', () => { + cy.get('[node-id$="col2\\/test.txt"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('[node-id$="col1\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="col2\\/test.txt"]') + .should('be.visible') + }) + + it('should move a collection', () => { + cy.get('[node-id$="test_col\\/col1"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=col2]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('[node-id$="test_col\\/col1"]') + .should('not.exist') + cy.get('[node-id$="col2\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="col2\\/col1\\/test.txt"]') + .should('be.visible') + }) + + it('should copy a collection', () => { + cy.get('[node-id$="test_col\\/col2\\/col1"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test_col]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('[node-id$="test_col\\/col2\\/col1"]') + .should('be.visible') + cy.get('[node-id$="col2\\/col1\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="test_col\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col\\/col1\\/test.txt"]') + .should('be.visible') + }) + + it('should move multiple items', () => { + cy.get('[node-id$="test_col\\/col2"]') + .should('be.visible') + .trigger('click') + cy.get('[node-id$="test_col\\/col1"]') + .should('be.visible') + .trigger('click', { ctrlKey: true }) + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=col3]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('[node-id$="test_col\\/col1"]') + .should('not.exist') + cy.get('[node-id$="test_col\\/col2"]') + .should('not.exist') + cy.get('[node-id$="col3\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="col3\\/col1\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="col3\\/col2"]') + .should('be.visible') + .click() + cy.get('[node-id$="col3\\/col2\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="col3\\/col2\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="col3\\/col2\\/col1\\/test.txt"]') + .should('be.visible') + }) + + it('should copy multiple items', () => { + cy.get('[node-id$="test_col\\/col3\\/col2"]') + .should('be.visible') + .trigger('click') + cy.get('[node-id$="test_col\\/col3\\/col1"]') + .should('be.visible') + .trigger('click', { ctrlKey: true }) + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test_col]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('[node-id$="test_col\\/col3\\/col1"]') + .should('be.visible') + cy.get('[node-id$="test_col\\/col3\\/col2"]') + .should('be.visible') + cy.get('[node-id$="test_col\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col\\/col1\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="test_col\\/col2"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col\\/col2\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="test_col\\/col2\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col\\/col2\\/col1\\/test.txt"]') + .should('be.visible') + }) + + it('should not move to a sub collection', () => { + cy.get('[node-id$="test_col\\/col2"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test_col\\/col2\\/col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('[node-id$="test_col\\/col2\\/col1\\/col2"]') + .should('not.exist') + cy.get('[node-id$="test_col\\/col2"]') + .should('be.visible') + }) + + it('should not copy to a sub collection', () => { + cy.get('[node-id$="test_col\\/col2"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test_col\\/col2\\/col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('[node-id$="test_col\\/col2\\/col1\\/col2"]') + .should('not.exist') + cy.get('[node-id$="test_col\\/col2"]') + .should('be.visible') + }) + + }) + + +}) \ No newline at end of file From 73bcf6374453fdd1bdd7c9f9ba1754e9bd60c581 Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Fri, 23 Jul 2021 08:20:51 +0100 Subject: [PATCH 2/8] move drag and drop tests to documents and collections specs --- cypress/integration/04_document_spec.js | 371 ++++++++++----- cypress/integration/05_collection_spec.js | 446 +++++++++++++------ cypress/integration/07_drag_and_drop_spec.js | 209 --------- 3 files changed, 560 insertions(+), 466 deletions(-) delete mode 100644 cypress/integration/07_drag_and_drop_spec.js diff --git a/cypress/integration/04_document_spec.js b/cypress/integration/04_document_spec.js index 22a5e3e7..08de8902 100644 --- a/cypress/integration/04_document_spec.js +++ b/cypress/integration/04_document_spec.js @@ -1,140 +1,275 @@ /// +import { FSApi } from '../../fusion-studio-extension/src/common/api'; context('Document Operations', () => { describe('working with tree view', () => { + let fetchSpy; + const connection = { + server: Cypress.env('API_HOST'), + username: 'admin', + password: '', + }; before(() => { + new Cypress.Promise(async resolve => { + await FSApi.remove(connection, '/db/test', true).catch(e => { }); + await FSApi.newCollection(connection, '/db/test'); + await FSApi.newCollection(connection, '/db/test/col1'); + await FSApi.save(connection, '/db/test/col1/doc1', ''); + await FSApi.save(connection, '/db/test/col1/doc2', ''); + resolve(); + }) cy.connect() - cy.visit('/') + cy.visit('/', { + onBeforeLoad: win => fetchSpy = cy.spy(win, 'fetch'), + }) cy.get(`[node-id=${CSS.escape('admin@' + Cypress.env('API_HOST'))}]`) - // TODO(DP): might have to improve by adding more before / after hooks to prevent dangling documents - // see #400 + // TODO(DP): might have to improve by adding more before / after hooks to prevent dangling documents + // see #400 + }) + after(() => { + // delete the test colelction + // new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test', true).then(resolve).catch(resolve)) }) + it('should display creation options', () => { + cy.get('.fusion-view', { timeout: 55000 }) + .should('be.visible') + cy.get('.fusion-item') + .click() + // (DP): start workaround for #413 + cy.get('[node-id$=db]') + .click() + .prev().should('not.have.class', 'fa-spin').wait(1) + // (DP): end workaround for #413 + // all we need is the final part of the node-id attribute + cy.get('[node-id$=test]') + .click() + .prev().should('not.have.class', 'fa-spin').wait(1) + cy.get('[node-id$=test]') + .rightclick() + cy.get('.p-Menu') + .should('be.visible') + .contains('New document...') + .trigger('mousemove') + cy.get('[data-command="fusion.new-document"]') + .contains('Empty document') + .click() + cy.focused() + .type('untitled_1{enter}') + cy.get('.fusion-view') + .contains('untitled_1') - describe('db context menu', () => { - it('should display creation options', () => { - cy.get('.ReactVirtualized__Grid', { timeout: 55000 }) - .should('be.visible') - cy.get('.fusion-item') - .click() - // all we need is the final part of the node-id attribute - cy.get('[node-id$=db]') - .rightclick() - .then(() => { - cy.get('.p-Menu') - .should('be.visible') - .contains('New document') - .trigger('mousemove') - cy.get('[data-command="fusion.new-document"] > .p-Menu-itemLabel') - .should('be.visible') - .click() - }) - // (DP): start workaround for #413 - cy.get('[node-id$=db]') - .trigger('mousemove') - .type('{enter}') - // end workaround for #413 - cy.get('.ReactVirtualized__Grid') - .contains('untitled-1') + // TODO(DP): + // - add test for #413 : change order, remove workaround, might need a call to focused() + // - check if tree view is deselected (it is but need not be), + // - check if Explorer is updated properly (seems inconsistent need to double click) + // - check if editor window is opening the newly create doc in a new tab (it doesn't) + // - two doc create routes one with follow-up dialog (xquery lib) one without (txt, xml) + }) - // TODO(DP): - // - add test for #413 : change order, remove workaround, might need a call to focused() - // - check if tree view is deselected (it is but need not be), - // - check if Explorer is updated properly (seems inconsistent need to double click) - // - check if editor window is opening the newly create doc in a new tab (it doesn't) - // - two file create routes one with follow-up dialog (xquery lib) one without (txt, xml) - }) + // see https://github.com/cypress-io/cypress/pull/15388/docs# + // see #414 + + it('should let users edit new document', () => { + cy.get('[node-id$=untitled_1]') + .dblclick() + if (Cypress.platform === 'darwin') { + cy.get('.view-line') + .type('asdf{meta+s}') + } else { + cy.get('.view-line') + .type('asdf{ctrl+s}') + } + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/untitled_1', { + method: 'PUT', + body: 'asdf', + }); + }) + // see #414 workaround is to run this after editing and saving the document, + // we should be able to rename before entering content + it('should let users rename documents', () => { + cy.get('[node-id$=untitled_1]') + .rightclick() + cy.get('[data-command="fusion.rename"]') + .should('be.visible') + .contains('Rename') + .click() + .focused() + .type('test.txt{enter}') + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/test.txt', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/untitled_1' }, + }); + }) + + it('should display document properties', () => { + cy.get('[node-id$="test.txt"]') + .rightclick() + cy.get('.p-Menu') + .should('be.visible') + .find('[data-command="fusion.properties"]') + .contains('Properties...') + .click(); + cy.get('.dialogTitle') + .should('contain.text', 'Properties') + // rename doc -> text.xml + cy.get('.value > .theia-input') + .clear() + .type('test.xml') + // check properties table + cy.get('.dialogContent') + .find('.keys > tr') + .should('have.length', 11) + .contains('Media Type') + cy.get('.dialogContent') + .find('.keys > tr') + .contains('Owner') + // check permissions table + cy.get('.dialogContent') + .find('.permissions-editor > tr') + .should('have.length', 3) + .contains('user') + cy.get('.main') + .click() + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/test.xml', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/test.txt' }, + }); + }) - // see https://github.com/cypress-io/cypress/pull/15388/files# - // see #414 + it('should not create duplicate documents', () => { + cy.get('[node-id$=test]') + .rightclick() + cy.get('.p-Menu') + .should('be.visible') + .contains('New document') + .trigger('mousemove') + cy.get('[data-command="fusion.new-document-template:xml"] > .p-Menu-itemLabel') + .should('be.visible') + .click() + cy.get('.fs-inline-input > .theia-input') + .clear() + .type('test.xml{enter}') + cy.get('.error') + .should('exist') + .should('contain.text', 'Item already exists') + }) - it('should let users edit new document', () => { - cy.get('[node-id$=untitled-1]') - .dblclick() - if( Cypress.platform === 'darwin') { - cy.get('.view-line') - .type('asdf{meta+s}') - } else { - cy.get('.view-line') - .type('asdf{ctrl+s}') - } - }) - // see #414 workaround is to run this after editing and saving the document, - // we should be able to rename before entering content - it('should let users rename documents', () => { - cy.get('[node-id$=untitled-1]') - .rightclick() - cy.get('[data-command="fusion.rename"] > .p-Menu-itemLabel') - .should('be.visible') - .click() - .focused() - .type('test.txt{enter}') + it('should move a document', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test\\/test.xml"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/col1/test.xml', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/test.xml' }, }) + cy.get('[node-id$="test\\/test.xml"]') + .should('not.exist') + cy.get('[node-id$="col1\\/test.xml"]') + .should('be.visible') + }) - it('should display document properties', () => { - cy.get('[node-id$=test\\.txt]') - .rightclick() - .type('{alt+enter}', { force: true }) - cy.get('.dialogTitle') - .should('contain.text', 'Properties') - // rename file -> text.xml - cy.get('.value > .theia-input') - .clear() - .type('test.xml') - // check properties table - cy.get('.dialogContent') - .find('.keys > tr') - .should('have.length', 11) - .contains('Media Type') - cy.get('.dialogContent') - .find('.keys > tr') - .contains('Owner') - // check permissions table - cy.get('.dialogContent') - .find('.permissions-editor > tr') - .should('have.length', 3) - .contains('user') - cy.get('.main') - .click() + it('should copy a document', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="col1\\/test.xml"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/test.xml', { + method: 'PUT', + headers: { 'x-fs-copy-source': '/db/test/col1/test.xml' }, }) + cy.get('[node-id$="col1\\/test.xml"]') + .should('be.visible') + cy.get('[node-id$="test\\/test.xml"]') + .should('be.visible') + }) - it('should not create duplicate documents', () => { - cy.get('[node-id$=db]') - .rightclick() - .then(() => { - cy.get('.p-Menu') - .should('be.visible') - .contains('New document') - .trigger('mousemove') - cy.get('[data-command="fusion.new-document-template:xml"] > .p-Menu-itemLabel') - .should('be.visible') - .click() - cy.get('.fs-inline-input > .theia-input') - .clear() - .type('test.xml{enter}') - cy.get('.error') - .should('exist') - .should('contain.text', 'Item already exists') - }) + it('should move more than one document', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="col1\\/doc1"]') + .should('be.visible') + .click() + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$="col1\\/doc2"]') + .should('be.visible') + .click({ ctrlKey: true }) + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/doc1', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/col1/doc1' }, + }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/doc2', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/col1/doc2' }, }) + cy.get('[node-id$="col1\\/doc1"]') + .should('not.exist') + cy.get('[node-id$="col1\\/doc2"]') + .should('not.exist') + cy.get('[node-id$="test\\/doc1"]') + .should('be.visible') + cy.get('[node-id$="test\\/doc2"]') + .should('be.visible') + }) - it('should let users delete documents', () => { - cy.get('[node-id$=test\\.xml]') - .rightclick() - cy.get('[data-command="fusion.delete"] > .p-Menu-itemLabel') - .should('be.visible') - .click() - cy.get('.main') - .click() - // make sure all test files are gone see #400 - cy.get('[node-id$=untitled-1]') - .should('not.exist') - cy.get('[node-id$=test\\.txt]') - .should('not.exist') - cy.get('[node-id$=test\\.xml]') - .should('not.exist') - cy.get('[node-id$=untitled-2]') - .should('not.exist') + it('should copy more than one document', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test\\/doc1"]') + .should('be.visible') + .click() + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$="test\\/doc2"]') + .should('be.visible') + .click({ ctrlKey: true }) + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/col1/doc1', { + method: 'PUT', + headers: { 'x-fs-copy-source': '/db/test/doc1' }, + }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/col1/doc2', { + method: 'PUT', + headers: { 'x-fs-copy-source': '/db/test/doc2' }, }) + cy.get('[node-id$="test\\/doc1"]') + .should('be.visible') + cy.get('[node-id$="test\\/doc2"]') + .should('be.visible') + cy.get('[node-id$="col1\\/doc1"]') + .should('be.visible') + cy.get('[node-id$="col1\\/doc2"]') + .should('be.visible') + }) + + it('should let users delete documents', () => { + cy.get('[node-id$="test\\/test.xml"]') + .rightclick() + cy.get('[data-command="fusion.delete"] > .p-Menu-itemLabel') + .should('be.visible') + .click() + cy.get('.main') + .click() + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/test.xml', { method: 'DELETE' }); + // make sure all test docs are gone see #400 + cy.get('[node-id$=test\\/untitled_1]') + .should('not.exist') + cy.get('[node-id$="test\\/test.txt"]') + .should('not.exist') + cy.get('[node-id$="test\\/test.xml"]') + .should('not.exist') + cy.get('[node-id$=test\\/untitled-2]') + .should('not.exist') }) }) }) \ No newline at end of file diff --git a/cypress/integration/05_collection_spec.js b/cypress/integration/05_collection_spec.js index 3a561b75..fcf2a190 100644 --- a/cypress/integration/05_collection_spec.js +++ b/cypress/integration/05_collection_spec.js @@ -1,164 +1,332 @@ /// +import { FSApi } from '../../fusion-studio-extension/src/common/api'; context('Collection Operations', () => { - let fetchSpy; describe('working with tree view', () => { + const connection = { + server: Cypress.env('API_HOST'), + username: 'admin', + password: '', + }; before(() => { + // prepare collections used in the test + new Cypress.Promise(async resolve => { + await FSApi.remove(connection, '/db/test', true).catch(e => { }); + await FSApi.newCollection(connection, '/db/test'); + await FSApi.newCollection(connection, '/db/test/col1'); + await FSApi.save(connection, '/db/test/col1/test.txt', 'test text file'); + resolve(); + }) cy.connect() cy.visit('/'); }) beforeEach(() => { - cy.window().then(win => fetchSpy = cy.spy(win, 'fetch').as('fetch')); + cy.window().then(win => cy.spy(win, 'fetch').as('fetch')); + }) + after(() => { + // delete the test colelction + new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test', true).then(resolve).catch(resolve)) }) - describe('db context menu', () => { - it('should display creation options', () => { - cy.get('.fusion-view') - .should('be.visible') - cy.get('.fusion-item') - .click() - // all we need is the final part of the node-id attribute - // (DP): start workaround for #413 - cy.get('[node-id$=db]') - .click() - cy.get('.fa-spinner') - .should('not.exist') - // (DP): end workaround for #413 - cy.get('@fetch').should('be.calledWith', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db'); - cy.get('[node-id$=db]') - .rightclick(); - cy.get('.p-Menu') - .should('be.visible') - .find('[data-command="fusion.new-collection"]') - .should('be.visible') - .contains('New collection') - .click() - cy.focused() - .type('{enter}') - cy.get('.fusion-view') - .contains('untitled-1') - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/untitled-1', { method: 'PUT' }); - }) + it('should display creation options', () => { + cy.get('.fusion-view') + .should('be.visible') + cy.get('.fusion-item') + .click() + // all we need is the final part of the node-id attribute + // (DP): start workaround for #413 + cy.get('[node-id$=db]') + .click() + .prev().should('not.have.class', 'fa-spin') + // (DP): end workaround for #413 + cy.get('[node-id$=test]') + .click() + .prev().should('not.have.class', 'fa-spin') + cy.get('@fetch').should('be.calledWith', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db'); + cy.get('@fetch').should('be.calledWith', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db/test'); + cy.get('[node-id$=test]') + .rightclick(); + cy.get('.p-Menu') + .should('be.visible') + .find('[data-command="fusion.new-collection"]') + .should('be.visible') + .contains('New collection') + .click() + cy.focused() + .type('{enter}') + cy.get('.fusion-view') + .contains('untitled-1') + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/untitled-1', { method: 'PUT' }); + }) + + it('should let users rename collection', () => { + cy.get('[node-id$=untitled-1]') + .rightclick() + cy.get('[data-command="fusion.rename"]') + .should('be.visible') + .contains('Rename') + .click() + cy.focused() + .type('test_col{enter}') + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/untitled-1' }, + }); + cy.get('.fusion-view') + .contains('test_col') + cy.get('[node-id$=untitled-1]') + .should('not.exist') + }) + + it('should display collection properties', () => { + cy.get('[node-id$=test_col]') + .click() + .type('{alt+enter}', { force: true }) + cy.get('.dialogTitle') + .should('contain.text', 'Properties') + // rename file -> text.xml + cy.get('.value > .theia-input') + .should('have.value', 'test_col') + .clear() + .type('test_col2') + // check properties table + cy.get('.dialogContent') + .find('.keys > tr') + .should('have.length', 7) + .should('contain', 'Created') + .should('contain', 'Owner') + .should('contain', 'Group') + // check permissions table + cy.get('.dialogContent') + .find('.permissions-editor > tr') + .should('have.length', 3) + .should('contain', 'user') + .should('contain', 'group') + .should('contain', 'other') + cy.get('.main') + .click() + cy.get('.dialogBlock') + .should('not.exist'); + cy.get('[node-id$=test_col2]') + .should('exist') + cy.get('[node-id$=test_col]') + .should('not.exist') + }) + + it('should not create duplicate collection', () => { + cy.get('[node-id$=test]') + .rightclick() + .then(() => { + cy.get('.p-Menu') + .should('be.visible') + .contains('New collection') + .trigger('mousemove') + cy.get('[data-command="fusion.new-collection"]') + .should('be.visible') + .click() + cy.focused() + .clear() + .type('test_col2{enter}') + cy.get('.error') + .should('exist') + .should('contain.text', 'Item already exists') + }) + }) + + it('should create nested collection', () => { + cy.get('[node-id$=test_col2]') + .click() + .rightclick() + cy.get('.p-Menu') + .should('be.visible') + .contains('New collection') + cy.get('[data-command="fusion.new-collection"]') + .should('be.visible') + .click() + cy.focused() + .clear() + .type('test_colA{enter}') + // TODO(DP): we migh want to check the proper nesting more explicitely, + // but that is already covered by checking for this collection after deleting + // its parent collection + cy.get('.fusion-view') + .contains('test_colA') + .should('exist') + }) - it('should let users rename collection', () => { - cy.get('[node-id$=untitled-1]') - .rightclick() - cy.get('[data-command="fusion.rename"]') - .should('be.visible') - .contains('Rename') - .click() - cy.focused() - .type('test_col{enter}') - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test_col', { - method: 'PUT', - headers: { 'x-fs-move-source': '/db/untitled-1' }, - }); - cy.get('.fusion-view') - .contains('test_col') - cy.get('[node-id$=untitled-1]') - .should('not.exist') + it('should move a collection', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test\\/col1"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test_col2]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/col1', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/col1' }, }) + cy.get('[node-id$="test_col\\/col1"]') + .should('not.exist') + cy.get('[node-id$="test_col2\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col2\\/col1\\/test.txt"]') + .should('be.visible') + }) - it('should display collection properties', () => { - cy.get('[node-id$=test_col]') - .click() - .type('{alt+enter}', { force: true }) - cy.get('.dialogTitle') - .should('contain.text', 'Properties') - // rename file -> text.xml - cy.get('.value > .theia-input') - .should('have.value', 'test_col') - .clear() - .type('test_col2') - // check properties table - cy.get('.dialogContent') - .find('.keys > tr') - .should('have.length', 7) - .should('contain', 'Created') - .should('contain', 'Owner') - .should('contain', 'Group') - // check permissions table - cy.get('.dialogContent') - .find('.permissions-editor > tr') - .should('have.length', 3) - .should('contain', 'user') - .should('contain', 'group') - .should('contain', 'other') - cy.get('.main') - .click() - cy.get('.dialogBlock') - .should('not.exist'); - cy.get('[node-id$=test_col2]') - .should('exist') - cy.get('[node-id$=test_col]') - .should('not.exist') + it('should copy a collection', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test\\/test_col2\\/col1"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1', { + method: 'PUT', + headers: { 'x-fs-copy-source': '/db/test/test_col2/col1' }, }) + cy.get('[node-id$="test\\/test_col2\\/col1"]') + .should('be.visible') + cy.get('[node-id$="test\\/test_col2\\/col1\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="test\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test\\/col1\\/test.txt"]') + .should('be.visible') + }) - it('should not create duplicate collection', () => { - cy.get('[node-id$=db]') - .rightclick() - .then(() => { - cy.get('.p-Menu') - .should('be.visible') - .contains('New collection') - .trigger('mousemove') - cy.get('[data-command="fusion.new-collection"]') - .should('be.visible') - .click() - cy.focused() - .clear() - .type('test_col2{enter}') - cy.get('.error') - .should('exist') - .should('contain.text', 'Item already exists') - }) + it('should move more than one collection', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test_col2\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col2\\/test_colA"]') + .should('be.visible') + .click({ ctrlKey: true }) + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test\\/col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/test_col2/col1' }, }) + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/test_colA', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/test_col2/test_colA' }, + }) + cy.get('[node-id$="test_col2\\/col1"]') + .should('not.exist') + cy.get('[node-id$="test_col2\\/test_colA"]') + .should('not.exist') + cy.get('[node-id$="col1\\/test_colA"]') + .should('be.visible') + cy.get('[node-id$="col1\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="col1\\/col1\\/test.txt"]') + .should('be.visible') + }) - it('should create nested collection', () => { - cy.get('[node-id$=test_col2]') - .click() - .rightclick() - cy.get('.p-Menu') - .should('be.visible') - .contains('New collection') - cy.get('[data-command="fusion.new-collection"]') - .should('be.visible') - .click() - cy.focused() - .clear() - .type('test_colA{enter}') - // TODO(DP): we migh want to check the proper nesting more explicitely, - // but that is already covered by checking for this collection after deleting - // its parent collection - cy.get('.fusion-view') - .contains('test_colA') + it('should copy more than one collection', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="col1\\/test_colA"]') + .should('be.visible') + .click() + cy.get('[node-id$="col1\\/col1"]') + .should('be.visible') + .click({ ctrlKey: true }) + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test\\/test_col2]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/col1', { + method: 'PUT', + headers: { 'x-fs-copy-source': '/db/test/col1/col1' }, + }) + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/test_colA', { + method: 'PUT', + headers: { 'x-fs-copy-source': '/db/test/col1/test_colA' }, }) + cy.get('[node-id$="col1\\/col1"]') + .should('be.visible') + cy.get('[node-id$="col1\\/test_colA"]') + .should('be.visible') + cy.get('[node-id$="col1\\/col1\\/test.txt"]') + .should('be.visible') + cy.get('[node-id$="test_col2\\/test_colA"]') + .should('be.visible') + cy.get('[node-id$="test_col2\\/col1"]') + .should('be.visible') + .click() + cy.get('[node-id$="test_col2\\/col1\\/test.txt"]') + .should('be.visible') + }) + it('should not move a collection to one of its sub-collections', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test\\/col1"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test\\/col1\\/col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + cy.get('@fetch').should('not.be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1/col1', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/test/col1' }, + }) + cy.get('[node-id$="test\\/col1"]') + .should('be.visible') + cy.get('[node-id$="test\\/col1\\/col1\\/col1"]') + .should('not.exist') + cy.get('[node-id$="col1\\/col1\\/test.txt"]') + .should('be.visible') + }) - it('should let users delete collection', () => { - cy.get('[node-id$=test_col2]') - .rightclick() - cy.get('[data-command="fusion.delete"]') - .should('be.visible') - .contains('Delete') - .click() - cy.get('.main') - .click() - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test_col2', { method: 'DELETE' }); - // make sure all test files are gone see #400, including those produced by failed create commands - cy.get('[node-id$=untitled-1]') - .should('not.exist') - cy.get('[node-id$=untitled-2]') - .should('not.exist') - cy.get('[node-id$=test_col]') - .should('not.exist') - cy.get('[node-id$=test_col1]') - .should('not.exist') - cy.get('[node-id$=test_col2]') - .should('not.exist') - cy.get('[node-id$=test_colA]') - .should('not.exist') + it('should not copy a collection to one of its sub-collections', () => { + const dataTransfer = new DataTransfer(); + cy.get('[node-id$="test\\/col1"]') + .should('be.visible') + .trigger('dragstart', { dataTransfer }) + cy.get('[node-id$=test\\/col1\\/col1]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer, ctrlKey: true }) + cy.get('@fetch').should('not.be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1/col1', { + method: 'PUT', + headers: { 'x-fs-move-source': '/db/test/test/col1' }, }) + cy.get('[node-id$="test\\/col1"]') + .should('be.visible') + cy.get('[node-id$="test\\/col1\\/col1\\/col1"]') + .should('not.exist') + cy.get('[node-id$="col1\\/col1\\/test.txt"]') + .should('be.visible') + }) + + it('should let users delete collection', () => { + cy.get('[node-id$=test_col2]') + .rightclick() + cy.get('[data-command="fusion.delete"]') + .should('be.visible') + .contains('Delete') + .click() + cy.get('.main') + .click() + cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2', { method: 'DELETE' }); + // make sure all test files are gone see #400, including those produced by failed create commands + cy.get('[node-id$=untitled-1]') + .should('not.exist') + cy.get('[node-id$=untitled-2]') + .should('not.exist') + cy.get('[node-id$=test_col]') + .should('not.exist') + cy.get('[node-id$=test_col1]') + .should('not.exist') + cy.get('[node-id$=test_col2]') + .should('not.exist') }) }) }) \ No newline at end of file diff --git a/cypress/integration/07_drag_and_drop_spec.js b/cypress/integration/07_drag_and_drop_spec.js deleted file mode 100644 index 24ba154a..00000000 --- a/cypress/integration/07_drag_and_drop_spec.js +++ /dev/null @@ -1,209 +0,0 @@ -/// -// TODO: add spies -import { FSApi } from '../../fusion-studio-extension/src/common/api'; -context('Fusion Studio', () => { - describe('Drag and drop', () => { - let dataTransfer; - const connection = { - server: Cypress.env('API_HOST'), - username: 'admin', - password: '', - }; - before(() => { - // prepare collections/documents used in the test - new Cypress.Promise(async resolve => { - await FSApi.remove(connection, '/db/test_col', true).catch(e => { }); - await FSApi.newCollection(connection, '/db/test_col'); - await FSApi.newCollection(connection, '/db/test_col/col1'); - await FSApi.newCollection(connection, '/db/test_col/col2'); - await FSApi.newCollection(connection, '/db/test_col/col3'); - await FSApi.save(connection, '/db/test_col/col1/test.txt', 'test text file'); - resolve(); - }) - cy.connect() - cy.visit('/') - cy.get('.fusion-view') - .should('be.visible') - .find('.fusion-item') - .click() - cy.get('.fusion-view') - .contains('db') - .click() - cy.get('.fusion-view') - .contains('test_col') - .click() - cy.get('.fusion-view') - .contains('col1') - .click() - cy.get('.fusion-view') - .contains('col2') - .click() - cy.get('.fusion-view') - .contains('col3') - .click() - cy.get('.fa-spinner') - .should('not.exist') - }) - beforeEach(() => { - cy.window().then(win => cy.spy(win, 'fetch').as('fetch')); - dataTransfer = new DataTransfer(); - }); - after(() => { - // clean the test colelction - new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test_col', true).then(resolve).catch(resolve)) - }) - - it('should move a document', () => { - cy.get('[node-id$="col1\\/test.txt"]') - .should('be.visible') - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=col2]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer }) - cy.get('[node-id$="col1\\/test.txt"]') - .should('not.exist') - cy.get('[node-id$="col2\\/test.txt"]') - .should('be.visible') - }) - - it('should copy a document', () => { - cy.get('[node-id$="col2\\/test.txt"]') - .should('be.visible') - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=col1]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('[node-id$="col1\\/test.txt"]') - .should('be.visible') - cy.get('[node-id$="col2\\/test.txt"]') - .should('be.visible') - }) - - it('should move a collection', () => { - cy.get('[node-id$="test_col\\/col1"]') - .should('be.visible') - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=col2]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer }) - cy.get('[node-id$="test_col\\/col1"]') - .should('not.exist') - cy.get('[node-id$="col2\\/col1"]') - .should('be.visible') - .click() - cy.get('[node-id$="col2\\/col1\\/test.txt"]') - .should('be.visible') - }) - - it('should copy a collection', () => { - cy.get('[node-id$="test_col\\/col2\\/col1"]') - .should('be.visible') - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=test_col]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('[node-id$="test_col\\/col2\\/col1"]') - .should('be.visible') - cy.get('[node-id$="col2\\/col1\\/test.txt"]') - .should('be.visible') - cy.get('[node-id$="test_col\\/col1"]') - .should('be.visible') - .click() - cy.get('[node-id$="test_col\\/col1\\/test.txt"]') - .should('be.visible') - }) - - it('should move multiple items', () => { - cy.get('[node-id$="test_col\\/col2"]') - .should('be.visible') - .trigger('click') - cy.get('[node-id$="test_col\\/col1"]') - .should('be.visible') - .trigger('click', { ctrlKey: true }) - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=col3]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer }) - cy.get('[node-id$="test_col\\/col1"]') - .should('not.exist') - cy.get('[node-id$="test_col\\/col2"]') - .should('not.exist') - cy.get('[node-id$="col3\\/col1"]') - .should('be.visible') - .click() - cy.get('[node-id$="col3\\/col1\\/test.txt"]') - .should('be.visible') - cy.get('[node-id$="col3\\/col2"]') - .should('be.visible') - .click() - cy.get('[node-id$="col3\\/col2\\/test.txt"]') - .should('be.visible') - cy.get('[node-id$="col3\\/col2\\/col1"]') - .should('be.visible') - .click() - cy.get('[node-id$="col3\\/col2\\/col1\\/test.txt"]') - .should('be.visible') - }) - - it('should copy multiple items', () => { - cy.get('[node-id$="test_col\\/col3\\/col2"]') - .should('be.visible') - .trigger('click') - cy.get('[node-id$="test_col\\/col3\\/col1"]') - .should('be.visible') - .trigger('click', { ctrlKey: true }) - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=test_col]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('[node-id$="test_col\\/col3\\/col1"]') - .should('be.visible') - cy.get('[node-id$="test_col\\/col3\\/col2"]') - .should('be.visible') - cy.get('[node-id$="test_col\\/col1"]') - .should('be.visible') - .click() - cy.get('[node-id$="test_col\\/col1\\/test.txt"]') - .should('be.visible') - cy.get('[node-id$="test_col\\/col2"]') - .should('be.visible') - .click() - cy.get('[node-id$="test_col\\/col2\\/test.txt"]') - .should('be.visible') - cy.get('[node-id$="test_col\\/col2\\/col1"]') - .should('be.visible') - .click() - cy.get('[node-id$="test_col\\/col2\\/col1\\/test.txt"]') - .should('be.visible') - }) - - it('should not move to a sub collection', () => { - cy.get('[node-id$="test_col\\/col2"]') - .should('be.visible') - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=test_col\\/col2\\/col1]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer }) - cy.get('[node-id$="test_col\\/col2\\/col1\\/col2"]') - .should('not.exist') - cy.get('[node-id$="test_col\\/col2"]') - .should('be.visible') - }) - - it('should not copy to a sub collection', () => { - cy.get('[node-id$="test_col\\/col2"]') - .should('be.visible') - .trigger('dragstart', { dataTransfer }) - cy.get('[node-id$=test_col\\/col2\\/col1]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('[node-id$="test_col\\/col2\\/col1\\/col2"]') - .should('not.exist') - cy.get('[node-id$="test_col\\/col2"]') - .should('be.visible') - }) - - }) - - -}) \ No newline at end of file From 17230f07152f45ded064bf54f36bf1a72db5108c Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Fri, 23 Jul 2021 08:20:51 +0100 Subject: [PATCH 3/8] fix: node id not updated after renaming --- fusion-studio-extension/src/browser/core.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fusion-studio-extension/src/browser/core.ts b/fusion-studio-extension/src/browser/core.ts index ea78f65b..7d33d16a 100644 --- a/fusion-studio-extension/src/browser/core.ts +++ b/fusion-studio-extension/src/browser/core.ts @@ -1320,7 +1320,9 @@ export class FSCore { node.uri = name; } node.nodeName = this.getName(name);; + delete(this.dict[node.nodeId]); node.nodeId = this.ID(node); + this.dict[node.nodeId] = node; return node; } From 0a74559678316871560c4b08ebf2adac16fdb602 Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Fri, 23 Jul 2021 08:20:51 +0100 Subject: [PATCH 4/8] added drag and drop upload tests --- cypress/integration/05_collection_spec.js | 118 ++++++++++++++++---- fusion-studio-extension/src/browser/core.ts | 2 +- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/cypress/integration/05_collection_spec.js b/cypress/integration/05_collection_spec.js index fcf2a190..2e87a2ac 100644 --- a/cypress/integration/05_collection_spec.js +++ b/cypress/integration/05_collection_spec.js @@ -3,13 +3,14 @@ import { FSApi } from '../../fusion-studio-extension/src/common/api'; context('Collection Operations', () => { describe('working with tree view', () => { + let fetchSpy; const connection = { server: Cypress.env('API_HOST'), username: 'admin', password: '', }; before(() => { - // prepare collections used in the test + // prepare collections and documents used in the test new Cypress.Promise(async resolve => { await FSApi.remove(connection, '/db/test', true).catch(e => { }); await FSApi.newCollection(connection, '/db/test'); @@ -18,13 +19,44 @@ context('Collection Operations', () => { resolve(); }) cy.connect() - cy.visit('/'); - }) - beforeEach(() => { - cy.window().then(win => cy.spy(win, 'fetch').as('fetch')); + cy.visit('/', { + onBeforeLoad(win) { + fetchSpy = cy.spy(win, 'fetch') + win.ExFile = class extends win.File { + constructor(root, data, fileName, options) { + super(data, fileName, options); + this.root = root; + } + webkitGetAsEntry() { + const me = this; + return { + isDirectory: false, + isFile: true, + fullPath: this.root + this.name, + file: callback => callback(this), + }; + } + } + win.ExDir = class extends win.ExFile { + constructor(root, entries, fileName, options) { + super(root, [], fileName, options); + this.entries = entries.map(entry => entry.webkitGetAsEntry()); + } + webkitGetAsEntry() { + const me = this; + return { + isDirectory: true, + isFile: false, + fullPath: this.root + this.name, + createReader: () => ({ readEntries: callback => callback(this.entries) }), + }; + } + } + } + }); }) after(() => { - // delete the test colelction + // delete the test collection new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test', true).then(resolve).catch(resolve)) }) @@ -42,8 +74,8 @@ context('Collection Operations', () => { cy.get('[node-id$=test]') .click() .prev().should('not.have.class', 'fa-spin') - cy.get('@fetch').should('be.calledWith', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db'); - cy.get('@fetch').should('be.calledWith', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db/test'); + fetchSpy.calledWith(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db'); + fetchSpy.calledWith(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/explorer?uri=/db/test'); cy.get('[node-id$=test]') .rightclick(); cy.get('.p-Menu') @@ -56,7 +88,7 @@ context('Collection Operations', () => { .type('{enter}') cy.get('.fusion-view') .contains('untitled-1') - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/untitled-1', { method: 'PUT' }); + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/untitled-1', { method: 'PUT' }); }) it('should let users rename collection', () => { @@ -68,7 +100,7 @@ context('Collection Operations', () => { .click() cy.focused() .type('test_col{enter}') - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/untitled-1' }, }); @@ -154,6 +186,54 @@ context('Collection Operations', () => { .should('exist') }) + it('should upload a document', () => { + cy.window().then(win => { + const file = new win.ExFile('/', [new Blob(['sample text content.'])], 'test.txt', { type: 'text/plain' }) + + const originalDataTransfer = new win.DataTransfer(); + originalDataTransfer.items.add(file); + const dataTransfer = { + ...originalDataTransfer, + items: [file], + files: [file], + }; + dataTransfer.getData = (...args) => originalDataTransfer.getData(...args); + + cy.get('[node-id$=test]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/test.txt', { method: 'PUT' }) + cy.get('[node-id$="test\\/test.txt"]') + .should('be.visible') + }) + }) + + it('should upload a collection', () => { + cy.window().then(win => { + const file = new win.ExFile('/col/', [new Blob(['sample text content.'])], 'test2.txt', { type: 'text/plain' }) + const dir = new win.ExDir('/', [file], 'col') + + const originalDataTransfer = new win.DataTransfer(); + originalDataTransfer.items.add(file); + const dataTransfer = { + ...originalDataTransfer, + items: [dir], + files: [dir], + }; + dataTransfer.getData = (...args) => originalDataTransfer.getData(...args); + + cy.get('[node-id$=test]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/col', { method: 'PUT' }) + cy.get('[node-id$="test\\/col"]') + .should('be.visible') + .click() + cy.get('[node-id$="col\\/test2.txt"]') + .should('be.visible') + }) + }) + it('should move a collection', () => { const dataTransfer = new DataTransfer(); cy.get('[node-id$="test\\/col1"]') @@ -162,7 +242,7 @@ context('Collection Operations', () => { cy.get('[node-id$=test_col2]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer }) - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/col1', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/col1', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/col1' }, }) @@ -183,7 +263,7 @@ context('Collection Operations', () => { cy.get('[node-id$=test]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1', { method: 'PUT', headers: { 'x-fs-copy-source': '/db/test/test_col2/col1' }, }) @@ -210,11 +290,11 @@ context('Collection Operations', () => { cy.get('[node-id$=test\\/col1]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer }) - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/test_col2/col1' }, }) - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/test_colA', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/test_colA', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/test_col2/test_colA' }, }) @@ -243,11 +323,11 @@ context('Collection Operations', () => { cy.get('[node-id$=test\\/test_col2]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/col1', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/col1', { method: 'PUT', headers: { 'x-fs-copy-source': '/db/test/col1/col1' }, }) - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/test_colA', { + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2/test_colA', { method: 'PUT', headers: { 'x-fs-copy-source': '/db/test/col1/test_colA' }, }) @@ -274,7 +354,7 @@ context('Collection Operations', () => { cy.get('[node-id$=test\\/col1\\/col1]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer }) - cy.get('@fetch').should('not.be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1/col1', { + fetchSpy.neverCalledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1/col1', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/test/col1' }, }) @@ -294,7 +374,7 @@ context('Collection Operations', () => { cy.get('[node-id$=test\\/col1\\/col1]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer, ctrlKey: true }) - cy.get('@fetch').should('not.be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1/col1', { + fetchSpy.neverCalledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/col1/col1/col1', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/test/col1' }, }) @@ -315,7 +395,7 @@ context('Collection Operations', () => { .click() cy.get('.main') .click() - cy.get('@fetch').should('be.calledWithMatch', Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2', { method: 'DELETE' }); + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/collection?uri=/db/test/test_col2', { method: 'DELETE' }); // make sure all test files are gone see #400, including those produced by failed create commands cy.get('[node-id$=untitled-1]') .should('not.exist') diff --git a/fusion-studio-extension/src/browser/core.ts b/fusion-studio-extension/src/browser/core.ts index 7d33d16a..d479da15 100644 --- a/fusion-studio-extension/src/browser/core.ts +++ b/fusion-studio-extension/src/browser/core.ts @@ -460,7 +460,7 @@ export class FSCore { this.startLoading(node); const docs = await FSApi.saveDocuments(node.connectionNode.connection, node.collection, documents); this.endLoading(node); - this.load(node, node.uri); + this.asyncLoad(node); return docs; } catch (error) { this.endLoading(node); From fd9318b0440e62f5fdbfb5c12b486c9de0ad3ad5 Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Fri, 18 Jun 2021 08:57:30 +0100 Subject: [PATCH 5/8] split upload tests between document and collection --- cypress/integration/04_document_spec.js | 32 ++++++++++- cypress/integration/05_collection_spec.js | 68 ++++------------------- cypress/support/commands.js | 68 +++++++++++++++++------ 3 files changed, 92 insertions(+), 76 deletions(-) diff --git a/cypress/integration/04_document_spec.js b/cypress/integration/04_document_spec.js index 08de8902..d86afd63 100644 --- a/cypress/integration/04_document_spec.js +++ b/cypress/integration/04_document_spec.js @@ -28,7 +28,11 @@ context('Document Operations', () => { }) after(() => { // delete the test colelction - // new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test', true).then(resolve).catch(resolve)) + new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test', true).then(resolve).catch(resolve)) + }) + afterEach(() => { + // make sure the tree has rendered all its items properly + cy.wait(10) }) it('should display creation options', () => { cy.get('.fusion-view', { timeout: 55000 }) @@ -90,7 +94,7 @@ context('Document Operations', () => { cy.get('[node-id$=untitled_1]') .rightclick() cy.get('[data-command="fusion.rename"]') - .should('be.visible') + .should('be.visible') .contains('Rename') .click() .focused() @@ -152,6 +156,30 @@ context('Document Operations', () => { cy.get('.error') .should('exist') .should('contain.text', 'Item already exists') + cy.get('.fs-inline-input > .theia-input') + .type('{esc}') + }) + + it('should upload a document', () => { + cy.extendedFiles().then(win => { + const file = new win.ExFile('/', [new Blob(['sample text content.'])], 'upload_test.txt', { type: 'text/plain' }) + + const originalDataTransfer = new win.DataTransfer(); + originalDataTransfer.items.add(file); + const dataTransfer = { + ...originalDataTransfer, + items: [file], + files: [file], + }; + dataTransfer.getData = (...args) => originalDataTransfer.getData(...args); + + cy.get('[node-id$=test]') + .trigger('dragover', { dataTransfer }) + .trigger('drop', { dataTransfer }) + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/upload_test.txt', { method: 'PUT' }) + cy.get('[node-id$="test\\/upload_test.txt"]') + .should('be.visible') + }) }) it('should move a document', () => { diff --git a/cypress/integration/05_collection_spec.js b/cypress/integration/05_collection_spec.js index 2e87a2ac..931b7d19 100644 --- a/cypress/integration/05_collection_spec.js +++ b/cypress/integration/05_collection_spec.js @@ -22,36 +22,6 @@ context('Collection Operations', () => { cy.visit('/', { onBeforeLoad(win) { fetchSpy = cy.spy(win, 'fetch') - win.ExFile = class extends win.File { - constructor(root, data, fileName, options) { - super(data, fileName, options); - this.root = root; - } - webkitGetAsEntry() { - const me = this; - return { - isDirectory: false, - isFile: true, - fullPath: this.root + this.name, - file: callback => callback(this), - }; - } - } - win.ExDir = class extends win.ExFile { - constructor(root, entries, fileName, options) { - super(root, [], fileName, options); - this.entries = entries.map(entry => entry.webkitGetAsEntry()); - } - webkitGetAsEntry() { - const me = this; - return { - isDirectory: true, - isFile: false, - fullPath: this.root + this.name, - createReader: () => ({ readEntries: callback => callback(this.entries) }), - }; - } - } } }); }) @@ -59,6 +29,10 @@ context('Collection Operations', () => { // delete the test collection new Cypress.Promise(resolve => FSApi.remove(connection, '/db/test', true).then(resolve).catch(resolve)) }) + afterEach(() => { + // make sure the tree has rendered all its items properly + cy.wait(10) + }) it('should display creation options', () => { cy.get('.fusion-view') @@ -186,32 +160,10 @@ context('Collection Operations', () => { .should('exist') }) - it('should upload a document', () => { - cy.window().then(win => { - const file = new win.ExFile('/', [new Blob(['sample text content.'])], 'test.txt', { type: 'text/plain' }) - - const originalDataTransfer = new win.DataTransfer(); - originalDataTransfer.items.add(file); - const dataTransfer = { - ...originalDataTransfer, - items: [file], - files: [file], - }; - dataTransfer.getData = (...args) => originalDataTransfer.getData(...args); - - cy.get('[node-id$=test]') - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer }) - fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/test.txt', { method: 'PUT' }) - cy.get('[node-id$="test\\/test.txt"]') - .should('be.visible') - }) - }) - it('should upload a collection', () => { - cy.window().then(win => { - const file = new win.ExFile('/col/', [new Blob(['sample text content.'])], 'test2.txt', { type: 'text/plain' }) - const dir = new win.ExDir('/', [file], 'col') + cy.extendedFiles().then(win => { + const file = new win.ExFile('/uploaded_col/', [new Blob(['sample text content.'])], 'uploaded_test.txt', { type: 'text/plain' }) + const dir = new win.ExDir('/', [file], 'uploaded_col') const originalDataTransfer = new win.DataTransfer(); originalDataTransfer.items.add(file); @@ -225,11 +177,11 @@ context('Collection Operations', () => { cy.get('[node-id$=test]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer }) - fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/col', { method: 'PUT' }) - cy.get('[node-id$="test\\/col"]') + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/uploaded_col', { method: 'PUT' }) + cy.get('[node-id$="test\\/uploaded_col"]') .should('be.visible') .click() - cy.get('[node-id$="col\\/test2.txt"]') + cy.get('[node-id$="uploaded_col\\/uploaded_test.txt"]') .should('be.visible') }) }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 4f47715b..783f7e99 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -17,26 +17,62 @@ // assumes default 'admin' user and '' password // actual server URL is retrieved via ENV Cypress.Commands.add("connect", () => { - // conn_val mimics actual app behavior, its value is inconsequential for establishing a connection - let conn_val = 'admin@' + Cypress.env('API_HOST') - let nested = { "name": "localhost", "server": Cypress.env('API_HOST'), "username": "admin", "password": "", "users": [], "groups": [] } - let obj = {} - obj[conn_val] = nested + // conn_val mimics actual app behavior, its value is inconsequential for establishing a connection + let conn_val = 'admin@' + Cypress.env('API_HOST') + let nested = { "name": "localhost", "server": Cypress.env('API_HOST'), "username": "admin", "password": "", "users": [], "groups": [] } + let obj = {} + obj[conn_val] = nested - localStorage.setItem('connections', JSON.stringify(obj)) + localStorage.setItem('connections', JSON.stringify(obj)) }) Cypress.Commands.overwrite('visit', (orig, url, options) => { - // this is a fix to include the process variable when using the Electron browser - return orig(url, Cypress.isBrowser('electron') ? { - ...options, - onBeforeLoad(win) { - win.process = Cypress.config('process'); - if (options?.onBeforeLoad) { - options.onBeforeLoad(win); - } - } - } : options); + // this is a fix to include the process variable when using the Electron browser + return orig(url, Cypress.isBrowser('electron') ? { + ...options, + onBeforeLoad(win) { + win.process = Cypress.config('process'); + if (options?.onBeforeLoad) { + options.onBeforeLoad(win); + } + } + } : options); + +}) +Cypress.Commands.add("extendedFiles", () => { + return cy.window().then(win => { + win.ExFile = class extends win.File { + constructor(root, data, fileName, options) { + super(data, fileName, options); + this.root = root; + } + webkitGetAsEntry() { + const me = this; + return { + isDirectory: false, + isFile: true, + fullPath: this.root + this.name, + file: callback => callback(this), + }; + } + } + win.ExDir = class extends win.ExFile { + constructor(root, entries, fileName, options) { + super(root, [], fileName, options); + this.entries = entries.map(entry => entry.webkitGetAsEntry()); + } + webkitGetAsEntry() { + const me = this; + return { + isDirectory: true, + isFile: false, + fullPath: this.root + this.name, + createReader: () => ({ readEntries: callback => callback(this.entries) }), + }; + } + } + return win; + }); }) // // From 800fa001c00c0d1a73dc3fd78dba02b10c0d7096 Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Fri, 18 Jun 2021 08:57:53 +0100 Subject: [PATCH 6/8] added upload dialog test --- cypress/integration/04_document_spec.js | 34 ++++++++++++++++++++++++- cypress/plugins/index.js | 1 + 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/cypress/integration/04_document_spec.js b/cypress/integration/04_document_spec.js index d86afd63..4596b972 100644 --- a/cypress/integration/04_document_spec.js +++ b/cypress/integration/04_document_spec.js @@ -160,7 +160,7 @@ context('Document Operations', () => { .type('{esc}') }) - it('should upload a document', () => { + it('should upload a document using drag and drop', () => { cy.extendedFiles().then(win => { const file = new win.ExFile('/', [new Blob(['sample text content.'])], 'upload_test.txt', { type: 'text/plain' }) @@ -182,6 +182,38 @@ context('Document Operations', () => { }) }) + it('should upload a document using the upload dialog', () => { + cy.writeFile(require('path').resolve(Cypress.env('homedir'), 'upload_file_test.txt'), 'sample text content'); + cy.get('[node-id$=test]') + .rightclick() + cy.get('[data-command="fusion.upload-document"]') + .should('be.visible') + .contains('Upload document(s)') + .click() + cy.get('.dialogBlock .theia-Tree.theia-FileTree') + const timer = Date.now(); + new Cypress.Promise((resolve, reject) => { + function tick() { + const file = cy.$$('.theia-TreeNode:contains(upload_file_test.txt)'); + if (file.length) { + cy.wrap(file[0]) + .click({ force: true }) + cy.get('.main').click(); + fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxqs/fusiondb/document?uri=/db/test/upload_file_test.txt', { method: 'PUT' }) + cy.get('[node-id$="test\\/upload_file_test.txt"]') + .should('be.visible') + resolve(); + } else { + if (Date.now() < timer + 5000) { + cy.get('.theia-FileTree.theia-FileDialog.ps .ps__rail-y').click('bottom', { force: true }) + cy.wait(100).then(tick); + } + } + } + tick() + }) + }) + it('should move a document', () => { const dataTransfer = new DataTransfer(); cy.get('[node-id$="test\\/test.xml"]') diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index be8c3120..8b51d857 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -58,5 +58,6 @@ module.exports = (on, config) => { report: process.report, traceDeprecation: process.traceDeprecation, }; + config.env.homedir = require('os').homedir(); return config; } From e9b9576e02e7d2a288add3cc02d98486d280be07 Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Thu, 10 Jun 2021 11:25:53 +0100 Subject: [PATCH 7/8] expand a collapsed collection when creating items --- fusion-studio-extension/src/browser/core.ts | 76 +++++++++++++++++++-- fusion-studio-extension/src/classes/node.ts | 2 + 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/fusion-studio-extension/src/browser/core.ts b/fusion-studio-extension/src/browser/core.ts index d479da15..140cb228 100644 --- a/fusion-studio-extension/src/browser/core.ts +++ b/fusion-studio-extension/src/browser/core.ts @@ -1,6 +1,6 @@ import { injectable, inject } from "inversify"; import { v4 } from "uuid"; -import { FSNode, FSDocumentNode, FSCollectionNode, FSToolbarNode, FSConnectionNode, FSItemNode, FSSecurityNode, FSUsersNode, FSGroupsNode, FSUserNode, FSGroupNode, FSContainerNode, FSIndexesNode, FSIndexNode, FSRestNode, FSRestURINode, FSRestMethodNode } from "../classes/node"; +import { FSNode, FSDocumentNode, FSCollectionNode, FSToolbarNode, FSConnectionNode, FSItemNode, FSSecurityNode, FSUsersNode, FSGroupsNode, FSUserNode, FSGroupNode, FSContainerNode, FSIndexesNode, FSIndexNode, FSRestNode, FSRestURINode, FSRestMethodNode, FSLoadEvent } from "../classes/node"; import { open, TreeNode, CompositeTreeNode, ConfirmDialog, SingleTextInputDialog, OpenerService, StatusBar, StatusBarAlignment, WidgetManager } from "@theia/core/lib/browser"; import { WorkspaceService } from "@theia/workspace/lib/browser"; import { OpenFileDialogProps, FileDialogService } from "@theia/filesystem/lib/browser"; @@ -65,6 +65,7 @@ export class FSCore { updating = false; renaming = ''; dict: Record = {}; + loadEvents: Record = {}; setLabelProvider(labelProvider: FSLabelProviderContribution) { this._labelProvider = labelProvider; @@ -192,6 +193,9 @@ export class FSCore { } protected removeNode(child: FSNode) { + if (this.loadEvents[child.id]) { + delete(this.loadEvents[child.id]); + } const removeNodeId = (node: FSNode) => { delete(this.dict[node.nodeId]); if (CompositeTreeNode.is(node)) { @@ -254,8 +258,24 @@ export class FSCore { } } - public expand(node: CompositeTreeNode) { - this.model && this.model.expandNode(node as any); + public async expand(node: CompositeTreeNode) { + this.model && await this.model.expandNode(node as any); + } + + public expandAndWait(node: FSContainerNode) { + return new Promise(resolve => { + this.addLoadEvent(node, node => { + resolve(null); + return true; + }); + this.expand(node); + }) + } + + public async ensureExpanded(node: FSContainerNode) { + if (!node.expanded && !node.loaded) { + await this.expandAndWait(node); + } } public getNode(id: string): FSNode | undefined { @@ -360,10 +380,54 @@ export class FSCore { return false; } - protected endLoading(node: TreeNode): void { + protected async endLoading(node: TreeNode) { if (FSNode.is(node)) { node.loading = false; - this.refresh(); + await this.refresh(); + if (this.loadEvents[node.id]) { + // const a = + this.loadEvents[node.id] = await (await Promise.all(this.loadEvents[node.id].map(async event => { + const result = event(node); + if (!result) { + return event; + } + if (result === true) { + return null; + } + return await result ? null : event + }))).filter(event => event != null) as FSLoadEvent[]; + if (this.loadEvents[node.id].length < 1) { + delete(this.loadEvents[node.id]); + } + } + } + (window as any).loadEvents = this.loadEvents; + } + + protected addLoadEvent(node: TreeNode, event: FSLoadEvent): void { + if (FSNode.is(node)) { + if (!this.loadEvents[node.id]) { + this.loadEvents[node.id] = [event]; + } else { + if (!this.loadEvents[node.id].find(value => value === event)) { + this.loadEvents[node.id].push(event); + } + } + } + } + + protected removeLoadEvent(node: TreeNode, event?: FSLoadEvent): void { + if (FSNode.is(node)) { + if (this.loadEvents[node.id]) { + if (event) { + this.loadEvents[node.id] = this.loadEvents[node.id].filter(value => value != event); + if (this.loadEvents[node.id].length < 1) { + delete(this.loadEvents[node.id]); + } + } else { + delete(this.loadEvents[node.id]); + } + } } } @@ -1450,6 +1514,7 @@ export class FSCore { return false; } const collection = this.node as FSCollectionNode; + await this.ensureExpanded(collection); const validator = (input: string) => input !== '' && !this.fileExists(input); let initialName = this.newName(validator); if (extension) { @@ -1545,6 +1610,7 @@ export class FSCore { } } if (FSNode.isCollection(collection)) { + await this.ensureExpanded(collection); const validator = (input: string) => input !== '' && !this.fileExists(input); const dialog = new SingleTextInputDialog({ initialValue: this.newName(validator), diff --git a/fusion-studio-extension/src/classes/node.ts b/fusion-studio-extension/src/classes/node.ts index 1e4760b9..ed94ad47 100644 --- a/fusion-studio-extension/src/classes/node.ts +++ b/fusion-studio-extension/src/classes/node.ts @@ -5,6 +5,8 @@ import { FSRestURI, FSRestMethod } from "./rest"; export type FSNodeType = 'connection' | 'toolbar' | 'item' | 'users' | 'groups' | 'user' | 'group' | 'security' | 'indexes' | 'index' | 'rest' | 'rest-uri' | 'rest-method'; +export type FSLoadEvent = (mode: FSNode) => void | true | Promise; + export interface FSNode extends TreeNode { type: FSNodeType; connectionNode: FSConnectionNode; From c943319ca7a47c1f8dbcdb8bb55a76118b6b2933 Mon Sep 17 00:00:00 2001 From: Charafeddine Cheraa Date: Mon, 26 Jul 2021 08:01:03 +0100 Subject: [PATCH 8/8] fix: test fail because it tries to drag an item while the tree is still rendering --- cypress/integration/04_document_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress/integration/04_document_spec.js b/cypress/integration/04_document_spec.js index 4596b972..28025e3a 100644 --- a/cypress/integration/04_document_spec.js +++ b/cypress/integration/04_document_spec.js @@ -222,6 +222,7 @@ context('Document Operations', () => { cy.get('[node-id$=col1]') .trigger('dragover', { dataTransfer }) .trigger('drop', { dataTransfer }) + .prev().should('not.have.class', 'fa-spin').wait(1) fetchSpy.calledWithMatch(Cypress.env('API_HOST') + '/exist/restxq/fusiondb/document?uri=/db/test/col1/test.xml', { method: 'PUT', headers: { 'x-fs-move-source': '/db/test/test.xml' },