mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
feat(frontend): deactivate alias buttons if user is not owner
These buttons and their functionality only work if the user is the owner, so it doesn't make sense to make it possible to press them otherwise… Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
107ec7a522
commit
09e56a418e
5 changed files with 122 additions and 5 deletions
|
@ -1,5 +1,65 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AliasesListEntry disables button in AliasesListEntry if it's not primary 1`] = `
|
||||||
|
<div>
|
||||||
|
<li
|
||||||
|
class="list-group-item d-flex flex-row justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
test-primary
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="me-2 btn btn-light"
|
||||||
|
data-testid="aliasButtonMakePrimary"
|
||||||
|
disabled=""
|
||||||
|
title="editor.modal.aliases.makePrimary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
BootstrapIconMock_StarFill
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="text-danger btn btn-light"
|
||||||
|
data-testid="aliasButtonRemove"
|
||||||
|
disabled=""
|
||||||
|
title="editor.modal.aliases.removeAlias"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
BootstrapIconMock_X
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`AliasesListEntry disables button in AliasesListEntry if it's primary 1`] = `
|
||||||
|
<div>
|
||||||
|
<li
|
||||||
|
class="list-group-item d-flex flex-row justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
test-primary
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="me-2 text-warning btn btn-light"
|
||||||
|
data-testid="aliasIsPrimary"
|
||||||
|
disabled=""
|
||||||
|
title="editor.modal.aliases.isPrimary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
BootstrapIconMock_Star
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="text-danger btn btn-light"
|
||||||
|
data-testid="aliasButtonRemove"
|
||||||
|
disabled=""
|
||||||
|
title="editor.modal.aliases.removeAlias"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
BootstrapIconMock_X
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`AliasesListEntry renders an AliasesListEntry that is not primary 1`] = `
|
exports[`AliasesListEntry renders an AliasesListEntry that is not primary 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<li
|
<li
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { addAlias } from '../../../../api/alias'
|
import { addAlias } from '../../../../api/alias'
|
||||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
import { useIsOwner } from '../../../../hooks/common/use-is-owner'
|
||||||
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
|
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
|
||||||
import { updateMetadata } from '../../../../redux/note-details/methods'
|
import { updateMetadata } from '../../../../redux/note-details/methods'
|
||||||
import { testId } from '../../../../utils/test-id'
|
import { testId } from '../../../../utils/test-id'
|
||||||
|
@ -25,6 +26,7 @@ export const AliasesAddForm: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { showErrorNotification } = useUiNotifications()
|
const { showErrorNotification } = useUiNotifications()
|
||||||
const noteId = useApplicationState((state) => state.noteDetails.id)
|
const noteId = useApplicationState((state) => state.noteDetails.id)
|
||||||
|
const isOwner = useIsOwner()
|
||||||
const [newAlias, setNewAlias] = useState('')
|
const [newAlias, setNewAlias] = useState('')
|
||||||
|
|
||||||
const onAddAlias = useCallback(
|
const onAddAlias = useCallback(
|
||||||
|
@ -54,6 +56,7 @@ export const AliasesAddForm: React.FC = () => {
|
||||||
placeholder={t('editor.modal.aliases.addAlias') ?? undefined}
|
placeholder={t('editor.modal.aliases.addAlias') ?? undefined}
|
||||||
onChange={onNewAliasInputChange}
|
onChange={onNewAliasInputChange}
|
||||||
isInvalid={!newAliasValid}
|
isInvalid={!newAliasValid}
|
||||||
|
disabled={!isOwner}
|
||||||
required={true}
|
required={true}
|
||||||
{...testId('addAliasInput')}
|
{...testId('addAliasInput')}
|
||||||
/>
|
/>
|
||||||
|
@ -61,7 +64,7 @@ export const AliasesAddForm: React.FC = () => {
|
||||||
type={'submit'}
|
type={'submit'}
|
||||||
variant='light'
|
variant='light'
|
||||||
className={'text-secondary ms-2'}
|
className={'text-secondary ms-2'}
|
||||||
disabled={!newAliasValid || newAlias === ''}
|
disabled={!isOwner || !newAliasValid || newAlias === ''}
|
||||||
title={t('editor.modal.aliases.addAlias') ?? undefined}
|
title={t('editor.modal.aliases.addAlias') ?? undefined}
|
||||||
{...testId('addAliasButton')}>
|
{...testId('addAliasButton')}>
|
||||||
<UiIcon icon={IconPlus} />
|
<UiIcon icon={IconPlus} />
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import * as AliasModule from '../../../../api/alias'
|
import * as AliasModule from '../../../../api/alias'
|
||||||
import type { Alias } from '../../../../api/alias/types'
|
import type { Alias } from '../../../../api/alias/types'
|
||||||
import * as NoteDetailsReduxModule from '../../../../redux/note-details/methods'
|
import * as NoteDetailsReduxModule from '../../../../redux/note-details/methods'
|
||||||
|
import { mockNoteOwnership } from '../../../../test-utils/note-ownership'
|
||||||
import { mockI18n } from '../../../markdown-renderer/test-utils/mock-i18n'
|
import { mockI18n } from '../../../markdown-renderer/test-utils/mock-i18n'
|
||||||
import * as useUiNotificationsModule from '../../../notifications/ui-notification-boundary'
|
import * as useUiNotificationsModule from '../../../notifications/ui-notification-boundary'
|
||||||
import { AliasesListEntry } from './aliases-list-entry'
|
import { AliasesListEntry } from './aliases-list-entry'
|
||||||
|
@ -15,6 +16,8 @@ import React from 'react'
|
||||||
jest.mock('../../../../api/alias')
|
jest.mock('../../../../api/alias')
|
||||||
jest.mock('../../../../redux/note-details/methods')
|
jest.mock('../../../../redux/note-details/methods')
|
||||||
jest.mock('../../../notifications/ui-notification-boundary')
|
jest.mock('../../../notifications/ui-notification-boundary')
|
||||||
|
// This needs to be mocked here in addition to note-ownership.ts, because jest doesn't work otherwise
|
||||||
|
jest.mock('../../../../hooks/common/use-application-state')
|
||||||
|
|
||||||
const deletePromise = Promise.resolve()
|
const deletePromise = Promise.resolve()
|
||||||
const markAsPrimaryPromise = Promise.resolve({ name: 'mock', primaryAlias: true, noteId: 'mock' })
|
const markAsPrimaryPromise = Promise.resolve({ name: 'mock', primaryAlias: true, noteId: 'mock' })
|
||||||
|
@ -32,12 +35,13 @@ describe('AliasesListEntry', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterEach(() => {
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks()
|
||||||
jest.resetModules()
|
jest.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders an AliasesListEntry that is primary', async () => {
|
it('renders an AliasesListEntry that is primary', async () => {
|
||||||
|
mockNoteOwnership('test', 'test')
|
||||||
const testAlias: Alias = {
|
const testAlias: Alias = {
|
||||||
name: 'test-primary',
|
name: 'test-primary',
|
||||||
primaryAlias: true,
|
primaryAlias: true,
|
||||||
|
@ -54,7 +58,19 @@ describe('AliasesListEntry', () => {
|
||||||
expect(NoteDetailsReduxModule.updateMetadata).toBeCalled()
|
expect(NoteDetailsReduxModule.updateMetadata).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("disables button in AliasesListEntry if it's primary", () => {
|
||||||
|
mockNoteOwnership('test2', 'test')
|
||||||
|
const testAlias: Alias = {
|
||||||
|
name: 'test-primary',
|
||||||
|
primaryAlias: true,
|
||||||
|
noteId: 'test-note-id'
|
||||||
|
}
|
||||||
|
const view = render(<AliasesListEntry alias={testAlias} />)
|
||||||
|
expect(view.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
it('renders an AliasesListEntry that is not primary', async () => {
|
it('renders an AliasesListEntry that is not primary', async () => {
|
||||||
|
mockNoteOwnership('test', 'test')
|
||||||
const testAlias: Alias = {
|
const testAlias: Alias = {
|
||||||
name: 'test-non-primary',
|
name: 'test-non-primary',
|
||||||
primaryAlias: false,
|
primaryAlias: false,
|
||||||
|
@ -77,4 +93,15 @@ describe('AliasesListEntry', () => {
|
||||||
await markAsPrimaryPromise
|
await markAsPrimaryPromise
|
||||||
expect(NoteDetailsReduxModule.updateMetadata).toBeCalled()
|
expect(NoteDetailsReduxModule.updateMetadata).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("disables button in AliasesListEntry if it's not primary", () => {
|
||||||
|
mockNoteOwnership('test2', 'test')
|
||||||
|
const testAlias: Alias = {
|
||||||
|
name: 'test-primary',
|
||||||
|
primaryAlias: false,
|
||||||
|
noteId: 'test-note-id'
|
||||||
|
}
|
||||||
|
const view = render(<AliasesListEntry alias={testAlias} />)
|
||||||
|
expect(view.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { deleteAlias, markAliasAsPrimary } from '../../../../api/alias'
|
import { deleteAlias, markAliasAsPrimary } from '../../../../api/alias'
|
||||||
import type { Alias } from '../../../../api/alias/types'
|
import type { Alias } from '../../../../api/alias/types'
|
||||||
|
import { useIsOwner } from '../../../../hooks/common/use-is-owner'
|
||||||
import { updateMetadata } from '../../../../redux/note-details/methods'
|
import { updateMetadata } from '../../../../redux/note-details/methods'
|
||||||
import { testId } from '../../../../utils/test-id'
|
import { testId } from '../../../../utils/test-id'
|
||||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||||
|
@ -29,6 +30,7 @@ export interface AliasesListEntryProps {
|
||||||
export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) => {
|
export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { showErrorNotification } = useUiNotifications()
|
const { showErrorNotification } = useUiNotifications()
|
||||||
|
const isOwner = useIsOwner()
|
||||||
|
|
||||||
const onRemoveClick = useCallback(() => {
|
const onRemoveClick = useCallback(() => {
|
||||||
deleteAlias(alias.name)
|
deleteAlias(alias.name)
|
||||||
|
@ -60,6 +62,7 @@ export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) =>
|
||||||
<Button
|
<Button
|
||||||
className={'me-2'}
|
className={'me-2'}
|
||||||
variant='light'
|
variant='light'
|
||||||
|
disabled={!isOwner}
|
||||||
title={t('editor.modal.aliases.makePrimary') ?? undefined}
|
title={t('editor.modal.aliases.makePrimary') ?? undefined}
|
||||||
onClick={onMakePrimaryClick}
|
onClick={onMakePrimaryClick}
|
||||||
{...testId('aliasButtonMakePrimary')}>
|
{...testId('aliasButtonMakePrimary')}>
|
||||||
|
@ -69,6 +72,7 @@ export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) =>
|
||||||
<Button
|
<Button
|
||||||
variant='light'
|
variant='light'
|
||||||
className={'text-danger'}
|
className={'text-danger'}
|
||||||
|
disabled={!isOwner}
|
||||||
title={t('editor.modal.aliases.removeAlias') ?? undefined}
|
title={t('editor.modal.aliases.removeAlias') ?? undefined}
|
||||||
onClick={onRemoveClick}
|
onClick={onRemoveClick}
|
||||||
{...testId('aliasButtonRemove')}>
|
{...testId('aliasButtonRemove')}>
|
||||||
|
|
23
frontend/src/test-utils/note-ownership.ts
Normal file
23
frontend/src/test-utils/note-ownership.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import * as useApplicationStateModule from '../hooks/common/use-application-state'
|
||||||
|
import type { ApplicationState } from '../redux/application-state'
|
||||||
|
|
||||||
|
jest.mock('../hooks/common/use-application-state')
|
||||||
|
export const mockNoteOwnership = (ownUsername: string, noteOwner: string) => {
|
||||||
|
jest.spyOn(useApplicationStateModule, 'useApplicationState').mockImplementation((fn) => {
|
||||||
|
return fn({
|
||||||
|
noteDetails: {
|
||||||
|
permissions: {
|
||||||
|
owner: noteOwner
|
||||||
|
}
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
username: ownUsername
|
||||||
|
}
|
||||||
|
} as ApplicationState)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue