Files
relicario/extension/src/popup/components/__tests__/generator-popover.test.ts

125 lines
5.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('../../popup', async () => {
const sendMessage = vi.fn();
return { sendMessage };
});
import { openGeneratorPopover, closeGeneratorPopover } from '../generator-popover';
import { sendMessage } from '../../popup';
import type { GeneratorRequest } from '../../../shared/types';
const DEFAULT_REQ: GeneratorRequest = {
kind: 'random',
length: 20,
classes: { lower: true, upper: true, digits: true, symbols: true },
symbol_charset: { kind: 'safe_only' },
};
function setupAnchor(): HTMLElement {
document.body.innerHTML = '<button id="anchor">gen</button>';
return document.getElementById('anchor')!;
}
describe('generator-popover', () => {
beforeEach(() => {
vi.mocked(sendMessage).mockReset();
vi.mocked(sendMessage).mockResolvedValue({ ok: true, data: { password: 'Kj7%pW@2xNq!8rMvT' } });
});
it('opens a popover with Random kind by default', async () => {
const anchor = setupAnchor();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked: vi.fn() });
await new Promise((r) => setTimeout(r, 200));
expect(document.querySelector('.generator-popover')).not.toBeNull();
expect(document.querySelector('#gen-kind-random')?.classList.contains('active')).toBe(true);
});
it('sends generate_password on knob change (debounced)', async () => {
const anchor = setupAnchor();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked: vi.fn() });
await new Promise((r) => setTimeout(r, 200));
const slider = document.querySelector('#gen-length') as HTMLInputElement;
slider.value = '32';
slider.dispatchEvent(new Event('input', { bubbles: true }));
await new Promise((r) => setTimeout(r, 200));
const calls = vi.mocked(sendMessage).mock.calls.filter(
([msg]) => (msg as { type: string }).type === 'generate_password',
);
const latest = calls[calls.length - 1]![0] as { request: GeneratorRequest };
expect(latest.request.kind).toBe('random');
if (latest.request.kind === 'random') {
expect(latest.request.length).toBe(32);
}
});
it('BIP39 toggle swaps to generate_passphrase', async () => {
const anchor = setupAnchor();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked: vi.fn() });
await new Promise((r) => setTimeout(r, 200));
(document.getElementById('gen-kind-bip39') as HTMLButtonElement).click();
await new Promise((r) => setTimeout(r, 200));
const calls = vi.mocked(sendMessage).mock.calls;
expect(calls.some(([msg]) => (msg as { type: string }).type === 'generate_passphrase')).toBe(true);
});
it('use-this-value invokes onPicked with current preview and closes', async () => {
const anchor = setupAnchor();
const onPicked = vi.fn();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked });
await new Promise((r) => setTimeout(r, 200));
(document.querySelector('#gen-use') as HTMLButtonElement).click();
expect(onPicked).toHaveBeenCalledWith('Kj7%pW@2xNq!8rMvT');
expect(document.querySelector('.generator-popover')).toBeNull();
});
it('save-as-default sends update_vault_settings with the current request', async () => {
vi.mocked(sendMessage).mockImplementation(async (msg: any) => {
if (msg.type === 'generate_password') return { ok: true, data: { password: 'abc' } };
if (msg.type === 'get_vault_settings') {
return { ok: true, data: { settings: {
trash_retention: { kind: 'days', value: 30 },
field_history_retention: { kind: 'forever' },
generator_defaults: DEFAULT_REQ,
attachment_caps: {},
autofill_origin_acks: {},
} } };
}
if (msg.type === 'update_vault_settings') return { ok: true };
return { ok: false, error: 'unhandled' };
});
const anchor = setupAnchor();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked: vi.fn() });
await new Promise((r) => setTimeout(r, 200));
(document.querySelector('#gen-save-default') as HTMLButtonElement).click();
await new Promise((r) => setTimeout(r, 50));
const updateCall = vi.mocked(sendMessage).mock.calls.find(
([m]) => (m as any).type === 'update_vault_settings',
);
expect(updateCall).toBeDefined();
const msg = updateCall![0] as { settings: { generator_defaults: GeneratorRequest } };
expect(msg.settings.generator_defaults.kind).toBe('random');
});
it('disables use-button when no char class selected (Random)', async () => {
const anchor = setupAnchor();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked: vi.fn() });
await new Promise((r) => setTimeout(r, 200));
for (const id of ['gen-lower', 'gen-upper', 'gen-digits', 'gen-symbols']) {
const cb = document.getElementById(id) as HTMLInputElement;
cb.checked = false;
cb.dispatchEvent(new Event('change', { bubbles: true }));
}
const useBtn = document.querySelector('#gen-use') as HTMLButtonElement;
expect(useBtn.disabled).toBe(true);
});
it('closeGeneratorPopover removes the DOM + handlers', async () => {
const anchor = setupAnchor();
openGeneratorPopover({ anchor, initial: DEFAULT_REQ, onPicked: vi.fn() });
await new Promise((r) => setTimeout(r, 200));
closeGeneratorPopover();
expect(document.querySelector('.generator-popover')).toBeNull();
});
});