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

Update <SelectArrayInput> to use default record representation when used inside <ReferenceArrayInput> #9902

Merged
merged 6 commits into from
Jun 6, 2024
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
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
Loading