Introduce shared/state.ts as a service-locator so popup components (item-detail, item-form, trash, devices, settings, etc.) work in both the popup and vault tab bundles. Both entry points register themselves as the host; components import from shared/state instead of popup.ts. Vault.ts now delegates to the real popup components, removing ~300 lines of placeholder renderers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
74 lines
3.8 KiB
TypeScript
74 lines
3.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
||
vi.mock('../../../shared/state', async () => {
|
||
const sendMessage = vi.fn();
|
||
const escapeHtml = (s: string): string => s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]!));
|
||
return { sendMessage, escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||
});
|
||
|
||
import { renderAttachmentsDisclosure, wireAttachmentsDisclosure } from '../attachments-disclosure';
|
||
import { sendMessage } from '../../../shared/state';
|
||
import type { AttachmentRef } from '../../../shared/types';
|
||
|
||
const REF1: AttachmentRef = { id: 'a1', filename: 'doc.pdf', mime_type: 'application/pdf', size: 12345, created: 1700000000 };
|
||
const REF2: AttachmentRef = { id: 'a2', filename: 'photo.png', mime_type: 'image/png', size: 240000, created: 1700000001 };
|
||
|
||
describe('attachments-disclosure render', () => {
|
||
it('renders empty state with no rows in edit mode', () => {
|
||
const html = renderAttachmentsDisclosure({ itemId: 'i1', attachments: [], mode: 'edit', onChange: vi.fn() });
|
||
expect(html).toContain('attachments');
|
||
expect(html).toContain('+ attach file');
|
||
expect(html).not.toContain('attachment-row');
|
||
});
|
||
|
||
it('renders rows + remove buttons in edit mode', () => {
|
||
const html = renderAttachmentsDisclosure({ itemId: 'i1', attachments: [REF1, REF2], mode: 'edit', onChange: vi.fn() });
|
||
expect(html).toContain('doc.pdf');
|
||
expect(html).toContain('photo.png');
|
||
expect(html).toContain('×');
|
||
expect(html).toContain('attachment-row__thumb'); // image-mime row gets thumb hook
|
||
});
|
||
|
||
it('renders rows + download buttons in view mode (no add btn)', () => {
|
||
const html = renderAttachmentsDisclosure({ itemId: 'i1', attachments: [REF1], mode: 'view' });
|
||
expect(html).toContain('↓');
|
||
expect(html).not.toContain('+ attach file');
|
||
});
|
||
});
|
||
|
||
describe('attachments-disclosure wiring', () => {
|
||
beforeEach(() => {
|
||
vi.mocked(sendMessage).mockReset();
|
||
});
|
||
|
||
it('clicking + attach triggers file input click', () => {
|
||
document.body.innerHTML = renderAttachmentsDisclosure({ itemId: 'i1', attachments: [], mode: 'edit', onChange: vi.fn() });
|
||
const fileInput = document.querySelector('.attachments-disclosure__file-input') as HTMLInputElement;
|
||
const clickSpy = vi.spyOn(fileInput, 'click');
|
||
wireAttachmentsDisclosure(document.body, { itemId: 'i1', attachments: [], mode: 'edit', onChange: vi.fn() });
|
||
(document.querySelector('.attachment-add-btn') as HTMLButtonElement).click();
|
||
expect(clickSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
it('clicking × calls onChange with the attachment removed', () => {
|
||
const onChange = vi.fn();
|
||
document.body.innerHTML = renderAttachmentsDisclosure({ itemId: 'i1', attachments: [REF1, REF2], mode: 'edit', onChange });
|
||
wireAttachmentsDisclosure(document.body, { itemId: 'i1', attachments: [REF1, REF2], mode: 'edit', onChange });
|
||
(document.querySelectorAll('.attachment-row__remove')[0] as HTMLElement).click();
|
||
expect(onChange).toHaveBeenCalledWith([REF2]);
|
||
});
|
||
|
||
it('clicking ↓ in view mode sends download_attachment', async () => {
|
||
vi.mocked(sendMessage).mockResolvedValueOnce({ ok: true, data: { bytes: new ArrayBuffer(10), filename: 'doc.pdf', mimeType: 'application/pdf' } });
|
||
document.body.innerHTML = renderAttachmentsDisclosure({ itemId: 'i1', attachments: [REF1], mode: 'view' });
|
||
wireAttachmentsDisclosure(document.body, { itemId: 'i1', attachments: [REF1], mode: 'view' });
|
||
(document.querySelector('.attachment-row__download') as HTMLElement).click();
|
||
await new Promise((r) => setTimeout(r, 50));
|
||
expect(vi.mocked(sendMessage)).toHaveBeenCalledWith(expect.objectContaining({
|
||
type: 'download_attachment',
|
||
itemId: 'i1',
|
||
attachmentId: 'a1',
|
||
}));
|
||
});
|
||
});
|