Skip to content

Commit

Permalink
add value handling and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ecraig12345 committed Dec 2, 2021
1 parent 2e57e43 commit 4b7494b
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 5 deletions.
75 changes: 72 additions & 3 deletions packages/react-input/src/components/Input/Input.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,88 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { render, RenderResult, fireEvent, screen } from '@testing-library/react';
import { Input } from './Input';
import { isConformant } from '../../common/isConformant';

function getInput(): HTMLInputElement {
return screen.getByRole('textbox') as HTMLInputElement;
}

describe('Input', () => {
let renderedComponent: RenderResult | undefined;

afterEach(() => {
if (renderedComponent) {
renderedComponent.unmount();
renderedComponent = undefined;
}
});

isConformant({
Component: Input,
displayName: 'Input',
primarySlot: 'input',
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<Input />);
expect(result.container).toMatchSnapshot();
});

it('respects value', () => {
renderedComponent = render(<Input value="hello" />);
expect(getInput().value).toEqual('hello');
});

it('respects updates to value', () => {
renderedComponent = render(<Input value="hello" />);
expect(getInput().value).toEqual('hello');

renderedComponent.rerender(<Input value="world" />);
expect(getInput().value).toEqual('world');
});

it('respects defaultValue', () => {
renderedComponent = render(<Input defaultValue="hello" />);
expect(getInput().value).toEqual('hello');
});

it('ignores updates to defaultValue', () => {
renderedComponent = render(<Input defaultValue="hello" />);
expect(getInput().value).toEqual('hello');

renderedComponent.rerender(<Input defaultValue="world" />);
expect(getInput().value).toEqual('hello');
});

it('prefers value over defaultValue', () => {
renderedComponent = render(<Input value="hello" defaultValue="world" />);
expect(getInput().value).toEqual('hello');
});

it('with value, calls onChange but does not update on text entry', () => {
const onChange = jest.fn();
renderedComponent = render(<Input value="hello" onChange={onChange} />);
const input = getInput();
fireEvent.change(input, { target: { value: 'world' } });
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][1]).toEqual({ value: 'world' });
expect(input.value).toBe('hello');
});

it('with defaultValue, calls onChange and updates value on text entry', () => {
const onChange = jest.fn();
renderedComponent = render(<Input defaultValue="hello" onChange={onChange} />);
const input = getInput();
fireEvent.change(input, { target: { value: 'world' } });
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][1]).toEqual({ value: 'world' });
expect(input.value).toBe('world');
});

it('does not call onChange when value prop updates', () => {
const onChange = jest.fn();
renderedComponent = render(<Input value="hello" onChange={onChange} />);
renderedComponent.rerender(<Input value="world" onChange={onChange} />);
expect(onChange).toHaveBeenCalledTimes(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports[`Input renders a default state 1`] = `
<input
class=""
type="text"
value=""
/>
</span>
</div>
Expand Down
17 changes: 15 additions & 2 deletions packages/react-input/src/components/Input/useInput.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as React from 'react';
import { getPartitionedNativeProps, resolveShorthand, useEventCallback } from '@fluentui/react-utilities';
import {
getPartitionedNativeProps,
resolveShorthand,
useControllableState,
useEventCallback,
} from '@fluentui/react-utilities';
import type { InputProps, InputState } from './Input.types';

/**
Expand All @@ -14,10 +19,16 @@ import type { InputProps, InputState } from './Input.types';
export const useInput = (props: InputProps, ref: React.Ref<HTMLInputElement>): InputState => {
const { size = 'medium', appearance = 'outline', inline = false, onChange } = props;

const [value, setValue] = useControllableState({
state: props.value,
defaultState: props.defaultValue,
initialState: undefined,
});

const nativeProps = getPartitionedNativeProps({
props,
primarySlotTagName: 'input',
excludedPropNames: ['size', 'onChange'],
excludedPropNames: ['size', 'onChange', 'value', 'defaultValue'],
});

const state: InputState = {
Expand Down Expand Up @@ -46,9 +57,11 @@ export const useInput = (props: InputProps, ref: React.Ref<HTMLInputElement>): I
}),
};

state.input.value = value;
state.input.onChange = useEventCallback(ev => {
const newValue = ev.target.value;
onChange?.(ev, { value: newValue });
setValue(newValue);
});

return state;
Expand Down

0 comments on commit 4b7494b

Please sign in to comment.