Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Children, isValidElement, useRef, type ReactNode } from 'react';
import set from 'lodash/set.js';
import { FormDataConsumer } from '../../form/FormDataConsumer';
import type { ArrayInputContextValue } from './ArrayInputContext';
import { useEvent } from '../../util';
Expand Down Expand Up @@ -31,15 +32,18 @@ export const useGetArrayInputNewItemDefaults = (

// ArrayInput used for an array of objects
// (e.g. authors: [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Doe' }])
const defaultValue = initialDefaultValue.current;
const defaultValue = { ...initialDefaultValue.current };
Children.forEach(inputs, input => {
if (
isValidElement(input) &&
input.type !== FormDataConsumer &&
input.props.source
) {
defaultValue[input.props.source] =
input.props.defaultValue ?? null;
set(
defaultValue,
input.props.source,
input.props.defaultValue ?? null
);
}
});
return defaultValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,197 @@ describe('<SimpleFormIterator />', () => {
expect(screen.queryAllByLabelText('ra.action.remove').length).toBe(1);
});

it('should not reuse removed values for nested sources when adding a new item', async () => {
render(
<Wrapper>
<SimpleForm
record={{
id: 1,
venueList: [
{
venue: 'Madison Square Garden, New York',
details: {
stageManagerId: '101',
ticketTier: null,
language: null,
},
},
{
venue: 'Wembley Stadium, London',
details: {
stageManagerId: '102',
ticketTier: 'premium',
language: 'en',
},
},
{
venue: 'Tokyo Dome, Tokyo',
details: {
stageManagerId: '103',
ticketTier: 'vip',
language: 'ja',
},
},
],
}}
>
<ArrayInput source="venueList">
<SimpleFormIterator>
<TextInput source="venue" label="Venue" />
<TextInput
source="details.stageManagerId"
label="Stage Manager ID"
/>
<TextInput
source="details.ticketTier"
label="Ticket Tier"
/>
<TextInput
source="details.language"
label="Language"
/>
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Wrapper>
);

await waitFor(() => {
expect(
screen
.queryAllByLabelText('Stage Manager ID')
.map(
inputElement => (inputElement as HTMLInputElement).value
)
).toEqual(['101', '102', '103']);
});

const lastItem = screen
.queryAllByLabelText('Venue')[2]
.closest('li') as HTMLElement;
const removeLastButton = getByLabelText(
lastItem,
'ra.action.remove'
).closest('button') as HTMLButtonElement;

fireEvent.click(removeLastButton);
await waitFor(() => {
expect(screen.queryAllByLabelText('Venue').length).toEqual(2);
});

fireEvent.click(
screen
.getByLabelText('ra.action.add')
.closest('button') as HTMLButtonElement
);

await waitFor(() => {
expect(screen.queryAllByLabelText('Venue').length).toEqual(3);
});

expect(
screen
.queryAllByLabelText('Stage Manager ID')
.map(inputElement => (inputElement as HTMLInputElement).value)
).toEqual(['101', '102', '']);
expect(
screen
.queryAllByLabelText('Ticket Tier')
.map(inputElement => (inputElement as HTMLInputElement).value)
).toEqual(['', 'premium', '']);
expect(
screen
.queryAllByLabelText('Language')
.map(inputElement => (inputElement as HTMLInputElement).value)
).toEqual(['', 'en', '']);
});

it('should create nested null defaults for nested sources when adding a new item', async () => {
const save = jest.fn();

render(
<Wrapper>
<SimpleForm
onSubmit={save}
record={{
id: 1,
venueList: [
{
venue: 'Tokyo Dome, Tokyo',
details: {
stageManagerId: '103',
ticketTier: 'vip',
language: 'ja',
},
},
],
}}
>
<ArrayInput source="venueList">
<SimpleFormIterator>
<TextInput source="venue" label="Venue" />
<TextInput
source="details.stageManagerId"
label="Stage Manager ID"
/>
<TextInput
source="details.ticketTier"
label="Ticket Tier"
/>
<TextInput
source="details.language"
label="Language"
/>
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Wrapper>
);

const firstItem = screen
.queryAllByLabelText('Venue')[0]
.closest('li') as HTMLElement;
const removeFirstButton = getByLabelText(
firstItem,
'ra.action.remove'
).closest('button') as HTMLButtonElement;

fireEvent.click(removeFirstButton);
await waitFor(() => {
expect(screen.queryAllByLabelText('Venue').length).toEqual(0);
});

fireEvent.click(
screen
.getByLabelText('ra.action.add')
.closest('button') as HTMLButtonElement
);

await waitFor(() => {
expect(screen.queryAllByLabelText('Venue').length).toEqual(1);
});

fireEvent.click(screen.getByText('ra.action.save'));

await waitFor(() => {
expect(save).toHaveBeenCalled();
});

expect(save.mock.calls[0][0]).toEqual({
id: 1,
venueList: [
{
venue: null,
details: {
stageManagerId: null,
ticketTier: null,
language: null,
},
},
],
});
});

it('should remove children row on remove button click', async () => {
const emails = [{ email: 'foo@bar.com' }, { email: 'bar@foo.com' }];

Expand Down
Loading