Skip to content

Commit

Permalink
Merge pull request #9902 from marmelab/select-array-input-record-repr…
Browse files Browse the repository at this point in the history
…esentation

Update `<SelectArrayInput>` to use default record representation when used inside `<ReferenceArrayInput>`
  • Loading branch information
adguernier authored Jun 6, 2024
2 parents a72fd48 + 65bb3ce commit 67c3b10
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 75 deletions.
11 changes: 10 additions & 1 deletion packages/ra-ui-materialui/src/input/CheckboxGroupInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { AdminContext } from '../AdminContext';
import { SimpleForm } from '../form';
import { CheckboxGroupInput } from './CheckboxGroupInput';
import { InsideReferenceArrayInput } from './CheckboxGroupInput.stories';

describe('<CheckboxGroupInput />', () => {
const defaultProps = {
Expand Down Expand Up @@ -145,7 +146,7 @@ describe('<CheckboxGroupInput />', () => {
it('should use optionText with an element value as text identifier', () => {
const Foobar = () => {
const record = useRecordContext();
return <span data-testid="label">{record.foobar}</span>;
return <span data-testid="label">{record?.foobar}</span>;
};
render(
<AdminContext dataProvider={testDataProvider()}>
Expand Down Expand Up @@ -383,4 +384,12 @@ describe('<CheckboxGroupInput />', () => {

expect(screen.queryByRole('progressbar')).toBeNull();
});

describe('inside ReferenceArrayInput', () => {
it('should use the recordRepresentation as optionText', async () => {
render(<InsideReferenceArrayInput />);

await screen.findByText('Option 1 (This is option 1)');
});
});
});
57 changes: 40 additions & 17 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import englishMessages from 'ra-language-english';
import { Typography } from '@mui/material';
import FavoriteBorder from '@mui/icons-material/FavoriteBorder';
import Favorite from '@mui/icons-material/Favorite';
import { required, testDataProvider, useRecordContext } from 'ra-core';
import {
Resource,
TestMemoryRouter,
required,
testDataProvider,
useRecordContext,
} from 'ra-core';
import { useFormContext } from 'react-hook-form';

import { AdminContext } from '../AdminContext';
Expand All @@ -13,6 +19,7 @@ import { SimpleForm } from '../form';
import { CheckboxGroupInput } from './CheckboxGroupInput';
import { ReferenceArrayInput } from './ReferenceArrayInput';
import { TextInput } from './TextInput';
import { Admin } from 'react-admin';

export default { title: 'ra-ui-materialui/input/CheckboxGroupInput' };

Expand Down Expand Up @@ -61,23 +68,39 @@ const dataProvider = testDataProvider({
});

export const InsideReferenceArrayInput = () => (
<AdminContext
dataProvider={dataProvider}
i18nProvider={i18nProvider}
defaultTheme="light"
>
<Create
resource="posts"
record={{ options: [1, 2] }}
sx={{ width: 600 }}
<TestMemoryRouter initialEntries={['/posts/create']}>
<Admin
dataProvider={dataProvider}
i18nProvider={i18nProvider}
defaultTheme="light"
>
<SimpleForm>
<ReferenceArrayInput reference="options" source="options">
<CheckboxGroupInput />
</ReferenceArrayInput>
</SimpleForm>
</Create>
</AdminContext>
<Resource
name="options"
recordRepresentation={record =>
`${record.name} (${record.details})`
}
/>
<Resource
name="posts"
create={
<Create
resource="posts"
record={{ options: [1, 2] }}
sx={{ width: 600 }}
>
<SimpleForm>
<ReferenceArrayInput
reference="options"
source="options"
>
<CheckboxGroupInput />
</ReferenceArrayInput>
</SimpleForm>
</Create>
}
/>
</Admin>
</TestMemoryRouter>
);

export const Disabled = () => (
Expand Down
17 changes: 14 additions & 3 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import FormControl, { FormControlProps } from '@mui/material/FormControl';
import FormGroup from '@mui/material/FormGroup';
import FormHelperText from '@mui/material/FormHelperText';
import { CheckboxProps } from '@mui/material/Checkbox';
import { FieldTitle, useInput, ChoicesProps, useChoicesContext } from 'ra-core';
import {
FieldTitle,
useInput,
ChoicesProps,
useChoicesContext,
useGetRecordRepresentation,
} from 'ra-core';

import { CommonInputProps } from './CommonInputProps';
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
Expand Down Expand Up @@ -101,7 +107,7 @@ export const CheckboxGroupInput: FunctionComponent<
onBlur,
onChange,
options,
optionText = 'name',
optionText,
optionValue = 'id',
parse,
resource: resourceProp,
Expand Down Expand Up @@ -156,6 +162,8 @@ export const CheckboxGroupInput: FunctionComponent<
...rest,
});

const getRecordRepresentation = useGetRecordRepresentation(resource);

const handleCheck = useCallback(
(event, isChecked) => {
let newValue;
Expand Down Expand Up @@ -232,7 +240,10 @@ export const CheckboxGroupInput: FunctionComponent<
id={id}
onChange={handleCheck}
options={options}
optionText={optionText}
optionText={
optionText ??
(isFromReference ? getRecordRepresentation : 'name')
}
optionValue={optionValue}
translateChoice={translateChoice ?? !isFromReference}
value={value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe('<RadioButtonGroupInput />', () => {
it('should use optionText with an element value as text identifier', () => {
const Foobar = () => {
const record = useRecordContext();
return <span data-testid="label">{record.longname}</span>;
return <span data-testid="label">{record?.longname}</span>;
};
render(
<AdminContext dataProvider={testDataProvider()}>
Expand Down Expand Up @@ -477,9 +477,9 @@ describe('<RadioButtonGroupInput />', () => {
it('should use the recordRepresentation as optionText', async () => {
render(<InsideReferenceArrayInput />);

await screen.findByText('Lifestyle');
await screen.findByText('Tech');
await screen.findByText('People');
await screen.findByText('Lifestyle (Lifestyle details)');
await screen.findByText('Tech (Tech details)');
await screen.findByText('People (People details)');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import { RadioButtonGroupInput } from './RadioButtonGroupInput';
import { FormInspector } from './common';
import { ReferenceInput } from './ReferenceInput';
import { ReferenceArrayInput } from './ReferenceArrayInput';
import { testDataProvider } from 'ra-core';
import { Resource, TestMemoryRouter, testDataProvider } from 'ra-core';
import { Admin } from 'react-admin';

export default { title: 'ra-ui-materialui/input/RadioButtonGroupInput' };

const choices = [
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People' },
{ id: 'tech', name: 'Tech', details: 'Tech details' },
{ id: 'lifestyle', name: 'Lifestyle', details: 'Lifestyle details' },
{ id: 'people', name: 'People', details: 'People details' },
];

export const Basic = () => (
Expand Down Expand Up @@ -77,23 +78,39 @@ const dataProvider = testDataProvider({
} as any);

export const InsideReferenceArrayInput = () => (
<AdminContext
dataProvider={dataProvider}
i18nProvider={i18nProvider}
defaultTheme="light"
>
<Create
resource="posts"
record={{ options: [1, 2] }}
sx={{ width: 600 }}
<TestMemoryRouter initialEntries={['/posts/create']}>
<Admin
dataProvider={dataProvider}
i18nProvider={i18nProvider}
defaultTheme="light"
>
<SimpleForm>
<ReferenceArrayInput reference="categories" source="category">
<RadioButtonGroupInput />
</ReferenceArrayInput>
</SimpleForm>
</Create>
</AdminContext>
<Resource
name="categories"
recordRepresentation={record =>
`${record.name} (${record.details})`
}
/>
<Resource
name="posts"
create={
<Create
resource="posts"
record={{ options: [1, 2] }}
sx={{ width: 600 }}
>
<SimpleForm>
<ReferenceArrayInput
reference="categories"
source="category"
>
<RadioButtonGroupInput />
</ReferenceArrayInput>
</SimpleForm>
</Create>
}
/>
</Admin>
</TestMemoryRouter>
);

export const InsideReferenceArrayInputWithError = () => (
Expand Down
17 changes: 14 additions & 3 deletions packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
import { RadioGroupProps } from '@mui/material/RadioGroup';
import { FormControlProps } from '@mui/material/FormControl';
import get from 'lodash/get';
import { useInput, FieldTitle, ChoicesProps, useChoicesContext } from 'ra-core';
import {
useInput,
FieldTitle,
ChoicesProps,
useChoicesContext,
useGetRecordRepresentation,
} from 'ra-core';

import { CommonInputProps } from './CommonInputProps';
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
Expand Down Expand Up @@ -93,7 +99,7 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
onBlur,
onChange,
options = defaultOptions,
optionText = 'name',
optionText,
optionValue = 'id',
parse,
resource: resourceProp,
Expand Down Expand Up @@ -143,6 +149,8 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
...rest,
});

const getRecordRepresentation = useGetRecordRepresentation(resource);

const { error, invalid } = fieldState;

if (isPending) {
Expand Down Expand Up @@ -193,7 +201,10 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
<RadioButtonGroupInputItem
key={get(choice, optionValue)}
choice={choice}
optionText={optionText}
optionText={
optionText ??
(isFromReference ? getRecordRepresentation : 'name')
}
optionValue={optionValue}
source={id}
translateChoice={translateChoice ?? !isFromReference}
Expand Down
31 changes: 28 additions & 3 deletions packages/ra-ui-materialui/src/input/SelectArrayInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
DifferentIdTypes,
TranslateChoice,
InsideArrayInput,
InsideReferenceArrayInput,
InsideReferenceArrayInputDefaultValue,
} from './SelectArrayInput.stories';

describe('<SelectArrayInput />', () => {
Expand Down Expand Up @@ -177,7 +179,7 @@ describe('<SelectArrayInput />', () => {
it('should use optionText with an element value as text identifier', () => {
const Foobar = () => {
const record = useRecordContext();
return <span>{record.foobar}</span>;
return <span>{record?.foobar}</span>;
};
render(
<AdminContext dataProvider={testDataProvider()}>
Expand Down Expand Up @@ -560,7 +562,7 @@ describe('<SelectArrayInput />', () => {
});
});

it('should recive a value on change when creating a new choice', async () => {
it('should receive a value on change when creating a new choice', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {});
const choices = [...defaultProps.choices];
const newChoice = { id: 'js_fatigue', name: 'New Kid On The Block' };
Expand Down Expand Up @@ -603,7 +605,7 @@ describe('<SelectArrayInput />', () => {
});
});

it('should show selected values when ids type are inconsistant', async () => {
it('should show selected values when ids type are inconsistent', async () => {
render(<DifferentIdTypes />);
await waitFor(() => {
expect(screen.queryByText('artist_1')).not.toBeNull();
Expand Down Expand Up @@ -667,4 +669,27 @@ describe('<SelectArrayInput />', () => {
fireEvent.click(screen.getByLabelText('Add'));
expect(await screen.findAllByText('Foo')).toHaveLength(2);
});

describe('inside ReferenceArrayInput', () => {
it('should use the recordRepresentation as optionText', async () => {
render(<InsideReferenceArrayInput />);
await screen.findByText('Leo Tolstoy');
});
it('should not change an undefined value to empty string', async () => {
const onSuccess = jest.fn();
render(
<InsideReferenceArrayInputDefaultValue onSuccess={onSuccess} />
);
const input = await screen.findByDisplayValue('War and Peace');
fireEvent.change(input, { target: { value: 'War' } });
screen.getByText('Save').click();
await waitFor(() => {
expect(onSuccess).toHaveBeenCalledWith(
expect.objectContaining({ authors: undefined }),
expect.anything(),
expect.anything()
);
});
});
});
});
Loading

0 comments on commit 67c3b10

Please sign in to comment.