Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PaletteEdit: improve unit tests #57645

Merged
merged 12 commits into from
Jan 12, 2024
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- `ToggleGroupControl`: Update button size in large variant to be 32px ([#57338](https://github.com/WordPress/gutenberg/pull/57338)).
- `Tooltip`: improve unit tests ([#57345](https://github.com/WordPress/gutenberg/pull/57345)).
- `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)).
- `PaletteEdit`: improve unit tests ([#57645](https://github.com/WordPress/gutenberg/pull/57645)).

### Experimental

Expand Down
329 changes: 319 additions & 10 deletions packages/components/src/palette-edit/test/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/**
* External dependencies
*/
import { render, fireEvent, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

/**
* Internal dependencies
*/
import PaletteEdit, { getNameForPosition } from '..';
import type { PaletteElement } from '../types';

const noop = () => {};

describe( 'getNameForPosition', () => {
test( 'should return 1 by default', () => {
const slugPrefix = 'test-';
Expand Down Expand Up @@ -82,18 +85,324 @@ describe( 'getNameForPosition', () => {

describe( 'PaletteEdit', () => {
const defaultProps = {
colors: [ { color: '#ffffff', name: 'Base', slug: 'base' } ],
onChange: jest.fn(),
paletteLabel: 'Test label',
emptyMessage: 'Test empty message',
canOnlyChangeValues: true,
canReset: true,
slugPrefix: '',
onChange: noop,
};

it( 'opens color selector for color palettes', () => {
render( <PaletteEdit { ...defaultProps } /> );
fireEvent.click( screen.getByLabelText( 'Color: Base' ) );
expect( screen.getByLabelText( 'Hex color' ) ).toBeInTheDocument();
Comment on lines -94 to -97
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be covered by the "can update color palette value" test.

const colors = [
{ color: '#1a4548', name: 'Primary', slug: 'primary' },
{ color: '#0000ff', name: 'Secondary', slug: 'secondary' },
];
const gradients = [
{
gradient:
'linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)',
name: 'Pale ocean',
slug: 'pale-ocean',
},
{
gradient:
'linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)',
name: 'Midnight',
slug: 'midnight',
},
];

it( 'shows heading label', () => {
render( <PaletteEdit { ...defaultProps } colors={ colors } /> );

const paletteLabel = screen.getByRole( 'heading', {
level: 2,
name: 'Test label',
} );

expect( paletteLabel ).toBeVisible();
} );

it( 'shows heading label with custom heading level', () => {
render(
<PaletteEdit
{ ...defaultProps }
colors={ colors }
paletteLabelHeadingLevel={ 5 }
/>
);

expect(
screen.getByRole( 'heading', {
level: 5,
name: 'Test label',
} )
).toBeVisible();
} );

it( 'shows empty message', () => {
render(
<PaletteEdit
{ ...defaultProps }
emptyMessage={ 'Test empty message' }
/>
);

expect( screen.getByText( 'Test empty message' ) ).toBeVisible();
} );

it( 'shows an option to remove all colors', async () => {
const user = userEvent.setup();
render( <PaletteEdit { ...defaultProps } colors={ colors } /> );

await user.click(
screen.getByRole( 'button', {
name: 'Color options',
} )
);

expect(
screen.getByRole( 'button', {
name: 'Remove all colors',
} )
).toBeVisible();
} );

it( 'shows a reset option when the `canReset` prop is enabled', async () => {
const user = userEvent.setup();
render(
<PaletteEdit { ...defaultProps } colors={ colors } canReset />
);

await user.click(
screen.getByRole( 'button', {
name: 'Color options',
} )
);
expect(
screen.getByRole( 'button', {
name: 'Reset colors',
} )
).toBeVisible();
} );

it( 'does not show a reset colors option when `canReset` is disabled', async () => {
const user = userEvent.setup();
render( <PaletteEdit { ...defaultProps } colors={ colors } /> );

await user.click(
screen.getByRole( 'button', {
name: 'Color options',
} )
);
expect(
screen.queryByRole( 'button', {
name: 'Reset colors',
} )
).not.toBeInTheDocument();
} );

it( 'calls the `onChange` with the new color appended', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

render(
<PaletteEdit
{ ...defaultProps }
colors={ colors }
onChange={ onChange }
/>
);

await user.click(
screen.getByRole( 'button', {
name: 'Add color',
} )
);

await waitFor( () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onChange is debounced, so we need to use waitFor. It appears to have been debounced for performance improvements. See #40285.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To better mimic actual user behavior, it might be better to click the "Done" button rather than do a wait for the onChange.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think onChange is called because the "Done" button only clears the internal editing state. Strictly speaking, this button should probably be called "Back" or "Exit Edit Mode"...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point.

expect( onChange ).toHaveBeenCalledWith( [
...colors,
{
color: '#000',
name: 'Color 1',
slug: 'color-1',
},
] );
} );
} );

it( 'calls the `onChange` with the new gradient appended', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

render(
<PaletteEdit
{ ...defaultProps }
gradients={ gradients }
onChange={ onChange }
/>
);

await user.click(
screen.getByRole( 'button', {
name: 'Add gradient',
} )
);

await waitFor( () => {
expect( onChange ).toHaveBeenCalledWith( [
...gradients,
{
gradient:
'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)',
name: 'Color 1',
slug: 'color-1',
t-hamano marked this conversation as resolved.
Show resolved Hide resolved
},
] );
} );
} );

it( 'can not add new colors when `canOnlyChangeValues` is enabled', () => {
render( <PaletteEdit { ...defaultProps } canOnlyChangeValues /> );

expect(
screen.queryByRole( 'button', {
name: 'Add color',
} )
).not.toBeInTheDocument();
} );

it( 'can remove a color', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

render(
<PaletteEdit
{ ...defaultProps }
colors={ colors }
onChange={ onChange }
/>
);

await user.click(
screen.getByRole( 'button', {
name: 'Color options',
} )
);
await user.click(
screen.getByRole( 'button', {
name: 'Show details',
} )
);
await user.click( screen.getByText( 'Primary' ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great example of our tests doubling as accessibility tests. The fact that there's no labeled button to click means that this part of the UI is not accessible. Let's fix this in a follow-up.

await user.click(
screen.getByRole( 'button', {
name: 'Remove color',
} )
);

await waitFor( () => {
expect( onChange ).toHaveBeenCalledWith( [ colors[ 1 ] ] );
} );
} );

it( 'can update palette name', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

render(
<PaletteEdit
{ ...defaultProps }
colors={ colors }
onChange={ onChange }
/>
);

await user.click(
screen.getByRole( 'button', {
name: 'Color options',
} )
);
await user.click(
screen.getByRole( 'button', {
name: 'Show details',
} )
);
await user.click( screen.getByText( 'Primary' ) );
const nameInput = screen.getByRole( 'textbox', {
name: 'Color name',
} );
await user.clear( nameInput );
await user.type( nameInput, 'Primary Updated' );

await waitFor( () => {
expect( onChange ).toHaveBeenCalledWith( [
{
...colors[ 0 ],
name: 'Primary Updated',
slug: 'primary-updated',
},
colors[ 1 ],
] );
} );
} );

it( 'can update color palette value', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

render(
<PaletteEdit
{ ...defaultProps }
colors={ colors }
onChange={ onChange }
/>
);

await user.click( screen.getByLabelText( 'Color: Primary' ) );
const hexInput = screen.getByRole( 'textbox', {
name: 'Hex color',
} );
await user.clear( hexInput );
await user.type( hexInput, '000000' );

await waitFor( () => {
expect( onChange ).toHaveBeenCalledWith( [
{
...colors[ 0 ],
color: '#000000',
},
colors[ 1 ],
] );
} );
} );

it( 'can update gradient palette value', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

render(
<PaletteEdit
{ ...defaultProps }
gradients={ gradients }
onChange={ onChange }
/>
);

await user.click( screen.getByLabelText( 'Gradient: Pale ocean' ) );

const typeSelectElement = screen.getByRole( 'combobox', {
name: 'Type',
} );
await user.selectOptions( typeSelectElement, 'radial-gradient' );

await waitFor( () => {
expect( onChange ).toHaveBeenCalledWith( [
{
...gradients[ 0 ],
gradient:
'radial-gradient(rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)',
},
gradients[ 1 ],
] );
} );
} );
} );
Loading