GH-2812 Hide delete template and edit template icons unless user has permission (#2818)

* hide delete template and edit template icons unless user has permission

* fix jest tests
This commit is contained in:
Doug Lauder 2022-04-15 12:59:26 -04:00 committed by GitHub
parent ebf02366b4
commit 554453c9e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 60 deletions

View file

@ -15,6 +15,7 @@ import {MemoryRouter, Router} from 'react-router-dom'
import Mutator from '../../mutator' import Mutator from '../../mutator'
import {Utils} from '../../utils' import {Utils} from '../../utils'
import {Team} from '../../store/teams' import {Team} from '../../store/teams'
import {IUser} from '../../user'
import {mockDOM, mockStateStore, wrapDNDIntl} from '../../testUtils' import {mockDOM, mockStateStore, wrapDNDIntl} from '../../testUtils'
import BoardTemplateSelector from './boardTemplateSelector' import BoardTemplateSelector from './boardTemplateSelector'
@ -47,6 +48,15 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
updateAt: 0, updateAt: 0,
modifiedBy: 'user-1', modifiedBy: 'user-1',
} }
const me: IUser = {
id: 'user-id-1',
username: 'username_1',
email: '',
props: {},
create_at: 0,
update_at: 0,
is_bot: false
}
const template1Title = 'Template 1' const template1Title = 'Template 1'
const globalTemplateTitle = 'Template Global' const globalTemplateTitle = 'Template Global'
const boardTitle = 'Board 1' const boardTitle = 'Board 1'
@ -59,9 +69,8 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
current: team1, current: team1,
}, },
users: { users: {
me: { me,
id: 'user_id_1', boardUsers: [me],
},
}, },
boards: { boards: {
boards: [ boards: [
@ -88,6 +97,14 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
dateDisplayPropertyId: 'id-5', dateDisplayPropertyId: 'id-5',
}, },
], ],
membersInBoards: {
['1']: {userId: me.id, schemeAdmin: true},
['2']: {userId: me.id, schemeAdmin: true},
},
myBoardMemberships: {
['1']: {userId: me.id, schemeAdmin: true},
['2']: {userId: me.id, schemeAdmin: true},
},
cards: [], cards: [],
views: [], views: [],
}, },

View file

