Skip to content
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

[FormControl][material-next] Add FormControl component #39032

Merged
merged 18 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/mui-base/src/FormControl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type {
FormControlRootSlotPropsOverrides,
FormControlState,
UseFormControlContextReturnValue,
FormControlOwnProps,
} from './FormControl.types';

export * from './formControlClasses';
Expand Down
18 changes: 11 additions & 7 deletions packages/mui-material-next/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,23 @@ If you need to prevent default on a `key-up` and/or `key-down` event, then besid

This is to ensure that default is prevented when the `ButtonBase` root is not a native button, for example, when the root element used is a `span`.

## InputBase
## FormControl

### Removed the `inputComponent` prop
### Renamed `FormControlState`

The `inputComponent` is deprecated in favor of `slots.input`:
The `FormControlState` interface was renamed to `FormControlContextValue`:

```diff
<InputBase
- inputComponent="textarea"
+ slots={{ input: 'textarea' }}
/>
-import { FormControlState } from '@mui/material';
+import { FormControlContextValue } from '@mui/material-next';
```

### Removed the `standard` variant

The standard variant is no longer supported in Material You, use the `filled` or `outlined` variants instead.

## InputBase

### Removed `inputProps`

`inputProps` are deprecated in favor of `slotProps.input`:
Expand Down
133 changes: 0 additions & 133 deletions packages/mui-material-next/src/FormControl/FormControl.d.ts

This file was deleted.

105 changes: 88 additions & 17 deletions packages/mui-material-next/src/FormControl/FormControl.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable mocha/no-skipped-tests */
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import { describeConformance, act, createRenderer } from '@mui-internal/test-utils';
import { describeConformance, act, createRenderer, fireEvent } from '@mui-internal/test-utils';
import FormControl, { formControlClasses as classes } from '@mui/material-next/FormControl';
// TODO: replace with material-next/OutlinedInput
// TODO v6: replace with material-next/FilledInput
import InputBase from '@mui/material-next/InputBase';
// TODO: replace with material-next/Select
import { CssVarsProvider, extendTheme } from '@mui/material-next/styles';
// TODO v6: replace with material-next/Select
import Select from '@mui/material/Select';
import useFormControl from './useFormControl';

Expand All @@ -24,10 +24,19 @@ describe('<FormControl />', () => {
describeConformance(<FormControl />, () => ({
classes,
inheritComponent: 'div',
ThemeProvider: CssVarsProvider,
createTheme: extendTheme,
render,
refInstanceof: window.HTMLDivElement,
testComponentPropWith: 'fieldset',
muiName: 'MuiFormControl',
slots: {
root: {
expectedClassName: classes.root,
testWithElement: 'fieldset',
},
},
testRootOverrides: { slotName: 'root', slotClassName: classes.root },
testComponentPropWith: 'fieldset',
testVariantProps: { margin: 'dense' },
skip: ['componentsProp'],
}));
Expand Down Expand Up @@ -85,8 +94,7 @@ describe('<FormControl />', () => {
});
});

// TODO: needs InputBase + FormControl integrated
describe.skip('prop: disabled', () => {
describe('prop: disabled', () => {
it('will be unfocused if it gets disabled', () => {
const readContext = spy();
const { container, setProps } = render(
Expand Down Expand Up @@ -134,36 +142,97 @@ describe('<FormControl />', () => {
});
});

describe('input', () => {
// TODO: needs InputBase + FormControl integrated
it.skip('should be filled when a value is set', () => {
describe('registering input', () => {
it("should warn if more than one input is rendered regardless how it's nested", () => {
expect(() => {
render(
<FormControl>
<InputBase />
<div>
{/* should work regardless how it's nested */}
<InputBase />
</div>
</FormControl>,
);
}).toErrorDev([
'MUI: There are multiple `InputBase` components inside a FormControl.\nThis creates visual inconsistencies, only use one `InputBase`.',
// React 18 Strict Effects run mount effects twice
React.version.startsWith('18') &&
'MUI: There are multiple `InputBase` components inside a FormControl.\nThis creates visual inconsistencies, only use one `InputBase`.',
]);
});

it('should not warn if only one input is rendered', () => {
expect(() => {
render(
<FormControl>
<InputBase />
</FormControl>,
);
}).not.toErrorDev();
});

it('should not warn when toggling between inputs', () => {
// this will ensure that deregistering was called during unmount
function ToggleFormInputs() {
const [flag, setFlag] = React.useState(true);

return (
<FormControl>
{flag ? (
<InputBase />
) : (
// TODO v6: use material-next/Select
<Select native>
<option value="">empty</option>
</Select>
)}
<button type="button" onClick={() => setFlag(!flag)}>
toggle
</button>
</FormControl>
);
}

const { getByText } = render(<ToggleFormInputs />);
expect(() => {
fireEvent.click(getByText('toggle'));
}).not.toErrorDev();
});
});

// TODO v6: needs FilledInput + FormControl integrated
// eslint-disable-next-line mocha/no-skipped-tests
describe.skip('input', () => {
it('should be filled when a value is set', () => {
const readContext = spy();
render(
<FormControl>
{/* TODO v6: use material-next/FilledInput */}
<InputBase value="bar" />
<TestComponent contextCallback={readContext} />
</FormControl>,
);
expect(readContext.args[0][0]).to.have.property('filled', true);
});

// TODO: needs InputBase + FormControl integrated
it.skip('should be filled when a value is set through inputProps', () => {
it('should be filled when a value is set through inputProps', () => {
const readContext = spy();
render(
<FormControl>
{/* TODO v6: use material-next/FilledInput */}
<InputBase inputProps={{ value: 'bar' }} />
<TestComponent contextCallback={readContext} />
</FormControl>,
);
expect(readContext.args[0][0]).to.have.property('filled', true);
});

// TODO: needs InputBase + FormControl integrated
it.skip('should be filled when a defaultValue is set', () => {
it('should be filled when a defaultValue is set', () => {
const readContext = spy();
render(
<FormControl>
{/* TODO v6: use material-next/FilledInput */}
<InputBase defaultValue="bar" />
<TestComponent contextCallback={readContext} />
</FormControl>,
Expand All @@ -175,18 +244,19 @@ describe('<FormControl />', () => {
const readContext = spy();
render(
<FormControl>
{/* TODO v6: use material-next/FilledInput */}
<InputBase endAdornment={<div />} />
<TestComponent contextCallback={readContext} />
</FormControl>,
);
expect(readContext.args[0][0]).to.have.property('adornedStart', false);
});

// TODO: needs InputBase + FormControl integrated
it.skip('should be adornedStart with a startAdornment', () => {
it('should be adornedStart with a startAdornment', () => {
const readContext = spy();
render(
<FormControl>
{/* TODO v6: use material-next/FilledInput */}
<InputBase startAdornment={<div />} />
<TestComponent contextCallback={readContext} />
</FormControl>,
Expand All @@ -195,7 +265,8 @@ describe('<FormControl />', () => {
});
});

// TODO: unskip and refactor when integrating material-next/Select
// TODO v6: needs material-next/Select + FormControl integrated
// eslint-disable-next-line mocha/no-skipped-tests
describe.skip('select', () => {
it('should not be adorned without a startAdornment', () => {
const readContext = spy();
Expand Down
Loading