Skip to content

Commit

Permalink
fix: dispatch change event on blur after prevented mousedown (#6150)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored and vaadin-bot committed Jul 13, 2023
1 parent 161d7de commit 9cda079
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 16 deletions.
30 changes: 25 additions & 5 deletions packages/password-field/src/vaadin-password-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export class PasswordField extends TextField {
super();
this._setType('password');
this.__boundRevealButtonClick = this._onRevealButtonClick.bind(this);
this.__boundRevealButtonTouchend = this._onRevealButtonTouchend.bind(this);
this.__boundRevealButtonMouseDown = this._onRevealButtonMouseDown.bind(this);
this.__lastChange = '';
}

/** @protected */
Expand Down Expand Up @@ -157,7 +158,7 @@ export class PasswordField extends TextField {
btn.disabled = this.disabled;

btn.addEventListener('click', this.__boundRevealButtonClick);
btn.addEventListener('touchend', this.__boundRevealButtonTouchend);
btn.addEventListener('mousedown', this.__boundRevealButtonMouseDown);
},
});
this.addController(this._revealButtonController);
Expand All @@ -172,6 +173,19 @@ export class PasswordField extends TextField {
}
}

/**
* Override an event listener inherited from `InputControlMixin`
* to store the value at the moment of the native `change` event.
* @param {Event} event
* @protected
* @override
*/
_onChange(event) {
super._onChange(event);

this.__lastChange = this.inputElement.value;
}

/**
* Override method inherited from `FocusMixin` to mark field as focused
* when focus moves to the reveal button using Shift Tab.
Expand Down Expand Up @@ -208,6 +222,12 @@ export class PasswordField extends TextField {

if (!focused) {
this._setPasswordVisible(false);

// Detect if `focusout` was prevented and if so, dispatch `change` event manually.
if (this.__lastChange !== this.inputElement.value) {
this.__lastChange = this.inputElement.value;
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
}
} else {
const isButtonFocused = this.getRootNode().activeElement === this._revealNode;
// Remove focus-ring from the field when the reveal button gets focused
Expand Down Expand Up @@ -243,10 +263,10 @@ export class PasswordField extends TextField {
}

/** @private */
_onRevealButtonTouchend(e) {
// Cancel the following click event
_onRevealButtonMouseDown(e) {
// Cancel the following focusout event
e.preventDefault();
this._togglePasswordVisibility();

// Focus the input to avoid problem with password still visible
// when user clicks the reveal button and then clicks outside.
this.inputElement.focus();
Expand Down
51 changes: 40 additions & 11 deletions packages/password-field/test/password-field.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync, focusout, makeSoloTouchEvent, mousedown, nextRender } from '@vaadin/testing-helpers';
import { fire, fixtureSync, focusout, makeSoloTouchEvent, mousedown, nextRender } from '@vaadin/testing-helpers';
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import '../src/vaadin-password-field.js';
Expand Down Expand Up @@ -46,24 +46,53 @@ describe('password-field', () => {
expect(input.type).to.equal('text');
});

it('should prevent touchend event on reveal button', () => {
const event1 = makeSoloTouchEvent('touchend', null, revealButton);
expect(event1.defaultPrevented).to.be.true;
expect(input.type).to.equal('text');

const event2 = makeSoloTouchEvent('touchend', null, revealButton);
expect(event2.defaultPrevented).to.be.true;
expect(input.type).to.equal('password');
it('should prevent mousedown event on reveal button', () => {
const event = fire(revealButton, 'mousedown');
expect(event.defaultPrevented).to.be.true;
});

it('should focus the input on reveal button touchend', () => {
it('should focus the input on reveal button mousedown', () => {
const spy = sinon.spy(input, 'focus');

makeSoloTouchEvent('touchend', null, revealButton);
fire(revealButton, 'mousedown');

expect(spy.calledOnce).to.be.true;
});

it('should dispatch change event on focusout after changing the value', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

input.value = 'test';

focusout(input);

expect(spy.calledOnce).to.be.true;
});

it('should not dispatch change event on focusout if value is the same', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

focusout(input);

expect(spy.called).to.be.false;
});

it('should not dispatch change event on focusout after native change', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

input.value = 'test';
fire(input, 'change');

spy.resetHistory();

focusout(input);

expect(spy.called).to.be.false;
});

it('should toggle aria-pressed attribute on reveal button click', () => {
revealButton.click();
expect(revealButton.getAttribute('aria-pressed')).to.equal('true');
Expand Down

0 comments on commit 9cda079

Please sign in to comment.