@ -3,9 +3,14 @@
import {render, within, act, waitFor} from '@testing-library/react' import {render, within, act, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import React from 'react' import React from 'react'
import {MockStoreEnhanced} from 'redux-mock-store'
import {Provider as ReduxProvider} from 'react-redux'
import {Board, IPropertyTemplate} from '../../blocks/board' import {Board, IPropertyTemplate} from '../../blocks/board'
import {wrapDNDIntl} from '../../testUtils' import {mockStateStore, wrapDNDIntl} from '../../testUtils'
import {IUser} from '../../user'
import {Team} from '../../store/teams'
import BoardTemplateSelectorItem from './boardTemplateSelectorItem' import BoardTemplateSelectorItem from './boardTemplateSelectorItem'
@ -42,6 +47,14 @@ jest.mock('../../utils')
jest.mock('../../mutator') jest.mock('../../mutator')
describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => { describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
const team1: Team = {
id: 'team-1',
title: 'Team 1',
signupToken: '',
updateAt: 0,
modifiedBy: 'user-1',
}
const template: Board = { const template: Board = {
id: '1', id: '1',
teamId: 'team-1', teamId: 'team-1',
@ -80,19 +93,44 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
properties: {}, properties: {},
} }
const me: IUser = {
id: 'user-id-1',
username: 'username_1',
email: '',
props: {},
create_at: 0,
update_at: 0,
is_bot: false
}
let store:MockStoreEnhanced<unknown, unknown>
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
const state = {
teams: {
current: team1,
},
boards: {
current: '1',
myBoardMemberships: {
['1']: {userId: me.id, schemeAdmin: true},
},
}
}
store = mockStateStore([], state)
}) })
test('should match snapshot', async () => { test('should match snapshot', async () => {
const {container} = render(wrapDNDIntl( const {container} = render(wrapDNDIntl(
<BoardTemplateSelectorItem <ReduxProvider store={store}>
isActive={false} <BoardTemplateSelectorItem
template={template} isActive={false}
onSelect={jest.fn()} template={template}
onDelete={jest.fn()} onSelect={jest.fn()}
onEdit={jest.fn()} onDelete={jest.fn()}
/> onEdit={jest.fn()}
/>
</ReduxProvider>
, ,
)) ))
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
@ -100,13 +138,15 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
test('should match snapshot when active', async () => { test('should match snapshot when active', async () => {
const {container} = render(wrapDNDIntl( const {container} = render(wrapDNDIntl(
<BoardTemplateSelectorItem <ReduxProvider store={store}>
isActive={true} <BoardTemplateSelectorItem
template={template} isActive={true}
onSelect={jest.fn()} template={template}
onDelete={jest.fn()} onSelect={jest.fn()}
onEdit={jest.fn()} onDelete={jest.fn()}
/> onEdit={jest.fn()}
/>
</ReduxProvider>
, ,
)) ))
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
@ -114,13 +154,15 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
test('should match snapshot with global template', async () => { test('should match snapshot with global template', async () => {
const {container} = render(wrapDNDIntl( const {container} = render(wrapDNDIntl(
<BoardTemplateSelectorItem <ReduxProvider store={store}>
isActive={false} <BoardTemplateSelectorItem
template={globalTemplate} isActive={false}
onSelect={jest.fn()} template={globalTemplate}
onDelete={jest.fn()} onSelect={jest.fn()}
onEdit={jest.fn()} onDelete={jest.fn()}
/> onEdit={jest.fn()}
/>
</ReduxProvider>
, ,
)) ))
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
@ -131,13 +173,15 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
const onDelete = jest.fn() const onDelete = jest.fn()
const onEdit = jest.fn() const onEdit = jest.fn()
const {container} = render(wrapDNDIntl( const {container} = render(wrapDNDIntl(
<BoardTemplateSelectorItem <ReduxProvider store={store}>
isActive={false} <BoardTemplateSelectorItem
template={template} isActive={false}
onSelect={onSelect} template={template}
onDelete={onDelete} onSelect={onSelect}
onEdit={onEdit} onDelete={onDelete}
/> onEdit={onEdit}
/>
</ReduxProvider>
, ,
)) ))
userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!) userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!)
@ -152,13 +196,15 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
const onDelete = jest.fn() const onDelete = jest.fn()
const onEdit = jest.fn() const onEdit = jest.fn()
const {container} = render(wrapDNDIntl( const {container} = render(wrapDNDIntl(
<BoardTemplateSelectorItem <ReduxProvider store={store}>
isActive={false} <BoardTemplateSelectorItem
template={template} isActive={false}
onSelect={onSelect} template={template}
onDelete={onDelete} onSelect={onSelect}
onEdit={onEdit} onDelete={onDelete}
/> onEdit={onEdit}
/>
</ReduxProvider>
, ,
)) ))
userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!) userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!)
@ -176,13 +222,15 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
const root = document.createElement('div') const root = document.createElement('div')
root.setAttribute('id', 'focalboard-root-portal') root.setAttribute('id', 'focalboard-root-portal')
render(wrapDNDIntl( render(wrapDNDIntl(
<BoardTemplateSelectorItem <ReduxProvider store={store}>
isActive={false} <BoardTemplateSelectorItem
template={template} isActive={false}
onSelect={onSelect} template={template}
onDelete={onDelete} onSelect={onSelect}
onEdit={onEdit} onDelete={onDelete}
/> onEdit={onEdit}
/>
</ReduxProvider>
, ,
), {container: document.body.appendChild(root)}) ), {container: document.body.appendChild(root)})
act(() => { act(() => {

View file

@ -9,8 +9,10 @@ import DeleteIcon from '../../widgets/icons/delete'
import EditIcon from '../../widgets/icons/edit' import EditIcon from '../../widgets/icons/edit'
import DeleteBoardDialog from '../sidebar/deleteBoardDialog' import DeleteBoardDialog from '../sidebar/deleteBoardDialog'
import BoardPermissionGate from '../permissions/boardPermissionGate'
import './boardTemplateSelectorItem.scss' import './boardTemplateSelectorItem.scss'
import {Constants} from "../../constants" import {Constants, Permission} from "../../constants"
type Props = { type Props = {
isActive: boolean isActive: boolean
@ -43,19 +45,31 @@ const BoardTemplateSelectorItem = (props: Props) => {
{/* don't show template menu options for default templates */} {/* don't show template menu options for default templates */}
{template.teamId !== Constants.globalTeamId && {template.teamId !== Constants.globalTeamId &&
<div className='actions'> <div className='actions'>
<IconButton <BoardPermissionGate
icon={<DeleteIcon/>} boardId={template.id}
title={intl.formatMessage({id: 'BoardTemplateSelector.delete-template', defaultMessage: 'Delete'})} teamId={template.teamId}
onClick={(e: React.MouseEvent) => { permissions={[Permission.DeleteBoard]}
e.stopPropagation() >
setDeleteOpen(true) <IconButton
}} icon={<DeleteIcon/>}
/> title={intl.formatMessage({id: 'BoardTemplateSelector.delete-template', defaultMessage: 'Delete'})}
<IconButton onClick={(e: React.MouseEvent) => {
icon={<EditIcon/>} e.stopPropagation()
title={intl.formatMessage({id: 'BoardTemplateSelector.edit-template', defaultMessage: 'Edit'})} setDeleteOpen(true)
onClick={onEditHandler} }}
/> />
</BoardPermissionGate>
<BoardPermissionGate
boardId={template.id}
teamId={template.teamId}
permissions={[Permission.ManageBoardCards, Permission.ManageBoardProperties]}
>
<IconButton
icon={<EditIcon/>}
title={intl.formatMessage({id: 'BoardTemplateSelector.edit-template', defaultMessage: 'Edit'})}
onClick={onEditHandler}
/>
</BoardPermissionGate>
</div>} </div>}
{deleteOpen && {deleteOpen &&
<DeleteBoardDialog <DeleteBoardDialog