From 026b94092e9e052924aaecedff52cfb0d49be121 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Fri, 1 May 2026 19:48:32 -0400 Subject: [PATCH] ext(affordances): wirePasswordReveal toggle --- .../__tests__/password-tools.test.ts | 49 +++++++++++++++++++ .../shared/form-affordances/password-tools.ts | 28 +++++++++++ 2 files changed, 77 insertions(+) create mode 100644 extension/src/shared/form-affordances/__tests__/password-tools.test.ts create mode 100644 extension/src/shared/form-affordances/password-tools.ts diff --git a/extension/src/shared/form-affordances/__tests__/password-tools.test.ts b/extension/src/shared/form-affordances/__tests__/password-tools.test.ts new file mode 100644 index 0000000..168acc4 --- /dev/null +++ b/extension/src/shared/form-affordances/__tests__/password-tools.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { wirePasswordReveal } from '../password-tools'; + +describe('wirePasswordReveal', () => { + let form: HTMLElement; + + beforeEach(() => { + form = document.createElement('div'); + form.innerHTML = ` + + + `; + document.body.appendChild(form); + }); + + afterEach(() => { + document.body.removeChild(form); + }); + + it('flips input.type and glyph on click', () => { + wirePasswordReveal(form); + const btn = form.querySelector('#reveal-password-btn') as HTMLButtonElement; + const input = form.querySelector('#f-password') as HTMLInputElement; + expect(input.type).toBe('password'); + expect(btn.textContent).toBe('⊙'); + + btn.click(); + expect(input.type).toBe('text'); + expect(btn.textContent).toBe('⊘'); + expect(btn.title).toBe('hide'); + + btn.click(); + expect(input.type).toBe('password'); + expect(btn.textContent).toBe('⊙'); + expect(btn.title).toBe('reveal'); + }); + + it('teardown returned by wirePasswordReveal resets to password', () => { + const teardown = wirePasswordReveal(form); + const btn = form.querySelector('#reveal-password-btn') as HTMLButtonElement; + const input = form.querySelector('#f-password') as HTMLInputElement; + btn.click(); // now revealed + expect(input.type).toBe('text'); + teardown(); + expect(input.type).toBe('password'); + expect(btn.textContent).toBe('⊙'); + expect(btn.title).toBe('reveal'); + }); +}); diff --git a/extension/src/shared/form-affordances/password-tools.ts b/extension/src/shared/form-affordances/password-tools.ts new file mode 100644 index 0000000..dacf023 --- /dev/null +++ b/extension/src/shared/form-affordances/password-tools.ts @@ -0,0 +1,28 @@ +import { GLYPH_REVEAL, GLYPH_HIDE } from '../glyphs'; + +/// Returns a teardown fn the caller must invoke on unmount. +export function wirePasswordReveal(form: HTMLElement): () => void { + const btn = form.querySelector('#reveal-password-btn'); + const input = form.querySelector('#f-password'); + if (!btn || !input) return () => {}; + + const handler = () => { + if (input.type === 'password') { + input.type = 'text'; + btn.textContent = GLYPH_HIDE; + btn.title = 'hide'; + } else { + input.type = 'password'; + btn.textContent = GLYPH_REVEAL; + btn.title = 'reveal'; + } + }; + btn.addEventListener('click', handler); + + return () => { + btn.removeEventListener('click', handler); + input.type = 'password'; + btn.textContent = GLYPH_REVEAL; + btn.title = 'reveal'; + }; +}