diff --git a/addon-test-support/@ember/test-helpers/dom/-is-maxlength-constrained.ts b/addon-test-support/@ember/test-helpers/dom/-is-maxlength-constrained.ts new file mode 100644 index 000000000..8d855cf3d --- /dev/null +++ b/addon-test-support/@ember/test-helpers/dom/-is-maxlength-constrained.ts @@ -0,0 +1,15 @@ +/** + @private + @param {Element} element the element to check + @returns {boolean} `true` when the element should constrain input by the maxlength attribute, `false` otherwise +*/ +export default function isMaxLengthConstrained(element: Element) { + // ref: https://html.spec.whatwg.org/multipage/input.html#concept-input-apply + const constrainedInputTypes = ['text', 'search', 'url', 'tel', 'email', 'password']; + return ( + element.hasAttribute('maxlength') && + (element.tagName === 'TEXTAREA' || + (element.tagName === 'INPUT' && + constrainedInputTypes.indexOf((element as HTMLInputElement).type) > -1)) + ); +} diff --git a/addon-test-support/@ember/test-helpers/dom/fill-in.ts b/addon-test-support/@ember/test-helpers/dom/fill-in.ts index 1e20fbde9..cbaffbcce 100644 --- a/addon-test-support/@ember/test-helpers/dom/fill-in.ts +++ b/addon-test-support/@ember/test-helpers/dom/fill-in.ts @@ -1,5 +1,6 @@ import getElement from './-get-element'; import isFormControl from './-is-form-control'; +import isMaxLengthConstrained from './-is-maxlength-constrained'; import { __focus__ } from './focus'; import settled from '../settled'; import fireEvent from './fire-event'; @@ -52,6 +53,16 @@ export default function fillIn(target: Target, text: string): Promise { __focus__(element); + // if (isMaxLengthConstrained(element) && text.length > element.getAttribute('maxlength')) + // throw + // remove else block + // should this be before the focus? + if (isMaxLengthConstrained(element)) { + element.value = text.slice(0, element.getAttribute('maxlength')); + } else { + element.value = text; + } + element.value = text; } else if (isContentEditable(element)) { __focus__(element); @@ -60,7 +71,6 @@ export default function fillIn(target: Target, text: string): Promise { } else { throw new Error('`fillIn` is only usable on form controls or contenteditable elements.'); } - fireEvent(element, 'input'); fireEvent(element, 'change'); diff --git a/addon-test-support/@ember/test-helpers/dom/type-in.ts b/addon-test-support/@ember/test-helpers/dom/type-in.ts index 113038f16..320e3c55c 100644 --- a/addon-test-support/@ember/test-helpers/dom/type-in.ts +++ b/addon-test-support/@ember/test-helpers/dom/type-in.ts @@ -5,6 +5,7 @@ import isFormControl, { FormControl } from './-is-form-control'; import { __focus__ } from './focus'; import { Promise } from 'rsvp'; import fireEvent from './fire-event'; +import isMaxLengthConstrained from './-is-maxlength-constrained'; import Target from './-target'; import { __triggerKeyEvent__ } from './trigger-key-event'; import { log } from '@ember/test-helpers/dom/-logging'; @@ -94,8 +95,18 @@ function keyEntry(element: FormControl, character: string): () => void { .then(() => __triggerKeyEvent__(element, 'keydown', characterKey, options)) .then(() => __triggerKeyEvent__(element, 'keypress', characterKey, options)) .then(() => { - element.value = element.value + character; - fireEvent(element, 'input'); + const newValue = element.value + character; + const shouldTruncate = + isMaxLengthConstrained(element) && + newValue.length > Number(element.getAttribute('maxlength')); + // if (shouldTruncate) + // throw + // else + // set value, fire input event + if (!shouldTruncate) { + element.value = newValue; + fireEvent(element, 'input'); + } }) .then(() => __triggerKeyEvent__(element, 'keyup', characterKey, options)); }; diff --git a/tests/unit/dom/fill-in-test.js b/tests/unit/dom/fill-in-test.js index 258e80fea..ff54e6476 100644 --- a/tests/unit/dom/fill-in-test.js +++ b/tests/unit/dom/fill-in-test.js @@ -199,4 +199,39 @@ module('DOM Helper: fillIn', function (hooks) { assert.strictEqual(document.activeElement, element, 'activeElement updated'); assert.equal(element.value, ''); }); + + test('filling an input with a maxlength with suitable value', async function(assert) { + element = buildInstrumentedElement('input'); + const maxLengthString = 'f'; + element.setAttribute('maxlength', maxLengthString.length); + + await setupContext(context); + + await fillIn(element, maxLengthString); + + assert.verifySteps(clickSteps); + assert.equal( + element.value, + maxLengthString, + `fillIn respects input attribute [maxlength=${maxLengthString.length}]` + ); + }); + + test('filling an input with a maxlength with too long value', async function(assert) { + element = buildInstrumentedElement('input'); + const maxLengthString = 'f'; + const tooLongString = maxLengthString.concat('oo'); + element.setAttribute('maxlength', maxLengthString.length); + + await setupContext(context); + + await fillIn(element, tooLongString); + + assert.verifySteps(clickSteps); + assert.equal( + element.value, + maxLengthString, + `fillIn respects input attribute [maxlength=${maxLengthString.length}]` + ); + }); }); diff --git a/tests/unit/dom/type-in-test.js b/tests/unit/dom/type-in-test.js index 6a821526d..4502ba25f 100644 --- a/tests/unit/dom/type-in-test.js +++ b/tests/unit/dom/type-in-test.js @@ -208,4 +208,26 @@ module('DOM Helper: typeIn', function (hooks) { assert.verifySteps(expectedEvents); assert.equal(runcount, 1, 'debounced function only called once'); }); + + test('filling an input with a maxlength', async function(assert) { + element = buildInstrumentedElement('input'); + const maxLengthString = 'f'; + const tooLongString = maxLengthString.concat('oo'); + element.setAttribute('maxlength', maxLengthString.length); + + await setupContext(context); + + await typeIn(`#${element.id}`, tooLongString); + // only the first 'input' event is fired since the input is truncated + const truncatedEvents = expectedEvents.filter((type, index) => { + return type !== 'input' || index === 4; + }); + + assert.verifySteps(truncatedEvents); + assert.equal( + element.value, + maxLengthString, + `typeIn respects input attribute [maxlength=${maxLengthString.length}]` + ); + }); });