mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #9636 from overleaf/ab-prevent-create-rename-tag-existing-name
[web] Prevent creating/renaming a tag to an existing name GitOrigin-RevId: 44bb35a4152238ce21fa6e0d4d211cc5b25481e8
This commit is contained in:
parent
7608d37c0a
commit
9f4df9c0f4
5 changed files with 86 additions and 6 deletions
|
@ -601,6 +601,7 @@
|
|||
"tab_connecting": "",
|
||||
"tab_no_longer_connected": "",
|
||||
"tag_name_cannot_exceed_characters": "",
|
||||
"tag_name_is_already_used": "",
|
||||
"tags": "",
|
||||
"tags_slash_folders": "",
|
||||
"take_short_survey": "",
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import { createTag } from '../../util/api'
|
||||
import { MAX_TAG_LENGTH } from '../../util/tag'
|
||||
|
||||
|
@ -20,6 +21,7 @@ export default function CreateTagModal({
|
|||
onCreate,
|
||||
onClose,
|
||||
}: CreateTagModalProps) {
|
||||
const { tags } = useProjectListContext()
|
||||
const { t } = useTranslation()
|
||||
const { isError, runAsync, status } = useAsync<Tag>()
|
||||
|
||||
|
@ -47,10 +49,12 @@ export default function CreateTagModal({
|
|||
setValidationError(
|
||||
t('tag_name_cannot_exceed_characters', { maxLength: MAX_TAG_LENGTH })
|
||||
)
|
||||
} else if (tagName && tags.find(tag => tag.name === tagName)) {
|
||||
setValidationError(t('tag_name_is_already_used', { tagName }))
|
||||
} else if (validationError) {
|
||||
setValidationError(undefined)
|
||||
}
|
||||
}, [tagName, t, validationError])
|
||||
}, [tagName, tags, t, validationError])
|
||||
|
||||
if (!show) {
|
||||
return null
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import { renameTag } from '../../util/api'
|
||||
import { MAX_TAG_LENGTH } from '../../util/tag'
|
||||
|
||||
|
@ -20,8 +21,9 @@ export default function RenameTagModal({
|
|||
onRename,
|
||||
onClose,
|
||||
}: RenameTagModalProps) {
|
||||
const { tags } = useProjectListContext()
|
||||
const { t } = useTranslation()
|
||||
const { isLoading, isError, runAsync } = useAsync()
|
||||
const { isLoading, isError, runAsync, status } = useAsync()
|
||||
|
||||
const [newTagName, setNewTagName] = useState<string>()
|
||||
const [validationError, setValidationError] = useState<string>()
|
||||
|
@ -52,10 +54,16 @@ export default function RenameTagModal({
|
|||
setValidationError(
|
||||
t('tag_name_cannot_exceed_characters', { maxLength: MAX_TAG_LENGTH })
|
||||
)
|
||||
} else if (
|
||||
newTagName &&
|
||||
newTagName !== tag?.name &&
|
||||
tags.find(tag => tag.name === newTagName)
|
||||
) {
|
||||
setValidationError(t('tag_name_is_already_used', { tagName: newTagName }))
|
||||
} else if (validationError) {
|
||||
setValidationError(undefined)
|
||||
}
|
||||
}, [newTagName, t, validationError])
|
||||
}, [newTagName, tags, tag?.name, t, validationError])
|
||||
|
||||
if (!tag) {
|
||||
return null
|
||||
|
@ -100,7 +108,13 @@ export default function RenameTagModal({
|
|||
<Button
|
||||
onClick={() => runRenameTag(tag._id)}
|
||||
bsStyle="primary"
|
||||
disabled={isLoading || !newTagName?.length || !!validationError}
|
||||
disabled={
|
||||
isLoading ||
|
||||
status === 'pending' ||
|
||||
newTagName === tag?.name ||
|
||||
!newTagName?.length ||
|
||||
!!validationError
|
||||
}
|
||||
>
|
||||
{isLoading ? <>{t('renaming')} …</> : t('rename')}
|
||||
</Button>
|
||||
|
|
|
@ -1885,9 +1885,8 @@
|
|||
"create_first_project": "Create First Project",
|
||||
"you_dont_have_any_repositories": "You don’t have any repositories",
|
||||
"tag_name_cannot_exceed_characters": "Tag name cannot exceed __maxLength__ characters",
|
||||
"tag_name_is_already_used": "Tag \"__tagName__\" already exists",
|
||||
"save_changes": "Save changes",
|
||||
"tag_name_cannot_exceed_characters": "Tag name cannot exceed __maxLength__ characters",
|
||||
"overleaf_labs": "Overleaf Labs",
|
||||
"labs_program_already_participating": "You are enrolled in Labs",
|
||||
"labs_program_not_participating": "You are not enrolled in Labs",
|
||||
"labs_program_benefits": "__appName__ is always looking for new ways to help users work more quickly and effectively. By joining Overleaf Labs, you can participate in experiments that explore innovative ideas in the space of collaborative writing and publishing.",
|
||||
|
|
|
@ -104,6 +104,34 @@ describe('<TagsList />', function () {
|
|||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
})
|
||||
|
||||
it('Create button is disabled with error message when tag name is too long', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value: 'This is a very very very very very very long tag name',
|
||||
},
|
||||
})
|
||||
|
||||
const createButton = within(modal).getByRole('button', { name: 'Create' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
screen.getByText('Tag name cannot exceed 50 characters')
|
||||
})
|
||||
|
||||
it('Create button is disabled with error message when tag name is already used', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value: 'Tag 1',
|
||||
},
|
||||
})
|
||||
|
||||
const createButton = within(modal).getByRole('button', { name: 'Create' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
screen.getByText('Tag "Tag 1" already exists')
|
||||
})
|
||||
|
||||
it('filling the input and clicking Create sends a request', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
|
@ -157,6 +185,40 @@ describe('<TagsList />', function () {
|
|||
expect(renameButton.hasAttribute('disabled')).to.be.true
|
||||
})
|
||||
|
||||
it('Rename button is disabled with error message when tag name is too long', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value: 'This is a very very very very very very long tag name',
|
||||
},
|
||||
})
|
||||
|
||||
const createButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
screen.getByText('Tag name cannot exceed 50 characters')
|
||||
})
|
||||
|
||||
it('Rename button is disabled with no error message when tag name is unchanged', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const createButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
})
|
||||
|
||||
it('Rename button is disabled with error message when tag name is already used', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value: 'Another tag',
|
||||
},
|
||||
})
|
||||
|
||||
const createButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
screen.getByText('Tag "Another tag" already exists')
|
||||
})
|
||||
|
||||
it('filling the input and clicking Rename sends a request', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
|
|
Loading…
Reference in a new issue