diff --git a/components/templates/__tests__/template-picker-manage.test.tsx b/components/templates/__tests__/template-picker-manage.test.tsx index 06cb0a164..339b83dcb 100644 --- a/components/templates/__tests__/template-picker-manage.test.tsx +++ b/components/templates/__tests__/template-picker-manage.test.tsx @@ -1,5 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; +import { useTemplateStore } from '@/stores/template-store'; +import type { EmailTemplate } from '@/lib/template-types'; // The template picker offers "Manage templates", so making or fixing a // template doesn't need a trip to Settings in the middle of a message. @@ -19,4 +21,19 @@ describe('TemplatePicker', () => { fireEvent.click(screen.getByText('done')); expect(screen.getByRole('dialog', { name: 'picker_title' })).toBeTruthy(); }); + + // The sections (favorites, recent, each category) are a list: without + // keys React warned, which the dev build shows as an error overlay. + it('renders its sections without a React key warning', () => { + const template = (id: string, category: string, isFavorite = false): EmailTemplate => ({ + id, name: `T ${id}`, subject: '', body: '', category, isFavorite, + createdAt: '2026-10-01T00:00:00Z', updatedAt: '2026-10-01T00:00:00Z', usageCount: 0, + } as EmailTemplate); + useTemplateStore.setState({ templates: [template('a', 'Work', true), template('b', 'Work'), template('c', 'Home'), template('d', '')] }); + const error = vi.spyOn(console, 'error').mockImplementation(() => {}); + render(); + expect(screen.getByText('T c')).toBeTruthy(); + expect(error.mock.calls.some((call) => String(call[0]).includes('unique "key"'))).toBe(false); + error.mockRestore(); + }); }); diff --git a/components/templates/template-picker.tsx b/components/templates/template-picker.tsx index 03587e2a0..7daffab08 100644 --- a/components/templates/template-picker.tsx +++ b/components/templates/template-picker.tsx @@ -97,10 +97,10 @@ export function TemplatePicker({ isOpen, onClose, onSelect }: TemplatePickerProp ); - const renderSection = (title: string, items: EmailTemplate[]) => { + const renderSection = (key: string, title: string, items: EmailTemplate[]) => { if (items.length === 0) return null; return ( -
+

{title}

@@ -171,15 +171,16 @@ export function TemplatePicker({ isOpen, onClose, onSelect }: TemplatePickerProp ) ) : ( <> - {renderSection(t('section_favorites'), favorites)} - {renderSection(t('section_recent'), recentFiltered)} + {renderSection('favorites', t('section_favorites'), favorites)} + {renderSection('recent', t('section_recent'), recentFiltered)} {categorizedEntries.map(([cat, items]) => renderSection( + `category:${cat}`, cat, items.filter((i) => !shownIds.has(i.id)) ) )} - {renderSection(t('section_uncategorized'), uncategorizedFiltered)} + {renderSection('uncategorized', t('section_uncategorized'), uncategorizedFiltered)} )}