Skip to content

Commit

Permalink
feat(FormikSpinButton): implement FormikSpinButton
Browse files Browse the repository at this point in the history
  • Loading branch information
kevicency committed Dec 12, 2018
1 parent fe57b6c commit 8799395
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 0 deletions.
70 changes: 70 additions & 0 deletions src/FormikSpinButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { FieldProps } from 'formik'
import { ISpinButtonProps, SpinButton } from 'office-ui-fabric-react'
import * as React from 'react'
import { createFakeEvent, Omit } from './utils'

export function mapFieldToSpinButton<T = any>(
{ form, field }: FieldProps<T>,
{
min,
max,
onIncrement,
onDecrement,
onValidate,
}: Pick<
ISpinButtonProps,
'min' | 'max' | 'onIncrement' | 'onDecrement' | 'onValidate'
> = {}
): Pick<
ISpinButtonProps,
'value' | 'onIncrement' | 'onDecrement' | 'onValidate' | 'onBlur'
> {
const handleIncrement = (value: string) => {
const newValue = onIncrement
? onIncrement(value)
: Math.min(typeof max === 'number' ? max : -Infinity, +value + 1)

form.setFieldValue(field.name, newValue)
field.onBlur(createFakeEvent(field))

return `${newValue}`
}
const handleDecrement = (value: string) => {
const newValue = onDecrement
? onDecrement(value)
: Math.max(typeof min === 'number' ? min : Infinity, +value - 1)

form.setFieldValue(field.name, newValue)
field.onBlur(createFakeEvent(field))

return `${newValue}`
}
const handleValidate = (value: string) => {
const newValue = onValidate ? onValidate(value) : +value

form.setFieldValue(field.name, newValue)

return `${newValue}`
}

return {
value: field.value,
onIncrement: handleIncrement,
onDecrement: handleDecrement,
onValidate: handleValidate,
onBlur: () => field.onBlur(createFakeEvent(field)),
}
}

export type FormikSpinButtonProps<T = any> = Omit<ISpinButtonProps, 'value'> &
FieldProps<T>

export function FormikSpinButton<T = any>({
field,
form,
...props
}: FormikSpinButtonProps<T>) {
return (
<SpinButton {...props} {...mapFieldToSpinButton({ field, form }, props)} />
)
}
114 changes: 114 additions & 0 deletions src/__tests__/FormikSpinButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Field, FieldProps, Form, Formik } from 'formik'
import { SpinButton } from 'office-ui-fabric-react'
import * as React from 'react'
import renderer from 'react-test-renderer'
import { FormikSpinButton, mapFieldToSpinButton } from '../FormikSpinButton'
import { noop, serialize } from './utils'

function createFieldProps(
value: number | string = 5
): FieldProps<{ test: number | string }> {
return {
field: {
value,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'test',
},
form: { setFieldValue: jest.fn(), handleBlur: jest.fn(() => jest.fn()) },
} as any
}

test('<FormikSpinButton /> renders correctly as a field component', () => {
const component = renderer.create(
<Formik initialValues={{ isChecked: true }} onSubmit={noop}>
<Form>
<Field name="test" label="Count" component={FormikSpinButton} />
</Form>
</Formik>
)

expect(component.toJSON()).toMatchSnapshot()
})

test('<FormikSpinButton /> renders a Fabric <SpinButton />', () => {
const label = 'Test'
const fieldProps = createFieldProps()

const formikSpinButton = renderer.create(
<FormikSpinButton {...fieldProps} label={label} />
)
const fabricSpinButton = renderer.create(
<SpinButton {...mapFieldToSpinButton(fieldProps, {})} label={label} />
)
expect(serialize(formikSpinButton)).toBe(serialize(fabricSpinButton))
})

test('mapFieldToSpinButton() maps FieldProps to ISpinButtonProps', () => {
const { field, form } = createFieldProps()
const props = mapFieldToSpinButton({ form, field }, { min: 0, max: 10 })

expect(props.value).toBe(field.value)

props.onIncrement!('9')

expect(form.setFieldValue).toHaveBeenCalledTimes(1)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, 10)
expect(field.onBlur).toHaveBeenCalledTimes(1)

props.onIncrement!('10')

expect(form.setFieldValue).toHaveBeenCalledTimes(2)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, 10)
expect(field.onBlur).toHaveBeenCalledTimes(2)

props.onDecrement!('1')

expect(form.setFieldValue).toHaveBeenCalledTimes(3)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, 0)
expect(field.onBlur).toHaveBeenCalledTimes(3)

props.onDecrement!('0')

expect(form.setFieldValue).toHaveBeenCalledTimes(4)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, 0)
expect(field.onBlur).toHaveBeenCalledTimes(4)

props.onValidate!('5')

expect(form.setFieldValue).toHaveBeenCalledTimes(5)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, 5)
expect(field.onBlur).toHaveBeenCalledTimes(4)
})

