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
1 change: 1 addition & 0 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ function Search({
similarSearchHash={similarSearchHash}
type={type}
hasResults={searchResults?.search?.hasResults}
queryJSON={queryJSON}
/>
</View>
);
Expand Down
6 changes: 6 additions & 0 deletions src/libs/SearchQueryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@ function getFilterDisplayValue(
return getCardDescription(cardList?.[cardID]) || filterValue;
}
if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return getReportName(reports?.[`${ONYXKEYS.COLLECTION.REPORT}${filterValue}`]) || filterValue;
}
if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT || filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TOTAL || filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.PURCHASE_AMOUNT) {
Expand Down Expand Up @@ -1288,6 +1289,10 @@ function isDefaultExpensesQuery(queryJSON: SearchQueryJSON) {
return queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE && !queryJSON.status && !queryJSON.filters && !queryJSON.groupBy && !queryJSON.policyID;
}

function isDefaultExpenseReportsQuery(queryJSON: SearchQueryJSON) {
return queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT && !queryJSON.status && !queryJSON.filters && !queryJSON.groupBy && !queryJSON.policyID;
}

/**
* Always show `No category` and `No tag` as the first option
*/
Expand Down Expand Up @@ -1424,6 +1429,7 @@ export {
getCurrentSearchQueryJSON,
getQueryWithoutFilters,
isDefaultExpensesQuery,
isDefaultExpenseReportsQuery,
sortOptionsWithEmptyValue,
shouldHighlight,
getAllPolicyValues,
Expand Down
15 changes: 10 additions & 5 deletions src/pages/Search/EmptySearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import MenuItem from '@components/MenuItem';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
import ScrollView from '@components/ScrollView';
import {SearchScopeProvider} from '@components/Search/SearchScopeProvider';
import type {SearchQueryJSON} from '@components/Search/types';
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
Expand All @@ -39,6 +40,7 @@ import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import Navigation from '@libs/Navigation/Navigation';
import {areAllGroupPoliciesExpenseChatDisabled, getDefaultChatEnabledPolicy, getGroupPaidPoliciesWithExpenseChatEnabled} from '@libs/PolicyUtils';
import {generateReportID, hasEmptyReportsForPolicy, hasViolations as hasViolationsReportUtils, reportSummariesOnyxSelector} from '@libs/ReportUtils';
import {isDefaultExpenseReportsQuery, isDefaultExpensesQuery} from '@libs/SearchQueryUtils';
import type {SearchTypeMenuSection} from '@libs/SearchUIUtils';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
import {showContextMenu} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
Expand All @@ -54,6 +56,7 @@ type EmptySearchViewProps = {
similarSearchHash: number;
type: SearchDataTypes;
hasResults: boolean;
queryJSON?: SearchQueryJSON;
};

type EmptySearchViewContentProps = EmptySearchViewProps & {
Expand Down Expand Up @@ -83,7 +86,7 @@ type EmptySearchViewItem = {

type ReportSummary = ReturnType<typeof reportSummariesOnyxSelector>[number];

function EmptySearchView({similarSearchHash, type, hasResults}: EmptySearchViewProps) {
function EmptySearchView({similarSearchHash, type, hasResults, queryJSON}: EmptySearchViewProps) {
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const {typeMenuSections, CreateReportConfirmationModal: SearchMenuCreateReportConfirmationModal} = useSearchTypeMenuSections();

Expand Down Expand Up @@ -117,6 +120,7 @@ function EmptySearchView({similarSearchHash, type, hasResults}: EmptySearchViewP
introSelected={introSelected}
hasSeenTour={hasSeenTour}
searchMenuCreateReportConfirmationModal={SearchMenuCreateReportConfirmationModal}
queryJSON={queryJSON}
/>
</SearchScopeProvider>
);
Expand All @@ -126,8 +130,7 @@ const hasTransactionsSelector = (transactions: OnyxCollection<Transaction>) =>
Object.values(transactions ?? {}).filter((transaction) => transaction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length > 0;

const hasExpenseReportsSelector = (reports: OnyxCollection<Report>) =>
Object.values(reports ?? {}).filter((report) => report?.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT && report?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length >
0;
Object.values(reports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.EXPENSE && report?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length > 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why need to change this? It doesn't seem right to me, shouldn't check EXPENSE_REPORT here as the method name suggest?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When I saw the check report?.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, I was quite confused, so I reviewed all instances of report?.type both in the actual data and in the type definitions. After checking, I believe that using CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT here was a mistake, so I have corrected it.


function EmptySearchViewContent({
similarSearchHash,
Expand All @@ -142,6 +145,7 @@ function EmptySearchViewContent({
introSelected,
hasSeenTour,
searchMenuCreateReportConfirmationModal,
queryJSON,
}: EmptySearchViewContentProps) {
const theme = useTheme();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -339,7 +343,7 @@ function EmptySearchViewContent({
lottieWebViewStyles: {backgroundColor: theme.travelBG, ...styles.emptyStateFolderWebStyles, ...styles.tripEmptyStateLottieWebView},
};
case CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT:
if (hasResults) {
if (hasResults && (!queryJSON || !isDefaultExpenseReportsQuery(queryJSON) || hasExpenseReports)) {
return {
...defaultViewItemHeader,
title: translate('search.searchResults.emptyResults.title'),
Expand Down Expand Up @@ -393,7 +397,7 @@ function EmptySearchViewContent({
}
// eslint-disable-next-line no-fallthrough
case CONST.SEARCH.DATA_TYPES.EXPENSE:
if (hasResults) {
if (hasResults && (!queryJSON || !isDefaultExpensesQuery(queryJSON) || hasTransactions)) {
return {
...defaultViewItemHeader,
title: translate('search.searchResults.emptyResults.title'),
Expand Down Expand Up @@ -495,6 +499,7 @@ function EmptySearchViewContent({
hasExpenseReports,
defaultChatEnabledPolicyID,
handleCreateReportClick,
queryJSON,
]);

return (
Expand Down
62 changes: 62 additions & 0 deletions tests/ui/components/EmptySearchViewTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,68 @@ describe('EmptySearchView', () => {
expect(screen.queryByText(translateLocal('report.newReport.createExpense'))).not.toBeOnTheScreen();
});
});

it('should show "emptyExpenseResults" when the user has deleted all expenses, even though hasResults remains true', async () => {
const policy = createPaidGroupPolicy();
await act(async () => {
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);
});

// Given: A query string for expense search
const queryString = buildQueryStringFromFilterFormValues({
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
});
const queryJSON = buildSearchQueryJSON(queryString);

// When rendering the EmptySearchView component
render(
<Wrapper>
<EmptySearchView
similarSearchHash={queryJSON?.similarSearchHash ?? 1}
type={dataType}
hasResults
queryJSON={queryJSON}
/>
</Wrapper>,
);

await waitForBatchedUpdatesWithAct();

expect(screen.getByText(translateLocal('search.searchResults.emptyExpenseResults.title'))).toBeVisible();
});
});

describe('type is expense Report', () => {
const dataType = CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT;

it('should show "emptyReportResults" when the user has deleted all expenses, even though hasResults remains true', async () => {
const policy = createPaidGroupPolicy();
await act(async () => {
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);
});

// Given: A query string for expense report search
const queryString = buildQueryStringFromFilterFormValues({
type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
});
const queryJSON = buildSearchQueryJSON(queryString);

// When rendering the EmptySearchView component
render(
<Wrapper>
<EmptySearchView
similarSearchHash={queryJSON?.similarSearchHash ?? 1}
type={dataType}
hasResults
queryJSON={queryJSON}
/>
</Wrapper>,
);

await waitForBatchedUpdatesWithAct();

expect(screen.getByText(translateLocal('search.searchResults.emptyReportResults.title'))).toBeVisible();
});
});

describe('type is Invoice', () => {
Expand Down
Loading