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 +
+ + + + 7 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu. + + +
+
+
+ Count +
+ +
+
+ + + +
+
+
+
+
+ + Count + + +
+
+ + Count Empty + + + +
+
+ + Count Not Empty + + + +
+
+ + Percent Empty + + + +
+
+ + Percent Not Empty + + + +
+
+ + Count Value + + + +
+
+ + Count Unique Values + + + +
+
+
+ +
{ setShowCalculationsMenu(newShowOptions) } + const ScrollingComponent = withScrolling('div') + const hStrength = createHorizontalStrength(Utils.isMobile() ? 60 : 250) + const vStrength = createVerticalStrength(Utils.isMobile() ? 60 : 250) + return ( -
+
{ ))}
}
-
+ ) }