diff --git a/components/email/__tests__/thread-list-item-source-folder.test.tsx b/components/email/__tests__/thread-list-item-source-folder.test.tsx index 023ab287d..672aad8da 100644 --- a/components/email/__tests__/thread-list-item-source-folder.test.tsx +++ b/components/email/__tests__/thread-list-item-source-folder.test.tsx @@ -1,4 +1,5 @@ -import { render, screen } from '@testing-library/react'; +import { Profiler } from 'react'; +import { act, render, screen } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ThreadListItem } from '../thread-list-item'; import { useSettingsStore, DEFAULT_KEYWORDS } from '@/stores/settings-store'; @@ -56,6 +57,7 @@ function renderThread(emails: Email[]) { ); } +const noop = () => {}; const single = () => [makeEmail()]; const conversation = () => [ makeEmail({ id: 'email-1', receivedAt: '2024-01-15T10:00:00Z' }), @@ -123,5 +125,20 @@ describe('ThreadListItem folder chip in search results (#1172)', () => { renderThread(emails()); expect(screen.getByTitle('Archive')).toBeTruthy(); }); + + it('does not re-render a row without a folder when a search starts', () => { + // The folder's own list stays on screen until the results arrive; its + // rows carry no sourceFolder and have no chip to show. + const [thread] = groupEmailsByThread(emails().map((e) => ({ ...e, sourceFolder: undefined }))); + const onRender = vi.fn(); + render( + + + , + ); + onRender.mockClear(); + act(() => useEmailStore.setState({ searchQuery: 'invoice', searchMailboxId: '' })); + expect(onRender).not.toHaveBeenCalled(); + }); }); }); diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx index 95d7c3bff..0719eb98d 100644 --- a/components/email/thread-list-item.tsx +++ b/components/email/thread-list-item.tsx @@ -253,9 +253,13 @@ function SourceFolderTag({ name }: { name: string }) { * Whether rows show their folder chip: in the aggregate "All …" views, and in * search results that span every folder rather than the one picked in the * search panel (#1172). Rows of a single-folder list carry no sourceFolder. + * + * Such a row never subscribes to the view state: starting a search flips it + * while the old list is still on screen, and every row re-rendered for a chip + * it has nothing to put in. */ -function useShowSourceFolder(): boolean { - return useEmailStore((state) => state.isUnifiedView || isUnscopedSearchActive(state)); +function useShowSourceFolder(sourceFolder: string | undefined): boolean { + return useEmailStore((state) => !!sourceFolder && (state.isUnifiedView || isUnscopedSearchActive(state))); } /** @@ -377,7 +381,7 @@ const SingleEmailItem = React.forwardRef( const interfaceLayout = useSettingsStore((state) => state.interfaceLayout); const hideJunkAvatarImages = currentMailboxRole === 'junk' && !showAvatarsInJunk; // Show the originating folder in the aggregate "All …" views. - const showSourceFolder = useShowSourceFolder() && !!email.sourceFolder; + const showSourceFolder = useShowSourceFolder(email.sourceFolder); const getAccountById = useAccountStore((state) => state.getAccountById); const account = email.accountId ? getAccountById(email.accountId) : undefined; const accountColor = account?.avatarColor; @@ -932,7 +936,7 @@ export const ThreadListItem = React.forwardRef state.getAccountById); const threadAccount = latestEmail.accountId ? getAccountById(latestEmail.accountId) : undefined; const threadAccountColor = threadAccount?.avatarColor;