fix(settings): guard notification panel against non-array status payload

The notifications settings panel white-screened with "x.map is not a
function" when a status payload reached the card as the whole
{ channels, events } object instead of the channels array (events is an
object map, not an array). Guard the channel map with an Array.isArray
check, default the mood reminder-hour read to optional chaining, and
add a regression test that renders the empty state instead of crashing
on a malformed payload.
This commit is contained in:
Marc-André Bombeck committed 2026-06-06 23:14:04 +02:00
1 parent 0e3e0b0135
commit 79ce836335
4 files changed
+22 -3

No files matched your search

@@ -201,7 +201,7 @@ export function NotificationsBody({
</label>
)}
{status && status.channels.length > 0 && (
{Array.isArray(status?.channels) && status.channels.length > 0 && (
<div
className="flex flex-wrap gap-1.5"
aria-hidden="true"
@@ -71,6 +71,20 @@ describe("<NotificationStatusCard />", () => {
expect(html).not.toContain('data-testid="notification-status-list"');
});
it("renders empty state instead of crashing when data is not an array", () => {
// Regression: the status endpoint returns `{ channels, events }` where
// `events` is an object map. A shape-drifted / poisoned cache read that
// hands the whole object to the card must not reach `.map` on a
// non-array and white-screen the notifications panel.
useQueryMock.mockReturnValue({
data: { channels: [], events: {} } as unknown as MockChannel[],
isLoading: false,
});
const html = render(<NotificationStatusCard />);
expect(html).toContain("No channels configured yet");
expect(html).not.toContain('data-testid="notification-status-list"');
});
it("paints Active badge for a healthy channel", () => {
setChannels([
{
@@ -84,7 +84,7 @@ export function MoodReminderCard({
});
const enabled = optimistic ?? profile?.moodReminderEnabled ?? false;
const reminderHour = optimisticHour ?? prefs?.mood.reminderHour ?? 22;
const reminderHour = optimisticHour ?? prefs?.mood?.reminderHour ?? 22;
async function handleToggle(next: boolean) {
setOptimistic(next);
@@ -119,7 +119,12 @@ export function NotificationStatusCard() {
);
}
const channels = data ?? [];
// Defensive: the status endpoint returns `{ channels, events }` where
// `events` is an object map, not an array. A stale or shape-drifted
// payload (or a cache read that kept the whole object instead of the
// `.channels` slice) must never reach `.map` on a non-array — guard the
// type here rather than white-screening the whole notifications panel.
const channels = Array.isArray(data) ? data : [];
if (channels.length === 0) {
return (
<div