test('mapFieldToSpinButton() with custom change handlers', () => {
const { field, form } = createFieldProps()
const props = mapFieldToSpinButton(
{ form, field },
{
onIncrement: value => `${+value.replace(/[\D\s]/g, '') + 1} cm`,
onDecrement: value => `${+value.replace(/[\D\s]/g, '') - 1} cm`,
onValidate: value => `${+value.replace(/[\D\s]/g, '')} cm`,
}
)

expect(props.value).toBe(field.value)

props.onIncrement!('9 cm')

expect(form.setFieldValue).toHaveBeenCalledTimes(1)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, '10 cm')
expect(field.onBlur).toHaveBeenCalledTimes(1)

props.onDecrement!('1 cm')

expect(form.setFieldValue).toHaveBeenCalledTimes(2)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, '0 cm')
expect(field.onBlur).toHaveBeenCalledTimes(2)

props.onValidate!('5 cm')

expect(form.setFieldValue).toHaveBeenCalledTimes(3)
expect(form.setFieldValue).toHaveBeenCalledWith(field.name, '5 cm')
expect(field.onBlur).toHaveBeenCalledTimes(2)
})
103 changes: 103 additions & 0 deletions src/__tests__/__snapshots__/FormikSpinButton.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<FormikSpinButton /> renders correctly as a field component 1`] = `
<form
onReset={[Function]}
onSubmit={[Function]}
>
<div
className="css-47"
>
<div
className="css-48"
>
<label
className="ms-Label root-54"
htmlFor="input2"
id="Label1"
>
Count
</label>
</div>
<div
className="css-51"
>
<input
aria-disabled={false}
aria-labelledby="Label1"
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={0}
autoComplete="off"
className="ms-spinButton-input css-52"
data-lpignore={true}
id="input2"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
readOnly={false}
role="spinbutton"
type="text"
value="0"
/>
<span
className="css-53"
>
<button
checked={false}
className="ms-Button ms-Button--icon ms-UpButton root-55"
data-is-focusable={false}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
tabIndex={-1}
type="button"
>
<div
className="ms-Button-flexContainer flexContainer-56"
>
<i
aria-hidden={true}
className="ms-Button-icon icon-63"
data-icon-name="ChevronUpSmall"
role="presentation"
/>
</div>
</button>
<button
checked={false}
className="ms-Button ms-Button--icon ms-DownButton root-55"
data-is-focusable={false}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
tabIndex={-1}
type="button"
>
<div
className="ms-Button-flexContainer flexContainer-56"
>
<i
aria-hidden={true}
className="ms-Button-icon icon-63"
data-icon-name="ChevronDownSmall"
role="presentation"
/>
</div>
</button>
</span>
</div>
</div>
</form>
`;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './FormikDropdown'
export * from './FormikPeoplePicker'
export * from './FormikRating'
export * from './FormikSlider'
export * from './FormikSpinButton'
export * from './FormikSwatchColorPicker'
export * from './FormikTextField'
export * from './FormikToggle'
39 changes: 39 additions & 0 deletions stories/FormikSpinButton.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// tslint:disable:jsx-no-lambda

import { Field, FieldProps, Formik } from 'formik'
import * as React from 'react'
import { FormikSpinButton } from '../src/FormikSpinButton'
import { StoryForm } from './StoryForm'
import { handleSubmit } from './utils'

class Values {
public rating: number = 3
public length: string = '10 cm'
}

export const FormikSpinButtonStory = () => (
<Formik initialValues={new Values()} onSubmit={handleSubmit}>
<StoryForm title="Spin Button">
<Field
name="rating"
label="SpinButton"
min={0}
max={5}
component={FormikSpinButton}
/>
<br />
<Field
name="length"
render={(fieldProps: FieldProps<Values>) => (
<FormikSpinButton
{...fieldProps}
onIncrement={value => `${+value.replace(/[\D\s]/g, '') + 1} cm`}
onDecrement={value => `${+value.replace(/[\D\s]/g, '') - 1} cm`}
onValidate={value => `${+value.replace(/[\D\s]/g, '')} cm`}
label="SpinButton w/ custom format"
/>
)}
/>
</StoryForm>
</Formik>
)
2 changes: 2 additions & 0 deletions stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FormikDropdownStory } from './FormikDropdown.story'
import { FormikPeoplePickerStory } from './FormikPeoplePicker.story'
import { FormikRatingStory } from './FormikRating.story'
import { FormikSliderStory } from './FormikSlider.story'
import { FormikSpinButtonStory } from './FormikSpinButton.story'
import { FormikSwatchColorPickerStory } from './FormikSwatchColorPicker.story'
import { FormikTextFieldStory } from './FormikTextField.story'
import { FormikToggleStory } from './FormikToggle.story'
Expand All @@ -23,6 +24,7 @@ storiesOf('formik-office-ui-fabric-react', module)
.add('FormikPeoplePicker', FormikPeoplePickerStory)
.add('FormikRating', FormikRatingStory)
.add('FormikSlider', FormikSliderStory)
.add('FormikSpinButton', FormikSpinButtonStory)
.add('FormikSwatchColorPicker', FormikSwatchColorPickerStory)
.add('FormikTextField', FormikTextFieldStory)
.add('FormikToggle', FormikToggleStory)

0 comments on commit 8799395

Please sign in to comment.