fix(ext/popup): preserve unsupported-kind fields + totp expanded state

Two fixes from the T3+T4 code review:

C1 (Critical): renderSectionBlock previously rendered all fields
regardless of kind. For fields with kind url/date/month_year/totp/etc.
(from CLI-created items), the editor showed a blank value input; if
the user typed anything, the input handler cast the kind to the
wrong thing and silently overwrote the structured value with a
string — destroying data. Fix: filter editor to supported kinds
(text/password/concealed); key data-* attributes by field.id (not
by index) so handlers look up the correct field regardless of what
the render loop emitted. Unsupported-kind fields survive save
untouched. A small muted note "N fields of unsupported kind (edit
via CLI)" flags preserved entries. +2 tests.

I1 (Important): totp.ts's kind-toggle reRender read the module-
scope sectionsExpanded flag which was only updated on structural
mutations — so toggling the disclosure open without adding/removing
anything left the flag stale, and clicking Random/BIP39 collapsed
the disclosure. Fix: read data-expanded from the live DOM before
innerHTML swap.
This commit is contained in:
adlee-was-taken
2026-04-24 18:51:23 -04:00
parent e1d32b0379
commit 6ba9ccfa4c
5 changed files with 98 additions and 29 deletions

View File

@@ -105,7 +105,7 @@ describe('wireSectionsEditor', () => {
] }];
document.body.innerHTML = renderSectionsEditor(sections, true);
wireSectionsEditor(document.body, sections, vi.fn());
const deleteBtn = document.querySelector('[data-delete-field="0-0"]') as HTMLButtonElement;
const deleteBtn = document.querySelector('[data-delete-field="f0"]') as HTMLButtonElement;
deleteBtn.click();
expect(sections[0].fields).toHaveLength(1);
expect(sections[0].fields[0].id).toBe('f1');
@@ -142,7 +142,7 @@ describe('wireSectionsEditor', () => {
document.body.innerHTML = renderSectionsEditor(sections, true);
const rerender = vi.fn();
wireSectionsEditor(document.body, sections, rerender);
const labelInput = document.querySelector('[data-field-label="0-0"]') as HTMLInputElement;
const labelInput = document.querySelector('[data-field-label="f0"]') as HTMLInputElement;
labelInput.value = 'new';
labelInput.dispatchEvent(new Event('input', { bubbles: true }));
expect(sections[0].fields[0].label).toBe('new');
@@ -155,9 +155,45 @@ describe('wireSectionsEditor', () => {
] }];
document.body.innerHTML = renderSectionsEditor(sections, true);
wireSectionsEditor(document.body, sections, vi.fn());
const valueInput = document.querySelector('[data-field-value-input="0-0"]') as HTMLInputElement;
const valueInput = document.querySelector('[data-field-value-input="f0"]') as HTMLInputElement;
valueInput.value = 'new';
valueInput.dispatchEvent(new Event('input', { bubbles: true }));
expect(sections[0].fields[0].value).toEqual({ kind: 'text', value: 'new' });
});
});
describe('wireSectionsEditor preserves unsupported-kind fields on save', () => {
it('renders preserved note when section contains unsupported-kind fields', () => {
const sections: Section[] = [{
name: 'mixed',
fields: [
{ id: 'f0000001', label: 'note', kind: 'text',
value: { kind: 'text', value: 'ok' }, hidden_by_default: false },
{ id: 'f0000002', label: 'when', kind: 'date' as any,
value: { kind: 'date', value: '2026-01-01' } as any, hidden_by_default: false },
],
}];
document.body.innerHTML = renderSectionsEditor(sections, true);
expect(document.body.innerHTML).toContain('1 field of unsupported kind');
expect(document.body.innerHTML).not.toContain('f0000002');
});
it('add-text then save does not destroy unsupported-kind fields', () => {
const sections: Section[] = [{
name: 'mixed',
fields: [
{ id: 'f0000002', label: 'when', kind: 'date' as any,
value: { kind: 'date', value: '2026-01-01' } as any, hidden_by_default: false },
],
}];
document.body.innerHTML = renderSectionsEditor(sections, true);
wireSectionsEditor(document.body, sections, vi.fn());
const addText = document.querySelector('[data-add-field="text"][data-section-idx="0"]') as HTMLButtonElement;
addText.click();
expect(sections[0].fields).toHaveLength(2);
// Unsupported-kind field preserved untouched.
const dateField = sections[0].fields.find((f) => f.id === 'f0000002');
expect(dateField).toBeDefined();
expect(dateField!.value).toEqual({ kind: 'date', value: '2026-01-01' });
});
});