Skip to content

Commit

Permalink
test(clerk-js): Test cases for parity between PlainInput and FormCont…
Browse files Browse the repository at this point in the history
…rol (#2029)
  • Loading branch information
panteliselef authored Nov 3, 2023
1 parent 5fefc3f commit d65d36f
Show file tree
Hide file tree
Showing 3 changed files with 323 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-insects-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Tests for internal PlainInput component.
317 changes: 317 additions & 0 deletions packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
import { describe, it } from '@jest/globals';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { useFormControl } from '../../utils';
import { bindCreateFixtures } from '../../utils/test/createFixtures';
import { withCardStateProvider } from '../contexts';
import { Form } from '../Form';

const { createFixtures } = bindCreateFixtures('UserProfile');
const createField = (...params: Parameters<typeof useFormControl>) => {
const MockFieldWrapper = withCardStateProvider((props: Partial<Parameters<typeof Form.PlainInput>[0]>) => {
const field = useFormControl(...params);

return (
<>
{/* @ts-ignore*/}
<Form.PlainInput
{...field.props}
{...props}
/>
<button onClick={() => field.setError('some error')}>set error</button>
</>
);
});

return {
Field: MockFieldWrapper,
};
};

// TODO: Remove this once FormControl is no longer used
const createFormControl = (...params: Parameters<typeof useFormControl>) => {
const MockFieldWrapper = withCardStateProvider((props: Partial<Parameters<typeof Form.PlainInput>[0]>) => {
const field = useFormControl(...params);

return (
<>
{/* @ts-ignore*/}
<Form.Control
{...field.props}
{...props}
/>
<button onClick={() => field.setError('some error')}>set error</button>
</>
);
});

return {
Field: MockFieldWrapper,
};
};

describe('PlainInput', () => {
it('renders the component', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText } = render(<Field />, { wrapper });
expect(getByLabelText('some label')).toHaveValue('init value');
expect(getByLabelText('some label')).toHaveAttribute('name', 'firstname');
expect(getByLabelText('some label')).toHaveAttribute('placeholder', 'some placeholder');
expect(getByLabelText('some label')).toHaveAttribute('type', 'text');
expect(getByLabelText('some label')).toHaveAttribute('id', 'firstname-field');
expect(getByLabelText('some label')).not.toHaveAttribute('disabled');
expect(getByLabelText('some label')).not.toHaveAttribute('required');
expect(getByLabelText('some label')).toHaveAttribute('aria-invalid', 'false');
expect(getByLabelText('some label')).toHaveAttribute('aria-describedby', '');
expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'false');
expect(getByLabelText('some label')).toHaveAttribute('aria-disabled', 'false');
});

it('disabled', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText } = render(<Field isDisabled />, { wrapper });
expect(getByLabelText('some label')).toHaveValue('init value');
expect(getByLabelText('some label')).toHaveAttribute('disabled');
expect(getByLabelText('some label')).toHaveAttribute('aria-disabled', 'true');
});

it('required', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText, queryByText } = render(<Field isRequired />, { wrapper });
expect(getByLabelText('some label')).toHaveValue('init value');
expect(getByLabelText('some label')).toHaveAttribute('required');
expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'true');
expect(queryByText(/optional/i)).not.toBeInTheDocument();
});

it('optional', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText, getByText } = render(<Field isOptional />, { wrapper });
expect(getByLabelText('some label')).not.toHaveAttribute('required');
expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'false');
expect(getByText(/optional/i)).toBeInTheDocument();
});

it('with icon', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const Icon = () => <img alt='this is an icon' />;

const { getByAltText } = render(<Field icon={Icon} />, { wrapper });
expect(getByAltText(/this is an icon/i)).toBeInTheDocument();
});

it('with action label', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByRole } = render(<Field actionLabel={'take action'} />, { wrapper });
expect(getByRole('link', { name: /take action/i })).toBeInTheDocument();
expect(getByRole('link', { name: /take action/i })).not.toHaveAttribute('rel');
expect(getByRole('link', { name: /take action/i })).not.toHaveAttribute('target');
expect(getByRole('link', { name: /take action/i })).toHaveAttribute('href', '');
});

it('with error', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByRole, getByLabelText, getByText } = render(<Field />, { wrapper });

await act(() => userEvent.click(getByRole('button', { name: /set error/i })));

