refactor(ext/popup): migrate required-field markers to REQUIRED_PILL_HTML
Replaces ten <span class="req">*</span> sites across all seven type
forms with the shared REQUIRED_PILL_HTML snippet ('required' badge).
Adds a regression test pinning the new HTML in the login form.
Plan 2026-04-30 fullscreen UX phase 1 task 4.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('../../../../shared/state', () => ({
|
||||||
|
sendMessage: vi.fn(),
|
||||||
|
getState: () => ({ newType: 'login', generatorDefaults: null, error: null, loading: false, vaultSettings: null, entries: [] }),
|
||||||
|
setState: vi.fn(),
|
||||||
|
navigate: vi.fn(),
|
||||||
|
escapeHtml: (s: string) => s,
|
||||||
|
popOutToTab: vi.fn(),
|
||||||
|
isInTab: () => false,
|
||||||
|
openVaultTab: vi.fn(),
|
||||||
|
registerHost: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../generator-panel', () => ({
|
||||||
|
openGeneratorPanel: vi.fn(),
|
||||||
|
closeGeneratorPanel: vi.fn(),
|
||||||
|
isGeneratorPanelOpen: () => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { renderForm } from '../login';
|
||||||
|
|
||||||
|
describe('required-pill migration', () => {
|
||||||
|
beforeEach(() => { document.body.innerHTML = '<div id="app"></div>'; });
|
||||||
|
|
||||||
|
it('login form title uses the required pill', () => {
|
||||||
|
renderForm(document.getElementById('app')!, 'add', null);
|
||||||
|
const titleLabel = document.querySelector('label[for="f-title"]');
|
||||||
|
expect(titleLabel?.innerHTML).toContain('required');
|
||||||
|
expect(titleLabel?.innerHTML).not.toContain('<span class="req">*</span>');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/// Detail view has a styled card-silhouette signature block.
|
/// Detail view has a styled card-silhouette signature block.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, ManifestEntry, CardKind, Section, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, CardKind, Section, AttachmentRef } from '../../../shared/types';
|
||||||
import {
|
import {
|
||||||
renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
||||||
@@ -179,7 +180,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group"><label class="label" for="f-title">title <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="Amex Gold"></div>
|
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="Amex Gold"></div>
|
||||||
<div class="form-group"><label class="label" for="f-number">number</label>
|
<div class="form-group"><label class="label" for="f-number">number</label>
|
||||||
<input id="f-number" type="text" inputmode="numeric" value="${escapeHtml(c?.number ?? '')}" placeholder="378282246310005"></div>
|
<input id="f-number" type="text" inputmode="numeric" value="${escapeHtml(c?.number ?? '')}" placeholder="378282246310005"></div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
/// Primary attachment is referenced by ID from the item's attachments array.
|
/// Primary attachment is referenced by ID from the item's attachments array.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
||||||
import {
|
import {
|
||||||
renderSectionsEditor, wireSectionsEditor,
|
renderSectionsEditor, wireSectionsEditor,
|
||||||
@@ -91,11 +92,11 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="label" for="f-title">title <span class="req">*</span></label>
|
<label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(existing?.title ?? '')}" placeholder="Passport, Lease, etc.">
|
<input id="f-title" type="text" value="${escapeHtml(existing?.title ?? '')}" placeholder="Passport, Lease, etc.">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="label">primary attachment <span class="req">*</span></label>
|
<label class="label">primary attachment ${REQUIRED_PILL_HTML}</label>
|
||||||
${renderPrimary()}
|
${renderPrimary()}
|
||||||
<input type="file" id="primary-file-input" hidden />
|
<input type="file" id="primary-file-input" hidden />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/// Detail view shows a "profile card" signature block + plain rows.
|
/// Detail view shows a "profile card" signature block + plain rows.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
||||||
import {
|
import {
|
||||||
renderRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
renderRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
||||||
@@ -139,7 +140,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group"><label class="label" for="f-title">title <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="Aaron Lee · personal"></div>
|
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="Aaron Lee · personal"></div>
|
||||||
<div class="form-group"><label class="label" for="f-full-name">full name</label>
|
<div class="form-group"><label class="label" for="f-full-name">full name</label>
|
||||||
<input id="f-full-name" type="text" value="${escapeHtml(c?.full_name ?? '')}" placeholder="Aaron Lee"></div>
|
<input id="f-full-name" type="text" value="${escapeHtml(c?.full_name ?? '')}" placeholder="Aaron Lee"></div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
/// since <textarea type="password"> isn't a thing.
|
/// since <textarea type="password"> isn't a thing.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
||||||
import {
|
import {
|
||||||
renderRow, renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
renderRow, renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
||||||
@@ -128,9 +129,9 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group"><label class="label" for="f-title">title <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="github ssh"></div>
|
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="github ssh"></div>
|
||||||
<div class="form-group"><label class="label" for="f-key-material">key material <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-key-material">key material ${REQUIRED_PILL_HTML}</label>
|
||||||
<div style="position:relative;">
|
<div style="position:relative;">
|
||||||
<textarea id="f-key-material" rows="8" style="font-family:monospace;-webkit-text-security:disc;" placeholder="paste key here">${escapeHtml(c?.key_material ?? '')}</textarea>
|
<textarea id="f-key-material" rows="8" style="font-family:monospace;-webkit-text-security:disc;" placeholder="paste key here">${escapeHtml(c?.key_material ?? '')}</textarea>
|
||||||
<button type="button" id="key-show-btn" class="btn" style="position:absolute;right:6px;top:6px;font-size:10px;padding:2px 8px;">show</button>
|
<button type="button" id="key-show-btn" class="btn" style="position:absolute;right:6px;top:6px;font-size:10px;padding:2px 8px;">show</button>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/// field helpers introduced in Slice 2.
|
/// field helpers introduced in Slice 2.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, LoginCore, ManifestEntry, Section, TotpConfig, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, LoginCore, ManifestEntry, Section, TotpConfig, AttachmentRef } from '../../../shared/types';
|
||||||
import { DEFAULT_PASSWORD_REQUEST } from '../../../shared/types';
|
import { DEFAULT_PASSWORD_REQUEST } from '../../../shared/types';
|
||||||
import { base32Decode, base32Encode } from '../../../shared/base32';
|
import { base32Decode, base32Encode } from '../../../shared/base32';
|
||||||
@@ -249,7 +250,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group"><label class="label" for="f-title">title <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="GitHub"></div>
|
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="GitHub"></div>
|
||||||
<div class="form-group"><label class="label" for="f-url">url</label>
|
<div class="form-group"><label class="label" for="f-url">url</label>
|
||||||
<input id="f-url" type="text" value="${escapeHtml(url)}" placeholder="https://github.com/login"></div>
|
<input id="f-url" type="text" value="${escapeHtml(url)}" placeholder="https://github.com/login"></div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/// detail view; the form is just a big <textarea>.
|
/// detail view; the form is just a big <textarea>.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
||||||
import {
|
import {
|
||||||
renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
||||||
@@ -117,7 +118,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group"><label class="label" for="f-title">title <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="My recovery codes"></div>
|
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="My recovery codes"></div>
|
||||||
<div class="form-group"><label class="label" for="f-body">body</label>
|
<div class="form-group"><label class="label" for="f-body">body</label>
|
||||||
<textarea id="f-body" rows="10" placeholder="paste secrets here">${escapeHtml(body)}</textarea></div>
|
<textarea id="f-body" rows="10" placeholder="paste secrets here">${escapeHtml(body)}</textarea></div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
/// (TOTP vs Steam Guard) and a single secret input.
|
/// (TOTP vs Steam Guard) and a single secret input.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
|
||||||
|
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
|
||||||
import type { Item, ItemId, ManifestEntry, Section, TotpKind, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, Section, TotpKind, AttachmentRef } from '../../../shared/types';
|
||||||
import { base32Decode, base32Encode } from '../../../shared/base32';
|
import { base32Decode, base32Encode } from '../../../shared/base32';
|
||||||
import {
|
import {
|
||||||
@@ -218,7 +219,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
<button class="btn" id="popout-btn" title="Open in tab">⤴</button>
|
||||||
</div>
|
</div>
|
||||||
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
|
||||||
<div class="form-group"><label class="label" for="f-title">title <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="GitHub"></div>
|
<input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="GitHub"></div>
|
||||||
<div class="form-group"><label class="label">kind</label>
|
<div class="form-group"><label class="label">kind</label>
|
||||||
<div class="inline-row">
|
<div class="inline-row">
|
||||||
@@ -227,7 +228,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
</div>
|
</div>
|
||||||
<p class="muted" style="font-size:11px;margin-top:4px;" id="kind-blurb">${formKind === 'steam' ? 'Steam Mobile Authenticator (5-char alphanumeric)' : 'Standard time-based codes (6 digits)'}</p>
|
<p class="muted" style="font-size:11px;margin-top:4px;" id="kind-blurb">${formKind === 'steam' ? 'Steam Mobile Authenticator (5-char alphanumeric)' : 'Standard time-based codes (6 digits)'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group"><label class="label" for="f-secret">secret (base32) <span class="req">*</span></label>
|
<div class="form-group"><label class="label" for="f-secret">secret (base32) ${REQUIRED_PILL_HTML}</label>
|
||||||
<input id="f-secret" type="text" value="${escapeHtml(secretB32)}" placeholder="JBSWY3DPEHPK3PXP"></div>
|
<input id="f-secret" type="text" value="${escapeHtml(secretB32)}" placeholder="JBSWY3DPEHPK3PXP"></div>
|
||||||
<div class="form-group"><label class="label" for="f-issuer">issuer</label>
|
<div class="form-group"><label class="label" for="f-issuer">issuer</label>
|
||||||
<input id="f-issuer" type="text" value="${escapeHtml(c?.issuer ?? '')}" placeholder="GitHub"></div>
|
<input id="f-issuer" type="text" value="${escapeHtml(c?.issuer ?? '')}" placeholder="GitHub"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user