ext(affordances): wireGroupAutocomplete via <datalist>
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { wireGroupAutocomplete } from '../group-autocomplete';
|
||||||
|
|
||||||
|
describe('wireGroupAutocomplete', () => {
|
||||||
|
let form: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clean up any datalist from a prior test
|
||||||
|
document.getElementById('groups-datalist')?.remove();
|
||||||
|
form = document.createElement('div');
|
||||||
|
form.innerHTML = `<input id="f-group" type="text" />`;
|
||||||
|
document.body.appendChild(form);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('attaches datalist with all groups', async () => {
|
||||||
|
const sendMessage = vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
data: { groups: ['personal', 'work', 'finance'] },
|
||||||
|
});
|
||||||
|
await wireGroupAutocomplete(form, { sendMessage });
|
||||||
|
const list = document.getElementById('groups-datalist') as HTMLDataListElement | null;
|
||||||
|
expect(list).not.toBeNull();
|
||||||
|
const opts = Array.from(list!.querySelectorAll('option')).map((o) => o.value);
|
||||||
|
expect(opts).toEqual(['personal', 'work', 'finance']);
|
||||||
|
const input = form.querySelector('#f-group') as HTMLInputElement;
|
||||||
|
expect(input.getAttribute('list')).toBe('groups-datalist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is a no-op if SW returns error', async () => {
|
||||||
|
const sendMessage = vi.fn().mockResolvedValue({ ok: false, error: 'vault_locked' });
|
||||||
|
await wireGroupAutocomplete(form, { sendMessage });
|
||||||
|
const input = form.querySelector('#f-group') as HTMLInputElement;
|
||||||
|
expect(input.getAttribute('list')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
23
extension/src/shared/form-affordances/group-autocomplete.ts
Normal file
23
extension/src/shared/form-affordances/group-autocomplete.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export interface GroupAutocompleteOpts {
|
||||||
|
sendMessage: (msg: { type: 'list_groups' }) => Promise<{ ok: boolean; data?: { groups: string[] }; error?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DATALIST_ID = 'groups-datalist';
|
||||||
|
|
||||||
|
export async function wireGroupAutocomplete(form: HTMLElement, opts: GroupAutocompleteOpts): Promise<void> {
|
||||||
|
const input = form.querySelector<HTMLInputElement>('#f-group');
|
||||||
|
if (!input) return;
|
||||||
|
const resp = await opts.sendMessage({ type: 'list_groups' });
|
||||||
|
if (!resp.ok || !resp.data) return;
|
||||||
|
|
||||||
|
// Datalists must live in the document, not nested inside an input. Reuse if
|
||||||
|
// we've already mounted one this session.
|
||||||
|
let list = document.getElementById(DATALIST_ID) as HTMLDataListElement | null;
|
||||||
|
if (!list) {
|
||||||
|
list = document.createElement('datalist');
|
||||||
|
list.id = DATALIST_ID;
|
||||||
|
document.body.appendChild(list);
|
||||||
|
}
|
||||||
|
list.innerHTML = resp.data.groups.map((g) => `<option value="${g.replace(/"/g, '"')}"></option>`).join('');
|
||||||
|
input.setAttribute('list', DATALIST_ID);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user