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

Integrate ember-native-dom-helpers #325

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions addon/-private/execution_context.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getContext } from './helpers';
import AcceptanceExecutionContext from './execution_context/acceptance';
import IntegrationExecutionContext from './execution_context/integration';
import AcceptanceEmberExecutionContext from './execution_context/acceptance';
import IntegrationEmberExecutionContext from './execution_context/integration';

const executioncontexts = {
acceptance: AcceptanceExecutionContext,
integration: IntegrationExecutionContext
acceptance: AcceptanceEmberExecutionContext,
integration: IntegrationEmberExecutionContext
};

/*
Expand Down
23 changes: 23 additions & 0 deletions addon/-private/execution_context/acceptance-native-dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ExecutionContext from './native-dom-context';

import {
visit
} from 'ember-native-dom-helpers';

export default function AcceptanceExecutionContext(pageObjectNode) {
ExecutionContext.call(this, pageObjectNode);
}

AcceptanceExecutionContext.prototype = Object.create(ExecutionContext.prototype);

AcceptanceExecutionContext.prototype.visit = function() {
return visit(...arguments);
};

AcceptanceExecutionContext.prototype.runAsync = function(cb) {
window.wait().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be the last dependency on a global test helper, which means we still have to use setContext to be able to use PageObjects in integration tests. We can do the same thing ember-native-dom-helpers does here to make this completely independent:

import wait from 'ember-test-helpers/wait';

(window.wait || wait)().then(() => {});

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right , seems like it should be fixed.

cb(this);
});

return this.pageObjectNode;
};
6 changes: 5 additions & 1 deletion addon/-private/execution_context/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
throwBetterError
} from '../better-errors';

import $ from '-jquery';

/**
* @private
*
Expand All @@ -17,7 +19,9 @@ import {
*
* @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, { selector, pageObjectNode, pageObjectKey }) {
const $selection = $(selection);

if ($selection.is('[contenteditable][contenteditable!="false"]')) {
$selection.html(content);
} else if ($selection.is('[contenteditable="false"]')) {
Expand Down
20 changes: 20 additions & 0 deletions addon/-private/execution_context/integration-native-dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ExecutionContext from './native-dom-context';

import Ember from 'ember';
const { run } = Ember;

export default function IntegrationExecutionContext(pageObjectNode, testContext) {
ExecutionContext.call(this, pageObjectNode, testContext);
}

IntegrationExecutionContext.prototype = Object.create(ExecutionContext.prototype);

IntegrationExecutionContext.prototype.visit = function() {};

IntegrationExecutionContext.prototype.runAsync = function(cb) {
run(() => {
cb(this);
});

return this.pageObjectNode;
};
3 changes: 1 addition & 2 deletions addon/-private/execution_context/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ IntegrationExecutionContext.prototype = {
return this.pageObjectNode;
},

// Do nothing in integration test
visit: $.noop,
visit() {},

click(selector, container) {
this.$(selector, container).click();
Expand Down
138 changes: 138 additions & 0 deletions addon/-private/execution_context/native-dom-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import $ from '-jquery';

import {
click,
triggerEvent,
keyEvent
} from 'ember-native-dom-helpers';

import {
guardMultiple,
buildSelector,
findClosestValue
} from '../helpers';
import {
fillElement
} from './helpers';
import {
ELEMENT_NOT_FOUND,
throwBetterError
} from '../better-errors';

const KEYBOARD_EVENT_TYPES = ['keydown', 'keypress', 'keyup'];

export default function ExecutionContext(pageObjectNode, testContext) {
this.pageObjectNode = pageObjectNode;
this.testContext = testContext;
}

ExecutionContext.prototype = {
run(cb) {
return cb(this);
},

runAsync() {
throw new Error('not implemented');
},

click(selector, container) {
const el = this.$(selector, container)[0];
click(el);
},

fillIn(selector, container, options, content) {
let elements = this.$(selector, container).toArray();

elements.forEach((el) => {
fillElement(el, content, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo:

  1. Update function signature of fillElement to indicate that the first param is an element
  2. Update the usage of fillElement in the other places where it is used so that we are always passing an element and not a jQuery selection.

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';

return $(selector, testsContainer);
}
},

triggerEvent(selector, container, eventName, eventOptions) {
const element = this.$(selector, container)[0];

// `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.
if (typeof eventOptions.key === 'undefined' && typeof eventOptions.keyCode !== 'undefined') {
eventOptions.key = eventOptions.keyCode.toString();
delete eventOptions.keyCode;
}

if (KEYBOARD_EVENT_TYPES.indexOf(eventName) > -1) {
keyEvent(element, eventName, eventOptions.key, eventOptions);
} else {
triggerEvent(element, eventName, eventOptions);
}
},

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;
}
};

4 changes: 3 additions & 1 deletion addon/-private/helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Ember from 'ember';
import Ceibo from 'ceibo';

const { $, assert, get, isPresent } = Ember;
const { assert, get, isPresent } = Ember;

import $ from '-jquery';

class Selector {
constructor(node, scope, selector, filters) {
Expand Down
3 changes: 1 addition & 2 deletions addon/-private/properties/visitable.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Ember from 'ember';
import { assign } from '../helpers';
import { getExecutionContext } from '../execution_context';

const { $ } = Ember;
import $ from '-jquery';

function fillInDynamicSegments(path, params) {
return path.split('/').map(function(segment) {
Expand Down
19 changes: 18 additions & 1 deletion addon/extend.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
export { findElement } from './-private/extend/find-element';
export { findElementWithAssert } from './-private/extend/find-element-with-assert';
export { buildSelector, getContext, fullScope } from './-private/helpers';
export { register as registerExecutionContext } from './-private/execution_context';
import { register as registerExecutionContext } from './-private/execution_context';

import IntegrationNativeDOMContext from './-private/execution_context/integration-native-dom';
import AcceptanceNativeDOMContext from './-private/execution_context/acceptance-native-dom';
import IntegrationEmberContext from './-private/execution_context/integration';
import AcceptanceEmberContext from './-private/execution_context/acceptance';

function useNativeDOMHelpers(flag = true) {
if (flag) {
registerExecutionContext('integration', IntegrationNativeDOMContext);
registerExecutionContext('acceptance', AcceptanceNativeDOMContext);
} else {
registerExecutionContext('integration', IntegrationEmberContext);
registerExecutionContext('acceptance', AcceptanceEmberContext);
}
}

export { registerExecutionContext, useNativeDOMHelpers };
35 changes: 35 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ module.exports = {
enabled: this._shouldIncludeFiles(),
import: ['index.js']
};
},
jquery: function() {
return {
enabled: this._shouldIncludeFiles(),
vendor: ['dist/jquery.js'],
destDir: 'ecpo-jquery'
}
}
}
},
Expand All @@ -23,9 +30,37 @@ module.exports = {

this.app = app;

if (this._shouldIncludeFiles()) {
this.importJquery();
}

this._super.included.apply(this, arguments);
},

/*
* Import an amd '-jquery' shim which is used by ember-cli-page-object internally
*
* We don't want ember-cli-page-object's jquery ocassionaly leak into a real application.
* The following combo of shims supposed to isolate `ember-cli-page-object`'s `jquery`
* from the rest of application and expose internal version via amd module.
*/
importJquery: function() {
// jquery itself is included in the very beggining of vendor.js.
// At this point we don't have `define()` defined so we can't create an amd shim here.
//
// However we have to store reference to jquery and dispose it from the window
// in order to prevent its leakage to the application.
this.import('vendor/shims/ecpo-jquery-global.js', {
prepend: true
});
this.import('vendor/ecpo-jquery/dist/jquery.js', {
prepend: true
});

// finally define an amd shim for our internal jquery version
this.import('vendor/shims/ecpo-jquery.js');
},

