diff --git a/.changeset/gorgeous-insects-reply.md b/.changeset/gorgeous-insects-reply.md new file mode 100644 index 00000000000..a4d58e4ead5 --- /dev/null +++ b/.changeset/gorgeous-insects-reply.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Tests for internal PlainInput component. diff --git a/packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx new file mode 100644 index 00000000000..de6c28a21fa --- /dev/null +++ b/packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx @@ -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) => { + const MockFieldWrapper = withCardStateProvider((props: Partial[0]>) => { + const field = useFormControl(...params); + + return ( + <> + {/* @ts-ignore*/} + + + + ); + }); + + return { + Field: MockFieldWrapper, + }; +}; + +// TODO: Remove this once FormControl is no longer used +const createFormControl = (...params: Parameters) => { + const MockFieldWrapper = withCardStateProvider((props: Partial[0]>) => { + const field = useFormControl(...params); + + return ( + <> + {/* @ts-ignore*/} + + + + ); + }); + + 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(, { 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(, { 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(, { 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(, { 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 = () => this is an icon; + + const { getByAltText } = render(, { 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(, { 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(, { 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(, { 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(, { 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(, { 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(, { 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(, { 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 = () => this is an icon; + + const { getByAltText } = render(, { 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(, { 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(, { 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(, { wrapper }); + await act(() => fireEvent.focus(getByLabelText('some label'))); + await waitFor(() => { + expect(getByText('some info')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/clerk-js/src/ui/primitives/hooks/useFormControl.tsx b/packages/clerk-js/src/ui/primitives/hooks/useFormControl.tsx index 59897041545..98f10eea848 100644 --- a/packages/clerk-js/src/ui/primitives/hooks/useFormControl.tsx +++ b/packages/clerk-js/src/ui/primitives/hooks/useFormControl.tsx @@ -190,6 +190,7 @@ export const sanitizeInputProps = ( fieldId, label, clearFeedback, + infoText, ...inputProps } = obj; /* eslint-enable */