Skip to content

fix(Form): internal logic fixes #532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/wild-parents-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': minor
---

Form logic internal fixes.
2 changes: 1 addition & 1 deletion src/components/content/Tag/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const TagElement = tasty({
closable: '0 (2.5x - 1bw) 0 (1x - 1bw)',
},
fill: {
'': '#dark.04',
'': '#light',
...Object.keys(THEMES).reduce((map, type) => {
map[`[data-type="${type}"]`] = THEMES[type].fill;

Expand Down
24 changes: 19 additions & 5 deletions src/components/fields/Checkbox/checkbox-group.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { act, render, renderWithForm, userEvent } from '../../../test';
import { render, renderWithForm, userEvent } from '../../../test';
import { Field, Checkbox } from '../../../index';

jest.mock('../../../_internal/hooks/use-warn');
Expand All @@ -20,17 +20,24 @@ describe('<CheckboxGroup />', () => {

it('should work with new <Form />', async () => {
const { formInstance, getAllByRole } = renderWithForm(
<Checkbox.Group defaultValue={['one']} label="test" name="test">
<Checkbox.Group label="test" name="test">
<Checkbox value="one">One</Checkbox>
<Checkbox value="two">Two</Checkbox>
</Checkbox.Group>,
{
formProps: {
defaultValues: {
test: ['one'],
},
},
},
);
const checkbox = getAllByRole('checkbox');

expect(checkbox[0]).toBeChecked();
expect(checkbox[1]).not.toBeChecked();

await act(async () => await userEvent.click(checkbox[1]));
await userEvent.click(checkbox[1]);

expect(checkbox[0]).toBeChecked();
expect(checkbox[1]).toBeChecked();
Expand All @@ -40,17 +47,24 @@ describe('<CheckboxGroup />', () => {

it('should interop with <Form />', async () => {
const { getAllByRole, formInstance } = renderWithForm(
<Field name="test" defaultValue={['two']}>
<Field name="test">
<Checkbox.Group label="test">
<Checkbox value="one">Buy milk</Checkbox>
<Checkbox value="two">Buy coffee</Checkbox>
<Checkbox value="three">Buy bread</Checkbox>
</Checkbox.Group>
</Field>,
{
formProps: {
defaultValues: {
test: ['two'],
},
},
},
);

const checkbox = getAllByRole('checkbox');
await act(async () => await userEvent.click(checkbox[0]));
await userEvent.click(checkbox[0]);

expect(checkbox[0]).toBeChecked();
expect(checkbox[1]).toBeChecked();
Expand Down
8 changes: 4 additions & 4 deletions src/components/fields/Checkbox/checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import userEvent from '@testing-library/user-event';

import { act, render, renderWithForm } from '../../../test';
import { render, renderWithForm } from '../../../test';
import { Field } from '../../form';

import { Checkbox } from './Checkbox';
Expand All @@ -12,7 +12,7 @@ describe('<Checkbox />', () => {
const { getByRole } = render(<Checkbox>Test</Checkbox>);
const checkboxElement = getByRole('checkbox');

await act(async () => await userEvent.click(checkboxElement));
await userEvent.click(checkboxElement);

expect(checkboxElement).toBeChecked();
});
Expand All @@ -24,7 +24,7 @@ describe('<Checkbox />', () => {

const checkboxElement = getByRole('checkbox');

await act(async () => await userEvent.click(checkboxElement));
await userEvent.click(checkboxElement);

expect(checkboxElement).toBeChecked();
expect(formInstance.getFieldValue('test')).toBe(true);
Expand All @@ -39,7 +39,7 @@ describe('<Checkbox />', () => {

const checkboxElement = getByRole('checkbox');

await act(async () => await userEvent.click(checkboxElement));
await userEvent.click(checkboxElement);

expect(checkboxElement).toBeChecked();
expect(formInstance.getFieldValue('test')).toBe(true);
Expand Down
23 changes: 23 additions & 0 deletions src/components/fields/ComboBox/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { userEvent, within } from '@storybook/test';

import { SELECTED_KEY_ARG } from '../../../stories/FormFieldArgs';
import { baseProps } from '../../../stories/lists/baseProps';
import { Form, useForm } from '../../form/index';

import { ComboBox, CubeComboBoxProps } from './ComboBox';

Expand Down Expand Up @@ -32,6 +33,26 @@ const Template: StoryFn<CubeComboBoxProps<any>> = (
</>
);

const FormTemplate: StoryFn<CubeComboBoxProps<any>> = (
args: CubeComboBoxProps<any>,
) => {
const [form] = useForm();

return (
<Form form={form}>
<ComboBox name="combobox" {...args}>
<ComboBox.Item key="red">Red</ComboBox.Item>
<ComboBox.Item key="orange">Orange</ComboBox.Item>
<ComboBox.Item key="yellow">Yellow</ComboBox.Item>
<ComboBox.Item key="green">Green</ComboBox.Item>
<ComboBox.Item key="blue">Blue</ComboBox.Item>
<ComboBox.Item key="purple">Purple</ComboBox.Item>
<ComboBox.Item key="violet">Violet</ComboBox.Item>
</ComboBox>
</Form>
);
};

export const Default = Template.bind({});
Default.args = {};

Expand Down Expand Up @@ -101,3 +122,5 @@ With1LongOptionFiltered.play = async ({ canvasElement }) => {

await userEvent.type(combobox, 'Red');
};

export const WithinForm = FormTemplate.bind({});
10 changes: 9 additions & 1 deletion src/components/fields/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ReactElement,
RefObject,
useMemo,
useState,
} from 'react';
import {
useButton,
Expand Down Expand Up @@ -133,12 +134,19 @@ export const ComboBox = forwardRef(function ComboBox<T extends object>(
props: CubeComboBoxProps<T>,
ref: ForwardedRef<HTMLDivElement>,
) {
const [rerender, setRerender] = useState({});

props = useProviderProps(props);
props = useFormProps(props);
props = useFieldProps(props, {
valuePropsMapper: ({ value, onChange }) => ({
inputValue: value != null ? value : '',
onInputChange: (val) => onChange(val, !props.allowsCustomValue),
onInputChange: (val) => {
onChange(val, !props.allowsCustomValue);
if (rerender) {
setRerender({});
}
},
onSelectionChange: onChange,
}),
});
Expand Down
2 changes: 2 additions & 0 deletions src/components/fields/RadioGroup/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export interface CubeRadioProps
inputStyles?: Styles;
/* The visual type of the radio button */
type?: 'button' | 'radio';
value?: string;
onChange?: (value: string) => void;
}

function Radio(props: CubeRadioProps, ref) {
Expand Down
5 changes: 2 additions & 3 deletions src/components/fields/RadioGroup/radio.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
act,
renderWithForm,
renderWithRoot,
screen,
Expand All @@ -19,7 +18,7 @@ describe('<Radio /> and <RadioGroup />', () => {
</Radio.Group>,
);
const radio = getAllByRole('radio');
await act(async () => await userEvent.click(radio[0]));
await userEvent.click(radio[0]);

expect(radio[0]).toBeChecked();
});
Expand All @@ -33,7 +32,7 @@ describe('<Radio /> and <RadioGroup />', () => {
);
const radio = screen.getAllByRole('radio');

await act(async () => await userEvent.click(radio[0]));
await userEvent.click(radio[0]);

expect(radio[0]).toBeChecked();

Expand Down
19 changes: 7 additions & 12 deletions src/components/fields/Select/select.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
act,
renderWithForm,
renderWithRoot,
userEvent,
} from '../../../test/index';
import { renderWithForm, renderWithRoot, userEvent } from '../../../test';
import { Field } from '../../form';

import { Select } from './Select';
Expand All @@ -21,10 +16,10 @@ describe('<Select />', () => {
);

const select = getByRole('button');
await act(async () => await userEvent.click(select));
await userEvent.click(select);

const options = getAllByRole('option');
await act(async () => await userEvent.click(options[1]));
await userEvent.click(options[1]);

expect(select).toHaveTextContent('Red');
});
Expand All @@ -41,10 +36,10 @@ describe('<Select />', () => {
);

const select = getByRole('button');
await act(async () => await userEvent.click(select));
await userEvent.click(select);

const options = getAllByRole('option');
await act(async () => await userEvent.click(options[1]));
await userEvent.click(options[1]);

expect(select).toHaveTextContent('Red');
expect(formInstance.getFieldValue('test')).toBe('2');
Expand All @@ -60,10 +55,10 @@ describe('<Select />', () => {
);

const select = getByRole('button');
await act(async () => await userEvent.click(select));
await userEvent.click(select);

const options = getAllByRole('option');
await act(async () => await userEvent.click(options[1]));
await userEvent.click(options[1]);

expect(select).toHaveTextContent('Red');
expect(formInstance.getFieldValue('test')).toBe('2');
Expand Down
8 changes: 4 additions & 4 deletions src/components/fields/Switch/switch.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderWithForm, userEvent, render, act } from '../../../test';
import { renderWithForm, userEvent, render } from '../../../test';
import { Field, Switch } from '../../../index';

jest.mock('../../../_internal/hooks/use-warn');
Expand All @@ -8,7 +8,7 @@ describe('<Switch />', () => {
const { getByRole } = render(<Switch aria-label="test" />);
const switchElement = getByRole('switch');

await act(async () => await userEvent.click(switchElement));
await userEvent.click(switchElement);

expect(switchElement).toBeChecked();
});
Expand All @@ -22,7 +22,7 @@ describe('<Switch />', () => {

const switchElement = getByRole('switch');

await act(async () => await userEvent.click(switchElement));
await userEvent.click(switchElement);

expect(switchElement).toBeChecked();
expect(formInstance.getFieldValue('test')).toBe(true);
Expand All @@ -35,7 +35,7 @@ describe('<Switch />', () => {

const switchElement = getByRole('switch');

await act(async () => await userEvent.click(switchElement));
await userEvent.click(switchElement);

expect(switchElement).toBeChecked();
expect(formInstance.getFieldValue('test')).toBe(true);
Expand Down
14 changes: 6 additions & 8 deletions src/components/form/Form/ComplexForm.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,11 @@ const Template: StoryFn<typeof Form> = (args) => {
<Field label="Custom field" tooltip="What?">
<Block>Test</Block>
</Field>
<Field
<TextInput
name="email"
label="Email field"
type="email"
size="small"
rules={[
{ required: true, message: 'This field is required' },
{
Expand All @@ -269,13 +272,8 @@ const Template: StoryFn<typeof Form> = (args) => {
},
]}
necessityIndicator={'label'}
defaultValue="tenphi@gmail.com"
shouldUpdate={({ email }) => {
return !!email;
}}
>
<TextInput type="email" size="small" label="Email field" />
</Field>
shouldUpdate={(prevValues, { email }) => !!email}
/>
<Field name="password">
<PasswordInput label="Password field" />
</Field>
Expand Down
Loading
Loading