Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add internal has-input-value-changed event and reset on programmatic value clear (CP: 14) #1044

Merged
merged 12 commits into from
May 8, 2023
29 changes: 28 additions & 1 deletion src/vaadin-combo-box-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@
notify: true
},

/**
* Whether the input element has a non-empty value.
* @protected
*/
_hasInputValue: {
type: Boolean,
value: false,
observer: '_hasInputValueChanged',
},

/**
* Path for label of the item. If `items` is an array of objects, the
* `itemLabelPath` is used to fetch the displayed string label for each
Expand Down Expand Up @@ -265,7 +275,8 @@
'_templateOrRendererChanged(_itemTemplate, renderer)',
'_loadingChanged(loading)',
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
'_toggleElementChanged(_toggleElement)'
'_toggleElementChanged(_toggleElement)',
'_inputElementValueChanged(_inputElementValue)'
];
}

Expand Down Expand Up @@ -636,6 +647,7 @@
this.selectedItem = null;

if (this.allowCustomValue) {
this._inputElementValue = '';
this.value = '';
}

Expand Down Expand Up @@ -767,6 +779,10 @@
}
}

_inputElementValueChanged(inputElementValue) {
this._hasInputValue = inputElementValue != null ? inputElementValue.length > 0 : false;
}

/** @private */
_filterFromInput(e) {
if (!this.opened && !e.__fromClearButton && !this.autoOpenDisabled) {
Expand Down Expand Up @@ -1093,6 +1109,17 @@
this._clear();
}

/**
* Observer to notify about the change of private property.
*
* @private
*/
_hasInputValueChanged(hasValue, oldHasValue) {
if (hasValue || oldHasValue) {
this.dispatchEvent(new CustomEvent('has-input-value-changed'));
}
}

/**
* @param {boolean} invalid
* @protected
Expand Down
150 changes: 150 additions & 0 deletions test/events.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<!doctype html>
<html>

<head>
<meta charset="UTF-8">
<title>vaadin-combo-box events tests</title>

<script src="../../web-component-tester/browser.js"></script>
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../test-fixture/test-fixture.html">
<script src="../../iron-test-helpers/mock-interactions.js"></script>
<link rel="import" href="common-imports.html">
<script src="common.js"></script>
</head>

<body>
<test-fixture id="combo-box">
<template>
<vaadin-combo-box></vaadin-combo-box>
</template>
</test-fixture>
<script>
(ios ? describe.skip : describe)('events', () => {
function inputChar(target, char) {
target.value += char;
MockInteractions.keyDownOn(target, char.charCodeAt(0));
target.dispatchEvent(new CustomEvent('input', {bubbles: true, composed: true}));
}

function inputText(target, text) {
for (var i = 0; i < text.length; i++) {
inputChar(target, text[i]);
}
}

function arrowDown(target) {
MockInteractions.keyDownOn(target, 40);
}

function enter(target) {
MockInteractions.pressEnter(target);
}

function space(target) {
MockInteractions.pressSpace(target);
}

function esc(target) {
MockInteractions.keyDownOn(target, 27, null, 'Escape');
}

describe('has-input-value-changed event', () => {
let comboBox, input, clearButton, hasInputValueChangedSpy, valueChangedSpy;

beforeEach(async() => {
hasInputValueChangedSpy = sinon.spy();
valueChangedSpy = sinon.spy();
comboBox = fixture('combo-box');
comboBox.allowCustomValue = true;
input = comboBox.inputElement.inputElement;
comboBox.addEventListener('has-input-value-changed', hasInputValueChangedSpy);
comboBox.addEventListener('value-changed', valueChangedSpy);
input.focus();
});

describe('without value', () => {
describe('with user input', () => {
beforeEach(async() => {
inputText(input, 'foo');
hasInputValueChangedSpy.reset();
valueChangedSpy.reset();
});

it('should be fired when clearing the user input with Esc', async() => {
expect(input.value).to.equal('foo');
// Clear selection in the dropdown.
esc(input);
// Clear the user input.
esc(input);
expect(input.value).to.be.empty;
expect(hasInputValueChangedSpy.calledOnce).to.be.true;
});

it('should not fire the event when modifying input', async() => {
input.value = 'foobar';
input.dispatchEvent(new CustomEvent('input'));
expect(hasInputValueChangedSpy.called).to.be.false;
});

it('should fire the event once when removing input', async() => {
input.value = '';
input.dispatchEvent(new CustomEvent('input'));
expect(hasInputValueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledBefore(valueChangedSpy)).to.be.true;
});

it('should fire the event once on programmatic clear', async() => {
comboBox._clear();
expect(hasInputValueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledBefore(valueChangedSpy)).to.be.true;
});
});

describe('without user input', () => {
it('should fire the event once when entering input', async() => {
input.value = 'foo';
input.dispatchEvent(new CustomEvent('input'));
expect(hasInputValueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledBefore(valueChangedSpy)).to.be.true;
});

it('should not fire the event on programmatic clear', async() => {
comboBox._clear();
expect(hasInputValueChangedSpy.called).to.be.false;
});
});
});

describe('with value', () => {
beforeEach(async() => {
comboBox.clearButtonVisible = true;
clearButton = comboBox.inputElement.$.clearButton;
inputText(input, 'foo');
enter(input);
valueChangedSpy.reset();
hasInputValueChangedSpy.reset();
});

it('should be fired on clear button click', () => {
expect(input.value).to.equal('foo');
clearButton.click();
expect(input.value).to.be.empty;
expect(valueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledBefore(valueChangedSpy)).to.be.true;
});

it('should be fired when clearing the value with Esc', async() => {
esc(input);
expect(input.value).to.be.empty;
expect(valueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledOnce).to.be.true;
expect(hasInputValueChangedSpy.calledBefore(valueChangedSpy)).to.be.true;
});
});
});
});
</script>
</body>
</html>
3 changes: 2 additions & 1 deletion test/test-suites.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ window.VaadinComboBoxSuites = [
'item-template.html',
'vaadin-combo-box-dropdown.html',
'lazy-loading.html',
'validation.html'
'validation.html',
'events.html'
];

if (isPolymer2) {
Expand Down