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
6 changes: 6 additions & 0 deletions src/libs/CategoryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ function processCategoryNameSegments(categoryName: string): string[] {
}
}

// If all segments were empty but the original name is not empty,
// treat the whole name as a single segment (e.g., ":" or "::").
if (result.length === 0 && categoryName.trim() !== '') {
return [categoryName.trim()];
}

// If the original name ends with a colon (allowing trailing spaces), append a colon to the last segment.
const endsWithColon = categoryName.trim().endsWith(CONST.PARENT_CHILD_SEPARATOR);
if (endsWithColon && result.length > 0) {
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/CategoryOptionListUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,66 @@ describe('CategoryOptionListUtils', () => {
expect(getCategoryOptionTree(categories)).toStrictEqual(result);
});

it('handles colon‑only category names', () => {
const categories = {
':': {
enabled: true,
name: ':',
},
'::': {
enabled: true,
name: '::',
},
' : ': {
enabled: true,
name: ' : ',
},
'Normal:Category': {
enabled: true,
name: 'Normal:Category',
},
};

const result = getCategoryOptionTree(categories);

// The colon-only categories should appear as top‑level leaf items (no indentation)
// They should have the exact name as both text and searchText.
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
text: ':',
keyForList: ':',
searchText: ':',
isDisabled: false,
}),
expect.objectContaining({
text: '::',
keyForList: '::',
searchText: '::',
isDisabled: false,
}),
expect.objectContaining({
text: ':',
keyForList: ' : ',
searchText: ' : ',
isDisabled: false,
}),
expect.objectContaining({
text: 'Normal',
keyForList: 'Normal',
searchText: 'Normal',
isDisabled: true,
}),
expect.objectContaining({
text: ' Category',
keyForList: 'Normal:Category',
searchText: 'Normal:Category',
isDisabled: false,
}),
]),
);
});

it('sortCategories', () => {
const categoriesIncorrectOrdering = {
Taxi: {
Expand Down Expand Up @@ -1203,4 +1263,18 @@ describe('CategoryOptionListUtils', () => {
expect(sortCategories(categoriesIncorrectOrdering2, localeCompare)).toStrictEqual(result2);
expect(sortCategories(categoriesIncorrectOrdering3, localeCompare)).toStrictEqual(result3);
});

it('sortCategories keeps colon‑only categories', () => {
const categories = {
':': {enabled: true, name: ':'},
'::': {enabled: true, name: '::'},
'Normal:Category': {enabled: true, name: 'Normal:Category'},
};
const sorted = sortCategories(categories, localeCompare);
expect(sorted).toEqual([
{name: ':', enabled: true, pendingAction: undefined},
{name: '::', enabled: true, pendingAction: undefined},
{name: 'Normal:Category', enabled: true, pendingAction: undefined},
]);
});
});
36 changes: 35 additions & 1 deletion tests/unit/CategoryUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type {OnyxCollection} from 'react-native-onyx';
import {formatRequireItemizedReceiptsOverText, getAvailableNonPersonalPolicyCategories, isCategoryDescriptionRequired, isCategoryMissing} from '@libs/CategoryUtils';
import {
formatRequireItemizedReceiptsOverText,
getAvailableNonPersonalPolicyCategories,
getDecodedLeafCategoryName,
isCategoryDescriptionRequired,
isCategoryMissing,
processCategoryNameSegments,
} from '@libs/CategoryUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -207,4 +214,31 @@ describe('getAvailableNonPersonalPolicyCategories', () => {
expect(result[keyOther]?.TestCategory2).toBeDefined();
expect(result[keyOther]?.TestCategory3).toBeDefined();
});

describe('processCategoryNameSegments and getDecodedLeafCategoryName', () => {
describe('processCategoryNameSegments', () => {
it('returns a single segment for colon‑only names', () => {
expect(processCategoryNameSegments(':')).toEqual([':']);
expect(processCategoryNameSegments('::')).toEqual(['::']);
});

it('handles normal hierarchical categories unchanged (preserves leading spaces)', () => {
expect(processCategoryNameSegments('Food: Meat')).toEqual(['Food', ' Meat']);
expect(processCategoryNameSegments('A: B:')).toEqual(['A', ' B:']);
expect(processCategoryNameSegments('Parent:Child')).toEqual(['Parent', 'Child']);
});
});

describe('getDecodedLeafCategoryName', () => {
it('returns the leaf name for colon‑only categories', () => {
expect(getDecodedLeafCategoryName(':')).toEqual(':');
expect(getDecodedLeafCategoryName('::')).toEqual('::');
});

it('returns the leaf for normal hierarchies (trimmed)', () => {
expect(getDecodedLeafCategoryName('Food: Meat')).toEqual('Meat');
expect(getDecodedLeafCategoryName('A: B:')).toEqual('B:');
});
});
});
});
Loading