Skip to content

Commit

Permalink
[DataGrid] Fix support for singleSelect with numeric values (#2112)
Browse files Browse the repository at this point in the history
  • Loading branch information
m4theushw committed Jul 23, 2021
1 parent 42d2bd4 commit f1538f0
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 128 deletions.
2 changes: 1 addition & 1 deletion docs/pages/api-docs/data-grid/grid-col-def.md
Expand Up @@ -40,6 +40,6 @@ import { GridColDef } from '@material-ui/data-grid';
| <span class="prop-name optional">type<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">string</span> | <span class="prop-default">'string'<br /></span> | Type allows to merge this object with a default definition [GridColDef](/api/data-grid/grid-col-def/). |
| <span class="prop-name optional">valueFormatter<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">(params: GridValueFormatterParams) =&gt; GridCellValue</span> | | Function that allows to apply a formatter before rendering its value. |
| <span class="prop-name optional">valueGetter<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">(params: GridValueGetterParams) =&gt; GridCellValue</span> | | Function that allows to get a specific data instead of field to render in the cell. |
| <span class="prop-name optional">valueOptions<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">(string \| { label: string; value: any })[]</span> | | To be used in combination with `type: 'singleSelect'`. This is an array of the possible cell values and labels. |
| <span class="prop-name optional">valueOptions<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">(string \| number \| { label: string; value: any })[]</span> | | To be used in combination with `type: 'singleSelect'`. This is an array of the possible cell values and labels. |
| <span class="prop-name optional">valueParser<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">(value: GridCellValue, params?: GridCellParams) =&gt; GridCellValue</span> | | Function that takes the user-entered value and converts it to a value used internally. |
| <span class="prop-name optional">width<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">number</span> | <span class="prop-default">100<br /></span> | Set the width of the column. |
Expand Up @@ -5,14 +5,14 @@ import { GridCellParams } from '../../models/params/gridCellParams';
import { isEscapeKey } from '../../utils/keyboardUtils';

const renderSingleSelectOptions = (option) =>
typeof option === 'string' ? (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
) : (
typeof option === 'object' ? (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
) : (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
);

export function GridEditSingleSelectCell(props: GridCellParams & SelectProps) {
Expand Down
Expand Up @@ -8,14 +8,14 @@ import { GridColDef } from '../../../models/colDef/gridColDef';

const renderSingleSelectOptions = ({ valueOptions }: GridColDef) =>
['', ...valueOptions!].map((option) =>
typeof option === 'string' ? (
<option key={option} value={option}>
{option}
</option>
) : (
typeof option === 'object' ? (
<option key={option.value} value={option.value}>
{option.label}
</option>
) : (
<option key={option} value={option}>
{option}
</option>
),
);

Expand Down Expand Up @@ -44,16 +44,24 @@ export function GridFilterInputValue(props: GridTypeFilterInputValueProps & Text

const onFilterChange = React.useCallback(
(event) => {
let value = event.target.value;
// NativeSelect casts the value to a string.
if (type === 'singleSelect') {
const column = apiRef.current.getColumn(item.columnField);
value = column.valueOptions
.map((option) => (typeof option === 'object' ? option.value : option))
.find((optionValue) => String(optionValue) === value);
}

clearTimeout(filterTimeout.current);
const value = event.target.value;
setFilterValueState(value);
setIsApplying(true);
filterTimeout.current = setTimeout(() => {
applyValue({ ...item, value });
setIsApplying(false);
}, SUBMIT_FILTER_STROKE_TIME);
},
[applyValue, item],
[apiRef, applyValue, item, type],
);

React.useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/grid/_modules_/grid/models/colDef/gridColDef.ts
Expand Up @@ -79,7 +79,7 @@ export interface GridColDef {
/**
* To be used in combination with `type: 'singleSelect'`. This is an array of the possible cell values and labels.
*/
valueOptions?: Array<string | { value: any; label: string }>;
valueOptions?: Array<string | number | { value: any; label: string }>;
/**
* Allows to align the column values in cells.
*/
Expand Down
Expand Up @@ -6,13 +6,14 @@ export const getGridSingleSelectOperators: () => GridFilterOperator[] = () => [
{
value: 'is',
getApplyFilterFn: (filterItem: GridFilterItem) => {
if (!filterItem.columnField || !filterItem.value || !filterItem.operatorValue) {
if (filterItem.value == null || filterItem.value === '') {
return null;
}
return ({ value }): boolean => {
return typeof value === 'string'
? filterItem.value === value
: filterItem.value === (value as { value: any; label: string }).value;
if (typeof value === 'object') {
return filterItem.value === (value as { value: any; label: string }).value;
}
return filterItem.value === value;
};
},
InputComponent: GridFilterInputValue,
Expand All @@ -21,14 +22,14 @@ export const getGridSingleSelectOperators: () => GridFilterOperator[] = () => [
{
value: 'not',
getApplyFilterFn: (filterItem: GridFilterItem) => {
if (!filterItem.columnField || !filterItem.value || !filterItem.operatorValue) {
if (filterItem.value == null || filterItem.value === '') {
return null;
}

return ({ value }): boolean => {
return typeof value === 'string'
? filterItem.value !== value
: filterItem.value !== (value as { value: any; label: string }).value;
if (typeof value === 'object') {
return filterItem.value !== (value as { value: any; label: string }).value;
}
return filterItem.value !== value;
};
},
InputComponent: GridFilterInputValue,
Expand Down
205 changes: 168 additions & 37 deletions packages/grid/data-grid/src/tests/filtering.DataGrid.test.tsx
Expand Up @@ -2,9 +2,17 @@ import * as React from 'react';
import {
createClientRenderStrictMode, // @ts-expect-error need to migrate helpers to TypeScript
screen,
// @ts-expect-error need to migrate helpers to TypeScript
fireEvent,
} from 'test/utils';
import { expect } from 'chai';
import { DataGrid, GridToolbar } from '@material-ui/data-grid';
import { useFakeTimers } from 'sinon';
import {
DataGrid,
GridToolbar,
GridPreferencePanelsValue,
GridLinkOperator,
} from '@material-ui/data-grid';
import { getColumnValues } from 'test/utils/helperFn';

const isJSDOM = /jsdom/.test(window.navigator.userAgent);
Expand All @@ -21,18 +29,21 @@ describe('<DataGrid /> - Filter', () => {
brand: 'Nike',
isPublished: false,
country: 'United States',
status: 0,
},
{
id: 1,
brand: 'Adidas',
isPublished: true,
country: 'Germany',
status: 0,
},
{
id: 2,
brand: 'Puma',
isPublished: true,
country: 'Germany',
status: 2,
},
],
columns: [
Expand All @@ -43,6 +54,15 @@ describe('<DataGrid /> - Filter', () => {
type: 'singleSelect',
valueOptions: ['United States', 'Germany', 'France'],
},
{
field: 'status',
type: 'singleSelect',
valueOptions: [
{ value: 0, label: 'Payment Pending' },
{ value: 1, label: 'Shipped' },
{ value: 2, label: 'Delivered' },
],
},
],
};

Expand All @@ -52,8 +72,9 @@ describe('<DataGrid /> - Filter', () => {
operatorValue?: string;
value?: any;
field?: string;
state?: any;
}) => {
const { operatorValue, value, rows, columns, field = 'brand' } = props;
const { operatorValue, value, rows, columns, field = 'brand', ...other } = props;
return (
<div style={{ width: 300, height: 300 }}>
<DataGrid
Expand All @@ -70,6 +91,7 @@ describe('<DataGrid /> - Filter', () => {
],
}}
disableColumnFilter={false}
{...other}
/>
</div>
);
Expand Down Expand Up @@ -638,49 +660,158 @@ describe('<DataGrid /> - Filter', () => {
});
});

describe('select operators', () => {
it('should allow operator is', () => {
const { setProps } = render(<TestCase value="a" operatorValue="contains" />);
setProps({
field: 'country',
operatorValue: 'is',
value: 'United States',
describe('singleSelect operators', () => {
let clock;

beforeEach(() => {
clock = useFakeTimers();
});

afterEach(() => {
clock.restore();
});

describe('simple options', () => {
it('should allow operator is', () => {
const { setProps } = render(<TestCase value="a" operatorValue="contains" />);
setProps({
field: 'country',
operatorValue: 'is',
value: 'United States',
});
expect(getColumnValues()).to.deep.equal(['Nike']);
setProps({
field: 'country',
operatorValue: 'is',
value: 'Germany',
});
expect(getColumnValues()).to.deep.equal(['Adidas', 'Puma']);
setProps({
field: 'country',
operatorValue: 'is',
value: '',
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
});
expect(getColumnValues()).to.deep.equal(['Nike']);
setProps({
field: 'country',
operatorValue: 'is',
value: 'Germany',

it('should allow operator not', () => {
const { setProps } = render(<TestCase value="a" operatorValue="contains" />);
setProps({
field: 'country',
operatorValue: 'not',
value: 'United States',
});
expect(getColumnValues()).to.deep.equal(['Adidas', 'Puma']);
setProps({
field: 'country',
operatorValue: 'not',
value: 'Germany',
});
expect(getColumnValues()).to.deep.equal(['Nike']);
setProps({
field: 'country',
operatorValue: 'not',
value: '',
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
});
expect(getColumnValues()).to.deep.equal(['Adidas', 'Puma']);
setProps({
field: 'country',
operatorValue: 'is',
value: '',

it('should work with numeric values', () => {
render(
<TestCase
rows={[
{ id: 1, name: 'Hair Dryer', voltage: 220 },
{ id: 2, name: 'Dishwasher', voltage: 110 },
{ id: 3, name: 'Microwave', voltage: 220 },
]}
columns={[
{ field: 'name' },
{ field: 'voltage', type: 'singleSelect', valueOptions: [220, 110] },
]}
state={{
preferencePanel: {
open: true,
openedPanelValue: GridPreferencePanelsValue.filters,
},
filter: {
items: [{ id: 123, columnField: 'voltage', value: '', operatorValue: 'is' }],
linkOperator: GridLinkOperator.And,
},
}}
/>,
);
expect(getColumnValues()).to.deep.equal(['Hair Dryer', 'Dishwasher', 'Microwave']);
fireEvent.change(screen.getByLabelText('Value'), { target: { value: '220' } });
clock.tick(600); // Wait for the debounce
expect(getColumnValues()).to.deep.equal(['Hair Dryer', 'Microwave']);
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
});

it('should allow operator not', () => {
const { setProps } = render(<TestCase value="a" operatorValue="contains" />);
setProps({
field: 'country',
operatorValue: 'not',
value: 'United States',
describe('complex options', () => {
it('should allow operator is', () => {
const { setProps } = render(<TestCase value="a" operatorValue="contains" />);
setProps({
field: 'status',
operatorValue: 'is',
value: 0,
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas']);
setProps({
field: 'status',
operatorValue: 'is',
value: 2,
});
expect(getColumnValues()).to.deep.equal(['Puma']);
setProps({
field: 'status',
operatorValue: 'is',
value: '',
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
});
expect(getColumnValues()).to.deep.equal(['Adidas', 'Puma']);
setProps({
field: 'country',
operatorValue: 'not',
value: 'Germany',

it('should allow operator not', () => {
const { setProps } = render(<TestCase value="a" operatorValue="contains" />);
setProps({
field: 'status',
operatorValue: 'not',
value: 0,
});
expect(getColumnValues()).to.deep.equal(['Puma']);
setProps({
field: 'status',
operatorValue: 'not',
value: 2,
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas']);
setProps({
field: 'status',
operatorValue: 'not',
value: '',
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
});
expect(getColumnValues()).to.deep.equal(['Nike']);
setProps({
field: 'country',
operatorValue: 'not',
value: '',

it('should work with numeric values', () => {
render(
<TestCase
state={{
preferencePanel: {
open: true,
openedPanelValue: GridPreferencePanelsValue.filters,
},
filter: {
items: [{ id: 123, columnField: 'status', value: '', operatorValue: 'is' }],
linkOperator: GridLinkOperator.And,
},
}}
/>,
);
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
fireEvent.change(screen.getByLabelText('Value'), { target: { value: '2' } });
clock.tick(600);
expect(getColumnValues()).to.deep.equal(['Puma']);
});
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
});
});

Expand Down

0 comments on commit f1538f0

Please sign in to comment.