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

Referenced items won't be selected when IDs type differ in <ReferenceArrayInput> and <AutoCompleteInput> #8500

Merged
merged 23 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
50a5684
bug fix and stories
antoinefricker Dec 13, 2022
1e4ffa5
WIP test
antoinefricker Dec 13, 2022
0353a7b
Fix tests
antoinefricker Dec 13, 2022
3670f60
Working on tests / Story to test nullish values
antoinefricker Dec 14, 2022
6abfec5
Work on nullish tests
antoinefricker Dec 14, 2022
3c34031
Fix nullish tests
antoinefricker Dec 14, 2022
c3334e9
Error in imports
antoinefricker Dec 14, 2022
d7acf97
Fix Datagrid
antoinefricker Dec 14, 2022
28ec43c
Integrates review comments
antoinefricker Dec 14, 2022
c457f14
Update packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
antoinefricker Dec 14, 2022
ccc7af7
Update packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
antoinefricker Dec 14, 2022
5c6964f
Update packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
antoinefricker Dec 14, 2022
4d0c56b
Update packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx
antoinefricker Dec 14, 2022
c283d65
Simplify NullishValuesSupport / fix renaming issues
antoinefricker Dec 14, 2022
567f4c3
Update packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx
antoinefricker Dec 14, 2022
90e90dd
Renaming story component
antoinefricker Dec 15, 2022
07013c8
Corrections from review
antoinefricker Dec 15, 2022
c42b44f
Leave create item code alone
antoinefricker Dec 15, 2022
3c2495d
Add comment for unexpected code
antoinefricker Dec 15, 2022
a008e1d
Update packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx
antoinefricker Dec 15, 2022
c427d96
Let the errors flood
antoinefricker Dec 15, 2022
16dd719
Merge branch 'reference-inconsistant-types-ids' of github.com:marmela…
antoinefricker Dec 15, 2022
3c51a69
More verbose comment for unclear code section on option deselection
antoinefricker Dec 16, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import fakeRestProvider from 'ra-data-fakerest';

import { AdminContext } from '../AdminContext';
import { Datagrid, List } from '../list';
import { ReferenceArrayField } from './ReferenceArrayField';
import { TextField } from './TextField';

const fakeData = {
bands: [{ id: 1, name: 'band_1', members: [1, '2', '3'] }],
artists: [
{ id: 1, name: 'artist_1' },
{ id: 2, name: 'artist_2' },
{ id: 3, name: 'artist_3' },
{ id: 4, name: 'artist_4' },
],
};
const dataProvider = fakeRestProvider(fakeData, true);

export default { title: 'ra-ui-materialui/fields/ReferenceArrayField' };

export const HandlingIdsDiscrependies = () => {
return (
<AdminContext dataProvider={dataProvider}>
<List resource="bands" sx={{ width: 600 }}>
<Datagrid>
<TextField source="name" fullWidth />
<ReferenceArrayField
fullWidth
source="members"
reference="artists"
>
<Datagrid>
<TextField source="id" />
<TextField source="name" />
</Datagrid>
</ReferenceArrayField>
</Datagrid>
</List>
</AdminContext>
);
};
22 changes: 22 additions & 0 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import expect from 'expect';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
Expand All @@ -17,6 +18,7 @@ import {
InsideReferenceInput,
InsideReferenceInputDefaultValue,
Nullable,
NullishValuesHandling,
VeryLargeOptionsNumber,
} from './AutocompleteInput.stories';
import { act } from '@testing-library/react-hooks';
Expand Down Expand Up @@ -1477,4 +1479,24 @@ describe('<AutocompleteInput />', () => {
expect(input.value).toEqual('');
});
});

