diff --git a/addon-test-support/@ember/test-helpers/dom/-logging.ts b/addon-test-support/@ember/test-helpers/dom/-logging.ts new file mode 100644 index 000000000..c4c42827c --- /dev/null +++ b/addon-test-support/@ember/test-helpers/dom/-logging.ts @@ -0,0 +1,65 @@ +import Target from './-target'; + +/** + * Logs a debug message to the console if the `testHelperLogging` query + * parameter is set. + * + * @private + * @param {string} helperName Name of the helper + * @param {string|Element} target The target element or selector + */ +export function log(helperName: string, target: Target, ...args: any[]) { + if (loggingEnabled()) { + // eslint-disable-next-line no-console + console.log(`${helperName}(${[elementToString(target), ...args.filter(Boolean)].join(', ')})`); + } +} + +/** + * Returns whether the test helper logging is enabled or not via the + * `testHelperLogging` query parameter. + * + * @private + * @returns {boolean} true if enabled + */ +function loggingEnabled(): boolean { + return typeof location !== 'undefined' && location.search.indexOf('testHelperLogging') !== -1; +} + +/** + * This generates a human-readable description to a DOM element. + * + * @private + * @param {*} el The element that should be described + * @returns {string} A human-readable description + */ +export function elementToString(el: unknown) { + let desc: string; + if (el instanceof NodeList) { + if (el.length === 0) { + return 'empty NodeList'; + } + desc = Array.prototype.slice + .call(el, 0, 5) + .map(elementToString) + .join(', '); + return el.length > 5 ? `${desc}... (+${el.length - 5} more)` : desc; + } + if (!(el instanceof HTMLElement || el instanceof SVGElement)) { + return String(el); + } + + desc = el.tagName.toLowerCase(); + if (el.id) { + desc += `#${el.id}`; + } + if (el.className && !(el.className instanceof SVGAnimatedString)) { + desc += `.${String(el.className).replace(/\s+/g, '.')}`; + } + Array.prototype.forEach.call(el.attributes, function(attr: Attr) { + if (attr.name !== 'class' && attr.name !== 'id') { + desc += `[${attr.name}${attr.value ? `="${attr.value}"]` : ']'}`; + } + }); + return desc; +} diff --git a/addon-test-support/@ember/test-helpers/dom/blur.ts b/addon-test-support/@ember/test-helpers/dom/blur.ts index 8b8ebcaa1..b55be8ee0 100644 --- a/addon-test-support/@ember/test-helpers/dom/blur.ts +++ b/addon-test-support/@ember/test-helpers/dom/blur.ts @@ -4,6 +4,7 @@ import settled from '../settled'; import isFocusable from './-is-focusable'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** @private @@ -51,6 +52,8 @@ export function __blur__(element: HTMLElement | SVGElement): void { blur('input'); */ export default function blur(target: Target = document.activeElement!): Promise { + log('blur', target); + return nextTickPromise().then(() => { let element = getElement(target); if (!element) { diff --git a/addon-test-support/@ember/test-helpers/dom/click.ts b/addon-test-support/@ember/test-helpers/dom/click.ts index bf10014d4..be3430a04 100644 --- a/addon-test-support/@ember/test-helpers/dom/click.ts +++ b/addon-test-support/@ember/test-helpers/dom/click.ts @@ -6,6 +6,7 @@ import isFocusable from './-is-focusable'; import { nextTickPromise } from '../-utils'; import isFormControl from './-is-form-control'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** @private @@ -69,6 +70,8 @@ export function __click__(element: Element | Document, options: MouseEventInit): click('button', { shiftKey: true }); */ export default function click(target: Target, options: MouseEventInit = {}): Promise { + log('click', target); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `click`.'); diff --git a/addon-test-support/@ember/test-helpers/dom/double-click.ts b/addon-test-support/@ember/test-helpers/dom/double-click.ts index b6a13965c..4aa218ef2 100644 --- a/addon-test-support/@ember/test-helpers/dom/double-click.ts +++ b/addon-test-support/@ember/test-helpers/dom/double-click.ts @@ -5,6 +5,7 @@ import settled from '../settled'; import isFocusable from './-is-focusable'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** @private @@ -80,6 +81,8 @@ export function __doubleClick__(element: Element | Document, options: MouseEvent doubleClick('button', { shiftKey: true }); */ export default function doubleClick(target: Target, options: MouseEventInit = {}): Promise { + log('doubleClick', target); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `doubleClick`.'); 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 184dda1ea..232ae5c07 100644 --- a/addon-test-support/@ember/test-helpers/dom/fill-in.ts +++ b/addon-test-support/@ember/test-helpers/dom/fill-in.ts @@ -5,6 +5,7 @@ import settled from '../settled'; import fireEvent from './fire-event'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** Fill the provided text into the `value` property (or set `.innerHTML` when @@ -24,6 +25,8 @@ import Target from './-target'; fillIn('input', 'hello world'); */ export default function fillIn(target: Target, text: string): Promise { + log('fillIn', target, text); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `fillIn`.'); diff --git a/addon-test-support/@ember/test-helpers/dom/focus.ts b/addon-test-support/@ember/test-helpers/dom/focus.ts index 5758bf7b6..c7fca9564 100644 --- a/addon-test-support/@ember/test-helpers/dom/focus.ts +++ b/addon-test-support/@ember/test-helpers/dom/focus.ts @@ -4,6 +4,7 @@ import settled from '../settled'; import isFocusable from './-is-focusable'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** @private @@ -54,6 +55,8 @@ export function __focus__(element: HTMLElement | SVGElement): void { focus('input'); */ export default function focus(target: Target): Promise { + log('focus', target); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `focus`.'); diff --git a/addon-test-support/@ember/test-helpers/dom/tap.ts b/addon-test-support/@ember/test-helpers/dom/tap.ts index 5335a0809..7cf5a7df1 100644 --- a/addon-test-support/@ember/test-helpers/dom/tap.ts +++ b/addon-test-support/@ember/test-helpers/dom/tap.ts @@ -4,6 +4,7 @@ import { __click__ } from './click'; import settled from '../settled'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** Taps on the specified target. @@ -48,6 +49,8 @@ import Target from './-target'; tap('button'); */ export default function tap(target: Target, options: object = {}): Promise { + log('tap', target); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `tap`.'); diff --git a/addon-test-support/@ember/test-helpers/dom/trigger-event.ts b/addon-test-support/@ember/test-helpers/dom/trigger-event.ts index 61f58ffe6..b6d3fdeae 100644 --- a/addon-test-support/@ember/test-helpers/dom/trigger-event.ts +++ b/addon-test-support/@ember/test-helpers/dom/trigger-event.ts @@ -3,6 +3,7 @@ import fireEvent from './fire-event'; import settled from '../settled'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; /** * Triggers an event on the specified target. @@ -52,6 +53,8 @@ export default function triggerEvent( eventType: string, options?: object ): Promise { + log('triggerEvent', target, eventType); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `triggerEvent`.'); diff --git a/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts b/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts index 99695b4f0..dc30796ab 100644 --- a/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts +++ b/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts @@ -5,6 +5,7 @@ import settled from '../settled'; import { KEYBOARD_EVENT_TYPES, KeyboardEventType, isKeyboardEventType } from './fire-event'; import { nextTickPromise, isNumeric } from '../-utils'; import Target from './-target'; +import { log } from '@ember/test-helpers/dom/-logging'; export interface KeyModifiers { ctrlKey?: boolean; @@ -183,6 +184,8 @@ export default function triggerKeyEvent( key: number | string, modifiers: KeyModifiers = DEFAULT_MODIFIERS ): Promise { + log('triggerKeyEvent', target, eventType, key); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `triggerKeyEvent`.'); 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 de7354264..9d7e0002b 100644 --- a/addon-test-support/@ember/test-helpers/dom/type-in.ts +++ b/addon-test-support/@ember/test-helpers/dom/type-in.ts @@ -8,6 +8,7 @@ import { Promise } from 'rsvp'; import fireEvent from './fire-event'; import Target from './-target'; import { __triggerKeyEvent__ } from './trigger-key-event'; +import { log } from '@ember/test-helpers/dom/-logging'; export interface Options { delay?: number; @@ -38,6 +39,8 @@ export interface Options { * typeIn('hello world'); */ export default function typeIn(target: Target, text: string, options: Options = {}): Promise { + log('typeIn', target, text); + return nextTickPromise().then(() => { if (!target) { throw new Error('Must pass an element or selector to `typeIn`.'); diff --git a/tests/unit/dom/logging-test.js b/tests/unit/dom/logging-test.js new file mode 100644 index 000000000..14b434136 --- /dev/null +++ b/tests/unit/dom/logging-test.js @@ -0,0 +1,97 @@ +import { module, test } from 'qunit'; +import { + render, + settled, + setupContext, + setupRenderingContext, + teardownContext, + teardownRenderingContext, +} from '@ember/test-helpers'; +import hasEmberVersion from '@ember/test-helpers/has-ember-version'; +import { elementToString } from '@ember/test-helpers/dom/-logging'; +import hbs from 'htmlbars-inline-precompile'; + +module('elementToString()', function(hooks) { + if (!hasEmberVersion(2, 4)) { + return; + } + + hooks.beforeEach(async function() { + await setupContext(this); + await setupRenderingContext(this); + }); + + hooks.afterEach(async function() { + await settled(); + await teardownRenderingContext(this); + await teardownContext(this); + }); + + module('NodeLists', function() { + test('empty NodeList', async function(assert) { + await render(hbs``); + assert.equal(elementToString(this.element.querySelectorAll('h1')), 'empty NodeList'); + }); + + test('with single element', async function(assert) { + await render(hbs`

`); + assert.equal(elementToString(this.element.querySelectorAll('h1')), 'h1'); + }); + + test('with three elements', async function(assert) { + await render(hbs`

`); + assert.equal(elementToString(this.element.querySelectorAll('h1')), 'h1, h1, h1.foo'); + }); + + test('with five elements', async function(assert) { + await render(hbs`

`); + assert.equal(elementToString(this.element.querySelectorAll('h1')), 'h1, h1, h1.foo, h1, h1'); + }); + + test('with six elements', async function(assert) { + await render(hbs`

`); + assert.equal( + elementToString(this.element.querySelectorAll('h1')), + 'h1, h1, h1.foo, h1, h1... (+1 more)' + ); + }); + + test('with ten elements', async function(assert) { + await render( + hbs`

` + ); + assert.equal( + elementToString(this.element.querySelectorAll('h1')), + 'h1, h1, h1.foo, h1, h1... (+5 more)' + ); + }); + }); + + test('strings', async function(assert) { + assert.equal(elementToString('h1'), 'h1'); + assert.equal(elementToString('[data-test-foo]'), '[data-test-foo]'); + }); + + module('HTMLElements', function() { + test('IDs', async function(assert) { + await render(hbs`
`); + assert.equal(elementToString(this.element.querySelector('div')), 'div#map'); + }); + + test('CSS classes', async function(assert) { + await render(hbs`
`); + assert.equal(elementToString(this.element.querySelector('div')), 'div.foo.bar'); + }); + + test('attributes', async function(assert) { + await render(hbs``); + assert.equal(elementToString(this.element.querySelector('input')), 'input[type="password"]'); + + await render(hbs``); + assert.equal( + elementToString(this.element.querySelector('input')), + 'input[data-test-username]' + ); + }); + }); +});