mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
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> = {}): 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']);
|
|
});
|
|
});
|
|
});
|