diff --git a/cypress/integration/Radio.spec.ts b/cypress/integration/Radio.spec.ts new file mode 100644 index 0000000000..27f34eb221 --- /dev/null +++ b/cypress/integration/Radio.spec.ts @@ -0,0 +1,45 @@ +import * as h from '../helpers'; + +const getRadio = () => { + return cy.get(`[type="radio"]`); +}; + +describe('Radio', () => { + before(() => { + h.stories.visit(); + }); + + context(`given the Default story is rendered`, () => { + beforeEach(() => { + h.stories.load('Components|Inputs/Radio/React/Left Label/Radio', 'Default'); + }); + + it('should pass accessibility checks', () => { + cy.checkA11y(); + }); + + context('when clicked', () => { + beforeEach(() => { + cy.findByLabelText('E-mail').click(); + }); + + it('should be checked', () => { + getRadio().should('be.checked'); + }); + }); + }); + + context(`given the 'Disabled' story is rendered`, () => { + beforeEach(() => { + h.stories.load('Components|Inputs/Radio/React/Left Label/Radio', 'Disabled'); + }); + + it('should pass accessibility checks', () => { + cy.checkA11y(); + }); + + it('should be disabled', () => { + getRadio().should('be.disabled'); + }); + }); +}); diff --git a/cypress/integration/RadioGroup.spec.ts b/cypress/integration/RadioGroup.spec.ts new file mode 100644 index 0000000000..a3da5a7d95 --- /dev/null +++ b/cypress/integration/RadioGroup.spec.ts @@ -0,0 +1,29 @@ +import * as h from '../helpers'; + +describe('Radio Group', () => { + before(() => { + h.stories.visit(); + }); + ['Default', 'Alert', 'Error with Grow'].forEach(story => { + context(`given the '${story}' story is rendered`, () => { + beforeEach(() => { + h.stories.load('Components|Inputs/Radio/React/Top Label/Radio Group', story); + }); + + it('should pass accessibility checks', () => { + cy.checkA11y(); + }); + + context('when clicking one radio and then selecting another radio', () => { + beforeEach(() => { + cy.findByLabelText('E-mail').click(); + cy.findByLabelText('Mail').click(); + }); + + it('should one have one radio selected', () => { + cy.findByLabelText('Mail').should('be.checked'); + }); + }); + }); + }); +}); diff --git a/modules/form-field/react/stories/stories_Radio.tsx b/modules/form-field/react/stories/stories_Radio.tsx index 4ff40b2b3d..a33eb8e8bf 100644 --- a/modules/form-field/react/stories/stories_Radio.tsx +++ b/modules/form-field/react/stories/stories_Radio.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import {storiesOf} from '@storybook/react'; import withReadme from 'storybook-readme/with-readme'; -import {ControlledComponentWrapper} from '../../../../utils/storybook'; +import {StaticStates} from '@workday/canvas-kit-labs-react-core/lib/StaticStates'; +import { + ControlledComponentWrapper, + ComponentStatesTable, + permutateProps, +} from '../../../../utils/storybook'; import {Radio, RadioGroup} from '../../../radio/react'; import FormField from '../index'; @@ -72,7 +77,7 @@ storiesOf('Components|Inputs/Radio/React/Top Label/Radio', module) - + )); @@ -140,7 +145,89 @@ storiesOf('Components|Inputs/Radio/React/Left Label/Radio', module) - + + )) + .add('Disabled', () => ( + + + + + + )); + +storiesOf('Components|Inputs/Radio/React/Visual', module) + .addParameters({component: Radio}) + .addDecorator(withReadme(README)) + .add('States', () => ( +
+

Radio

+ + { + if (props.disabled && !['', 'hover'].includes(props.className)) { + return false; + } + return true; + } + )} + > + {props => ( + {}} // eslint-disable-line no-empty-function + label="Radio" + /> + )} + + +

Radio Group

+ + + {props => ( + + + + + + + + + )} + + +
)); diff --git a/modules/radio/react/spec/Radio.spec.tsx b/modules/radio/react/spec/Radio.spec.tsx index 5037cf60bb..fc09cf35c0 100644 --- a/modules/radio/react/spec/Radio.spec.tsx +++ b/modules/radio/react/spec/Radio.spec.tsx @@ -1,108 +1,107 @@ import * as React from 'react'; -import {mount} from 'enzyme'; +import {render, fireEvent} from '@testing-library/react'; import Radio from '../lib/Radio'; -import ReactDOMServer from 'react-dom/server'; -import {axe} from 'jest-axe'; -import FormField from '../../../form-field/react'; -describe('Radio Input', () => { +describe('Radio', () => { const cb = jest.fn(); afterEach(() => { cb.mockReset(); }); - test('render an radio input with id', () => { - const component = mount(); - expect(component.find('input').props().id).toBe('myRadio'); - component.unmount(); + describe('when rendered', () => { + it('should render an input with type=radio', () => { + const {getByRole} = render(); + expect(getByRole('radio')).toHaveProperty('type', 'radio'); + }); + + it('should be unchecked by default', () => { + const {getByRole} = render(); + expect(getByRole('radio')).toHaveProperty('checked', false); + }); }); - test('render an radio input with name', () => { - const component = mount(); - expect(component.find('input').props().name).toBe('myRadio'); - component.unmount(); + describe('when rendered with an id', () => { + it('should render a radio input with id', () => { + const id = 'myRadio'; + const {getByRole} = render(); + expect(getByRole('radio')).toHaveAttribute('id', id); + }); }); - test('render an radio input with value', () => { - const component = mount(); - expect(component.find('input').props().value).toBe('myRadio'); - component.unmount(); + describe('when rendered with a value', () => { + it('should render a radio input with value', () => { + const value = 'myRadio'; + const {getByDisplayValue} = render(); + expect(getByDisplayValue(value)).toBeDefined(); + }); }); - test('render an checked radio input', () => { - const component = mount(); - expect(component.find('input').props().checked).toBe(true); - component.unmount(); + describe('when rendered with checked=true', () => { + it('should render a checked radio input', () => { + const {getByRole} = render(); + expect(getByRole('radio')).toHaveProperty('checked', true); + }); }); - test('render an disabled radio input', () => { - const component = mount(); - expect(component.find('input').props().disabled).toBe(true); - component.unmount(); + describe('when rendered with disabled attribute', () => { + it('should render a disabled radio input', () => { + const {getByRole} = render(); + expect(getByRole('radio')).toHaveProperty('disabled', true); + }); }); - test('should call a callback function', () => { - const component = mount(); - const input = component.find('input'); - input.simulate('change'); + describe('when rendered without an id', () => { + it('should create a unique id for each instance', async () => { + const {getByLabelText} = render( +
+ ; + ; + + ); - expect(cb.mock.calls.length).toBe(1); - component.unmount(); - }); + const id1 = getByLabelText('label1').getAttribute('id'); + const id2 = getByLabelText('label2').getAttribute('id'); - test('Radio input should spread extra props', () => { - const component = mount(); - const input = component - .find('input') // TODO: Standardize on prop spread location (see #150) - .getDOMNode(); - expect(input.getAttribute('data-propspread')).toBe('test'); - component.unmount(); - }); + expect(id1).not.toEqual(id2); + }); + + it('should keep the same unique id if re-rendered', () => { + const {getByRole, rerender} = render(); + + const uniqueId = getByRole('radio').getAttribute('id'); + expect(getByRole('radio')).toHaveProperty('id', uniqueId); - test('Radio creates a unique id for each instance', async () => { - const fragment = mount( -
- ; - ; - - ); - - const id1 = fragment - .find('input') - .at(0) - .getDOMNode() - .getAttribute('id'); - - const id2 = fragment - .find('input') - .at(1) - .getDOMNode() - .getAttribute('id'); - - expect(id1).not.toEqual(id2); - fragment.unmount(); + rerender(); + + expect(getByRole('radio')).toHaveProperty('checked'); + expect(getByRole('radio')).toHaveProperty('id', uniqueId); + }); }); -}); -describe('Radio Accessibility', () => { - test('Radio should pass axe DOM accessibility guidelines', async () => { - const html = ReactDOMServer.renderToString( - - ); - expect(await axe(html)).toHaveNoViolations(); + describe('when rendered with extra, arbitrary props', () => { + it('should spread extra props onto the radio', () => { + const attr = 'test'; + const {getByRole} = render(); + expect(getByRole('radio')).toHaveAttribute('data-propspread', attr); + }); }); - test('Radio without a defined id should pass axe DOM accessibility guidelines', async () => { - const html = ReactDOMServer.renderToString(); - expect(await axe(html)).toHaveNoViolations(); + describe('when rendered with an input ref', () => { + it('should set the ref to the checkbox input element', () => { + const ref = React.createRef(); + + render(); + + expect(ref.current).not.toBeNull(); + expect(ref.current).toHaveAttribute('type', 'radio'); + }); }); - test('Radio using FormField should pass axe DOM accessibility guidelines', async () => { - const html = ReactDOMServer.renderToString( - - - - ); - expect(await axe(html)).toHaveNoViolations(); + describe('when clicked', () => { + it('should call a callback function', () => { + const {getByRole} = render(); + fireEvent.click(getByRole('radio')); + expect(cb).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/modules/radio/react/spec/RadioGroup.spec.tsx b/modules/radio/react/spec/RadioGroup.spec.tsx index 39e6db5e7c..657676205b 100644 --- a/modules/radio/react/spec/RadioGroup.spec.tsx +++ b/modules/radio/react/spec/RadioGroup.spec.tsx @@ -1,151 +1,72 @@ import * as React from 'react'; -import {mount} from 'enzyme'; -import Radio from '../lib/Radio'; -import RadioGroup from '../lib/RadioGroup'; +import {render, fireEvent} from '@testing-library/react'; +import {Radio, RadioGroup} from '../index'; -describe('Radio', () => { +describe('Radio Group', () => { const cb = jest.fn(); afterEach(() => { cb.mockReset(); }); - test('renders two radios as expected', () => { - const component = mount( - - - - - ); - - const inputs = component.find('input'); - - expect(inputs.length).toEqual(2); - expect(inputs.get(0).props.name).toEqual('contact'); - expect(inputs.get(1).props.name).toEqual('contact'); + describe('when rendered rendered with a name', () => { + test('should render a radio group with a name', () => { + const {getByTestId} = render( + + + + + ); + expect(getByTestId('radiogroup')).toHaveAttribute('name'); + }); }); - - test('renders two radios with name set at radio level as expected', () => { - const component = mount( - - - - - ); - - const inputs = component.find('input'); - - expect(inputs.length).toEqual(2); - expect(inputs.get(0).props.name).toEqual('contact'); - expect(inputs.get(1).props.name).toEqual('contact'); + describe('when rendered with a value', () => { + test('should render a selected radio that matches that value of a string', async () => { + const {getByLabelText} = render( + + + + + ); + + const phoneRadio = await getByLabelText('Phone'); + expect(phoneRadio).toHaveProperty('checked', true); + }); + test('should render a selected radio that matches that value of a index', async () => { + const {getByLabelText} = render( + + + + + ); + + const phoneRadio = await getByLabelText('Phone'); + expect(phoneRadio).toHaveProperty('checked', true); + }); }); - - test('renders two radios preselected with index as expected', () => { - const component = mount( - - - - - ); - - const inputs = component.find('input'); - - expect(inputs.length).toEqual(2); - expect(inputs.get(0).props.name).toEqual('contact'); - expect(inputs.get(1).props.name).toEqual('contact'); + describe('when rendered with extra, arbitrary props', () => { + it('should spread extra props onto the radio group', () => { + const attr = 'test'; + const {getByTestId} = render( + + + + + ); + expect(getByTestId('radiogroup')).toHaveAttribute('data-propspread', attr); + }); }); - - test('renders two radios preselected with value as expected', () => { - const component = mount( - - - - - ); - - const inputs = component.find('input'); - - expect(inputs.length).toEqual(2); - expect(inputs.get(0).props.name).toEqual('contact'); - expect(inputs.get(1).props.name).toEqual('contact'); - }); - - test('can switch to a different radio', () => { - const component = mount( - - - - - ); - - component - .find('input') - .at(1) - .simulate('change'); - - expect(cb.mock.calls.length).toBe(1); - expect(cb.mock.calls[0][0]).toBe('phone'); - }); - - test('can switch to a different radio without value', () => { - const component = mount( - - - - - ); - - component - .find('input') - .at(1) - .simulate('change'); - - expect(cb.mock.calls.length).toBe(1); - expect(cb.mock.calls[0][0]).toBe(1); - }); - - test('renders two radios and one disabled as expected', () => { - const component = mount( - - - - - {/* ensure random elements don't break anything */} - - ); - - const inputs = component.find('input'); - - expect(inputs.length).toEqual(3); - expect(inputs.get(1).props.disabled).toBe(true); - }); - - test('simulate onchange on a radio', () => { - const component = mount( - - - - - {/* ensure random elements don't break anything */} - - ); - - component - .find('input') - .at(2) - .simulate('change'); - - expect(cb.mock.calls.length).toBe(1); - }); - - test('RadioGroup should spread extra props', () => { - const component = mount( - - - - - ); - const container = component.at(0).getDOMNode(); - expect(container.getAttribute('data-propspread')).toBe('test'); - component.unmount(); + describe('when clicked', () => { + it('should call a callback function', () => { + const {getByLabelText} = render( + + + + + ); + + fireEvent.click(getByLabelText('Phone')); + + expect(cb).toHaveBeenCalledWith('phone'); + }); }); });