fix(ext/login): dispatch input event after regenerate sets password (B1)
Programmatic input.value = newPassword does not fire input events, so
the strength-meter listener at shared/form-affordances/password-tools.ts:65
never re-rates the new value — meter stays stuck on the prior reading.
Extract applyGeneratedPassword(input, value) helper that sets value, type,
then dispatches new InputEvent('input', { bubbles: true }). Vitest covers
the dispatch + a sanity check that bubbling listeners fire.
This commit is contained in:
@@ -26,7 +26,7 @@ vi.mock('../../../../setup/setup-helpers', () => ({
|
|||||||
entropyText: vi.fn(() => ''),
|
entropyText: vi.fn(() => ''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { renderForm } from '../login';
|
import { renderForm, applyGeneratedPassword } from '../login';
|
||||||
import { sendMessage } from '../../../../shared/state';
|
import { sendMessage } from '../../../../shared/state';
|
||||||
|
|
||||||
describe('login form smart inputs', () => {
|
describe('login form smart inputs', () => {
|
||||||
@@ -154,3 +154,37 @@ describe('Login save shape', () => {
|
|||||||
expect(addCall).toBeUndefined();
|
expect(addCall).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('regenerate handler dispatches input event', () => {
|
||||||
|
it('dispatches an InputEvent on the input after value is set', () => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'password';
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
const dispatchSpy = vi.spyOn(input, 'dispatchEvent');
|
||||||
|
|
||||||
|
applyGeneratedPassword(input, 'sCMtTJkF%GN^mF#-N6D%');
|
||||||
|
|
||||||
|
expect(input.value).toBe('sCMtTJkF%GN^mF#-N6D%');
|
||||||
|
expect(input.type).toBe('text');
|
||||||
|
expect(dispatchSpy).toHaveBeenCalled();
|
||||||
|
const evt = dispatchSpy.mock.calls.find(c => c[0] instanceof InputEvent)?.[0] as InputEvent;
|
||||||
|
expect(evt).toBeDefined();
|
||||||
|
expect(evt.type).toBe('input');
|
||||||
|
expect(evt.bubbles).toBe(true);
|
||||||
|
|
||||||
|
document.body.removeChild(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bubbling listener fires when applyGeneratedPassword is called', () => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
let listenerFired = false;
|
||||||
|
input.addEventListener('input', () => { listenerFired = true; });
|
||||||
|
applyGeneratedPassword(input, 'newpass');
|
||||||
|
expect(listenerFired).toBe(true);
|
||||||
|
|
||||||
|
document.body.removeChild(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ import { wireTotpPreview, wireTotpQr } from '../../../shared/form-affordances/to
|
|||||||
import { wireNotesMonoToggle } from '../../../shared/form-affordances/notes-tools';
|
import { wireNotesMonoToggle } from '../../../shared/form-affordances/notes-tools';
|
||||||
import { scheduleRate } from '../../../setup/setup-helpers';
|
import { scheduleRate } from '../../../setup/setup-helpers';
|
||||||
|
|
||||||
|
/// Sets a generated password on an input, reveals it as plain text, then
|
||||||
|
/// dispatches a synthetic InputEvent so listeners (e.g. the strength meter)
|
||||||
|
/// re-evaluate the new value.
|
||||||
|
export function applyGeneratedPassword(input: HTMLInputElement, value: string): void {
|
||||||
|
input.value = value;
|
||||||
|
input.type = 'text';
|
||||||
|
input.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
/// Called by the dispatcher before each render. Stops any in-flight
|
/// Called by the dispatcher before each render. Stops any in-flight
|
||||||
/// tickers / intervals / listeners the previous view may have attached.
|
/// tickers / intervals / listeners the previous view may have attached.
|
||||||
export function teardown(): void {
|
export function teardown(): void {
|
||||||
@@ -433,7 +442,7 @@ export function renderForm(
|
|||||||
context: 'fill-field',
|
context: 'fill-field',
|
||||||
onPicked: (value) => {
|
onPicked: (value) => {
|
||||||
const pw = document.getElementById('f-password') as HTMLInputElement | null;
|
const pw = document.getElementById('f-password') as HTMLInputElement | null;
|
||||||
if (pw) { pw.value = value; pw.type = 'text'; }
|
if (pw) applyGeneratedPassword(pw, value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user