Skip to content
Open
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: 4 additions & 3 deletions src/libs/ReportLayoutUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {Str} from 'expensify-common';
import type {OnyxEntry} from 'react-native-onyx';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import type {GroupedTransactions} from '@src/types/onyx';
Expand Down Expand Up @@ -64,7 +65,7 @@ function groupTransactionsByCategory(transactions: Transaction[], report: OnyxEn

for (const transaction of transactions) {
const category = getCategory(transaction);
const categoryKey = isCategoryMissing(category) ? '' : category;
const categoryKey = isCategoryMissing(category) ? '' : getDecodedCategoryName(category);

if (!groups.has(categoryKey)) {
groups.set(categoryKey, []);
Expand All @@ -75,7 +76,7 @@ function groupTransactionsByCategory(transactions: Transaction[], report: OnyxEn
const result: GroupedTransactions[] = [];
for (const [categoryKey, transactionList] of groups) {
result.push({
groupName: categoryKey ? getDecodedCategoryName(categoryKey) : categoryKey,
groupName: categoryKey,
groupKey: categoryKey,
transactions: transactionList,
subTotalAmount: calculateGroupTotal(transactionList, reportCurrency),
Expand All @@ -99,7 +100,7 @@ function groupTransactionsByTag(transactions: Transaction[], report: OnyxEntry<R

for (const transaction of transactions) {
const tag = getTag(transaction);
const tagKey = isTagMissing(tag) ? '' : tag;
const tagKey = isTagMissing(tag) ? '' : Str.htmlDecode(tag);
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.

❌ CONSISTENCY-3 (docs)

The category grouping uses getDecodedCategoryName(category) (a dedicated wrapper from CategoryUtils), but the tag grouping uses Str.htmlDecode(tag) directly. These perform the same operation (HTML-decoding a string before using it as a grouping key), but use inconsistent approaches. This inconsistency also introduces a new import {Str} from 'expensify-common' that would be unnecessary if a wrapper were used.

For consistency, create a getDecodedTagName helper in TagUtils.ts (mirroring getDecodedCategoryName in CategoryUtils.ts) and use it here instead of calling Str.htmlDecode directly:

// In src/libs/TagUtils.ts
function getDecodedTagName(tagName: string) {
    return Str.htmlDecode(tagName);
}
// In src/libs/ReportLayoutUtils.ts
const tagKey = isTagMissing(tag) ? '' : getDecodedTagName(tag);

This also allows the import {Str} from 'expensify-common' to be removed from ReportLayoutUtils.ts.


Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.


if (!groups.has(tagKey)) {
groups.set(tagKey, []);
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/ReportLayoutUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,21 @@ describe('groupTransactionsByCategory', () => {
expect(travelGroup?.subTotalAmount).toBe(1000);
expect(travelGroup?.transactions).toHaveLength(2);
});

it('groups transactions with HTML-encoded and decoded category names into a single group', () => {
const report = createMockReport({currency: 'USD'});
const transactions = [
createMockTransaction({transactionID: '1', category: 'Auto (including Tolls &amp; Parking)', amount: -1000, currency: 'USD'}),
createMockTransaction({transactionID: '2', category: 'Auto (including Tolls & Parking)', amount: -2000, currency: 'USD'}),
];

const result = groupTransactionsByCategory(transactions, report, mockLocaleCompare);

expect(result).toHaveLength(1);
expect(result.at(0)?.groupKey).toBe('Auto (including Tolls & Parking)');
expect(result.at(0)?.transactions).toHaveLength(2);
expect(result.at(0)?.subTotalAmount).toBe(3000);
});
});

describe('groupTransactionsByTag', () => {
Expand Down Expand Up @@ -434,4 +449,19 @@ describe('groupTransactionsByTag', () => {
expect(projectAGroup?.subTotalAmount).toBe(1000);
expect(projectAGroup?.transactions).toHaveLength(2);
});

it('groups transactions with HTML-encoded and decoded tag names into a single group', () => {
const report = createMockReport({currency: 'USD'});
const transactions = [
createMockTransaction({transactionID: '1', tag: 'R&amp;D', amount: -1000, currency: 'USD'}),
createMockTransaction({transactionID: '2', tag: 'R&D', amount: -2000, currency: 'USD'}),
];

const result = groupTransactionsByTag(transactions, report, mockLocaleCompare);

expect(result).toHaveLength(1);
expect(result.at(0)?.groupKey).toBe('R&D');
expect(result.at(0)?.transactions).toHaveLength(2);
expect(result.at(0)?.subTotalAmount).toBe(3000);
});
});
Loading