mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
fix: stop the template picker from raising a React key error in development
This commit is contained in:
@@ -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(<TemplatePicker isOpen onClose={vi.fn()} onSelect={vi.fn()} />);
|
||||
expect(screen.getByText('T c')).toBeTruthy();
|
||||
expect(error.mock.calls.some((call) => String(call[0]).includes('unique "key"'))).toBe(false);
|
||||
error.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,10 +97,10 @@ export function TemplatePicker({ isOpen, onClose, onSelect }: TemplatePickerProp
|
||||
</button>
|
||||
);
|
||||
|
||||
const renderSection = (title: string, items: EmailTemplate[]) => {
|
||||
const renderSection = (key: string, title: string, items: EmailTemplate[]) => {
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div key={key} className="mb-4">
|
||||
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1 px-3">
|
||||
{title}
|
||||
</h3>
|
||||
@@ -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)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user