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:
parent
9837f13abc
commit
7cb25b9e17
@ -4,7 +4,8 @@
|
||||
"testFiles": [
|
||||
"**/login*.ts",
|
||||
"**/create*.ts",
|
||||
"**/manage*.ts"
|
||||
"**/manage*.ts",
|
||||
"**/group*.ts"
|
||||
],
|
||||
"env": {
|
||||
"username": "test-user",
|
||||
|
4
webapp/cypress/global.d.ts
vendored
4
webapp/cypress/global.d.ts
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ describe('Create and delete board / card', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiInitServer()
|
||||
cy.apiResetBoards()
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
})
|
||||
|
||||
|
58
webapp/cypress/integration/groupByProperty.ts
Normal file
58
webapp/cypress/integration/groupByProperty.ts
Normal 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')
|
||||
})
|
||||
})
|
@ -4,6 +4,7 @@
|
||||
describe('Manage groups', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiInitServer()
|
||||
cy.apiResetBoards()
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
})
|
||||
|
||||
|
@ -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')
|
||||
}
|
||||
})
|
||||
|
@ -3,7 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": [
|
||||
"cypress"
|
||||
"cypress",
|
||||
"@testing-library/cypress"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
603
webapp/package-lock.json
generated
603
webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -41,7 +41,10 @@ const Dialog = React.memo((props: Props) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='dialog' >
|
||||
<div
|
||||
role='dialog'
|
||||
className='dialog'
|
||||
>
|
||||
<div className='toolbar'>
|
||||
{
|
||||
!props.hideCloseButton &&
|
||||
|
@ -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"
|
||||
|
@ -103,7 +103,7 @@ const ViewHeader = React.memo((props: Props) => {
|
||||
<ViewHeaderGroupByMenu
|
||||
properties={board.fields.cardProperties}
|
||||
activeView={activeView}
|
||||
groupByPropertyName={groupByProperty?.name}
|
||||
groupByProperty={groupByProperty}
|
||||
/>}
|
||||
|
||||
{/* Display by */}
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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) => {
|
||||
|
Loading…
Reference in New Issue
Block a user