From bef1d3e4ff6aaf8b6143ce66d7a5e6169a08a86f Mon Sep 17 00:00:00 2001 From: liuhao1024 Date: Mon, 22 Jun 2026 07:54:09 +0800 Subject: [PATCH] fix(desktop): filter undefined entries in AttachmentList to prevent refText crash on session switch (#49624) * fix(desktop): filter undefined entries in AttachmentList to prevent refText crash on session switch When switching sessions, the attachments array can contain stale/undefined entries from the previous session's state. Accessing attachment.refText on an undefined entry throws TypeError, breaking session switching entirely. Fix: add .filter(Boolean) before .map() to skip undefined/null entries. Fixes #49614 * fix(desktop): update I18nConfigClient usage in attachment test The i18n config API changed from getLocale/saveLocale to getConfig/saveConfig. Update the test fixture to match. --- .../app/chat/composer/attachments.test.tsx | 69 +++++++++++++++++++ .../src/app/chat/composer/attachments.tsx | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/app/chat/composer/attachments.test.tsx diff --git a/apps/desktop/src/app/chat/composer/attachments.test.tsx b/apps/desktop/src/app/chat/composer/attachments.test.tsx new file mode 100644 index 00000000000..c31e5612f35 --- /dev/null +++ b/apps/desktop/src/app/chat/composer/attachments.test.tsx @@ -0,0 +1,69 @@ +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it } from 'vitest' + +import { I18nProvider } from '@/i18n/context' + +import { AttachmentList } from './attachments' +import type { ComposerAttachment } from '@/store/composer' + +function makeAttachment(id: string, label = 'test.pdf'): ComposerAttachment { + return { id, kind: 'file', label } +} + +function renderWithI18n(ui: React.ReactNode) { + return render( + ({}), saveConfig: async () => ({ ok: true }) }}> + {ui} + + ) +} + +describe('AttachmentList', () => { + afterEach(() => { + cleanup() + }) + + it('renders valid attachments', () => { + const attachments = [makeAttachment('a', 'doc.pdf'), makeAttachment('b', 'img.png')] + renderWithI18n() + expect(screen.getByText('doc.pdf')).toBeDefined() + expect(screen.getByText('img.png')).toBeDefined() + }) + + it('renders empty list without error', () => { + renderWithI18n() + const container = screen.getByTestId?.('composer-attachments') ?? document.querySelector('[data-slot="composer-attachments"]') + expect(container).toBeDefined() + }) + + it('does not crash when attachments array contains undefined entries', () => { + // Repro: session switch can leave stale/undefined entries in the + // attachments array, causing a TypeError at attachment.refText. + const attachments = [ + makeAttachment('a', 'good.pdf'), + undefined as unknown as ComposerAttachment, + makeAttachment('b', 'also-good.png') + ] + + expect(() => { + renderWithI18n() + }).not.toThrow() + + // Only valid attachments should render + expect(screen.getByText('good.pdf')).toBeDefined() + expect(screen.getByText('also-good.png')).toBeDefined() + }) + + it('does not crash when attachments array contains null entries', () => { + const attachments = [ + null as unknown as ComposerAttachment, + makeAttachment('a', 'valid.txt') + ] + + expect(() => { + renderWithI18n() + }).not.toThrow() + + expect(screen.getByText('valid.txt')).toBeDefined() + }) +}) diff --git a/apps/desktop/src/app/chat/composer/attachments.tsx b/apps/desktop/src/app/chat/composer/attachments.tsx index 6229c9da8bd..5b353436404 100644 --- a/apps/desktop/src/app/chat/composer/attachments.tsx +++ b/apps/desktop/src/app/chat/composer/attachments.tsx @@ -20,7 +20,7 @@ export function AttachmentList({ }) { return (
- {attachments.map(attachment => ( + {attachments.filter(Boolean).map(attachment => ( ))}