1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-12-24 13:43:12 +02:00

[GH-1826] Cypress test for group by different property (#1966)

* Cypress command for resetting boards added.

* Cypress test for group board by different property added:
 - using @testing-library/cypress
 - minor fixes for `PropertyMenu` and `Dialog` components
 - bug in `ViewHeaderGroupByMenu` fixed: use `groupByProperty` passed in props instead of one from active view

* Jest snapshots updated.

* Use only case-sensitive strings for names in Cypress test.

* Cypress commands for adding new board and new group added.

* Jest snapshot for new test updated.

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
kamre 2021-12-15 19:57:58 +03:00 committed by GitHub
parent 9837f13abc
commit 7cb25b9e17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 575 additions and 249 deletions

View File

@ -4,7 +4,8 @@
"testFiles": [
"**/login*.ts",
"**/create*.ts",
"**/manage*.ts"
"**/manage*.ts",
"**/group*.ts"
],
"env": {
"username": "test-user",

View File

@ -15,5 +15,9 @@ declare namespace Cypress {
apiGetMe: () => Chainable<string>
apiChangePassword: (userId: string, oldPassword: string, newPassword: string) => Chainable
apiInitServer: () => Chainable
apiDeleteBlock: (id: string) => Chainable
apiResetBoards: () => Chainable
uiCreateNewBoard: (title?: string) => Chainable
uiAddNewGroup: (name?: string) => Chainable
}
}

View File

@ -8,6 +8,7 @@ describe('Create and delete board / card', () => {
beforeEach(() => {
cy.apiInitServer()
cy.apiResetBoards()
localStorage.setItem('welcomePageViewed', 'true')
})

View File

@ -0,0 +1,58 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
describe('Group board by different properties', () => {
beforeEach(() => {
cy.apiInitServer()
cy.apiResetBoards()
localStorage.setItem('welcomePageViewed', 'true')
})
it('MM-T4291 Group by different property', () => {
cy.visit('/')
// Create new board
cy.uiCreateNewBoard('Testing')
// Add a new group
cy.uiAddNewGroup('Group 1')
// Add a new card to the group
cy.log('**Add a new card to the group**')
cy.findAllByRole('button', {name: '+ New'}).eq(1).click()
cy.findByRole('dialog').should('exist')
cy.findByTestId('select-non-editable').findByText('Group 1').should('exist')
cy.get('#mainBoardBody').findByText('Untitled').should('exist')
// Add new select property
cy.log('**Add new select property**')
cy.findAllByRole('button', {name: '+ Add a property'}).click()
cy.findAllByRole('button', {name: 'Select'}).click()
cy.findByRole('textbox', {name: 'Select'}).type('{enter}')
cy.findByRole('dialog').findByRole('button', {name: 'Select'}).should('exist')
// Close card dialog
cy.log('**Close card dialog**')
cy.findByRole('button', {name: 'Close dialog'}).click()
cy.findByRole('dialog').should('not.exist')
// Group by new select property
cy.log('**Group by new select property**')
cy.findByRole('button', {name: /Group by:/}).click()
cy.findByRole('button', {name: 'Status'}).get('.CheckIcon').should('exist')
cy.findByRole('button', {name: 'Select'}).click()
cy.findByTitle(/empty Select property/).contains('No Select')
cy.get('#mainBoardBody').findByText('Untitled').should('exist')
// Add another new group
cy.log('**Add another new group**')
cy.findByRole('button', {name: '+ Add a group'}).click()
cy.findByRole('textbox', {name: 'New group'}).should('exist')
// Add a new card to another group
cy.log('**Add a new card to another group**')
cy.findAllByRole('button', {name: '+ New'}).eq(1).click()
cy.findByRole('dialog').should('exist')
cy.findAllByTestId('select-non-editable').last().findByText('New group').should('exist')
})
})

View File

@ -4,6 +4,7 @@
describe('Manage groups', () => {
beforeEach(() => {
cy.apiInitServer()
cy.apiResetBoards()
localStorage.setItem('welcomePageViewed', 'true')
})

View File

@ -1,6 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import '@testing-library/cypress/add-commands'
import {Board} from '../../src/blocks/board'
Cypress.Commands.add('apiRegisterUser', (data: Cypress.UserData, token?: string, failOnError?: boolean) => {
return cy.request({
method: 'POST',
@ -33,6 +37,13 @@ Cypress.Commands.add('apiLoginUser', (data: Cypress.LoginData) => {
})
})
const headers = () => ({
headers: {
'X-Requested-With': 'XMLHttpRequest',
Authorization: `Bearer ${localStorage.getItem('focalboardSessionId')}`,
},
})
Cypress.Commands.add('apiInitServer', () => {
const data: Cypress.UserData = {
username: Cypress.env('username'),
@ -42,11 +53,34 @@ Cypress.Commands.add('apiInitServer', () => {
return cy.apiRegisterUser(data, '', false).apiLoginUser(data)
})
const headers = () => ({
headers: {
'X-Requested-With': 'XMLHttpRequest',
Authorization: `Bearer ${localStorage.getItem('focalboardSessionId')}`,
},
Cypress.Commands.add('apiDeleteBlock', (id: string) => {
return cy.request({
method: 'DELETE',
url: `/api/v1/workspaces/0/blocks/${encodeURIComponent(id)}`,
...headers(),
})
})
const deleteBlocks = (ids: string[]) => {
if (ids.length === 0) {
return
}
const [id, ...other] = ids
cy.apiDeleteBlock(id).then(() => deleteBlocks(other))
}
Cypress.Commands.add('apiResetBoards', () => {
return cy.request({
method: 'GET',
url: '/api/v1/workspaces/0/blocks?type=board',
...headers(),
}).then((response) => {
if (Array.isArray(response.body)) {
const boards = response.body as Board[]
const toDelete = boards.filter((b) => !b.fields.isTemplate).map((b) => b.id)
deleteBlocks(toDelete)
}
})
})
Cypress.Commands.add('apiGetMe', () => {
@ -66,3 +100,27 @@ Cypress.Commands.add('apiChangePassword', (userId: string, oldPassword: string,
body,
})
})
Cypress.Commands.add('uiCreateNewBoard', (title?: string) => {
cy.log('**Create new empty board**')
cy.findByText('+ Add board').click()
cy.findByRole('button', {name: 'Empty board'}).click()
cy.findByPlaceholderText('Untitled board').should('exist')
if (title) {
cy.log('**Rename board**')
cy.findByPlaceholderText('Untitled board').type(`${title}{enter}`)
cy.findByRole('textbox', {name: title}).should('exist')
}
})
Cypress.Commands.add('uiAddNewGroup', (name?: string) => {
cy.log('**Add a new group**')
cy.findByRole('button', {name: '+ Add a group'}).click()
cy.findByRole('textbox', {name: 'New group'}).should('exist')
if (name) {
cy.log('**Rename group**')
cy.findByRole('textbox', {name: 'New group'}).type(`{selectall}${name}{enter}`)
cy.findByRole('textbox', {name}).should('exist')
}
})

View File

@ -3,7 +3,8 @@
"compilerOptions": {
"noEmit": true,
"types": [
"cypress"
"cypress",
"@testing-library/cypress"
]
},
"include": [

603
webapp/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -82,6 +82,7 @@
"devDependencies": {
"@formatjs/cli": "^3.2.0",
"@formatjs/ts-transformer": "^3.2.1",
"@testing-library/cypress": "^8.0.2",
"@testing-library/dom": "^7.31.2",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.5",

View File

@ -10,6 +10,7 @@ exports[`components/cardDialog already following card 1`] = `
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
@ -288,6 +289,7 @@ exports[`components/cardDialog return a cardDialog readonly 1`] = `
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
@ -395,6 +397,7 @@ exports[`components/cardDialog return cardDialog menu content 1`] = `
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
@ -760,6 +763,7 @@ exports[`components/cardDialog return cardDialog menu content and cancel delete
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
@ -1038,6 +1042,7 @@ exports[`components/cardDialog should match snapshot 1`] = `
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"

View File

@ -10,6 +10,7 @@ exports[`/components/confirmationDialogBox confirmDialog should match snapshot 1
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
@ -78,6 +79,7 @@ exports[`/components/confirmationDialogBox confirmDialog with Confirm Button Tex
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"

View File

@ -10,6 +10,7 @@ exports[`components/dialog should match snapshot 1`] = `
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
@ -44,6 +45,7 @@ exports[`components/dialog should return dialog and click on cancel button 1`] =
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"

View File

@ -41,7 +41,10 @@ const Dialog = React.memo((props: Props) => {
}
}}
>
<div className='dialog' >
<div
role='dialog'
className='dialog'
>
<div className='toolbar'>
{
!props.hideCloseButton &&

View File

@ -42,9 +42,15 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu 1`] = `
>
Status
</div>
<div
class="noicon"
/>
<svg
class="CheckIcon Icon"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<polyline
points="20,60 40,80 80,40"
/>
</svg>
</div>
<div
aria-label="Property 1"
@ -59,15 +65,9 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu 1`] = `
>
Property 1
</div>
<svg
class="CheckIcon Icon"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<polyline
points="20,60 40,80 80,40"
/>
</svg>
<div
class="noicon"
/>
</div>
<div
aria-label="Property 2"
@ -220,9 +220,15 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu and ung
>
Status
</div>
<div
class="noicon"
/>
<svg
class="CheckIcon Icon"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<polyline
points="20,60 40,80 80,40"
/>
</svg>
</div>
<div
aria-label="Property 1"
@ -237,15 +243,9 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu and ung
>
Property 1
</div>
<svg
class="CheckIcon Icon"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<polyline
points="20,60 40,80 80,40"
/>
</svg>
<div
class="noicon"
/>
</div>
<div
aria-label="Property 2"

View File

@ -103,7 +103,7 @@ const ViewHeader = React.memo((props: Props) => {
<ViewHeaderGroupByMenu
properties={board.fields.cardProperties}
activeView={activeView}
groupByPropertyName={groupByProperty?.name}
groupByProperty={groupByProperty}
/>}
{/* Display by */}

View File

@ -21,7 +21,7 @@ const mockedMutator = mocked(mutator, true)
const board = TestBlockFactory.createBoard()
const activeView = TestBlockFactory.createBoardView(board)
const propertyName = 'Status'
const property = board.fields.cardProperties.find((p) => p.name === 'Status')
describe('components/viewHeader/viewHeaderGroupByMenu', () => {
const state = {
@ -41,7 +41,7 @@ describe('components/viewHeader/viewHeaderGroupByMenu', () => {
<ReduxProvider store={store}>
<ViewHeaderGroupByMenu
activeView={activeView}
groupByPropertyName={propertyName}
groupByProperty={property}
properties={board.fields.cardProperties}
/>
</ReduxProvider>,
@ -57,7 +57,7 @@ describe('components/viewHeader/viewHeaderGroupByMenu', () => {
<ReduxProvider store={store}>
<ViewHeaderGroupByMenu
activeView={activeView}
groupByPropertyName={propertyName}
groupByProperty={property}
properties={board.fields.cardProperties}
/>
</ReduxProvider>,
@ -77,7 +77,7 @@ describe('components/viewHeader/viewHeaderGroupByMenu', () => {
<ReduxProvider store={store}>
<ViewHeaderGroupByMenu
activeView={activeView}
groupByPropertyName={propertyName}
groupByProperty={property}
properties={board.fields.cardProperties}
/>
</ReduxProvider>,

View File

@ -15,11 +15,11 @@ import CheckIcon from '../../widgets/icons/check'
type Props = {
properties: readonly IPropertyTemplate[]
activeView: BoardView
groupByPropertyName?: string
groupByProperty?: IPropertyTemplate
}
const ViewHeaderGroupByMenu = React.memo((props: Props) => {
const {properties, activeView, groupByPropertyName} = props
const {properties, activeView, groupByProperty} = props
const intl = useIntl()
return (
<MenuWrapper>
@ -33,7 +33,7 @@ const ViewHeaderGroupByMenu = React.memo((props: Props) => {
style={{color: 'rgb(var(--center-channel-color-rgb))'}}
id='groupByLabel'
>
{groupByPropertyName}
{groupByProperty?.name}
</span>
),
}}
@ -61,7 +61,7 @@ const ViewHeaderGroupByMenu = React.memo((props: Props) => {
key={option.id}
id={option.id}
name={option.name}
rightIcon={activeView.fields.groupById === option.id ? <CheckIcon/> : undefined}
rightIcon={groupByProperty?.id === option.id ? <CheckIcon/> : undefined}
onClick={(id) => {
if (activeView.fields.groupById === id) {
return

View File

@ -14,6 +14,7 @@ exports[`widgets/PropertyMenu should match snapshot 1`] = `
<input
class="PropertyMenu menu-textbox"
spellcheck="true"
title="test-property"
type="text"
value="test-property"
/>

View File

@ -114,6 +114,7 @@ const PropertyMenu = React.memo((props: Props) => {
onChange={(e) => {
setName(e.target.value)
}}
title={name}
value={name}
onBlur={() => props.onTypeAndNameChanged(props.propertyType, name)}
onKeyDown={(e) => {