diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json index 73bab3367..4d2f62d7d 100644 --- a/webapp/.eslintrc.json +++ b/webapp/.eslintrc.json @@ -139,63 +139,14 @@ } }, { - "files": ["e2e/**"], + "files": ["cypress/**"], "rules": { + "cypress/no-unnecessary-waiting": 0, "func-names": 0, "import/no-unresolved": 0, "max-nested-callbacks": 0, "no-process-env": 0, - "babel/no-unused-expressions": 0, - "no-unused-expressions": 0, - "jquery/no-ajax": 0, - "jquery/no-ajax-events": 0, - "jquery/no-animate": 0, - "jquery/no-attr": 0, - "jquery/no-bind": 0, - "jquery/no-class": 0, - "jquery/no-clone": 0, - "jquery/no-closest": 0, - "jquery/no-css": 0, - "jquery/no-data": 0, - "jquery/no-deferred": 0, - "jquery/no-delegate": 0, - "jquery/no-each": 0, - "jquery/no-extend": 0, - "jquery/no-fade": 0, - "jquery/no-filter": 0, - "jquery/no-find": 0, - "jquery/no-global-eval": 0, - "jquery/no-grep": 0, - "jquery/no-has": 0, - "jquery/no-hide": 0, - "jquery/no-html": 0, - "jquery/no-in-array": 0, - "jquery/no-is-array": 0, - "jquery/no-is-function": 0, - "jquery/no-is": 0, - "jquery/no-load": 0, - "jquery/no-map": 0, - "jquery/no-merge": 0, - "jquery/no-param": 0, - "jquery/no-parent": 0, - "jquery/no-parents": 0, - "jquery/no-parse-html": 0, - "jquery/no-prop": 0, - "jquery/no-proxy": 0, - "jquery/no-ready": 0, - "jquery/no-serialize": 0, - "jquery/no-show": 0, - "jquery/no-size": 0, - "jquery/no-sizzle": 0, - "jquery/no-slide": 0, - "jquery/no-submit": 0, - "jquery/no-text": 0, - "jquery/no-toggle": 0, - "jquery/no-trigger": 0, - "jquery/no-trim": 0, - "jquery/no-val": 0, - "jquery/no-when": 0, - "jquery/no-wrap": 0 + "no-unused-expressions": 0 } } ] diff --git a/webapp/cypress/global.d.ts b/webapp/cypress/global.d.ts index 6f88a9a0f..a7c8a4283 100644 --- a/webapp/cypress/global.d.ts +++ b/webapp/cypress/global.d.ts @@ -19,5 +19,12 @@ declare namespace Cypress { apiResetBoards: () => Chainable uiCreateNewBoard: (title?: string) => Chainable uiAddNewGroup: (name?: string) => Chainable + + /** + * Create a board on a given menu item. + * + * @param {string} item - one of the template menu options, ex. 'Empty board' + */ + uiCreateBoard: (item: string) => Chainable } } diff --git a/webapp/cypress/integration/createBoard.ts b/webapp/cypress/integration/createBoard.ts index f68555105..d7979419e 100644 --- a/webapp/cypress/integration/createBoard.ts +++ b/webapp/cypress/integration/createBoard.ts @@ -38,13 +38,9 @@ describe('Create and delete board / card', () => { }) it('Can create and delete a board and a card', () => { + // Visit a page and create new empty board cy.visit('/') - - // Create new empty board - cy.log('**Create new empty board**') - cy.contains('+ Add board').click({force: true}) - cy.contains('Empty board').click({force: true}) - cy.get('.BoardComponent').should('exist') + cy.uiCreateBoard('Empty board') // Change board title cy.log('**Change board title**') @@ -87,7 +83,10 @@ describe('Create and delete board / card', () => { // Close card dialog cy.log('**Close card dialog**') - cy.get('.Dialog.dialog-back .wrapper').click({force: true}) + cy.get('.Dialog Button[title=\'Close dialog\']'). + should('be.visible'). + click(). + wait(500) // Create a card by clicking on the + button cy.log('**Create a card by clicking on the + button**') @@ -131,11 +130,51 @@ describe('Create and delete board / card', () => { cy.get('.Sidebar .octo-sidebar-list'). contains(boardTitle). parent(). - next(). + parent(). + find('.MenuWrapper'). find('.Button.IconButton'). click({force: true}) cy.contains('Delete board').click({force: true}) cy.get('.DeleteBoardDialog button.danger').click({force: true}) cy.contains(boardTitle).should('not.exist') }) + + it('MM-T4433 Scrolls the kanban board when dragging card to edge', () => { + // Visit a page and create new empty board + cy.visit('/') + cy.uiCreateBoard('Empty board') + + // Create 10 empty groups + cy.log('**Create new empty groups**') + for (let i = 0; i < 10; i++) { + cy.contains('+ Add a group').scrollIntoView().should('be.visible').click() + cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('have.length', i + 1) + } + + // Create empty card in last group + cy.log('**Create new empty card in first group**') + cy.get('.octo-board-column').last().contains('+ New').scrollIntoView().click() + cy.get('.Dialog').should('exist') + cy.get('.Dialog Button[title=\'Close dialog\']').should('be.visible').click() + cy.get('.KanbanCard').scrollIntoView().should('exist') + + // Drag card to right corner and expect scroll to occur + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.get('.Kanban').invoke('scrollLeft').should('not.equal', 0).wait(1000) + + // wait necessary to let state change propagate + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.get('.KanbanCard'). + trigger('dragstart'). + wait(500) + + // wait necessary to trigger scroll animation for some time + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.get('.Kanban'). + trigger('dragover', {clientX: 400, clientY: Cypress.config().viewportHeight / 2}). + wait(3500). + trigger('dragend') + + cy.get('.Kanban').invoke('scrollLeft').should('equal', 0) + }) }) diff --git a/webapp/cypress/integration/manageGroups.ts b/webapp/cypress/integration/manageGroups.ts index 848813dbb..5fe557ad7 100644 --- a/webapp/cypress/integration/manageGroups.ts +++ b/webapp/cypress/integration/manageGroups.ts @@ -9,9 +9,9 @@ describe('Manage groups', () => { }) it('MM-T4284 Adding a group', () => { + // Visit a page and create new empty board cy.visit('/') - cy.contains('+ Add board').click({force: true}) - cy.contains('Empty board').click({force: true}) + cy.uiCreateBoard('Empty board') cy.contains('+ Add a group').click({force: true}) cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('exist') @@ -24,10 +24,10 @@ describe('Manage groups', () => { }) it('MM-T4285 Adding group color', () => { + // Visit a page and create new empty board cy.visit('/') + cy.uiCreateBoard('Empty board') - cy.contains('+ Add board').click({force: true}) - cy.contains('Empty board').click({force: true}) cy.contains('+ Add a group').click({force: true}) cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('exist') @@ -43,8 +43,11 @@ describe('Manage groups', () => { cy.contains('Orange').should('exist') // Click on green - cy.contains('Green').should('exist').click({force: true}) + cy.contains('Green').should('be.visible').click().wait(1000) // eslint-disable-line cypress/no-unnecessary-waiting }) + }) + + cy.get('.KanbanColumnHeader').last().within(() => { cy.get('.Label.propColorGreen').should('exist') }) }) diff --git a/webapp/cypress/support/commands.ts b/webapp/cypress/support/api_commands.ts similarity index 99% rename from webapp/cypress/support/commands.ts rename to webapp/cypress/support/api_commands.ts index ab236251e..405b957d9 100644 --- a/webapp/cypress/support/commands.ts +++ b/webapp/cypress/support/api_commands.ts @@ -111,6 +111,7 @@ Cypress.Commands.add('uiCreateNewBoard', (title?: string) => { cy.findByPlaceholderText('Untitled board').type(`${title}{enter}`) cy.findByRole('textbox', {name: title}).should('exist') } + cy.wait(500) }) Cypress.Commands.add('uiAddNewGroup', (name?: string) => { @@ -123,4 +124,5 @@ Cypress.Commands.add('uiAddNewGroup', (name?: string) => { cy.findByRole('textbox', {name: 'New group'}).type(`{selectall}${name}{enter}`) cy.findByRole('textbox', {name}).should('exist') } + cy.wait(500) }) diff --git a/webapp/cypress/support/index.ts b/webapp/cypress/support/index.ts index da5414ead..e2bf87045 100644 --- a/webapp/cypress/support/index.ts +++ b/webapp/cypress/support/index.ts @@ -1,4 +1,5 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import './commands' +import './api_commands' +import './ui_commands' diff --git a/webapp/cypress/support/ui_commands.ts b/webapp/cypress/support/ui_commands.ts new file mode 100644 index 000000000..268979a34 --- /dev/null +++ b/webapp/cypress/support/ui_commands.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +/* eslint-disable cypress/no-unnecessary-waiting */ + +Cypress.Commands.add('uiCreateBoard', (item: string) => { + cy.log(`Create new board: ${item}`) + + cy.contains('+ Add board').should('be.visible').click() + + return cy.contains(item).click().wait(1000) +}) diff --git a/webapp/package-lock.json b/webapp/package-lock.json index de849cc74..8af9b10b4 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -35,6 +35,7 @@ "react-day-picker": "^7.4.10", "react-dnd": "^14.0.2", "react-dnd-html5-backend": "^14.0.0", + "react-dnd-scrolling": "^1.2.1", "react-dnd-touch-backend": "^14.0.0", "react-dom": "^17.0.2", "react-hot-keys": "^2.6.2", @@ -11595,6 +11596,11 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -14637,6 +14643,11 @@ "prop-types": "^15.6.2" } }, + "node_modules/react-display-name": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz", + "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" + }, "node_modules/react-dnd": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-14.0.2.tgz", @@ -14657,6 +14668,23 @@ "dnd-core": "14.0.0" } }, + "node_modules/react-dnd-scrolling": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.2.1.tgz", + "integrity": "sha512-Bj4AvCrVv5x6Z9Yjodr2WHN12m3EPwpK8KR4dSIY/5uiHOFMvTYqbLw11rlpl5jSzTwh5DzyWDezJRZB6hN/sg==", + "dependencies": { + "hoist-non-react-statics": "3.x", + "lodash.throttle": "^4.1.1", + "prop-types": "15.x", + "raf": "^3.4.1", + "react-display-name": "^0.2.5" + }, + "peerDependencies": { + "react": "16.x || 17.x", + "react-dnd": "10.x || 11.x || 14.x", + "react-dom": "16.x || 17.x" + } + }, "node_modules/react-dnd-touch-backend": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-14.0.0.tgz", @@ -29027,6 +29055,11 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -31447,6 +31480,11 @@ "prop-types": "^15.6.2" } }, + "react-display-name": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz", + "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" + }, "react-dnd": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-14.0.2.tgz", @@ -31467,6 +31505,18 @@ "dnd-core": "14.0.0" } }, + "react-dnd-scrolling": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.2.1.tgz", + "integrity": "sha512-Bj4AvCrVv5x6Z9Yjodr2WHN12m3EPwpK8KR4dSIY/5uiHOFMvTYqbLw11rlpl5jSzTwh5DzyWDezJRZB6hN/sg==", + "requires": { + "hoist-non-react-statics": "3.x", + "lodash.throttle": "^4.1.1", + "prop-types": "15.x", + "raf": "^3.4.1", + "react-display-name": "^0.2.5" + } + }, "react-dnd-touch-backend": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-14.0.0.tgz", diff --git a/webapp/package.json b/webapp/package.json index 818152632..2e706ecae 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -20,8 +20,7 @@ "cypress:run:firefox": "cypress run --browser firefox", "cypress:run:edge": "cypress run --browser edge", "cypress:run:electron": "cypress run --browser electron", - "cypress:open": "cypress open", - "updatesnapshots": "jest --updateSnapshot" + "cypress:open": "cypress open" }, "dependencies": { "@draft-js-plugins/editor": "^4.1.0", @@ -51,6 +50,7 @@ "react-day-picker": "^7.4.10", "react-dnd": "^14.0.2", "react-dnd-html5-backend": "^14.0.0", + "react-dnd-scrolling": "^1.2.1", "react-dnd-touch-backend": "^14.0.0", "react-dom": "^17.0.2", "react-hot-keys": "^2.6.2", diff --git a/webapp/src/components/kanban/__snapshots__/kanban.test.tsx.snap b/webapp/src/components/kanban/__snapshots__/kanban.test.tsx.snap index ad8a9bbf8..b14008926 100644 --- a/webapp/src/components/kanban/__snapshots__/kanban.test.tsx.snap +++ b/webapp/src/components/kanban/__snapshots__/kanban.test.tsx.snap @@ -335,6 +335,165 @@ exports[`src/component/kanban/kanban return kanban and click on KanbanCalculatio 2 +