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

Fix FilterLiveSearch reset button does not reset the value #9149

Merged
merged 2 commits into from
Aug 1, 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
14 changes: 13 additions & 1 deletion docs/useList.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,19 @@ const { data, total } = useList({
// data will be [{ id: 1, name: 'Arnold' }] and total will be 1
```

The filtering capabilities are very limited. For instance, there is no "greater than" or "less than" operator. You can only filter on the equality of a field.
The filtering capabilities are very limited. A filter on a field is a simple string comparison. There is no "greater than" or "less than" operator. You can do a full-text filter by using the `q` filter.

```jsx
const { data, total } = useList({
data: [
{ id: 1, name: 'Arnold' },
{ id: 2, name: 'Sylvester' },
{ id: 3, name: 'Jean-Claude' },
],
filter: { q: 'arno' },
});
// data will be [{ id: 1, name: 'Arnold' }] and total will be 1
```

## `filterCallback`

Expand Down
246 changes: 135 additions & 111 deletions packages/ra-core/src/controller/list/useList.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,70 +21,6 @@ const UseList = ({
};

describe('<useList />', () => {
it('should filter string data based on the filter props', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved these tests further down to group them in the describe('filter') group, I didn't remove or modify them

const callback = jest.fn();
const data = [
{ id: 1, title: 'hello' },
{ id: 2, title: 'world' },
];

render(
<UseList
data={data}
filter={{ title: 'world' }}
sort={{ field: 'id', order: 'ASC' }}
callback={callback}
/>
);

expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [{ id: 2, title: 'world' }],
error: undefined,
total: 1,
})
);
});

it('should filter array data based on the filter props', async () => {
const callback = jest.fn();
const data = [
{ id: 1, items: ['one', 'two'] },
{ id: 2, items: ['three'] },
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
];

render(
<UseList
data={data}
filter={{ items: ['two', 'four', 'five'] }}
sort={{ field: 'id', order: 'ASC' }}
callback={callback}
/>
);

await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [
{ id: 1, items: ['one', 'two'] },
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
],
error: undefined,
total: 3,
})
);
});
});

it('should apply sorting correctly', async () => {
const callback = jest.fn();
const data = [
Expand Down Expand Up @@ -229,66 +165,154 @@ describe('<useList />', () => {
);
});

it('should filter array data based on the custom filter', async () => {
const callback = jest.fn();
const data = [
{ id: 1, items: ['one', 'two'] },
{ id: 2, items: ['three'] },
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
];
describe('filter', () => {
it('should filter string data based on the filter props', () => {
const callback = jest.fn();
const data = [
{ id: 1, title: 'hello' },
{ id: 2, title: 'world' },
];

render(
<UseList
data={data}
sort={{ field: 'id', order: 'ASC' }}
filterCallback={record => record.id > 2}
callback={callback}
/>
);
render(
<UseList
data={data}
filter={{ title: 'world' }}
sort={{ field: 'id', order: 'ASC' }}
callback={callback}
/>
);

await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
],
data: [{ id: 2, title: 'world' }],
error: undefined,
total: 2,
total: 1,
})
);
});
});

it('should filter data based on a custom filter with nested objects', () => {
const callback = jest.fn();
const data = [
{ id: 1, title: { name: 'hello' } },
{ id: 2, title: { name: 'world' } },
];
it('should filter array data based on the filter props', async () => {
const callback = jest.fn();
const data = [
{ id: 1, items: ['one', 'two'] },
{ id: 2, items: ['three'] },
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
];

render(
<UseList
data={data}
filter={{ title: { name: 'world' } }}
sort={{ field: 'id', order: 'ASC' }}
callback={callback}
/>
);
render(
<UseList
data={data}
filter={{ items: ['two', 'four', 'five'] }}
sort={{ field: 'id', order: 'ASC' }}
callback={callback}
/>
);

expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [{ id: 2, title: { name: 'world' } }],
error: undefined,
total: 1,
})
);
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [
{ id: 1, items: ['one', 'two'] },
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
],
error: undefined,
total: 3,
})
);
});
});

it('should filter array data based on the custom filter', async () => {
const callback = jest.fn();
const data = [
{ id: 1, items: ['one', 'two'] },
{ id: 2, items: ['three'] },
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
];

render(
<UseList
data={data}
sort={{ field: 'id', order: 'ASC' }}
filterCallback={record => record.id > 2}
callback={callback}
/>
);

await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [
{ id: 3, items: 'four' },
{ id: 4, items: ['five'] },
],
error: undefined,
total: 2,
})
);
});
});

it('should filter data based on a custom filter with nested objects', () => {
const callback = jest.fn();
const data = [
{ id: 1, title: { name: 'hello' } },
{ id: 2, title: { name: 'world' } },
];

render(
<UseList
data={data}
filter={{ title: { name: 'world' } }}
sort={{ field: 'id', order: 'ASC' }}
callback={callback}
/>
);

expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
sort: { field: 'id', order: 'ASC' },
isFetching: false,
isLoading: false,
data: [{ id: 2, title: { name: 'world' } }],
error: undefined,
total: 1,
})
);
});

it('should apply the q filter as a full-text filter', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only new test

const callback = jest.fn();
const data = [
{ id: 1, title: 'Abc', author: 'Def' }, // matches 'ab'
{ id: 2, title: 'Ghi', author: 'Jkl' }, // does not match 'ab'
{ id: 3, title: 'Mno', author: 'Abc' }, // matches 'ab'
];

render(
<UseList data={data} filter={{ q: 'ab' }} callback={callback} />
);

expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
data: [
{ id: 1, title: 'Abc', author: 'Def' },
{ id: 3, title: 'Mno', author: 'Abc' },
],
})
);
});
});
});
10 changes: 10 additions & 0 deletions packages/ra-core/src/controller/list/useList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ export const useList = <RecordType extends RaRecord = any>(
: recordValue.includes(filterValue)
: Array.isArray(filterValue)
? filterValue.includes(recordValue)
: filterName === 'q' // special full-text filter
? Object.keys(record).some(
key =>
typeof record[key] === 'string' &&
record[key]
.toLowerCase()
.includes(
(filterValue as string).toLowerCase()
)
)
: filterValue == recordValue; // eslint-disable-line eqeqeq
return result;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';

import { Basic } from './FilterLiveSearch.stories';

describe('FilterLiveSearch', () => {
it('renders an empty text input', () => {
render(<Basic />);
expect(
screen.getByPlaceholderText('ra.action.search').getAttribute('type')
).toBe('text');
expect(
screen
.getByPlaceholderText('ra.action.search')
.getAttribute('value')
).toBe('');
});
it('filters the list when typing', () => {
render(<Basic />);
expect(screen.queryAllByRole('listitem')).toHaveLength(27);
fireEvent.change(screen.getByPlaceholderText('ra.action.search'), {
target: { value: 'st' },
});
expect(screen.queryAllByRole('listitem')).toHaveLength(2); // Austria and Estonia
});
it('clears the filter when user click on the reset button', () => {
render(<Basic />);
fireEvent.change(screen.getByPlaceholderText('ra.action.search'), {
target: { value: 'st' },
});
expect(screen.queryAllByRole('listitem')).toHaveLength(2);
fireEvent.click(screen.getByLabelText('ra.action.clear_input_value'));
expect(screen.queryAllByRole('listitem')).toHaveLength(27);
});
});
Loading
Loading