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(filters): filters with != (not equal empty) should return non-blanks #1570

Merged
merged 1 commit into from
Jun 12, 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
7 changes: 5 additions & 2 deletions docs/column-functionalities/filters/input-filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ Examples:
- `<02/28/17` => lower than date `02/28/17`
- `2001-01-01..2002-02-22` => range between 2001-01-01 and 2002-02-22
- String type
- `<>John` (anything except the sub-string `John`)
- `<>John` => not containing the sub-string `John`
- `!=John` => not equal to the text `John` (note that this is **not** equivalent to `<>`)
- `John*` => starts with the sub-string `John`
- `*Doe` => ends with the sub-string `Doe`
- `ab..ef` => anything included between "af" and "ef"
- refer to ASCII table for each character assigned number
- refer to the ASCII table for each character assigned index
- `!= ` => get defined only data and exclude any `undefined`, `null` or empty string `''`
- notice the empty string in the search value `' '`

Note that you could also do the same kind of functionality by using the Compound Filter.

Expand Down
4 changes: 3 additions & 1 deletion examples/vite-demo-vanilla-bundle/src/examples/example11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,9 @@ export default class Example11 {
cost: (i % 33 === 0) ? null : Math.round(Math.random() * 10000) / 100,
completed: (randomFinish < new Date()),
product: { id: this.mockProducts()[randomItemId]?.id, itemName: this.mockProducts()[randomItemId]?.itemName, },
countryOfOrigin: (i % 2) ? { code: 'CA', name: 'Canada' } : { code: 'US', name: 'United States' },
countryOfOrigin: i % 33
? (i % 2) ? { code: 'CA', name: 'Canada' } : { code: 'US', name: 'United States' }
: null
};

if (!(i % 8)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,35 @@ describe('executeStringFilterCondition method', () => {
expect(output).toBe(true);
});

it('should return False when search term is a substring of the cell value and the operator is "<>" (not contains)', () => {
it('should return False when search term is a substring of the cell value and the operator is "<>" (not contains substring)', () => {
const searchTerms = ['bost'];
const options = { dataKey: '', operator: '<>', cellValue: 'abbostford', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(false);
});

it('should return True when search term is a substring of the cell value and the operator is "!=" (not contains) because "!=" compares agains the entire string', () => {
it('should return True when search term is a substring of the cell value and the operator is "!=" (not equal text) because "!=" compares agains the entire string', () => {
const searchTerms = ['bost'];
const options = { dataKey: '', operator: '!=', cellValue: 'abbostford', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(true);
});

it('should exclude anything undefined when search term is empty string value and the operator is "!=" (not equal text) because "!=" compares agains the entire string', () => {
const searchTerms = [''];
const options1 = { dataKey: '', operator: '!=', cellValue: null, fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const options2 = { dataKey: '', operator: '!=', cellValue: '', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const options3 = { dataKey: '', operator: '!=', cellValue: 'hello world', fieldType: FieldType.string, searchTerms } as FilterConditionOption;

const output1 = executeStringFilterCondition(options1, getFilterParsedText(searchTerms));
const output2 = executeStringFilterCondition(options2, getFilterParsedText(searchTerms));
const output3 = executeStringFilterCondition(options3, getFilterParsedText(searchTerms));

expect(output1).toBe(false);
expect(output2).toBe(false);
expect(output3).toBe(true);
});

it('should return True when input value provided starts with same substring and the operator is empty string', () => {
const searchTerms = ['abb'];
const options = { dataKey: '', operator: '', cellValue: 'abbostford', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
Expand Down Expand Up @@ -171,17 +186,17 @@ describe('executeStringFilterCondition method', () => {
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(false);
});

it('should return True when input value contains accent is searchTerms value does not contain accent when "ignoreAccentOnStringFilterAndSort" is set in grid options', () => {
const searchTerms = ['jose'];
const options = { dataKey: '', operator: 'EQ', cellValue: 'José', fieldType: FieldType.string, ignoreAccentOnStringFilterAndSort: true} as FilterConditionOption;
const options = { dataKey: '', operator: 'EQ', cellValue: 'José', fieldType: FieldType.string, ignoreAccentOnStringFilterAndSort: true } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(true);
});

it('should return False when input value contains accent is searchTerms value does not contain accent when "ignoreAccentOnStringFilterAndSort" is not set in grid options', () => {
const searchTerms = ['jose'];
const options = { dataKey: '', operator: 'EQ', cellValue: 'José', fieldType: FieldType.string, ignoreAccentOnStringFilterAndSort: false} as FilterConditionOption;
const options = { dataKey: '', operator: 'EQ', cellValue: 'José', fieldType: FieldType.string, ignoreAccentOnStringFilterAndSort: false } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(false);
});
Expand Down
15 changes: 15 additions & 0 deletions packages/common/src/services/__tests__/filter.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,21 @@ describe('FilterService', () => {
expect(output).toBe(true);
});

it('should return False, since firstname is filled, when input value from datacontext contains an operator "<>" and its value is an empty string', () => {
const searchTerms = ['<> '];
const mockColumn1 = { id: 'firstName', field: 'firstName', filterable: true } as Column;
jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn1]);

service.init(gridStub);
const columnFilter = { columnDef: mockColumn1, columnId: 'firstName', type: FieldType.string };
const filterCondition = service.parseFormInputFilterConditions(searchTerms, columnFilter);
const parsedSearchTerms = getParsedSearchTermsByFieldType(filterCondition.searchTerms, 'text');
const columnFilters = { firstname: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } } as ColumnFilters;
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });

expect(output).toBe(false);
});

it('should return False when input value from datacontext contains an operator >= and its value is greater than 10 substring but "autoParseInputFilterOperator" is set to false', () => {
const searchTerms = ['>=10'];
const mockColumn1 = { id: 'age', field: 'age', filterable: true, autoParseInputFilterOperator: false } as Column;
Expand Down
6 changes: 5 additions & 1 deletion packages/common/src/services/filter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,11 @@ export class FilterService {
// if search value has a regex match we will only keep the value without the operator
// in this case we need to overwrite the returned search values to truncate operator from the string search
if (Array.isArray(matches) && matches.length >= 1 && (Array.isArray(searchValues) && searchValues.length === 1)) {
searchValues[0] = searchTerm;
// string starts with a whitespace we'll trim only the first whitespace char
// e.g. " " becomes "" and " slick grid " becomes "slick grid " (notice last whitespace is kept)
searchValues[0] = (searchTerm.length > 0 && searchTerm.substring(0, 1) === ' ')
? searchTerm.substring(1)
: searchTerm;
}

return {
Expand Down
Loading
Loading