it.only('should handle nullish values', async () => {
render(<NullishValuesHandling />);

const checkInputValue = async (label: string, expected: any) => {
const input = (await screen.findByLabelText(
label
)) as HTMLInputElement;
await waitFor(() => {
expect(input.value).toStrictEqual(expected);
});
};

await checkInputValue('prefers_empty-string', '');
await checkInputValue('prefers_null', '');
await checkInputValue('prefers_undefined', '');
await checkInputValue('prefers_zero-string', '0');
await checkInputValue('prefers_zero-number', '0');
await checkInputValue('prefers_valid-value', '1');
});
});
86 changes: 83 additions & 3 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import * as React from 'react';
import { Admin, AdminContext } from 'react-admin';
import { Resource, required, useCreate, useRecordContext } from 'ra-core';
import { Admin, AdminContext, Datagrid, DatagridBody, List } from 'react-admin';
import {
Resource,
required,
useCreate,
useRecordContext,
RecordContextProvider,
} from 'ra-core';
import { createMemoryHistory } from 'history';
import {
Dialog,
DialogContent,
TextField,
DialogActions,
Button,
Stack,
TextField,
Typography,
TableRow,
TableCell,
} from '@mui/material';
import fakeRestProvider from 'ra-data-fakerest';

import { Edit } from '../detail';
import { SimpleForm } from '../form';
Expand Down Expand Up @@ -791,3 +800,74 @@ export const EmptyText = () => (
<Resource name="books" edit={BookEditWithEmptyText} />
</Admin>
);

const nullishValuesFakeData = {
fans: [
{ id: 'null', name: 'null', prefers: null },
{ id: 'undefined', name: 'undefined', prefers: undefined },
{ id: 'empty-string', name: 'empty string', prefers: '' },
{ id: 'zero-string', name: '0', prefers: 0 },
{ id: 'zero-number', name: '0', prefers: '0' },
{ id: 'valid-value', name: '1', prefers: 1 },
],
artists: [{ id: 0 }, { id: 1 }],
};

const NullishValuesDatagridRow = props => {
const { record, id } = props;
return (
<RecordContextProvider value={record}>
<TableRow>
<TableCell>
<span>
<b>Fan #{record.id}</b>
<br />
<code>{record.name}</code> [
<code>{typeof record.prefers}</code>]
</span>
</TableCell>
<TableCell>
<SimpleForm toolbar={<></>}>
<AutocompleteInput
id={`prefers_${id}`}
label={`prefers_${id}`}
fullWidth
source="prefers"
optionText={option => option.id}
choices={nullishValuesFakeData.artists}
helperText={false}
/>
</SimpleForm>
</TableCell>
</TableRow>
</RecordContextProvider>
);
};
const NullishValuesDatagridBody = props => (
<DatagridBody {...props} row={<NullishValuesDatagridRow />} />
);

export const NullishValuesHandling = () => {
return (
<AdminContext
dataProvider={fakeRestProvider(nullishValuesFakeData, false)}
>
<Typography variant="h6" gutterBottom>
Test nullish values
</Typography>
<Typography variant="body2">
Story demonstrates the handling of nullish values ; here listed
fans specify a prefered artist. <code>prefer</code> value is
evaluated again artists IDs.
</Typography>
<List resource="fans" actions={<></>}>
<Datagrid
body={<NullishValuesDatagridBody />}
bulkActionButtons={false}
>
<></>
</Datagrid>
</List>
</AdminContext>
);
};
16 changes: 13 additions & 3 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,10 @@ If you provided a React element for the optionText prop, you must also provide t
]);

const isOptionEqualToValue = (option, value) => {
return getChoiceValue(option) === getChoiceValue(value);
return (
getChoiceValue(option).toString() ===
getChoiceValue(value).toString()
);
};

