test(ext): vault-tab Backup & Restore panel

This commit is contained in:
adlee-was-taken
2026-04-28 22:17:09 -04:00
parent 419408bbad
commit c1f48ecb71

View File

@@ -0,0 +1,99 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
// backup-panel.ts only imports { sendMessage } from '../../shared/state'.
// We provide the full shared/state surface so TypeScript is satisfied.
vi.mock('../../../shared/state', () => ({
sendMessage: vi.fn(),
openVaultTab: vi.fn(),
registerHost: vi.fn(),
getState: vi.fn(),
setState: vi.fn(),
navigate: vi.fn(),
escapeHtml: (s: string) => s,
popOutToTab: vi.fn(),
isInTab: vi.fn(() => false),
}));
import { sendMessage } from '../../../shared/state';
import { renderBackupPanel, teardown } from '../backup-panel';
const mockSendMessage = sendMessage as ReturnType<typeof vi.fn>;
describe('backup panel — export', () => {
let app: HTMLElement;
beforeEach(() => {
mockSendMessage.mockReset();
teardown();
document.body.innerHTML = '<div id="app"></div>';
app = document.getElementById('app')!;
});
it('clicking export with no passphrase is a no-op', async () => {
renderBackupPanel(app);
vi.spyOn(window, 'prompt').mockReturnValue(null);
(app.querySelector('#export-btn') as HTMLButtonElement).click();
await new Promise((r) => setTimeout(r, 0));
expect(mockSendMessage).not.toHaveBeenCalled();
});
it('clicking export with a passphrase fires export_backup', async () => {
renderBackupPanel(app);
vi.spyOn(window, 'prompt').mockReturnValue('test-backup-pass');
mockSendMessage.mockResolvedValue({ ok: true, data: { bytes: new ArrayBuffer(123) } });
(app.querySelector('#include-image') as HTMLInputElement).checked = true;
(app.querySelector('#export-btn') as HTMLButtonElement).click();
await new Promise((r) => setTimeout(r, 50));
expect(mockSendMessage).toHaveBeenCalledWith({
type: 'export_backup',
passphrase: 'test-backup-pass',
includeImage: true,
});
});
it('export error surfaces in the status pre', async () => {
renderBackupPanel(app);
vi.spyOn(window, 'prompt').mockReturnValue('p');
mockSendMessage.mockResolvedValue({ ok: false, error: 'no reference image stored locally' });
(app.querySelector('#export-btn') as HTMLButtonElement).click();
await new Promise((r) => setTimeout(r, 50));
const status = app.querySelector('#export-status') as HTMLElement;
expect(status.textContent).toContain('Failed');
expect(status.textContent).toContain('no reference image');
});
});
describe('backup panel — restore', () => {
let app: HTMLElement;
beforeEach(() => {
mockSendMessage.mockReset();
teardown();
document.body.innerHTML = '<div id="app"></div>';
app = document.getElementById('app')!;
});
it('clicking restore without filling new-remote shows an error', async () => {
renderBackupPanel(app);
vi.spyOn(window, 'prompt').mockReturnValue('p');
// Simulate file picked — this reveals the fieldset with the restore button.
const fakeFile = new File([new Uint8Array([0x52, 0x42, 0x41, 0x4b, 0x01])], 'v.relbak');
const input = app.querySelector('#restore-file') as HTMLInputElement;
Object.defineProperty(input, 'files', { value: [fakeFile] });
input.dispatchEvent(new Event('change'));
(app.querySelector('#restore-btn') as HTMLButtonElement).click();
await new Promise((r) => setTimeout(r, 50));
const status = app.querySelector('#restore-status') as HTMLElement;
expect(status.textContent).toContain('fill in');
expect(mockSendMessage).not.toHaveBeenCalled();
});
});