diff --git a/cypress/integration/FormField.spec.ts b/cypress/integration/FormField.spec.ts new file mode 100644 index 0000000000..f62409a6dc --- /dev/null +++ b/cypress/integration/FormField.spec.ts @@ -0,0 +1,54 @@ +import * as h from '../helpers'; + +const getInput = () => { + return cy.get(`input`); +}; + +describe('Form Field', () => { + before(() => { + h.stories.visit(); + }); + + context(`given the Default story is rendered`, () => { + beforeEach(() => { + h.stories.load('Components|Inputs/Form Field/React', 'Default'); + }); + + it('should pass accessibility checks', () => { + cy.checkA11y(); + }); + + context('when clicking on the label', () => { + beforeEach(() => { + cy.findByLabelText('Label').click(); + }); + + it('should focus the input', () => { + getInput().should('be.focused'); + }); + }); + }); + + ['Hint Alert', 'Hint Error'].forEach(story => { + context(`given the '${story}' story is rendered`, () => { + beforeEach(() => { + h.stories.load('Components|Inputs/Form Field/React', story); + }); + + it('should pass accessibility and connect the input with the hint text', () => { + cy.checkA11y(); + getInput().should('have.attr', 'aria-describedby'); + }); + }); + }); + + context(`given the Label Required story is rendered`, () => { + beforeEach(() => { + h.stories.load('Components|Inputs/Form Field/React', 'Label Required'); + }); + + it('should pass accessibility and connect the input with the hint text', () => { + cy.get('abbr').should('have.attr', 'title', 'required'); + }); + }); +}); diff --git a/modules/form-field/react/spec/FormField.spec.tsx b/modules/form-field/react/spec/FormField.spec.tsx index 284782cd8e..387840f5bb 100644 --- a/modules/form-field/react/spec/FormField.spec.tsx +++ b/modules/form-field/react/spec/FormField.spec.tsx @@ -1,152 +1,133 @@ import * as React from 'react'; -import {mount} from 'enzyme'; -import {GrowthBehavior} from '@workday/canvas-kit-react-common'; -import FormField, {FormFieldErrorBehavior} from '../lib/FormField'; +import {render} from '@testing-library/react'; +import FormField from '../lib/FormField'; +import {ErrorType} from '@workday/canvas-kit-react-common'; describe('FormField', () => { - test('Set a string label', () => { - const label = 'Label'; - const component = mount( - - - - ); - - expect(component.find('label').text()).toBe(label); - - component.unmount(); + const cb = jest.fn(); + afterEach(() => { + cb.mockReset(); }); - test('Set a custom component', () => { - const label = ; - const component = mount( - - - - ); - - expect(component.contains(label)).toBeDefined(); - - component.unmount(); + describe('when rendered', () => { + it('should render a label with text Label', () => { + const label = 'Label'; + const {getByText} = render( + + + + ); + + expect(getByText('Label')).toContainHTML(label); + }); }); - - test('Set hint text', () => { - const hintText = 'Hint Text'; - const component = mount( - - - - ); - - expect(component.render().text()).toEqual(expect.stringContaining(hintText)); - - component.unmount(); + describe('when rendered with hint text', () => { + it('should render text below the input component', () => { + const label = 'Label'; + const hintText = 'Helpful text goes here.'; + const {container} = render( + + + + ); + + expect(container.querySelector('p')).toContainHTML(hintText); + }); }); - test('Set aria-describedby', () => { - const InputComponent: React.FunctionComponent = () => ; - const hintText = 'Hint Text'; - const hintId = 'hint-id'; - const component = mount( - - - - ); - - expect( - component - .find('Hint') - .at(0) - .prop('id') - ).toEqual(hintId); - expect(component.find(InputComponent).prop('aria-describedby')).toEqual(hintId); - - component.unmount(); + describe('when rendered as required', () => { + it('should add a required element to the label to indicate that it is required', () => { + const label = 'Label'; + const {getByText} = render( + + + + ); + + expect(getByText('*')).toHaveAttribute('title', 'required'); + }); }); - test('Sets id on input & htmlFor on label', () => { - const InputComponent: React.FunctionComponent = () => ; - const inputId = 'input-id'; - - const component = mount( - - - - ); - - expect(component.find(InputComponent).prop('id')).toEqual(inputId); - expect(component.find('Label').prop('htmlFor')).toEqual(inputId); - - component.unmount(); + describe('when rendered with useFieldset set to true', () => { + it('should render the FormField using a fieldset and a legend instead of a div and a label', () => { + const label = 'Label'; + const {container} = render( + + + + ); + + const fieldset = container.querySelector('fieldset'); + expect(container).toContainElement(fieldset); + expect(container).toContainHTML('legend'); + }); }); - test('Sets grow prop', () => { - const InputComponent: React.FunctionComponent = () => ; - - const component = mount( - - - - ); - - expect(component.find(InputComponent).props().grow).toEqual(true); - - component.unmount(); + describe('when rendered with extra, arbitrary props', () => { + it('should spread extra props onto the form field', () => { + const attr = 'test'; + const label = 'Label'; + const {container} = render( + + + + ); + + expect(container.querySelector('div')).toHaveAttribute('data-propspread', 'test'); + }); }); - - test('Sets error prop with aria label', () => { - const InputComponent: React.FunctionComponent = () => ( - - ); - - const component = mount( - - - - ); - - expect(component.find(InputComponent).props().error).toEqual(FormField.ErrorType.Error); - expect(component.find(InputComponent).prop('aria-invalid')).toBeTruthy(); - - component.unmount(); + describe('when rendered a hint id', () => { + it('the input and hint text should have matching ids for accessibility', () => { + const label = 'Label'; + const hintId = 'hintId'; + const hintText = 'Helpful text goes here.'; + const {container} = render( + + + + ); + + expect(container.querySelector('p')).toHaveAttribute('id', hintId); + expect(container.querySelector('input')).toHaveAttribute('aria-describedby', hintId); + }); }); - test('String child', () => { - const component = mount(Text); - - expect(component.children().text()).toBe('Text'); - - component.unmount(); + describe('when rendered a input id', () => { + it('the input and label should have matching ids for accessibility', () => { + const label = 'Label'; + const inputId = 'inputId'; + const hintText = 'Helpful text goes here.'; + const {container} = render( + + + + ); + + expect(container.querySelector('label')).toHaveAttribute('for', inputId); + expect(container.querySelector('input')).toHaveAttribute('id', inputId); + }); }); - - test('Uses fieldset and legend when useFieldset=true (for RadioGroup)', () => { - const InputComponent: React.FunctionComponent = () => ( - - ); - - const component = mount( - - - - ); - - expect(component.find('fieldset')).toHaveLength(1); - expect(component.find('legend')).toHaveLength(1); - - component.unmount(); + describe('when rendered with an error', () => { + it('the input should have aria-invalid for accessibility', () => { + const label = 'Label'; + const {container} = render( + + + + ); + expect(container.querySelector('input')).toHaveAttribute('aria-invalid', 'true'); + }); }); - - test('FormField should spread extra props', () => { - const InputComponent: React.FunctionComponent = () => ( - - ); - const component = mount( - - - - ); - const container = component.at(0).getDOMNode(); - expect(container.getAttribute('data-propspread')).toBe('test'); - component.unmount(); + describe('when rendered with no inputId', () => { + it('the input should have a unique id', () => { + const label = 'Label'; + const {container} = render( + + + + ); + const uniqueId = container.querySelector('input').getAttribute('id'); + expect(container.querySelector('input')).toHaveAttribute('id', uniqueId); + }); }); }); diff --git a/modules/form-field/react/stories/stories.tsx b/modules/form-field/react/stories/stories.tsx index 1d5373d0dc..3cdb973513 100644 --- a/modules/form-field/react/stories/stories.tsx +++ b/modules/form-field/react/stories/stories.tsx @@ -2,30 +2,94 @@ import * as React from 'react'; import {storiesOf} from '@storybook/react'; import withReadme from 'storybook-readme/with-readme'; - -import {Label, Hint} from '../index'; +import FormField from '../lib/FormField'; +import {ComponentStatesTable, permutateProps} from '../../../../utils/storybook'; +import {StaticStates} from '@workday/canvas-kit-labs-react-core'; +import {TextInput} from '../../../text-input/react'; import README from '../README.md'; +import {FormFieldLabelPosition} from '../lib/types'; + +const FormFieldStates = () => ( + + + {props => ( + + + + )} + + +); -storiesOf('Components|Inputs/Form Field/React/Label', module) - .addParameters({component: Label}) +storiesOf('Components|Inputs/Form Field/React', module) + .addParameters({component: FormField}) .addDecorator(withReadme(README)) .add('Default', () => (
- + + +
)) - .add('Required', () => ( + .add('Hint', () => (
- + + +
- )); -storiesOf('Components|Inputs/Form Field/React', module) - .addParameters({component: Hint}) - .addDecorator(withReadme(README)) - .add('Hint', () => ( + )) + .add('Hint Alert', () => (
- Hint - Hint - Hint + + + +
+ )) + .add('Hint Error', () => ( +
+ + + +
+ )) + .add('Label Required', () => ( +
+ + +
)); + +storiesOf('Components|Inputs/Form Field/React/Visual Testing', module) + .addParameters({ + component: FormField, + chromatic: { + disable: false, + }, + }) + .addDecorator(withReadme(README)) + .add('States', () => );