return (
Expand Down Expand Up @@ -740,11 +743,18 @@ const getSelectedItems = (
if (multiple) {
return (value || [])
.map(item =>
choices.find(choice => item === get(choice, optionValue))
choices.find(
choice =>
item?.toString() === get(choice, optionValue).toString()
)
)
.filter(item => !!item);
}
return choices.find(choice => get(choice, optionValue) === value) || '';
return (
choices.find(
choice => get(choice, optionValue).toString() === value.toString()
) || ''
);
};

const DefaultFilterToQuery = searchText => ({ q: searchText });
77 changes: 65 additions & 12 deletions packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import expect from 'expect';
import {
render,
screen,
Expand All @@ -21,6 +22,7 @@ import { DatagridInput } from './DatagridInput';
import { TextField } from '../field';
import { ReferenceArrayInput } from './ReferenceArrayInput';
import { SelectArrayInput } from './SelectArrayInput';
import { HandlingTypesDiscrepencies } from './ReferenceArrayInput.stories';

describe('<ReferenceArrayInput />', () => {
const defaultProps = {
Expand Down Expand Up @@ -85,7 +87,10 @@ describe('<ReferenceArrayInput />', () => {
const dataProvider = testDataProvider({
getList: () =>
// @ts-ignore
Promise.resolve({ data: [{ id: 1 }, { id: 2 }], total: 2 }),
Promise.resolve({
data: [{ id: 1 }, { id: 2 }],
total: 2,
}),
});
render(
<AdminContext dataProvider={dataProvider}>
Expand Down Expand Up @@ -187,32 +192,32 @@ describe('<ReferenceArrayInput />', () => {
.querySelector('input');

await waitFor(() => {
expect(getCheckbox1().checked).toEqual(true);
expect(getCheckbox2().checked).toEqual(false);
expect(getCheckbox1()?.checked).toEqual(true);
expect(getCheckbox2()?.checked).toEqual(false);
});

fireEvent.click(getCheckbox2());

await waitFor(() => {
expect(getCheckbox1().checked).toEqual(true);
expect(getCheckbox2().checked).toEqual(true);
expect(getCheckboxAll().checked).toEqual(true);
expect(getCheckbox1()?.checked).toEqual(true);
expect(getCheckbox2()?.checked).toEqual(true);
expect(getCheckboxAll()?.checked).toEqual(true);
});

fireEvent.click(getCheckboxAll());

await waitFor(() => {
expect(getCheckbox1().checked).toEqual(false);
expect(getCheckbox2().checked).toEqual(false);
expect(getCheckboxAll().checked).toEqual(false);
expect(getCheckbox1()?.checked).toEqual(false);
expect(getCheckbox2()?.checked).toEqual(false);
expect(getCheckboxAll()?.checked).toEqual(false);
});

fireEvent.click(getCheckboxAll());

await waitFor(() => {
expect(getCheckbox1().checked).toEqual(true);
expect(getCheckbox2().checked).toEqual(true);
expect(getCheckboxAll().checked).toEqual(true);
expect(getCheckbox1()?.checked).toEqual(true);
expect(getCheckbox2()?.checked).toEqual(true);
expect(getCheckboxAll()?.checked).toEqual(true);
});
});

Expand Down Expand Up @@ -244,4 +249,52 @@ describe('<ReferenceArrayInput />', () => {
});
});
});

it('should show selected values when ids type are inconsistant', async () => {
render(<HandlingTypesDiscrepencies />);
await waitFor(() => {
expect(
screen.queryByText('#1', {
selector: 'div.MuiChip-root .MuiChip-label',
})
).not.toBeNull();
});
expect(
screen.queryByText('#2', {
selector: 'div.MuiChip-root .MuiChip-label',
})
).not.toBeNull();
expect(
screen.queryByText('artist_3', { selector: 'div.MuiChip-root' })
).toBeNull();
});

it('should unselect a value whose id type is inconsistant', async () => {
render(<HandlingTypesDiscrepencies />);

const unselect = (queriedText: string) => () => {
const chip = screen.queryByText(queriedText, {
selector: '.MuiChip-label',
})?.nextSibling;
fireEvent.click(chip);
};

await waitFor(unselect('#1'));
await waitFor(unselect('#2'));

await waitFor(() => {
expect(
screen.queryByText('#1', {
selector: '.MuiChip-label',
})
).toBeNull();
});
await waitFor(() => {
expect(
screen.queryByText('#2', {
selector: '.MuiChip-label',
})
).toBeNull();
});
});
});
Loading