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

WIP: feature hookable #455

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
52 changes: 52 additions & 0 deletions addon-test-support/-private/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { typeOf } from '@ember/utils';
import { assert } from '@ember/debug';

const SUPPORTED_BEFORE_EVENTS = [
'blur'
];

const SUPPORTED_AFTER_EVENTS = [
'blur'
];

const validateHook = (hook, supportedEvents) => {
const isString = typeOf(hook) === 'string';

if (isString) {
assert('should be of `string` type', supportedEvents.includes(hook));
}

return typeOf(hook) === 'function';
}

export function hookable(property, hooks = {}) {
// Validate options
assert('should be of `object` type', typeOf(hooks) === 'object');

// Validate option keys
if (hooks.before) validateHook(hooks.before, SUPPORTED_BEFORE_EVENTS);
if (hooks.after) validateHook(hooks.after, SUPPORTED_AFTER_EVENTS);

return function(selector, options) {
const descriptor = property(selector, options);

return {
isDescriptor: true,

get(key) {
const fn = descriptor.get.call(this, key);

return function() {
// Async before and after hooks? Any use case for that?
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe async should be a first class citizen cause events/actions often cause async jobs. So you probably typically want to give validations to re-compute after you blur an input.

Not sure whether we want to support sync.. Are there any exampes for sync behavior?

hooks.before();
Copy link
Collaborator

Choose a reason for hiding this comment

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

it could be invoked the same way fn.call(this, ...args) does, otherwise it's doesn't seem to be useful, cause no access to the current node instance on which it was invoked,..


fn.call(this, ...arguments);

hooks.after();

return this;
}
}
}
}
}
1 change: 1 addition & 0 deletions addon-test-support/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { visitable } from './properties/visitable'; export { visitable };
export { findElement } from './extend/find-element';
export { findElementWithAssert } from './extend/find-element-with-assert';
export { buildSelector, getContext } from './-private/helpers';
export { hookable } from './-private/hooks';

export default {
attribute,
Expand Down
92 changes: 78 additions & 14 deletions tests/unit/-private/properties/fillable-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { moduleForProperty } from '../../../helpers/properties';
import { create, fillable, selectable } from 'ember-cli-page-object';
import { hookable, create, fillable, selectable } from 'ember-cli-page-object';

window.hookable = hookable;

/* eslint-disable no-console */
moduleForProperty('fillable', function(test) {
test("calls fillIn method belonging to execution context", async function(assert) {
assert.expect(1);
Expand All @@ -9,8 +12,12 @@ moduleForProperty('fillable', function(test) {
let expectedText = 'dummy text';
let page;

const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
page = create({
foo: fillable(expectedSelector)
foo: customFillable(expectedSelector)
});

await this.adapter.createTemplate(this, page, '<input>');
Expand Down Expand Up @@ -50,9 +57,13 @@ moduleForProperty('fillable', function(test) {
test(`looks for ${tagName} with ${attrName}`, async function(assert) {
let expectedText = 'dummy text';
let clue = 'clue';
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
scope: '.scope',
foo: fillable()
foo: customFillable()
});

await this.adapter.createTemplate(this, page, `<div class="scope">${template}</div>`);
Expand All @@ -67,9 +78,13 @@ moduleForProperty('fillable', function(test) {
test(`looks for [contenteditable] with ${attrName}`, async function(assert) {
let expectedText = 'dummy text';
let clue = 'clue';
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
scope: '.scope',
foo: fillable()
foo: customFillable()
});

await this.adapter.createTemplate(this, page, `<div class="scope"><div contenteditable ${attrName}="clue"></div></div>`);
Expand All @@ -83,8 +98,14 @@ moduleForProperty('fillable', function(test) {
test('looks for elements inside the scope', async function(assert) {
assert.expect(1);

// NOTE: works as expected
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});

let page = create({
foo: fillable('input', { scope: '.scope' })
foo: customFillable('input', { scope: '.scope' })
});

await this.adapter.createTemplate(this, page, '<div class="scope"><input></div>');
Expand All @@ -97,10 +118,16 @@ moduleForProperty('fillable', function(test) {
test("looks for elements inside page's scope", async function(assert) {
assert.expect(1);

// NOTE: works as expected
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});

let page = create({
scope: '.scope',

foo: fillable('input')
foo: customFillable('input'),
});

await this.adapter.createTemplate(this, page, '<div class="scope"><input></div>');
Expand All @@ -113,9 +140,13 @@ moduleForProperty('fillable', function(test) {
test('resets scope', async function(assert) {
assert.expect(1);

const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
scope: '.scope',
foo: fillable('input', { resetScope: true })
foo: customFillable('input', { resetScope: true })
});

await this.adapter.createTemplate(this, page, '<input>');
Expand All @@ -128,8 +159,14 @@ moduleForProperty('fillable', function(test) {
test('returns chainable object', async function(assert) {
assert.expect(1);

// NOTE: works as expected
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});

let page = create({
foo: fillable('input')
foo: customFillable('input')
});

await this.adapter.createTemplate(this, page, '<input>');
Expand All @@ -143,8 +180,13 @@ moduleForProperty('fillable', function(test) {
assert.expect(1);

let expectedSelector = 'input:eq(3)';

const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
foo: fillable('input', { at: 3 })
foo: customFillable('input', { at: 3 })
});

await this.adapter.createTemplate(this, page, '<input><input><input><input>');
Expand All @@ -159,8 +201,12 @@ moduleForProperty('fillable', function(test) {

let expectedSelector = 'input';
let expectedText = 'dummy text';
const customSelectable = hookable(selectable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
foo: selectable(expectedSelector)
foo: customSelectable(expectedSelector)
});

await this.adapter.createTemplate(this, page, '<input>');
Expand All @@ -176,8 +222,12 @@ moduleForProperty('fillable', function(test) {
let expectedContext = '#alternate-ember-testing';
let expectedSelector = 'input';
let expectedText = 'foo';
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
foo: fillable(expectedSelector, { testContainer: expectedContext })
foo: customFillable(expectedSelector, { testContainer: expectedContext })
});

await this.adapter.createTemplate(this, page, '<input>', { useAlternateContainer: true });
Expand All @@ -193,9 +243,13 @@ moduleForProperty('fillable', function(test) {
let expectedContext = '#alternate-ember-testing';
let expectedSelector = 'input';
let expectedText = 'foo';
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
testContainer: expectedContext,
foo: fillable(expectedSelector)
foo: customFillable(expectedSelector)
});

await this.adapter.createTemplate(this, page, '<input>', { useAlternateContainer: true });
Expand All @@ -207,12 +261,16 @@ moduleForProperty('fillable', function(test) {

test("raises an error when the element doesn't exist", async function(assert) {
assert.expect(1);
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});

let page = create({
foo: {
bar: {
baz: {
qux: fillable('input')
qux: customFillable('input')
}
}
}
Expand All @@ -226,8 +284,12 @@ moduleForProperty('fillable', function(test) {
});

test('raises an error when the element has contenteditable="false"', async function(assert) {
const customFillable = hookable(fillable, {
before() { console.log('before') },
after() { console.log('after') },
});
let page = create({
foo: fillable('div')
foo: customFillable('div')
});

await this.adapter.createTemplate(this, page, '<div contenteditable="false">');
Expand All @@ -237,3 +299,5 @@ moduleForProperty('fillable', function(test) {
}, /contenteditable/, 'Element should not be fillable because contenteditable="false"');
});
});

/* eslint-enable no-console */