Skip to content

Commit

Permalink
Merge pull request #869 from jeffhertzler/allow-typeIn-for-contentedi…
Browse files Browse the repository at this point in the history
…table

Allow type in for contenteditable
  • Loading branch information
rwjblue authored May 23, 2020
2 parents 6b2c904 + 80786e8 commit 9d6dc6c
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 17 deletions.
6 changes: 5 additions & 1 deletion addon-test-support/@ember/test-helpers/dom/-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ type Target = string | Element | Document | Window;

export default Target;

export interface HTMLElementContentEditable extends HTMLElement {
isContentEditable: true;
}

// eslint-disable-next-line require-jsdoc
export function isElement(target: any): target is Element {
return target.nodeType === Node.ELEMENT_NODE;
Expand All @@ -13,6 +17,6 @@ export function isDocument(target: any): target is Document {
}

// eslint-disable-next-line require-jsdoc
export function isContentEditable(element: Element): element is HTMLElement {
export function isContentEditable(element: Element): element is HTMLElementContentEditable {
return 'isContentEditable' in element && (element as HTMLElement).isContentEditable;
}
40 changes: 26 additions & 14 deletions addon-test-support/@ember/test-helpers/dom/type-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { __focus__ } from './focus';
import { Promise } from 'rsvp';
import fireEvent from './fire-event';
import guardForMaxlength from './-guard-for-maxlength';
import Target from './-target';
import Target, { isContentEditable, isDocument, HTMLElementContentEditable } from './-target';
import { __triggerKeyEvent__ } from './trigger-key-event';
import { log } from '@ember/test-helpers/dom/-logging';

Expand Down Expand Up @@ -47,23 +47,27 @@ export default function typeIn(target: Target, text: string, options: Options =
}

const element = getElement(target);

if (!element) {
throw new Error(`Element not found when calling \`typeIn('${target}')\``);
}
if (!isFormControl(element)) {
throw new Error('`typeIn` is only usable on form controls.');

if (isDocument(element) || (!isFormControl(element) && !isContentEditable(element))) {
throw new Error('`typeIn` is only usable on form controls or contenteditable elements.');
}

if (typeof text === 'undefined' || text === null) {
throw new Error('Must provide `text` when calling `typeIn`.');
}

if (element.disabled) {
throw new Error(`Can not \`typeIn\` disabled '${target}'.`);
}
if (isFormControl(element)) {
if (element.disabled) {
throw new Error(`Can not \`typeIn\` disabled '${target}'.`);
}

if ('readOnly' in element && element.readOnly) {
throw new Error(`Can not \`typeIn\` readonly '${target}'.`);
if ('readOnly' in element && element.readOnly) {
throw new Error(`Can not \`typeIn\` readonly '${target}'.`);
}
}

__focus__(element);
Expand All @@ -77,15 +81,18 @@ export default function typeIn(target: Target, text: string, options: Options =
}

// eslint-disable-next-line require-jsdoc
function fillOut(element: FormControl, text: string, delay: number) {
function fillOut(element: FormControl | HTMLElementContentEditable, text: string, delay: number) {
const inputFunctions = text.split('').map(character => keyEntry(element, character));
return inputFunctions.reduce((currentPromise, func) => {
return currentPromise.then(() => delayedExecute(delay)).then(func);
}, Promise.resolve(undefined));
}

// eslint-disable-next-line require-jsdoc
function keyEntry(element: FormControl, character: string): () => void {
function keyEntry(
element: FormControl | HTMLElementContentEditable,
character: string
): () => void {
let shiftKey = character === character.toUpperCase() && character !== character.toLowerCase();
let options = { shiftKey };
let characterKey = character.toUpperCase();
Expand All @@ -95,10 +102,15 @@ function keyEntry(element: FormControl, character: string): () => void {
.then(() => __triggerKeyEvent__(element, 'keydown', characterKey, options))
.then(() => __triggerKeyEvent__(element, 'keypress', characterKey, options))
.then(() => {
const newValue = element.value + character;
guardForMaxlength(element, newValue, 'typeIn');

element.value = newValue;
if (isFormControl(element)) {
const newValue = element.value + character;
guardForMaxlength(element, newValue, 'typeIn');

element.value = newValue;
} else {
const newValue = element.innerHTML + character;
element.innerHTML = newValue;
}
fireEvent(element, 'input');
})
.then(() => __triggerKeyEvent__(element, 'keyup', characterKey, options));
Expand Down
17 changes: 15 additions & 2 deletions tests/unit/dom/type-in-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,24 @@ module('DOM Helper: typeIn', function (hooks) {
assert.equal(element.value, 'foo');
});

test('typing in not a form control', async function (assert) {
test('typing in a contenteditable element', async function (assert) {
element = buildInstrumentedElement('div');
element.setAttribute('contenteditable', 'true');
await typeIn(element, 'foo');

assert.verifySteps(expectedEvents);
assert.strictEqual(document.activeElement, element, 'activeElement updated');
assert.equal(element.innerHTML, 'foo');
});

test('typing in a non-typable element', async function (assert) {
element = buildInstrumentedElement('div');

await setupContext(context);
assert.rejects(typeIn(`#${element.id}`, 'foo'), /`typeIn` is only usable on form controls/);
assert.rejects(
typeIn(`#${element.id}`, 'foo'),
/`typeIn` is only usable on form controls or contenteditable elements/
);
});

test('typing in a disabled element', async function (assert) {
Expand Down

0 comments on commit 9d6dc6c

Please sign in to comment.