From c3ee401b15be5a158a9527ca582218fc6a0b1453 Mon Sep 17 00:00:00 2001 From: Jon S Date: Fri, 17 Jan 2020 11:47:27 -0800 Subject: [PATCH 1/3] Tests and barebones --- .../src/components/Checkbox/CheckboxBase.tsx | 35 +++++ .../components/Checkbox/CheckboxBase-test.tsx | 131 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 packages/react/src/components/Checkbox/CheckboxBase.tsx create mode 100644 packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx diff --git a/packages/react/src/components/Checkbox/CheckboxBase.tsx b/packages/react/src/components/Checkbox/CheckboxBase.tsx new file mode 100644 index 0000000000..0d7771bed5 --- /dev/null +++ b/packages/react/src/components/Checkbox/CheckboxBase.tsx @@ -0,0 +1,35 @@ +import * as React from 'react' + +interface Props { + checked?: boolean + onChange?: any + onClick?: any + slots?: any +} + +const CheckboxBase: React.FunctionComponent = props => { + const { root: RootSlot = 'div' } = props.slots || {} + const [isChecked, setIsChecked] = React.useState(!!props.checked) + const realIsChecked = props.checked === undefined ? isChecked : !!props.checked + const onChange = React.useCallback( + (ev: any) => { + if (props.onClick) { + props.onClick(ev) + } + if (props.onChange) { + props.onChange(ev) + } + if (!ev.defaultPrevented) { + setIsChecked(!realIsChecked) + } + }, + [setIsChecked, realIsChecked, props.onChange], + ) + return ( + + + {props.children} + + ) +} +export default CheckboxBase diff --git a/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx b/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx new file mode 100644 index 0000000000..c0d2271e2f --- /dev/null +++ b/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx @@ -0,0 +1,131 @@ +import * as React from 'react' +import { mount } from 'enzyme' +import CheckboxBase from 'src/components/Checkbox/CheckboxBase' + +describe("Baby's first test", () => { + it('renders something', () => { + expect(mount()).toBeTruthy() + }) + + it('has an input', () => { + const control = mount() + const input = control + .find('input') + .first() + .getDOMNode() + expect(input).toBeTruthy() + }) + + it('can be checked', () => { + const control = mount() + const inputChecked = control + .find('input') + .first() + .prop('checked') + expect(inputChecked).toBe(true) + }) + + it('can be unchecked', () => { + const control = mount() + const inputChecked = control + .find('input') + .first() + .prop('checked') + expect(inputChecked).toBe(false) + }) + + it('can switch check state', () => { + const control = mount() + const inputChecked = control + .find('input') + .first() + .prop('checked') + expect(inputChecked).toBe(false) + control.setProps({ checked: true }) + const inputCheckedAfter = control + .find('input') + .first() + .prop('checked') + expect(inputCheckedAfter).toBe(true) + }) + + it('changes state when clicked', () => { + const control = mount() + expect( + control + .find('input') + .first() + .prop('checked'), + ).toBe(false) + control.simulate('click') + expect( + control + .find('input') + .first() + .prop('checked'), + ).toBe(true) + control.simulate('click') + expect( + control + .find('input') + .first() + .prop('checked'), + ).toBe(false) + }) + + it('calls onChange when checked state changes', () => { + const change = jest.fn() + const control = mount() // let, the only constant is change + control.simulate('click') + expect(change).toHaveBeenCalled() + }) + + it('does not change value when onChange prevents default', () => { + const change = jest.fn((e: any) => e.preventDefault()) + const control = mount() + expect( + control + .find('input') + .first() + .prop('checked'), + ).toBe(false) + control.simulate('click') + expect( + control + .find('input') + .first() + .prop('checked'), + ).toBe(false) + }) + + it('calls onClick', () => { + const click = jest.fn() + const control = mount() + control.simulate('click') + expect(click).toHaveBeenCalled() + }) + + it('renders content', () => { + const control = mount( + + + , + ) + expect(control.find('label').length).toBe(1) + }) + + it('can render with a different root type', () => { + const control = mount() + expect(control.find('span').length).toBe(1) + }) + + it('can render with a different input type', () => { + const MyInput = () => ( + + + + ) + const control = mount() + expect(control.find('a').length).toBe(1) + }) +}) From a42f0017c393cc6c2f55feadb1189146e342c8a0 Mon Sep 17 00:00:00 2001 From: Jon S Date: Fri, 17 Jan 2020 13:46:47 -0800 Subject: [PATCH 2/3] Mostly done checkin --- .../src/components/Checkbox/CheckboxBase.tsx | 29 +++++++++++--- .../components/Checkbox/CheckboxBase-test.tsx | 39 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Checkbox/CheckboxBase.tsx b/packages/react/src/components/Checkbox/CheckboxBase.tsx index 0d7771bed5..e8f5744a40 100644 --- a/packages/react/src/components/Checkbox/CheckboxBase.tsx +++ b/packages/react/src/components/Checkbox/CheckboxBase.tsx @@ -5,12 +5,17 @@ interface Props { onChange?: any onClick?: any slots?: any + slotProps?: any + classes?: any } const CheckboxBase: React.FunctionComponent = props => { - const { root: RootSlot = 'div' } = props.slots || {} - const [isChecked, setIsChecked] = React.useState(!!props.checked) - const realIsChecked = props.checked === undefined ? isChecked : !!props.checked + const { checked, classes = {}, slots = {}, slotProps = {}, ...rest } = props + const { root: RootSlot = 'div', input: InputSlot = 'input' } = slots + const { root: rootClass, input: inputClass } = classes + const { root: rootProps, input: inputProps } = slotProps + const [isChecked, setIsChecked] = React.useState(!!checked) + const realIsChecked = checked === undefined ? isChecked : !!checked const onChange = React.useCallback( (ev: any) => { if (props.onClick) { @@ -25,9 +30,23 @@ const CheckboxBase: React.FunctionComponent = props => { }, [setIsChecked, realIsChecked, props.onChange], ) + const onKeyDown = React.useCallback( + (ev: React.KeyboardEvent) => { + if (ev.keyCode === 13) { + setIsChecked(!realIsChecked) + } + }, + [setIsChecked, realIsChecked], + ) return ( - - + + {props.children} ) diff --git a/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx b/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx index c0d2271e2f..7cc8773845 100644 --- a/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx +++ b/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { mount } from 'enzyme' import CheckboxBase from 'src/components/Checkbox/CheckboxBase' +import * as keyboardKey from 'keyboard-key' describe("Baby's first test", () => { it('renders something', () => { @@ -128,4 +129,42 @@ describe("Baby's first test", () => { const control = mount() expect(control.find('a').length).toBe(1) }) + + it('changes on enter press', () => { + const control = mount() + control.simulate('keydown', { + keyCode: keyboardKey.Enter, + key: 'Enter', + }) + expect(control.find('input').prop('checked')).toBe(true) + }) + + describe('class handling', () => { + it('renders classes', () => { + const control = mount() + expect(control.find('div.foo').length).toBe(1) + }) + + it('renders classes for input', () => { + const control = mount() + expect(control.find('input.foo').length).toBe(1) + }) + }) + + describe('slotProps', () => { + it('renders slotProps for input', () => { + const control = mount() + expect(control.find('input[data-foo="bar"]').length).toBe(1) + }) + + it('renders slotProps for root', () => { + const control = mount() + expect(control.find('div[data-foo="bar"]').length).toBe(1) + }) + + it('applies unused props to the root element', () => { + const control = mount() + expect(control.find('div[data-foo="bar"]').length).toBe(1) + }) + }) }) From 0a62ec21ee31a1ec4fd64e919a748b77b0539d20 Mon Sep 17 00:00:00 2001 From: Jon S Date: Fri, 17 Jan 2020 16:01:50 -0800 Subject: [PATCH 3/3] final polish --- .../src/components/Checkbox/Checkbox.tsx | 10 +++- .../src/components/Checkbox/CheckboxBase.tsx | 58 ++++++++++++++----- .../test/specs/commonTests/isConformant.tsx | 4 ++ .../components/Checkbox/Checkbox-test.tsx | 3 +- .../components/Checkbox/CheckboxBase-test.tsx | 19 +++++- 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/react/src/components/Checkbox/Checkbox.tsx b/packages/react/src/components/Checkbox/Checkbox.tsx index 8ae819f263..17761c28ea 100644 --- a/packages/react/src/components/Checkbox/Checkbox.tsx +++ b/packages/react/src/components/Checkbox/Checkbox.tsx @@ -1,4 +1,5 @@ import { Accessibility, checkboxBehavior } from '@fluentui/accessibility' +import CheckboxBase from './CheckboxBase' import * as customPropTypes from '@fluentui/react-proptypes' import * as _ from 'lodash' import * as React from 'react' @@ -149,11 +150,14 @@ class Checkbox extends AutoControlledComponent, Checkb }) return ( - , Checkb }), })} {labelPosition === 'end' && labelElement} - + ) } } diff --git a/packages/react/src/components/Checkbox/CheckboxBase.tsx b/packages/react/src/components/Checkbox/CheckboxBase.tsx index e8f5744a40..1916aa0fd8 100644 --- a/packages/react/src/components/Checkbox/CheckboxBase.tsx +++ b/packages/react/src/components/Checkbox/CheckboxBase.tsx @@ -1,50 +1,76 @@ import * as React from 'react' -interface Props { +interface CheckboxBaseProps { checked?: boolean - onChange?: any - onClick?: any - slots?: any - slotProps?: any + onChange?: React.ChangeEventHandler + onClick?: React.MouseEventHandler + slots?: { + root?: any + input?: any + } + slotProps?: { + root?: any + input?: any + } classes?: any } -const CheckboxBase: React.FunctionComponent = props => { - const { checked, classes = {}, slots = {}, slotProps = {}, ...rest } = props +const Testhelper = (prop?: Function, propName?: string) => { + if (!prop) { + return {} + } + return { + [propName]: prop, + } +} + +const CheckboxBase: React.FunctionComponent = props => { + const { checked, onChange, onClick, classes = {}, slots = {}, slotProps = {}, ...rest } = props const { root: RootSlot = 'div', input: InputSlot = 'input' } = slots const { root: rootClass, input: inputClass } = classes const { root: rootProps, input: inputProps } = slotProps + const [isChecked, setIsChecked] = React.useState(!!checked) const realIsChecked = checked === undefined ? isChecked : !!checked - const onChange = React.useCallback( + + const onChangeHandler = React.useCallback( (ev: any) => { - if (props.onClick) { - props.onClick(ev) + if (onClick) { + onClick(ev) } - if (props.onChange) { - props.onChange(ev) + if (!ev.defaultPrevented) { + if (onChange) { + onChange(ev) + } } if (!ev.defaultPrevented) { setIsChecked(!realIsChecked) } }, - [setIsChecked, realIsChecked, props.onChange], + [setIsChecked, realIsChecked, onChange], ) + const onKeyDown = React.useCallback( (ev: React.KeyboardEvent) => { - if (ev.keyCode === 13) { - setIsChecked(!realIsChecked) + console.log(ev.keyCode) + switch (ev.keyCode) { + case 13: + case 32: { + setIsChecked(!realIsChecked) + } } }, [setIsChecked, realIsChecked], ) + return ( {props.children} diff --git a/packages/react/test/specs/commonTests/isConformant.tsx b/packages/react/test/specs/commonTests/isConformant.tsx index 2e3c9ea314..3e16599a66 100644 --- a/packages/react/test/specs/commonTests/isConformant.tsx +++ b/packages/react/test/specs/commonTests/isConformant.tsx @@ -32,6 +32,8 @@ export interface Conformant { rendersPortal?: boolean /** This component uses wrapper slot to wrap the 'meaningful' element. */ wrapperComponent?: React.ReactType + /** This component uses a base element */ + baseComponent?: React.ReactType } /** @@ -53,6 +55,7 @@ export default function isConformant( requiredProps = {}, rendersPortal = false, wrapperComponent = null, + baseComponent = null, } = options const { throwError } = helpers('isConformant', Component) @@ -61,6 +64,7 @@ export default function isConformant( const helperComponentNames = [ ...[Ref, RefFindNode], ...(wrapperComponent ? [wrapperComponent] : []), + ...(baseComponent ? [baseComponent] : []), ].map(getDisplayName) const toNextNonTrivialChild = (from: ReactWrapper) => { diff --git a/packages/react/test/specs/components/Checkbox/Checkbox-test.tsx b/packages/react/test/specs/components/Checkbox/Checkbox-test.tsx index 1a626d30a1..69a9531302 100644 --- a/packages/react/test/specs/components/Checkbox/Checkbox-test.tsx +++ b/packages/react/test/specs/components/Checkbox/Checkbox-test.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import Checkbox from 'src/components/Checkbox/Checkbox' +import CheckboxBase from 'src/components/Checkbox/CheckboxBase' import { isConformant, handlesAccessibility, @@ -7,7 +8,7 @@ import { } from 'test/specs/commonTests' describe('Checkbox', () => { - isConformant(Checkbox) + isConformant(Checkbox, { baseComponent: CheckboxBase }) handlesAccessibility(Checkbox, { defaultRootRole: 'checkbox' }) describe('HTML accessibility rules validation', () => { diff --git a/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx b/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx index 7cc8773845..5151e91ad4 100644 --- a/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx +++ b/packages/react/test/specs/components/Checkbox/CheckboxBase-test.tsx @@ -1,7 +1,6 @@ import * as React from 'react' import { mount } from 'enzyme' import CheckboxBase from 'src/components/Checkbox/CheckboxBase' -import * as keyboardKey from 'keyboard-key' describe("Baby's first test", () => { it('renders something', () => { @@ -99,6 +98,14 @@ describe("Baby's first test", () => { ).toBe(false) }) + it('does not call onChange value when onClick prevents default', () => { + const click = jest.fn((e: any) => e.preventDefault()) + const change = jest.fn() + const control = mount() + control.simulate('click') + expect(change).not.toHaveBeenCalled() + }) + it('calls onClick', () => { const click = jest.fn() const control = mount() @@ -133,12 +140,20 @@ describe("Baby's first test", () => { it('changes on enter press', () => { const control = mount() control.simulate('keydown', { - keyCode: keyboardKey.Enter, + keyCode: 13, key: 'Enter', }) expect(control.find('input').prop('checked')).toBe(true) }) + it('changes on space press', () => { + const control = mount() + control.simulate('keydown', { + keyCode: 32, + }) + expect(control.find('input').prop('checked')).toBe(true) + }) + describe('class handling', () => { it('renders classes', () => { const control = mount()