From 64d0023cc00864397b963f74202ed205e1c0cc3c Mon Sep 17 00:00:00 2001 From: Artem Gurzhii Date: Fri, 3 May 2019 14:02:10 +0200 Subject: [PATCH] hookable --- addon-test-support/-private/hooks.js | 52 +++++++++++ addon-test-support/index.js | 1 + .../unit/-private/properties/fillable-test.js | 92 ++++++++++++++++--- 3 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 addon-test-support/-private/hooks.js diff --git a/addon-test-support/-private/hooks.js b/addon-test-support/-private/hooks.js new file mode 100644 index 00000000..a08c3a8a --- /dev/null +++ b/addon-test-support/-private/hooks.js @@ -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? + hooks.before(); + + fn.call(this, ...arguments); + + hooks.after(); + + return this; + } + } + } + } +} diff --git a/addon-test-support/index.js b/addon-test-support/index.js index 70cc3cf4..3041888e 100644 --- a/addon-test-support/index.js +++ b/addon-test-support/index.js @@ -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, diff --git a/tests/unit/-private/properties/fillable-test.js b/tests/unit/-private/properties/fillable-test.js index 2f85a510..9aec5093 100644 --- a/tests/unit/-private/properties/fillable-test.js +++ b/tests/unit/-private/properties/fillable-test.js @@ -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); @@ -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, ''); @@ -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, `
${template}
`); @@ -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, `
`); @@ -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, '
'); @@ -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, '
'); @@ -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, ''); @@ -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, ''); @@ -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, ''); @@ -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, ''); @@ -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, '', { useAlternateContainer: true }); @@ -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, '', { useAlternateContainer: true }); @@ -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') } } } @@ -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, '
'); @@ -237,3 +299,5 @@ moduleForProperty('fillable', function(test) { }, /contenteditable/, 'Element should not be fillable because contenteditable="false"'); }); }); + +/* eslint-enable no-console */