import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useTemplateStore } from '../template-store'; import type { EmailTemplate } from '@/lib/template-types'; function makeTemplate(overrides: Partial = {}): EmailTemplate { return { id: 'test-id', name: 'Test Template', subject: '', body: '', isHTML: false, category: '', isFavorite: false, createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', ...overrides, }; } beforeEach(() => { useTemplateStore.setState({ templates: [], recentTemplateIds: [], deletedTemplateIds: {}, }); }); describe('deleteTemplate', () => { it('records a tombstone so the deletion can propagate through sync', () => { useTemplateStore.setState({ templates: [makeTemplate({ id: 'a' })] }); useTemplateStore.getState().deleteTemplate('a'); const state = useTemplateStore.getState(); expect(state.templates).toEqual([]); expect(Object.keys(state.deletedTemplateIds)).toEqual(['a']); expect(Date.parse(state.deletedTemplateIds.a)).not.toBeNaN(); }); }); describe('applySyncedState', () => { it('merges by id on server loads instead of clobbering local templates', () => { useTemplateStore.setState({ templates: [makeTemplate({ id: 'local' })] }); useTemplateStore.getState().applySyncedState( [makeTemplate({ id: 'remote' })], {}, { merge: true } ); const ids = useTemplateStore.getState().templates.map((t) => t.id).sort(); expect(ids).toEqual(['local', 'remote']); }); it('replaces wholesale on file imports', () => { useTemplateStore.setState({ templates: [makeTemplate({ id: 'local' })] }); useTemplateStore.getState().applySyncedState( [makeTemplate({ id: 'imported' })], {}, { merge: false } ); expect(useTemplateStore.getState().templates.map((t) => t.id)).toEqual(['imported']); }); it('leaves the store untouched when the blob has no templates array', () => { useTemplateStore.setState({ templates: [makeTemplate({ id: 'local' })] }); useTemplateStore.getState().applySyncedState(undefined, undefined, { merge: true }); expect(useTemplateStore.getState().templates.map((t) => t.id)).toEqual(['local']); }); it('removes templates deleted on another device and prunes recents', () => { // Recent enough to be inside the tombstone TTL regardless of when this runs. const deletedAt = new Date(Date.now() - 1000).toISOString(); useTemplateStore.setState({ templates: [makeTemplate({ id: 'a' }), makeTemplate({ id: 'b' })], recentTemplateIds: ['a', 'b'], }); useTemplateStore.getState().applySyncedState( [makeTemplate({ id: 'b' })], { a: deletedAt }, { merge: true } ); const state = useTemplateStore.getState(); expect(state.templates.map((t) => t.id)).toEqual(['b']); expect(state.recentTemplateIds).toEqual(['b']); expect(state.deletedTemplateIds).toEqual({ a: deletedAt }); }); }); describe('another tab writing templates', () => { it('reloads the list so this tab does not overwrite the other tab\'s template', async () => { useTemplateStore.setState({ templates: [makeTemplate({ id: 'mine' })] }); // What the other tab's persist middleware writes after adding a template. const otherTab = JSON.stringify({ state: { templates: [makeTemplate({ id: 'mine' }), makeTemplate({ id: 'theirs' })], recentTemplateIds: [], deletedTemplateIds: {}, }, version: 1, }); window.localStorage.setItem('template-storage', otherTab); window.dispatchEvent(new StorageEvent('storage', { key: 'template-storage', newValue: otherTab, storageArea: window.localStorage, })); await vi.waitFor(() => { expect(useTemplateStore.getState().templates.map((t) => t.id)).toEqual(['mine', 'theirs']); }); }); });