feat(ext): sync now button + device register from popup; vault tab parity
Closes three audit gaps in one pass: 1. Sync now button in the popup settings view (📤). Triggers the existing { type: 'sync' } SW message and surfaces success / failure inline. The SW message was already wired but had no UI entry point. 2. Device registration from the popup. The "Register this device" button on the devices view used to error out with a "not yet implemented" message; it now opens an inline name input (default = browser+OS), and on confirm sends a new register_this_device SW message that generates an ed25519 keypair via WASM, persists private_key + name to chrome.storage.local, and writes the public key to the remote devices.json. No setup-wizard detour. 3. Vault tab is now an authorized sender for popup-only SW messages. The router accepts vault.html alongside popup.html, so the fullscreen tab can drive the same flows. Test covers acceptance from the vault tab. New SW message: register_this_device { name }. Added to PopupMessage and POPUP_ONLY_TYPES, handled in router/popup-only.ts. Tests: 5 new vitest cases (3 in settings.test.ts, 2 in devices.test.ts) + 1 router test for vault-tab acceptance. All 194 extension tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -91,4 +91,42 @@ describe('devices view', () => {
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith('list');
|
||||
});
|
||||
|
||||
it('clicking register button reveals an inline name input', async () => {
|
||||
(chrome.storage.local.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ device_name: 'Unknown' });
|
||||
(sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
data: { devices: [{ name: 'CLI', public_key: 'k', added_at: 1 }] },
|
||||
});
|
||||
|
||||
await renderDevices(app);
|
||||
app.querySelector<HTMLButtonElement>('#register-btn')!.click();
|
||||
|
||||
expect(app.querySelector<HTMLInputElement>('#register-name-input')).not.toBeNull();
|
||||
expect(app.querySelector<HTMLButtonElement>('#register-confirm-btn')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('confirming register sends register_this_device with the entered name', async () => {
|
||||
(chrome.storage.local.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ device_name: 'Unknown' });
|
||||
// Initial list_devices.
|
||||
(sendMessage as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({ ok: true, data: { devices: [{ name: 'CLI', public_key: 'k', added_at: 1 }] } })
|
||||
// register_this_device.
|
||||
.mockResolvedValueOnce({ ok: true })
|
||||
// Re-render's list_devices.
|
||||
.mockResolvedValueOnce({ ok: true, data: { devices: [{ name: 'CLI', public_key: 'k', added_at: 1 }, { name: 'Test Browser', public_key: 'q', added_at: 2 }] } });
|
||||
// Re-render also re-reads device_name from storage.
|
||||
(chrome.storage.local.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ device_name: 'Test Browser' });
|
||||
|
||||
await renderDevices(app);
|
||||
app.querySelector<HTMLButtonElement>('#register-btn')!.click();
|
||||
const input = app.querySelector<HTMLInputElement>('#register-name-input')!;
|
||||
input.value = 'Test Browser';
|
||||
app.querySelector<HTMLButtonElement>('#register-confirm-btn')!.click();
|
||||
// Wait a microtask for the async handler to run.
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith({ type: 'register_this_device', name: 'Test Browser' });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user