mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
[web] Colour picker for tags (#12255)
* Base for color picker * React color picker and updated modals * Add tag color picker to mobile dashboard * Update existing tests and fix disable save button condition * CSS adaptations for desktop modal streched into mobile display * Update TagsController tests * Add aria-hidden label on color pickers * Fix linting * Fix project list test * Select random color when creating tag * Cleanup leftovers in project list context * Test cleanup * Pre-select custom color and store local color while picking * Add type to preset colors * Add css fix to override disabled button opacity * Skip redundant check * Fix linting * Add back btn-secondary on manage tag modal after rebase GitOrigin-RevId: a4cf24e85cc0ca01466f4bf9c77482be8360e68e
This commit is contained in:
committed by
Copybot
parent
fb6746a887
commit
04c204f989
+4
-4
@@ -799,9 +799,9 @@ describe('<ProjectListRoot />', function () {
|
||||
).findByText<HTMLElement>('More')
|
||||
fireEvent.click(moreDropdown)
|
||||
|
||||
const renameButton =
|
||||
screen.getAllByText<HTMLButtonElement>('Rename')[1] // first one is for the tag in the sidebar
|
||||
fireEvent.click(renameButton)
|
||||
const editButton =
|
||||
screen.getAllByText<HTMLButtonElement>('Rename')[0]
|
||||
fireEvent.click(editButton)
|
||||
|
||||
const modals = await screen.findAllByRole('dialog')
|
||||
const modal = modals[0]
|
||||
@@ -837,7 +837,7 @@ describe('<ProjectListRoot />', function () {
|
||||
fireEvent.click(moreDropdown)
|
||||
|
||||
const renameButton =
|
||||
within(actionsToolbar).getByText<HTMLButtonElement>('Rename') // first one is for the tag in the sidebar
|
||||
within(actionsToolbar).getByText<HTMLButtonElement>('Rename')
|
||||
fireEvent.click(renameButton)
|
||||
|
||||
const modals = await screen.findAllByRole('dialog')
|
||||
|
||||
+30
-24
@@ -28,7 +28,7 @@ describe('<TagsList />', function () {
|
||||
project_ids: [],
|
||||
})
|
||||
fetchMock.post('express:/tag/:tagId/projects', 200)
|
||||
fetchMock.post('express:/tag/:tagId/rename', 200)
|
||||
fetchMock.post('express:/tag/:tagId/edit', 200)
|
||||
fetchMock.delete('express:/tag/:tagId', 200)
|
||||
|
||||
renderWithProjectListContext(<TagsList />)
|
||||
@@ -79,7 +79,7 @@ describe('<TagsList />', function () {
|
||||
)
|
||||
})
|
||||
|
||||
describe('create modal', function () {
|
||||
describe('Create modal', function () {
|
||||
beforeEach(async function () {
|
||||
const newTagButton = screen.getByRole('button', {
|
||||
name: 'New Folder',
|
||||
@@ -156,22 +156,22 @@ describe('<TagsList />', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('rename modal', function () {
|
||||
describe('Edit modal', function () {
|
||||
beforeEach(async function () {
|
||||
const tag1Button = screen.getByText('Tag 1')
|
||||
|
||||
const renameButton = within(
|
||||
const editButton = within(
|
||||
tag1Button.closest('li') as HTMLElement
|
||||
).getByRole('button', {
|
||||
name: 'Rename',
|
||||
name: 'Edit',
|
||||
})
|
||||
|
||||
await fireEvent.click(renameButton)
|
||||
await fireEvent.click(editButton)
|
||||
})
|
||||
|
||||
it('modal is open', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
within(modal).getByRole('heading', { name: 'Rename Folder' })
|
||||
within(modal).getByRole('heading', { name: 'Edit Folder' })
|
||||
})
|
||||
|
||||
it('click on cancel closes the modal', async function () {
|
||||
@@ -182,14 +182,20 @@ describe('<TagsList />', function () {
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
})
|
||||
|
||||
it('Rename button is disabled when input is empty', async function () {
|
||||
it('Save button is disabled when input is empty', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const renameButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
const input = within(modal).getByRole('textbox')
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value: '',
|
||||
},
|
||||
})
|
||||
const saveButton = within(modal).getByRole('button', { name: 'Save' })
|
||||
|
||||
expect(renameButton.hasAttribute('disabled')).to.be.true
|
||||
expect(saveButton.hasAttribute('disabled')).to.be.true
|
||||
})
|
||||
|
||||
it('Rename button is disabled with error message when tag name is too long', async function () {
|
||||
it('Save 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, {
|
||||
@@ -198,18 +204,18 @@ describe('<TagsList />', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const createButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
const saveButton = within(modal).getByRole('button', { name: 'Save' })
|
||||
expect(saveButton.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 () {
|
||||
it('Save 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
|
||||
const saveButton = within(modal).getByRole('button', { name: 'Save' })
|
||||
expect(saveButton.hasAttribute('disabled')).to.be.true
|
||||
})
|
||||
|
||||
it('Rename button is disabled with error message when tag name is already used', async function () {
|
||||
it('Save 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, {
|
||||
@@ -218,20 +224,20 @@ describe('<TagsList />', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const createButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.true
|
||||
const saveButton = within(modal).getByRole('button', { name: 'Save' })
|
||||
expect(saveButton.hasAttribute('disabled')).to.be.true
|
||||
screen.getByText('Tag "Another tag" already exists')
|
||||
})
|
||||
|
||||
it('filling the input and clicking Rename sends a request', async function () {
|
||||
it('filling the input and clicking Save sends a request', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const input = within(modal).getByRole('textbox')
|
||||
fireEvent.change(input, { target: { value: 'New Tag Name' } })
|
||||
|
||||
const renameButton = within(modal).getByRole('button', { name: 'Rename' })
|
||||
expect(renameButton.hasAttribute('disabled')).to.be.false
|
||||
const saveButton = within(modal).getByRole('button', { name: 'Save' })
|
||||
expect(saveButton.hasAttribute('disabled')).to.be.false
|
||||
|
||||
await fireEvent.click(renameButton)
|
||||
await fireEvent.click(saveButton)
|
||||
|
||||
await waitFor(() => expect(fetchMock.called(`/tag/abc123def456/rename`)))
|
||||
|
||||
@@ -243,7 +249,7 @@ describe('<TagsList />', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete modal', function () {
|
||||
describe('Delete modal', function () {
|
||||
beforeEach(async function () {
|
||||
const tag1Button = screen.getByText('Another tag')
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import React from 'react'
|
||||
import { ColorPickerProvider } from '../../../../../frontend/js/features/project-list/context/color-picker-context'
|
||||
import { ProjectListProvider } from '../../../../../frontend/js/features/project-list/context/project-list-context'
|
||||
import { Project } from '../../../../../types/project/dashboard/api'
|
||||
import { projectsData } from '../fixtures/projects-data'
|
||||
@@ -33,7 +34,11 @@ export function renderWithProjectListContext(
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => <ProjectListProvider>{children}</ProjectListProvider>
|
||||
}) => (
|
||||
<ProjectListProvider>
|
||||
<ColorPickerProvider>{children}</ColorPickerProvider>
|
||||
</ProjectListProvider>
|
||||
)
|
||||
|
||||
return render(component, {
|
||||
wrapper: ProjectListProviderWrapper,
|
||||
|
||||
@@ -18,6 +18,7 @@ describe('TagsController', function () {
|
||||
removeProjectFromTag: sinon.stub().resolves(),
|
||||
removeProjectsFromTag: sinon.stub().resolves(),
|
||||
deleteTag: sinon.stub().resolves(),
|
||||
editTag: sinon.stub().resolves(),
|
||||
renameTag: sinon.stub().resolves(),
|
||||
createTag: sinon.stub().resolves(),
|
||||
},
|
||||
@@ -66,23 +67,49 @@ describe('TagsController', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('create a tag', function (done) {
|
||||
this.tag = { mock: 'tag' }
|
||||
this.TagsHandler.promises.createTag = sinon.stub().resolves(this.tag)
|
||||
this.req.session.user._id = this.userId = 'user-id-123'
|
||||
this.req.body = { name: (this.name = 'tag-name') }
|
||||
this.TagsController.createTag(this.req, {
|
||||
json: () => {
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.createTag,
|
||||
this.userId,
|
||||
this.name
|
||||
)
|
||||
done()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
describe('create a tag', function (done) {
|
||||
it('without a color', function (done) {
|
||||
this.tag = { mock: 'tag' }
|
||||
this.TagsHandler.promises.createTag = sinon.stub().resolves(this.tag)
|
||||
this.req.session.user._id = this.userId = 'user-id-123'
|
||||
this.req.body = { name: (this.name = 'tag-name') }
|
||||
this.TagsController.createTag(this.req, {
|
||||
json: () => {
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.createTag,
|
||||
this.userId,
|
||||
this.name
|
||||
)
|
||||
done()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('with a color', function (done) {
|
||||
this.tag = { mock: 'tag' }
|
||||
this.TagsHandler.promises.createTag = sinon.stub().resolves(this.tag)
|
||||
this.req.session.user._id = this.userId = 'user-id-123'
|
||||
this.req.body = {
|
||||
name: (this.name = 'tag-name'),
|
||||
color: (this.color = '#123456'),
|
||||
}
|
||||
this.TagsController.createTag(this.req, {
|
||||
json: () => {
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.createTag,
|
||||
this.userId,
|
||||
this.name,
|
||||
this.color
|
||||
)
|
||||
done()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -105,19 +132,21 @@ describe('TagsController', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('rename a tag', function () {
|
||||
describe('edit a tag', function () {
|
||||
beforeEach(function () {
|
||||
this.req.params.tagId = this.tagId = 'tag-id-123'
|
||||
this.req.session.user._id = this.userId = 'user-id-123'
|
||||
})
|
||||
|
||||
it('with a name', function (done) {
|
||||
this.req.body = { name: (this.name = 'new-name') }
|
||||
this.TagsController.renameTag(this.req, {
|
||||
it('with a name and no color', function (done) {
|
||||
this.req.body = {
|
||||
name: (this.name = 'new-name'),
|
||||
}
|
||||
this.TagsController.editTag(this.req, {
|
||||
status: code => {
|
||||
assert.equal(code, 204)
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.renameTag,
|
||||
this.TagsHandler.promises.editTag,
|
||||
this.userId,
|
||||
this.tagId,
|
||||
this.name
|
||||
@@ -130,6 +159,29 @@ describe('TagsController', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('with a name and color', function (done) {
|
||||
this.req.body = {
|
||||
name: (this.name = 'new-name'),
|
||||
color: (this.color = '#FF0011'),
|
||||
}
|
||||
this.TagsController.editTag(this.req, {
|
||||
status: code => {
|
||||
assert.equal(code, 204)
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.editTag,
|
||||
this.userId,
|
||||
this.tagId,
|
||||
this.name,
|
||||
this.color
|
||||
)
|
||||
done()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('without a name', function (done) {
|
||||
this.req.body = { name: undefined }
|
||||
this.TagsController.renameTag(this.req, {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('TagsHandler', function () {
|
||||
this.userId = ObjectId().toString()
|
||||
this.callback = sinon.stub()
|
||||
|
||||
this.tag = { user_id: this.userId, name: 'some name' }
|
||||
this.tag = { user_id: this.userId, name: 'some name', color: '#3399CC' }
|
||||
this.tagId = ObjectId().toString()
|
||||
this.projectId = ObjectId().toString()
|
||||
|
||||
@@ -54,11 +54,13 @@ describe('TagsHandler', function () {
|
||||
this.TagsHandler.createTag(
|
||||
this.tag.user_id,
|
||||
this.tag.name,
|
||||
this.tag.color,
|
||||
(err, resultTag) => {
|
||||
expect(err).to.not.exist
|
||||
this.TagMock.verify()
|
||||
expect(resultTag.user_id).to.equal(this.tag.user_id)
|
||||
expect(resultTag.name).to.equal(this.tag.name)
|
||||
expect(resultTag.color).to.equal(this.tag.color)
|
||||
done()
|
||||
}
|
||||
)
|
||||
@@ -70,6 +72,7 @@ describe('TagsHandler', function () {
|
||||
this.TagsHandler.createTag(
|
||||
this.tag.user_id,
|
||||
'this is a tag that is very very very very very very long',
|
||||
undefined,
|
||||
err => {
|
||||
expect(err.message).to.equal('Exceeded max tag length')
|
||||
done()
|
||||
@@ -96,11 +99,13 @@ describe('TagsHandler', function () {
|
||||
this.TagsHandler.createTag(
|
||||
this.tag.user_id,
|
||||
this.tag.name,
|
||||
this.tag.color,
|
||||
(err, resultTag) => {
|
||||
expect(err).to.not.exist
|
||||
this.TagMock.verify()
|
||||
expect(resultTag.user_id).to.equal(this.tag.user_id)
|
||||
expect(resultTag.name).to.equal(this.tag.name)
|
||||
expect(resultTag.color).to.equal(this.tag.color)
|
||||
done()
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user