fix(categories): fall back to gray when category color is unknown (#675)

## Problem

`getCategoryColorClasses(color)` in `resources/js/types/category.ts`
returned `colorMap[color]` directly. When a category's `color` is
**not** one of the known `CATEGORY_COLORS` (a legacy value, or any color
the frontend map doesn't cover), the lookup is `undefined`. Consumers
then read `.bg` off it — e.g. the transaction categorizer command
palette:

```tsx
const colorClasses = getCategoryColorClasses(category.color);
// ...
className={cn(..., colorClasses.bg)}   // 💥 Cannot read properties of undefined (reading 'bg')
```

This threw `TypeError: Cannot read properties of undefined (reading
'bg')` and crashed the categorize-transactions view.

**Sentry:** `PHP-LARAVEL-4H` (frontend, error/high) — first seen
2026-07-14, in `use-categorize-transactions` chunk,
`getCategoryColorClasses` → `Array.map` over categories.

## Fix

Mirror the guard that already exists in the sibling
`getCategoryChartColor` (which does `?? 'var(--color-gray-500)'`): fall
back to the gray classes when the color isn't in the map.

```diff
- return colorMap[color];
+ return colorMap[color] ?? colorMap.gray;
```

One line, behavior-preserving for all valid colors, and it turns a crash
into a neutral gray swatch for the rare out-of-enum color.

## Test

Added two cases to `resources/js/types/category.test.ts`:
- a known color still returns its own classes,
- an unknown color falls back to gray instead of returning `undefined`.

Ran locally (minimal vitest config): 4/4 pass.

🤖 Found and fixed autonomously via the Sentry monitoring loop.
This commit is contained in:
Víctor Falcón 2026-07-14 22:24:52 +02:00 committed by GitHub
parent 91ffeb917d
commit 19e0c24f9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 2 deletions

View File

@ -1,6 +1,10 @@
import { setTranslations } from '@/utils/i18n';
import { afterEach, describe, expect, it } from 'vitest';
import { getCategoryTypeLabel } from './category';
import {
type CategoryColor,
getCategoryColorClasses,
getCategoryTypeLabel,
} from './category';
describe('getCategoryTypeLabel', () => {
afterEach(() => {
@ -25,3 +29,19 @@ describe('getCategoryTypeLabel', () => {
expect(getCategoryTypeLabel('transfer')).toBe('Transfer');
});
});
describe('getCategoryColorClasses', () => {
it('returns the classes for a known color', () => {
expect(getCategoryColorClasses('blue')).toEqual({
bg: 'bg-blue-100 dark:bg-blue-700',
text: 'text-blue-700 dark:text-blue-100',
});
});
it('falls back to gray for an unknown color instead of returning undefined', () => {
const classes = getCategoryColorClasses('chartreuse' as CategoryColor);
expect(classes).toEqual(getCategoryColorClasses('gray'));
expect(classes.bg).toBeDefined();
});
});

View File

@ -235,7 +235,7 @@ export function getCategoryColorClasses(color: CategoryColor): {
},
};
return colorMap[color];
return colorMap[color] ?? colorMap.gray;
}
export function getCategoryChartColor(color: CategoryColor): string {