diff --git a/addon-test-support/-private/better-errors.js b/addon-test-support/-private/better-errors.js index a1a457d7..9f100eae 100644 --- a/addon-test-support/-private/better-errors.js +++ b/addon-test-support/-private/better-errors.js @@ -1,5 +1,6 @@ import EmberError from '@ember/error'; import Ceibo from 'ceibo'; +import { getRoot } from './helpers'; export const ELEMENT_NOT_FOUND = 'Element not found.'; @@ -8,13 +9,18 @@ export const ELEMENT_NOT_FOUND = 'Element not found.'; * * @param {Ceibo} node PageObject node containing the property that triggered the error * @param {string} key Key of PageObject property tht triggered the error - * @param {string} msg Error message + * @param {Error|string} err Error or error text * @param {Object} options * @param {string} options.selector Selector of element targeted by PageObject property * @return {Ember.Error} */ -export function throwBetterError(node, key, msg, { selector } = {}) { - let path = [key]; +export function throwBetterError(node, key, err, { selector } = {}) { + const executionContext = getRoot(node).__execution_context__; + const _key = key || (executionContext && executionContext.key); + + let fullErrorMessage = typeof err === Error ? err.message : err.toString(); + + let path = []; let current; for (current = node; current; current = Ceibo.parent(current)) { @@ -22,11 +28,16 @@ export function throwBetterError(node, key, msg, { selector } = {}) { } path[0] = 'page'; + if (_key && _key.trim().length > 0) { + path.push(_key); + } - let fullErrorMessage = `${msg}\n\nPageObject: '${path.join('.')}'`; + if (path.length > 0) { + fullErrorMessage += `\n\nPageObject: '${path.join('.')}'`; + } - if (selector) { - fullErrorMessage = `${fullErrorMessage}\n Selector: '${selector}'`; + if (typeof selector === 'string' && selector.trim().length > 0) { + fullErrorMessage += `\n Selector: '${selector}'`; } console.error(fullErrorMessage); diff --git a/addon-test-support/-private/dsl.js b/addon-test-support/-private/dsl.js index 2916a1ca..63ebe2c5 100644 --- a/addon-test-support/-private/dsl.js +++ b/addon-test-support/-private/dsl.js @@ -10,16 +10,17 @@ import { isPresent } from '../properties/is-present'; import { isVisible } from '../properties/is-visible'; import { text } from '../properties/text'; import { value } from '../properties/value'; - import { getRoot } from './helpers'; const thenDescriptor = { isDescriptor: true, - value() { - const root = getRoot(this); - const chainedRoot = root._chainedTree || root; + get() { + return function() { + // this is a chainable node instance + const { _promise } = getRoot(this).__execution_context__; - return chainedRoot._promise.then(...arguments); + return _promise.then(...arguments); + } } }; diff --git a/addon-test-support/-private/execution_context/acceptance.js b/addon-test-support/-private/execution_context/acceptance.js index ef17352d..62aceaf7 100644 --- a/addon-test-support/-private/execution_context/acceptance.js +++ b/addon-test-support/-private/execution_context/acceptance.js @@ -1,23 +1,22 @@ -import { run } from '../action'; -import { - guardMultiple, - buildSelector, - findClosestValue -} from '../helpers'; +import run from '../run'; +import $ from '-jquery'; import { fillElement, assertFocusable } from './helpers'; -import { - ELEMENT_NOT_FOUND, - throwBetterError -} from '../better-errors'; export default function AcceptanceExecutionContext(pageObjectNode) { this.pageObjectNode = pageObjectNode; } AcceptanceExecutionContext.prototype = { + get testContainer() { + // @todo: fix usage of private `_element` + return this.testContext ? + this.testContext._element : + '#ember-testing'; + }, + andThen(cb) { return window.wait().then(() => { cb(this); @@ -33,103 +32,36 @@ AcceptanceExecutionContext.prototype = { visit(path); }, - click(selector, container) { + click(element) { /* global click */ - click(selector, container); + click(element); }, - fillIn(selector, container, options, content) { - let $selection = find(selector, container || findClosestValue(this.pageObjectNode, 'testContainer')); - + fillIn(element, content) { /* global focus */ - focus($selection); + focus(element); - fillElement($selection, content, { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); + fillElement(element, content); /* global triggerEvent */ - triggerEvent(selector, container, 'input'); - triggerEvent(selector, container, 'change'); + triggerEvent(element, 'input'); + triggerEvent(element, 'change'); }, - triggerEvent(selector, container, options, eventName, eventOptions) { + triggerEvent(element, eventName, eventOptions) { /* global triggerEvent */ - triggerEvent(selector, container, eventName, eventOptions); + triggerEvent(element, eventName, eventOptions); }, - focus(selector, options) { - let $selection = this.findWithAssert(selector, options); - - assertFocusable($selection[0], { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); - - $selection.focus(); - }, - - blur(selector, options) { - let $selection = this.findWithAssert(selector, options); - - assertFocusable($selection[0], { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); + focus(element) { + assertFocusable(element); - $selection.blur(); + $(element).focus(); }, - assertElementExists(selector, options) { - /* global find */ - let result = find(selector, options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer')); + blur(element) { + assertFocusable(element); - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } + $(element).blur(); }, - - find(selector, options) { - let result; - - selector = buildSelector(this.pageObjectNode, selector, options); - - /* global find */ - result = find(selector, options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer')); - - guardMultiple(result, selector, options.multiple); - - return result; - }, - - findWithAssert(selector, options) { - let result; - - selector = buildSelector(this.pageObjectNode, selector, options); - - /* global find */ - result = find(selector, options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer')); - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } - - guardMultiple(result, selector, options.multiple); - - return result; - } }; diff --git a/addon-test-support/-private/execution_context/helpers.js b/addon-test-support/-private/execution_context/helpers.js index 7cc81be7..4ddbc7d1 100644 --- a/addon-test-support/-private/execution_context/helpers.js +++ b/addon-test-support/-private/execution_context/helpers.js @@ -1,7 +1,3 @@ -import { - throwBetterError -} from '../better-errors'; - import $ from '-jquery'; /** @@ -19,19 +15,13 @@ import $ from '-jquery'; * * @throws Will throw an error if called on a contenteditable element that has `contenteditable="false"` */ -export function fillElement(selection, content, { selector, pageObjectNode, pageObjectKey }) { +export function fillElement(selection, content) { const $selection = $(selection); if ($selection.is('[contenteditable][contenteditable!="false"]')) { $selection.html(content); } else if ($selection.is('[contenteditable="false"]')) { - throwBetterError( - pageObjectNode, - pageObjectKey, - 'Element cannot be filled because it has `contenteditable="false"`.', { - selector - } - ); + throw new Error('Element cannot be filled because it has `contenteditable="false"`.'); } else { $selection.val(content); } @@ -44,7 +34,7 @@ export function fillElement(selection, content, { selector, pageObjectNode, page * * @param {Element} element - the element to check */ -export function assertFocusable(element, { selector, pageObjectNode, pageObjectKey }) { +export function assertFocusable(element) { let $element = $(element); let error; @@ -60,12 +50,6 @@ export function assertFocusable(element, { selector, pageObjectNode, pageObjectK } if (error) { - throwBetterError( - pageObjectNode, - pageObjectKey, - `Element is not focusable because it is ${error}`, { - selector - } - ); + throw new Error(`Element is not focusable because it is ${error}`); } } diff --git a/addon-test-support/-private/execution_context/integration.js b/addon-test-support/-private/execution_context/integration.js index e8985d84..7759ef67 100644 --- a/addon-test-support/-private/execution_context/integration.js +++ b/addon-test-support/-private/execution_context/integration.js @@ -1,19 +1,10 @@ import $ from '-jquery'; -import { run } from '@ember/runloop'; -import { run as runAction } from '../action'; -import { - guardMultiple, - buildSelector, - findClosestValue -} from '../helpers'; +import { run as emberRunloopRun } from '@ember/runloop'; +import run from '../run'; import { fillElement, assertFocusable } from './helpers'; -import { - ELEMENT_NOT_FOUND, - throwBetterError -} from '../better-errors'; import wait from 'ember-test-helpers/wait'; export default function IntegrationExecutionContext(pageObjectNode, testContext) { @@ -22,8 +13,15 @@ export default function IntegrationExecutionContext(pageObjectNode, testContext) } IntegrationExecutionContext.prototype = { + get testContainer() { + // @todo: fix usage of private `_element` + return this.testContext ? + this.testContext._element : + '#ember-testing'; + }, + andThen(cb) { - run(() => { + emberRunloopRun(() => { cb(this) }); @@ -31,130 +29,37 @@ IntegrationExecutionContext.prototype = { }, runAsync(cb) { - return runAction(this.pageObjectNode, cb); + return run(this.pageObjectNode, cb); }, visit() {}, - click(selector, container) { - this.$(selector, container).click(); + click(element) { + $(element).click(); }, - fillIn(selector, container, options, content) { - let $selection = this.$(selector, container); - - fillElement($selection, content, { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); - - $selection.trigger('input'); - $selection.change(); - }, + fillIn(element, content) { + fillElement(element, content); - $(selector, container) { - if (container) { - return $(selector, container); - } else { - return this.testContext.$(selector); - } + $(element).trigger('input'); + $(element).change(); }, - triggerEvent(selector, container, options, eventName, eventOptions) { + triggerEvent(element, eventName, eventOptions) { let event = $.Event(eventName, eventOptions); - if (container) { - $(selector, container).trigger(event); - } else { - this.testContext.$(selector).trigger(event); - } - }, - - focus(selector, options) { - let $selection = this.findWithAssert(selector, options); - - assertFocusable($selection[0], { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); - - $selection.focus(); - }, - - blur(selector, options) { - let $selection = this.findWithAssert(selector, options); - - assertFocusable($selection[0], { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); - - $selection.blur(); + $(element).trigger(event); }, - assertElementExists(selector, options) { - let result; - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); + focus(element) { + assertFocusable(element); - if (container) { - result = $(selector, container); - } else { - result = this.testContext.$(selector); - } - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } + $(element).focus(); }, - find(selector, options) { - let result; - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); - - selector = buildSelector(this.pageObjectNode, selector, options); - - if (container) { - result = $(selector, container); - } else { - result = this.testContext.$(selector); - } + blur(element) { + assertFocusable(element); - guardMultiple(result, selector, options.multiple); - - return result; + $(element).blur(); }, - - findWithAssert(selector, options) { - let result; - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); - - selector = buildSelector(this.pageObjectNode, selector, options); - - if (container) { - result = $(selector, container); - } else { - result = this.testContext.$(selector); - } - - guardMultiple(result, selector, options.multiple); - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } - - return result; - } }; diff --git a/addon-test-support/-private/execution_context/native-events-context.js b/addon-test-support/-private/execution_context/native-events-context.js index e345f39b..54af3c16 100644 --- a/addon-test-support/-private/execution_context/native-events-context.js +++ b/addon-test-support/-private/execution_context/native-events-context.js @@ -1,5 +1,3 @@ -import $ from '-jquery'; - import { click, triggerEvent, @@ -8,20 +6,11 @@ import { blur } from 'ember-native-dom-helpers'; -import { run } from '../action'; -import { - guardMultiple, - buildSelector, - findClosestValue -} from '../helpers'; +import run from '../run'; import { fillElement, assertFocusable } from './helpers'; -import { - ELEMENT_NOT_FOUND, - throwBetterError -} from '../better-errors'; const KEYBOARD_EVENT_TYPES = ['keydown', 'keypress', 'keyup']; @@ -31,47 +20,29 @@ export default function ExecutionContext(pageObjectNode, testContext) { } ExecutionContext.prototype = { + get testContainer() { + // @todo: fix usage of private `_element` + return this.testContext ? + this.testContext._element : + '#ember-testing'; + }, + runAsync(cb) { return run(this.pageObjectNode, cb); }, - click(selector, container) { - const el = this.$(selector, container)[0]; + click(el) { click(el); }, - fillIn(selector, container, options, content) { - let elements = this.$(selector, container).toArray(); - - elements.forEach((el) => { - fillElement(el, content, { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); - - triggerEvent(el, 'input'); - triggerEvent(el, 'change'); - }); - }, - - $(selector, container) { - if (container) { - return $(selector, container); - } else { - // @todo: we should fixed usage of private `_element` - // after https://github.com/emberjs/ember-test-helpers/issues/184 is resolved - let testsContainer = this.testContext ? - this.testContext._element : - '#ember-testing'; + fillIn(element, content) { + fillElement(element, content); - return $(selector, testsContainer); - } + triggerEvent(element, 'input'); + triggerEvent(element, 'change'); }, - triggerEvent(selector, container, options, eventName, eventOptions) { - const element = this.$(selector, container)[0]; - + triggerEvent(element, eventName, eventOptions) { // `keyCode` is a deprecated property. // @see: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode // Due to this deprecation `ember-native-dom-helpers` doesn't accept `keyCode` as a `KeyboardEvent` option. @@ -87,76 +58,16 @@ ExecutionContext.prototype = { } }, - focus(selector, options) { - const element = this.findWithAssert(selector, options)[0]; - - assertFocusable(element, { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); + focus(element) { + assertFocusable(element); focus(element); }, - blur(selector, options) { - const element = this.findWithAssert(selector, options)[0]; - - assertFocusable(element, { - selector, - pageObjectNode: this.pageObjectNode, - pageObjectKey: options.pageObjectKey - }); + blur(element) { + assertFocusable(element); blur(element); }, - - assertElementExists(selector, options) { - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); - - let result = this.$(selector, container); - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } - }, - - find(selector, options) { - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); - - selector = buildSelector(this.pageObjectNode, selector, options); - - let result = this.$(selector, container); - - guardMultiple(result, selector, options.multiple); - - return result; - }, - - findWithAssert(selector, options) { - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); - - selector = buildSelector(this.pageObjectNode, selector, options); - - let result = this.$(selector, container); - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } - - guardMultiple(result, selector, options.multiple); - - return result; - } }; diff --git a/addon-test-support/-private/execution_context/rfc268.js b/addon-test-support/-private/execution_context/rfc268.js index f7ffff9f..3093261f 100644 --- a/addon-test-support/-private/execution_context/rfc268.js +++ b/addon-test-support/-private/execution_context/rfc268.js @@ -1,10 +1,4 @@ -import $ from '-jquery'; -import { run } from '../action'; -import { - guardMultiple, - buildSelector, - findClosestValue, -} from '../helpers'; +import run from '../run'; import { getRootElement, visit, @@ -15,16 +9,16 @@ import { focus, blur } from '../compatibility'; -import { - ELEMENT_NOT_FOUND, - throwBetterError -} from '../better-errors'; export default function ExecutionContext(pageObjectNode) { this.pageObjectNode = pageObjectNode; } ExecutionContext.prototype = { + get testContainer() { + return getRootElement(); + }, + runAsync(cb) { return run(this.pageObjectNode, cb); }, @@ -33,92 +27,29 @@ ExecutionContext.prototype = { return visit(path); }, - click(selector, container, options) { - return this.invokeHelper(selector, options, click); + click(element) { + return click(element); }, - fillIn(selector, container, options, content) { - return this.invokeHelper(selector, options, fillIn, content); + fillIn(selector, content) { + return fillIn(selector, content); }, - triggerEvent(selector, container, options, eventName, eventOptions) { + triggerEvent(element, eventName, eventOptions) { if (typeof eventOptions.key !== 'undefined' || typeof eventOptions.keyCode !== 'undefined') { const key = eventOptions.key || eventOptions.keyCode; - return this.invokeHelper(selector, options, triggerKeyEvent, eventName, key, eventOptions); - } - - return this.invokeHelper(selector, options, triggerEvent, eventName, eventOptions); - }, - - focus(selector, options) { - selector = buildSelector(this.pageObjectNode, selector, options); - return this.invokeHelper(selector, options, focus); - }, - - blur(selector, options) { - selector = buildSelector(this.pageObjectNode, selector, options); - return this.invokeHelper(selector, options, blur); - }, - - assertElementExists(selector, options) { - let result = this.getElements(selector, options); - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); + return triggerKeyEvent(element, eventName, key, eventOptions); } - }, - find(selector, options) { - selector = buildSelector(this.pageObjectNode, selector, options); - let result = this.getElements(selector, options); - - guardMultiple(result, selector, options.multiple); - - return result; + return triggerEvent(element, eventName, eventOptions); }, - findWithAssert(selector, options) { - selector = buildSelector(this.pageObjectNode, selector, options); - let result = this.getElements(selector, options); - - guardMultiple(result, selector, options.multiple); - - if (result.length === 0) { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - ELEMENT_NOT_FOUND, - { selector } - ); - } - - return result; + focus(element) { + return focus(element); }, - getElements(selector, options) { - let container = options.testContainer || findClosestValue(this.pageObjectNode, 'testContainer'); - if (container) { - return $(selector, container); - } else { - return $(selector, getRootElement()); - } + blur(element) { + return blur(element); }, - - invokeHelper(selector, options, helper, ...args) { - let element = this.getElements(selector, options)[0]; - return helper(element, ...args).catch((e) => { - throwBetterError( - this.pageObjectNode, - options.pageObjectKey, - e.message || e.toString(), - { selector } - ); - }); - } }; diff --git a/addon-test-support/-private/helpers.js b/addon-test-support/-private/helpers.js index 8a6a4b05..90c882d4 100644 --- a/addon-test-support/-private/helpers.js +++ b/addon-test-support/-private/helpers.js @@ -1,6 +1,5 @@ export { assign } from '@ember/polyfills'; import { A } from '@ember/array'; -import { assert } from '@ember/debug'; import { get } from '@ember/object'; import { isPresent } from '@ember/utils'; import Ceibo from 'ceibo'; @@ -92,13 +91,6 @@ class Selector { } } -export function guardMultiple(items, selector, supportMultiple) { - assert( - `"${selector}" matched more than one element. If this is not an error use { multiple: true }`, - supportMultiple || items.length <= 1 - ); -} - /** * @public * @@ -143,7 +135,12 @@ export function guardMultiple(items, selector, supportMultiple) { * @return {string} Fully qualified selector */ export function buildSelector(node, targetSelector, options) { - return (new Selector(node, options.scope, targetSelector, options)).toString(); + return (new Selector( + node, + options.scope, + targetSelector, + options + )).toString(); } /** diff --git a/addon-test-support/-private/action.js b/addon-test-support/-private/run.js similarity index 78% rename from addon-test-support/-private/action.js rename to addon-test-support/-private/run.js index 4a58e7b8..cb53da1f 100644 --- a/addon-test-support/-private/action.js +++ b/addon-test-support/-private/run.js @@ -10,33 +10,36 @@ import Ceibo from 'ceibo'; * @param {Function} cb Some async activity callback * @returns {Ceibo} */ -export function run(node, cb) { - const adapter = getExecutionContext(node); - const chainedRoot = getRoot(node)._chainedTree; +export default function run(node, cb) { + const chainedNode = chainable(node); + const _cb = cb.bind(chainedNode); + + let executionContext; + const chainedRoot = getRoot(chainedNode); + if (isChainedNode(node)) { + executionContext = chainedRoot.__execution_context__; + } else { + executionContext = chainedRoot.__execution_context__ = getExecutionContext(chainedNode); + } - if (typeof adapter.andThen === 'function') { + if (typeof executionContext.andThen === 'function') { // With old ember-testing helpers, we don't make the difference between // chanined VS independent action invocations. Awaiting for the previous // action settlement, before invoke a new action, is a part of // the legacy testing helpers adapters for backward compat reasons - chainedRoot._promise = adapter.andThen(cb); - - return node; - } else if (!chainedRoot) { + executionContext._promise = executionContext.andThen(_cb); + } else if (isChainedNode(node)) { // Our root is already the root of the chained tree, // we need to wait on its promise if it has one so the // previous invocations can resolve before we run ours. - let root = getRoot(node) - root._promise = resolve(root._promise).then(() => cb(adapter)); - - return node; + executionContext._promise = resolve(executionContext._promise).then(() => _cb(executionContext)); } else { // Store our invocation result on the chained root // so that chained calls can find it to wait on it. - chainedRoot._promise = cb(adapter); - - return chainable(node); + executionContext._promise = _cb(executionContext); } + + return chainedNode; } export function chainable(branch) { diff --git a/addon-test-support/extend/action.js b/addon-test-support/extend/action.js new file mode 100644 index 00000000..daba5aa1 --- /dev/null +++ b/addon-test-support/extend/action.js @@ -0,0 +1,49 @@ +import { findElementWithAssert } from 'ember-cli-page-object/extend'; +import { throwBetterError } from '../-private/better-errors'; +import run from '../-private/run'; +import { getRoot } from '../-private/helpers'; + +const NOT_A_FUNCTION_ERROR = 'Argument passed to `action` must be a function.'; + +export default function action(fn) { + return { + isDescriptor: true, + + get(key) { + if (typeof fn !== 'function') { + throwBetterError(this, key, NOT_A_FUNCTION_ERROR); + } + + return function(...args) { + return run(this, function(context) { + // @todo: better handling of possible arg types + const formattedArgs = args.length ? `"${args.join('", "')}"` : ''; + + context.key = `${key}(${formattedArgs})`; + + getRoot(this).__execution_context__ = context; + + return fn.bind(this)(...args); + }); + } + } + } +} + +export function invokeHelper(node, selector, query, cb) { + const context = getRoot(node).__execution_context__; + + const _query = Object.assign({ multiple: true }, query); + + try { + const domElements = findElementWithAssert(node, selector, _query).get(); + + return Promise.all(domElements.map((element) => { + return cb(context, element); + })).then(undefined, (e) => { + throwBetterError(node, context.key, e, { selector }) + }); + } catch (e) { + throwBetterError(node, context.key, e, { selector }) + } +} diff --git a/addon-test-support/extend/find-element-with-assert.js b/addon-test-support/extend/find-element-with-assert.js index 25d399b8..de3678ef 100644 --- a/addon-test-support/extend/find-element-with-assert.js +++ b/addon-test-support/extend/find-element-with-assert.js @@ -1,4 +1,12 @@ -import { getExecutionContext } from '../-private/execution_context'; +import { + buildSelector, +} from '../-private/helpers'; +import { + ELEMENT_NOT_FOUND, + throwBetterError +} from '../-private/better-errors'; + +import { findElement } from './find-element'; /** * @public @@ -36,5 +44,18 @@ import { getExecutionContext } from '../-private/execution_context'; * @throws Will throw an error if multiple elements are matched by selector and multiple option is not set */ export function findElementWithAssert(pageObjectNode, targetSelector, options = {}) { - return getExecutionContext(pageObjectNode).findWithAssert(targetSelector, options); + const result = findElement(pageObjectNode, targetSelector, options); + const selector = options.selector + || buildSelector(pageObjectNode, targetSelector, options); + + if (result.length === 0) { + throwBetterError( + pageObjectNode, + options.pageObjectKey, + ELEMENT_NOT_FOUND, + { selector } + ); + } + + return result; } diff --git a/addon-test-support/extend/find-element.js b/addon-test-support/extend/find-element.js index ddd3ed21..8e31cd1c 100644 --- a/addon-test-support/extend/find-element.js +++ b/addon-test-support/extend/find-element.js @@ -1,3 +1,10 @@ +import { assert } from '@ember/debug'; +import $ from '-jquery'; +import { + buildSelector, + findClosestValue, +} from '../-private/helpers'; + import { getExecutionContext } from '../-private/execution_context'; /** @@ -34,5 +41,22 @@ import { getExecutionContext } from '../-private/execution_context'; * @throws Will throw an error if multiple elements are matched by selector and multiple option is not set */ export function findElement(pageObjectNode, targetSelector, options = {}) { - return getExecutionContext(pageObjectNode).find(targetSelector, options); + const selector = buildSelector(pageObjectNode, targetSelector, options); + const container = options.testContainer + || findClosestValue(pageObjectNode, 'testContainer') + || getExecutionContext(pageObjectNode).testContainer; + + const result = $(selector, container); + + guardMultiple(result, selector, options.multiple); + + return result; +} + + +function guardMultiple(items, selector, supportMultiple) { + assert( + `"${selector}" matched more than one element. If this is not an error use { multiple: true }`, + supportMultiple || items.length <= 1 + ); } diff --git a/addon-test-support/extend/index.js b/addon-test-support/extend/index.js index 00ea76fc..45495e4c 100644 --- a/addon-test-support/extend/index.js +++ b/addon-test-support/extend/index.js @@ -1,3 +1,4 @@ +export { default as action } from './action'; export { findElement } from './find-element'; export { findElementWithAssert } from './find-element-with-assert'; export { buildSelector, getContext, fullScope } from '../-private/helpers'; diff --git a/addon-test-support/macros/alias.js b/addon-test-support/macros/alias.js index 482d35d9..6eb437e2 100644 --- a/addon-test-support/macros/alias.js +++ b/addon-test-support/macros/alias.js @@ -3,8 +3,7 @@ import { getProperty, objectHasProperty } from '../-private/helpers'; -import { chainable } from '../-private/action' -import { getExecutionContext } from '../-private/execution_context' +import { chainable } from '../-private/run' const ALIASED_PROP_NOT_FOUND = 'PageObject does not contain aliased property'; @@ -99,9 +98,7 @@ export function alias(pathToProp, options = {}) { // child node rather than this node. value(...args); - return (typeof getExecutionContext(this).andThen === 'function') - ? this - : chainable(this); + return chainable(this); }; } }; diff --git a/addon-test-support/properties/blurrable.js b/addon-test-support/properties/blurrable.js index 377d6bd1..ed768bcf 100644 --- a/addon-test-support/properties/blurrable.js +++ b/addon-test-support/properties/blurrable.js @@ -1,5 +1,4 @@ -import { assign } from '../-private/helpers'; -import { getExecutionContext } from '../-private/execution_context'; +import action, { invokeHelper } from '../extend/action'; /** * @@ -63,18 +62,9 @@ import { getExecutionContext } from '../-private/execution_context'; * @return {Descriptor} */ export function blurrable(selector, userOptions = {}) { - return { - isDescriptor: true, - - get(key) { - return function() { - const executionContext = getExecutionContext(this); - const options = assign({ pageObjectKey: `${key}()` }, userOptions); - - return executionContext.runAsync((context) => { - return context.blur(selector, options); - }); - }; - } - }; + return action(function() { + return invokeHelper(this, selector, userOptions, ({ blur }, element) => { + return blur(element); + }); + }); } diff --git a/addon-test-support/properties/click-on-text.js b/addon-test-support/properties/click-on-text.js index 351629ac..a9add02c 100644 --- a/addon-test-support/properties/click-on-text.js +++ b/addon-test-support/properties/click-on-text.js @@ -1,6 +1,6 @@ -import { assign, findClosestValue } from '../-private/helpers'; -import { getExecutionContext } from '../-private/execution_context'; -import { buildSelector } from './click-on-text/helpers'; +import { findElement } from '../extend/index'; +import { assign } from '../-private/helpers'; +import action, { invokeHelper } from '../extend/action'; /** * Clicks on an element containing specified text. @@ -84,24 +84,23 @@ import { buildSelector } from './click-on-text/helpers'; * @param {string} options.testContainer - Context where to search elements in the DOM * @return {Descriptor} */ -export function clickOnText(selector, userOptions = {}) { - return { - isDescriptor: true, +export function clickOnText(scope, options = {}) { + return action(function(textToClick) { + const query = assign({}, options, { + contains: textToClick, + // we want to find the deepest node containing a text to click. + last: true + }); - get(key) { - return function(textToClick) { - let executionContext = getExecutionContext(this); - let options = assign({ pageObjectKey: `${key}("${textToClick}")`, contains: textToClick }, userOptions); + const childSelector = `${scope || ''} `; - return executionContext.runAsync((context) => { - let fullSelector = buildSelector(this, context, selector, options); - let container = options.testContainer || findClosestValue(this, 'testContainer'); - - context.assertElementExists(fullSelector, options); - - return context.click(fullSelector, container, options); - }); - }; + let selector; + if (findElement(this, childSelector, query).length) { + selector = childSelector; + } else { + selector = scope; } - }; + + return invokeHelper(this, selector, query, ({click}, element) => click(element)); + }); } diff --git a/addon-test-support/properties/click-on-text/helpers.js b/addon-test-support/properties/click-on-text/helpers.js deleted file mode 100644 index 9a66fed7..00000000 --- a/addon-test-support/properties/click-on-text/helpers.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - assign, - buildSelector as originalBuildSelector -} from '../../-private/helpers'; - -function childSelector(pageObjectNode, context, selector, options) { - // Suppose that we have something like `
` - // In this case
and