await waitFor(() => {
expect(getByLabelText('some label')).toHaveAttribute('aria-invalid', 'true');
expect(getByLabelText('some label')).toHaveAttribute('aria-describedby', 'error-firstname');
expect(getByText('some error')).toBeInTheDocument();
});
});

it('with info', async () => {
const { wrapper } = await createFixtures();
const { Field } = createField('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
infoText: 'some info',
});

const { getByLabelText, getByText } = render(<Field actionLabel={'take action'} />, { wrapper });
await act(() => fireEvent.focus(getByLabelText('some label')));
await waitFor(() => {
expect(getByText('some info')).toBeInTheDocument();
});
});
});

/**
* This tests ensure that the deprecated FormControl and PlainInput continue to behave the same and nothing broke during the refactoring.
*/
describe('Form control as text', () => {
it('renders the component', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText } = render(<Field />, { wrapper });
expect(getByLabelText('some label')).toHaveValue('init value');
expect(getByLabelText('some label')).toHaveAttribute('name', 'firstname');
expect(getByLabelText('some label')).toHaveAttribute('placeholder', 'some placeholder');
expect(getByLabelText('some label')).toHaveAttribute('type', 'text');
expect(getByLabelText('some label')).toHaveAttribute('id', 'firstname-field');
expect(getByLabelText('some label')).not.toHaveAttribute('disabled');
expect(getByLabelText('some label')).not.toHaveAttribute('required');
expect(getByLabelText('some label')).toHaveAttribute('aria-invalid', 'false');
expect(getByLabelText('some label')).toHaveAttribute('aria-describedby', '');
expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'false');
expect(getByLabelText('some label')).toHaveAttribute('aria-disabled', 'false');
});

it('disabled', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText } = render(<Field isDisabled />, { wrapper });
expect(getByLabelText('some label')).toHaveValue('init value');
expect(getByLabelText('some label')).toHaveAttribute('disabled');
expect(getByLabelText('some label')).toHaveAttribute('aria-disabled', 'true');
});

it('required', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText, queryByText } = render(<Field isRequired />, { wrapper });
expect(getByLabelText('some label')).toHaveValue('init value');
expect(getByLabelText('some label')).toHaveAttribute('required');
expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'true');
expect(queryByText(/optional/i)).not.toBeInTheDocument();
});

it('optional', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByLabelText, getByText } = render(<Field isOptional />, { wrapper });
expect(getByLabelText('some label')).not.toHaveAttribute('required');
expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'false');
expect(getByText(/optional/i)).toBeInTheDocument();
});

it('with icon', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const Icon = () => <img alt='this is an icon' />;

const { getByAltText } = render(<Field icon={Icon} />, { wrapper });
expect(getByAltText(/this is an icon/i)).toBeInTheDocument();
});

it('with action label', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByRole } = render(<Field actionLabel={'take action'} />, { wrapper });
expect(getByRole('link', { name: /take action/i })).toBeInTheDocument();
expect(getByRole('link', { name: /take action/i })).not.toHaveAttribute('rel');
expect(getByRole('link', { name: /take action/i })).not.toHaveAttribute('target');
expect(getByRole('link', { name: /take action/i })).toHaveAttribute('href', '');
});

it('with error', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
});

const { getByRole, getByLabelText, getByText } = render(<Field />, { wrapper });

await act(() => userEvent.click(getByRole('button', { name: /set error/i })));

await waitFor(() => {
expect(getByLabelText('some label')).toHaveAttribute('aria-invalid', 'true');
expect(getByLabelText('some label')).toHaveAttribute('aria-describedby', 'error-firstname');
expect(getByText('some error')).toBeInTheDocument();
});
});

it('with info', async () => {
const { wrapper } = await createFixtures();
const { Field } = createFormControl('firstname', 'init value', {
type: 'text',
label: 'some label',
placeholder: 'some placeholder',
infoText: 'some info',
});

const { getByLabelText, getByText } = render(<Field actionLabel={'take action'} />, { wrapper });
await act(() => fireEvent.focus(getByLabelText('some label')));
await waitFor(() => {
expect(getByText('some info')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export const sanitizeInputProps = (
fieldId,
label,
clearFeedback,
infoText,
...inputProps
} = obj;
/* eslint-enable */
Expand Down

0 comments on commit d65d36f

Please sign in to comment.