treeFor: function(/*name*/) {
if (!this._shouldIncludeFiles()) {
return;
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@
"ember-cli-babel": "^5.1.7",
"ember-cli-get-component-path-option": "^1.0.0",
"ember-cli-is-package-missing": "^1.0.0",
"ember-cli-node-assets": "^0.1.2",
"ember-cli-node-assets": "^0.2.2",
"ember-cli-normalize-entity-name": "^1.0.0",
"ember-cli-path-utils": "^1.0.0",
"ember-cli-string-utils": "^1.0.0",
"ember-cli-test-info": "^1.0.0",
"ember-cli-valid-component-name": "^1.0.0",
"ember-cli-version-checker": "^1.2.0",
"ember-native-dom-helpers": "^0.5.3",
"ember-test-helpers": "^0.6.3",
"jquery": "^3.2.1",
"rsvp": "^3.2.1"
},
"ember-addon": {
Expand Down
34 changes: 23 additions & 11 deletions tests/helpers/module-for-acceptance.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@ import { module } from 'qunit';
import Ember from 'ember';
import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';
import { useNativeDOMHelpers } from 'ember-cli-page-object/extend';

const { RSVP: { Promise } } = Ember;

export default function(name, options = {}) {
module(name, {
beforeEach() {
this.application = startApp();
[false, true].forEach(_useNativeDOMHelpers => {
let moduleName = name;
if (_useNativeDOMHelpers) {
moduleName += ' [native-dom-helpers]';
}

if (options.beforeEach) {
return options.beforeEach.apply(this, arguments);
}
},
module(moduleName, {
beforeEach() {
this.application = startApp();

afterEach() {
let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
return Promise.resolve(afterEach).then(() => destroyApp(this.application));
}
useNativeDOMHelpers(_useNativeDOMHelpers);

if (options.beforeEach) {
return options.beforeEach.apply(this, arguments);
}
},

afterEach() {
useNativeDOMHelpers(false);

let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
return Promise.resolve(afterEach).then(() => destroyApp(this.application));
}
});
});
}
Loading