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: incorrect format of search query string #58362

Merged
merged 3 commits into from
Mar 27, 2025
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
2 changes: 1 addition & 1 deletion src/libs/SearchQueryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial<SearchAdvanc
const amountFilter = buildAmountFilterQuery(filterValues);
filtersString.push(amountFilter);

return filtersString.join(' ').trim();
return filtersString.filter(Boolean).join(' ').trim();
}

/**
Expand Down
146 changes: 120 additions & 26 deletions tests/unit/Search/SearchQueryUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
// we need "dirty" object key names in these tests
import {getQueryWithUpdatedValues} from '@src/libs/SearchQueryUtils';
import {buildQueryStringFromFilterFormValues, getQueryWithUpdatedValues} from '@src/libs/SearchQueryUtils';
import type {SearchAdvancedFiltersForm} from '@src/types/form';

const personalDetailsFakeData = {
'johndoe@example.com': {
Expand All @@ -23,44 +24,137 @@ jest.mock('@libs/PersonalDetailsUtils', () => {
// We don't want to test or mock the grammar and the parser, so we're simply defining this string directly here.
const defaultQuery = `type:expense status:all sortBy:date sortOrder:desc`;

describe('getQueryWithUpdatedValues', () => {
test('returns default query for empty value', () => {
const userQuery = '';
describe('SearchQueryUtils', () => {
describe('getQueryWithUpdatedValues', () => {
test('returns default query for empty value', () => {
const userQuery = '';

const result = getQueryWithUpdatedValues(userQuery);
const result = getQueryWithUpdatedValues(userQuery);

expect(result).toEqual(defaultQuery);
});
expect(result).toEqual(defaultQuery);
});

test('returns query with updated amounts', () => {
const userQuery = 'foo test amount:20000';
test('returns query with updated amounts', () => {
const userQuery = 'foo test amount:20000';

const result = getQueryWithUpdatedValues(userQuery);
const result = getQueryWithUpdatedValues(userQuery);

expect(result).toEqual(`${defaultQuery} amount:2000000 foo test`);
});
expect(result).toEqual(`${defaultQuery} amount:2000000 foo test`);
});

test('returns query with user emails substituted', () => {
const userQuery = 'from:johndoe@example.com hello';
test('returns query with user emails substituted', () => {
const userQuery = 'from:johndoe@example.com hello';

const result = getQueryWithUpdatedValues(userQuery);
const result = getQueryWithUpdatedValues(userQuery);

expect(result).toEqual(`${defaultQuery} from:12345 hello`);
});
expect(result).toEqual(`${defaultQuery} from:12345 hello`);
});

test('returns query with user emails substituted and preserves user ids', () => {
const userQuery = 'from:johndoe@example.com to:112233';
test('returns query with user emails substituted and preserves user ids', () => {
const userQuery = 'from:johndoe@example.com to:112233';

const result = getQueryWithUpdatedValues(userQuery);
const result = getQueryWithUpdatedValues(userQuery);

expect(result).toEqual(`${defaultQuery} from:12345 to:112233`);
});
expect(result).toEqual(`${defaultQuery} from:12345 to:112233`);
});

test('returns query with all of the fields correctly substituted', () => {
const userQuery = 'from:9876,87654 to:janedoe@example.com hello amount:150 test';

test('returns query with all of the fields correctly substituted', () => {
const userQuery = 'from:9876,87654 to:janedoe@example.com hello amount:150 test';
const result = getQueryWithUpdatedValues(userQuery);

const result = getQueryWithUpdatedValues(userQuery);
expect(result).toEqual(`${defaultQuery} from:9876,87654 to:78901 amount:15000 hello test`);
});
});

expect(result).toEqual(`${defaultQuery} from:9876,87654 to:78901 amount:15000 hello test`);
describe('buildQueryStringFromFilterFormValues', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

@daledah Can you add another test case: should filter out empty values so the query contains no redundant spaces

Copy link
Contributor

Choose a reason for hiding this comment

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

And another case: should trim redundant zero decimals from amount

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think empty values are filtered out before the logics are called, so we need to test a different function here.
Zero decimals IMO are not trimmed by default, as we use cents in currency values.

test('simple filter value', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {
type: 'expense',
status: 'all',
policyID: '12345',
lessThan: '100',
};

const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual('sortBy:date sortOrder:desc type:expense status:all policyID:12345 amount<100');
});

test('with Policy ID', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {
policyID: '12345',
};

const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual('sortBy:date sortOrder:desc policyID:12345');
});

test('with keywords', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {
type: 'expense',
status: 'all',
policyID: '67890',
merchant: 'Amazon',
description: 'Electronics',
keyword: 'laptop',
category: ['electronics', 'gadgets'],
};

const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual('sortBy:date sortOrder:desc type:expense status:all policyID:67890 merchant:Amazon description:Electronics laptop category:electronics,gadgets');
});

test('currencies and categories', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {
type: 'expense',
status: 'all',
category: ['services', 'consulting'],
currency: ['USD', 'EUR'],
};

const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual('sortBy:date sortOrder:desc type:expense status:all category:services,consulting currency:USD,EUR');
});

test('empty filter values', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {};

const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual('sortBy:date sortOrder:desc');
});

test('array of from', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {
type: 'expense',
from: ['user1@gmail.com', 'user2@gmail.com'],
to: ['user3@gmail.com'],
};
const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual('sortBy:date sortOrder:desc type:expense from:user1@gmail.com,user2@gmail.com to:user3@gmail.com');
});

test('complex filter values', () => {
const filterValues: Partial<SearchAdvancedFiltersForm> = {
type: 'expense',
from: ['user1@gmail.com', 'user2@gmail.com'],
to: ['user3@gmail.com'],
dateAfter: '2025-03-01',
dateBefore: '2025-03-10',
lessThan: '1000',
greaterThan: '1',
category: ['finance', 'insurance'],
};
const result = buildQueryStringFromFilterFormValues(filterValues);

expect(result).toEqual(
'sortBy:date sortOrder:desc type:expense from:user1@gmail.com,user2@gmail.com to:user3@gmail.com category:finance,insurance date<2025-03-10 date>2025-03-01 amount>1 amount<1000',
);
});
});
});