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.
This commit is contained in:
liuhao1024 2026-06-22 07:54:09 +08:00 committed by GitHub
parent c768c4b71c
commit bef1d3e4ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 1 deletions

View file

@ -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(
<I18nProvider configClient={{ getConfig: async () => ({}), saveConfig: async () => ({ ok: true }) }}>
{ui}
</I18nProvider>
)
}
describe('AttachmentList', () => {
afterEach(() => {
cleanup()
})
it('renders valid attachments', () => {
const attachments = [makeAttachment('a', 'doc.pdf'), makeAttachment('b', 'img.png')]
renderWithI18n(<AttachmentList attachments={attachments} />)
expect(screen.getByText('doc.pdf')).toBeDefined()
expect(screen.getByText('img.png')).toBeDefined()
})
it('renders empty list without error', () => {
renderWithI18n(<AttachmentList attachments={[]} />)
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(<AttachmentList attachments={attachments} />)
}).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(<AttachmentList attachments={attachments} />)
}).not.toThrow()
expect(screen.getByText('valid.txt')).toBeDefined()
})
})

View file

@ -20,7 +20,7 @@ export function AttachmentList({
}) {
return (
<div className="flex max-w-full flex-wrap gap-1.5 px-1 pt-1" data-slot="composer-attachments">
{attachments.map(attachment => (
{attachments.filter(Boolean).map(attachment => (
<AttachmentPill attachment={attachment} key={attachment.id} onRemove={onRemove} />
))}
</div>