feat(ext/popup): attachments-disclosure shared component
Compact disclosure rendering attachment rows with an action column (× in edit, ↓ in view). Image-mime rows lazily decrypt + show a 16×16 thumb via object URLs; teardown revokes them on disclosure close. Edit mode adds a "+ attach file" button wired to a hidden file input that checks vault caps client-side before sending upload_attachment to SW. 6 new tests; total ~143. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../popup', async () => {
|
||||
const sendMessage = vi.fn();
|
||||
const escapeHtml = (s: string): string => s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]!));
|
||||
return { sendMessage, escapeHtml };
|
||||
});
|
||||
|
||||
import { renderAttachmentsDisclosure, wireAttachmentsDisclosure } from '../attachments-disclosure';
|
||||
import { sendMessage } from '../../popup';
|
||||
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',
|
||||
}));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user