1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-01-11 18:13:52 +02:00

Added better kanban edge scroll detection whilst dragging (#1913)

* added better kanban edge scroll detection whilst dragging

* update jest snapshots

* try to fix cypress on CI

* replace force clicks on cypress test

* make eslint happy

* fix unrelated failed tests and reorganize Cypress commands

* added test for drag direction from right to left

* make eslint happy

* cypress kanban drag test now checks other direction

* fix test and update eslit for cypress

Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Tim Lange 2022-01-07 19:58:28 +01:00 committed by GitHub
parent 3f66fb52cc
commit e1d5e77e0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 304 additions and 70 deletions

View File

@ -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
}
}
]

View File

@ -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
}
}

View File

@ -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)
})
})

View File

@ -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')
})
})

View File

@ -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)
})

View File

@ -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'

View File

@ -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)
})

View File

@ -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",

View File

@ -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",

View File

@ -335,6 +335,165 @@ exports[`src/component/kanban/kanban return kanban and click on KanbanCalculatio
2
</span>
</button>
<div
class="CalculationOptions css-2b097c-container"
>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
>
<span
id="aria-selection"
/>
<span
id="aria-context"
>
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.
</span>
</span>
<div
class="CalculationOptions__control CalculationOptions__control--is-focused CalculationOptions__control--menu-is-open css-1s59geg-Control"
>
<div
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
>
<div
class="CalculationOptions__single-value css-1brck82-singleValue"
>
Count
</div>
<input
aria-autocomplete="list"
class="css-wmatm6-dummyInput-DummyInput"
id="react-select-2-input"
readonly=""
tabindex="0"
value=""
/>
</div>
<div
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
>
<div
aria-hidden="true"
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"
/>
</svg>
</div>
<span
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
/>
<div
aria-hidden="true"
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
>
<i
class="CompassIcon icon-chevron-up ChevronUpIcon"
/>
</div>
</div>
</div>
<div
class="CalculationOptions__menu css-1rsmi4x-menu"
>
<div
class="CalculationOptions__menu-list css-g29tl0-MenuList"
>
<div
class="KanbanCalculationOptions_CustomOption active"
>
<span>
Count
</span>
</div>
<div
class="KanbanCalculationOptions_CustomOption "
>
<span>
Count Empty
<i
class="CompassIcon icon-chevron-right ChevronRightIcon"
/>
</span>
</div>
<div
class="KanbanCalculationOptions_CustomOption "
>
<span>
Count Not Empty
<i
class="CompassIcon icon-chevron-right ChevronRightIcon"
/>
</span>
</div>
<div
class="KanbanCalculationOptions_CustomOption "
>
<span>
Percent Empty
<i
class="CompassIcon icon-chevron-right ChevronRightIcon"
/>
</span>
</div>
<div
class="KanbanCalculationOptions_CustomOption "
>
<span>
Percent Not Empty
<i
class="CompassIcon icon-chevron-right ChevronRightIcon"
/>
</span>
</div>
<div
class="KanbanCalculationOptions_CustomOption "
>
<span>
Count Value
<i
class="CompassIcon icon-chevron-right ChevronRightIcon"
/>
</span>
</div>
<div
class="KanbanCalculationOptions_CustomOption "
>
<span>
Count Unique Values
<i
class="CompassIcon icon-chevron-right ChevronRightIcon"
/>
</span>
</div>
</div>
</div>
<input
name="calculation_options"
type="hidden"
value="count"
/>
</div>
</div>
<div
class="octo-spacer"

View File

@ -4,6 +4,8 @@
import React, {useCallback, useState} from 'react'
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
import withScrolling, {createHorizontalStrength, createVerticalStrength} from 'react-dnd-scrolling'
import {Position} from '../cardDetail/cardDetailContents'
import {Board, IPropertyOption, IPropertyTemplate, BoardGroup} from '../../blocks/board'
@ -187,8 +189,16 @@ const Kanban = (props: Props) => {
setShowCalculationsMenu(newShowOptions)
}
const ScrollingComponent = withScrolling('div')
const hStrength = createHorizontalStrength(Utils.isMobile() ? 60 : 250)
const vStrength = createVerticalStrength(Utils.isMobile() ? 60 : 250)
return (
<div className='Kanban'>
<ScrollingComponent
className='Kanban'
horizontalStrength={hStrength}
verticalStrength={vStrength}
>
<div
className='octo-board-header'
id='mainBoardHeader'
@ -298,7 +308,7 @@ const Kanban = (props: Props) => {
))}
</div>}
</div>
</div>
</ScrollingComponent>
)
}