[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:
Alexandre Bourdin
2023-04-13 08:04:03 +00:00
committed by Copybot
parent fb6746a887
commit 04c204f989
32 changed files with 1029 additions and 359 deletions
@@ -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')
@@ -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,