Skip to content

Commit

Permalink
Added .addCustomMethods method (closes DevExpress#1212)
Browse files Browse the repository at this point in the history
  • Loading branch information
kirovboris committed Feb 14, 2017
1 parent 2ecabd2 commit 98adf58
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 14 deletions.
58 changes: 51 additions & 7 deletions src/client-functions/selector-builder/add-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ function assertAddCustomDOMPropertiesOptions (properties) {
});
}

function addSnapshotPropertyShorthands (obj, getSelector, customDOMProperties) {
function assertAddCustomMethods (properties) {
assertType(is.nonNullObject, 'addCustomMethods', '"addCustomMethods" option', properties);

Object.keys(properties).forEach(prop => {
assertType(is.function, 'addCustomMethods', `Custom method '${prop}'`, properties[prop]);
});
}

function addSnapshotPropertyShorthands (obj, getSelector, customDOMProperties, customMethods) {
var properties = SNAPSHOT_PROPERTIES;

if (customDOMProperties)
Expand All @@ -111,6 +119,26 @@ function addSnapshotPropertyShorthands (obj, getSelector, customDOMProperties) {
});
});

if (customMethods) {
Object.keys(customMethods).forEach(prop => {
var customMethod = customMethods[prop];

var customMethodClientFunction = (new ClientFunctionBuilder(
(...args) => {
/* eslint-disable no-undef */
var node = selector();
/* eslint-enable no-undef */

return customMethod.apply(customMethod, [node].concat(args));
}, { dependencies: { customMethod, selector: getSelector() } }, { instantiation: prop }
)).getFunction();

Object.defineProperty(obj, prop, {
get: () => customMethodClientFunction
});
});
}

obj.getStyleProperty = prop => {
var callsite = getCallsite('getStyleProperty');

Expand Down Expand Up @@ -202,6 +230,7 @@ function convertFilterToClientFunctionIfNecessary (callsiteName, filter, depende
function createDerivativeSelectorWithFilter (getSelector, SelectorBuilder, selectorFn, filter, additionalDependencies) {
var collectionModeSelectorBuilder = new SelectorBuilder(getSelector(), { collectionMode: true });
var customDOMProperties = collectionModeSelectorBuilder.options.customDOMProperties;
var customMethods = collectionModeSelectorBuilder.options.customMethods;

var dependencies = {
selector: collectionModeSelectorBuilder.getFunction(),
Expand All @@ -211,7 +240,11 @@ function createDerivativeSelectorWithFilter (getSelector, SelectorBuilder, selec

dependencies = assign(dependencies, additionalDependencies);

var builder = new SelectorBuilder(selectorFn, { dependencies, customDOMProperties }, { instantiation: 'Selector' });
var builder = new SelectorBuilder(selectorFn, {
dependencies,
customDOMProperties,
customMethods
}, { instantiation: 'Selector' });

return builder.getFunction();
}
Expand Down Expand Up @@ -254,10 +287,20 @@ function addFilterMethods (obj, getSelector, SelectorBuilder) {
}

function addCustomDOMPropertiesMethod (obj, getSelector, SelectorBuilder) {
obj.addCustomDOMProperties = properties => {
assertAddCustomDOMPropertiesOptions(properties);
obj.addCustomDOMProperties = customDOMProperties => {
assertAddCustomDOMPropertiesOptions(customDOMProperties);

var builder = new SelectorBuilder(getSelector(), { customDOMProperties }, { instantiation: 'Selector' });

return builder.getFunction();
};
}

function addCustomMethodsMethod (obj, getSelector, SelectorBuilder) {
obj.addCustomMethods = customMethods => {
assertAddCustomMethods(customMethods);

var builder = new SelectorBuilder(getSelector(), { customDOMProperties: properties }, { instantiation: 'Selector' });
var builder = new SelectorBuilder(getSelector(), { customMethods }, { instantiation: 'Selector' });

return builder.getFunction();
};
Expand Down Expand Up @@ -462,9 +505,10 @@ function addHierarchicalSelectors (obj, getSelector, SelectorBuilder) {

}

export default function addAPI (obj, getSelector, SelectorBuilder, customDOMProperties) {
addSnapshotPropertyShorthands(obj, getSelector, customDOMProperties);
export default function addAPI (obj, getSelector, SelectorBuilder, customDOMProperties, customMethods) {
addSnapshotPropertyShorthands(obj, getSelector, customDOMProperties, customMethods);
addCustomDOMPropertiesMethod(obj, getSelector, SelectorBuilder);
addCustomMethodsMethod(obj, getSelector, SelectorBuilder);
addFilterMethods(obj, getSelector, SelectorBuilder);
addHierarchicalSelectors(obj, getSelector, SelectorBuilder);
addCounterProperties(obj, getSelector, SelectorBuilder);
Expand Down
24 changes: 21 additions & 3 deletions src/client-functions/selector-builder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
this._addBoundArgsSelectorGetter(resultPromise, args);

// OPTIMIZATION: use buffer function as selector not to trigger lazy property ahead of time
addAPI(resultPromise, () => resultPromise.selector, SelectorBuilder, this.options.customDOMProperties);
addAPI(resultPromise, () => resultPromise.selector, SelectorBuilder, this.options.customDOMProperties, this.options.customMethods);

return resultPromise;
}
Expand All @@ -78,6 +78,7 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
var dependencies = super.getFunctionDependencies();
var text = this.options.text;
var customDOMProperties = this.options.customDOMProperties;
var customMethods = this.options.customMethods;

if (typeof text === 'string')
text = new RegExp(escapeRe(text));
Expand All @@ -91,7 +92,8 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
},

boundArgs: this.options.boundArgs,
customDOMProperties: customDOMProperties
customDOMProperties: customDOMProperties,
customMethods: customMethods
});
}

Expand Down Expand Up @@ -135,7 +137,7 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
_decorateFunction (selectorFn) {
super._decorateFunction(selectorFn);

addAPI(selectorFn, () => selectorFn, SelectorBuilder, this.options.customDOMProperties);
addAPI(selectorFn, () => selectorFn, SelectorBuilder, this.options.customDOMProperties, this.options.customMethods);
}

_processResult (result, selectorArgs) {
Expand All @@ -144,6 +146,22 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
if (snapshot && !this.options.counterMode) {
this._addBoundArgsSelectorGetter(snapshot, selectorArgs);
createSnapshotMethods(snapshot);

if (this.options.customMethods) {
Object.keys(this.options.customMethods).forEach(prop => {
var customMethod = this.options.customMethods[prop];

snapshot[prop] = (new ClientFunctionBuilder(
(...args) => {
/* eslint-disable no-undef */
var node = selector();
/* eslint-enable no-undef */

return customMethod.apply(customMethod, [node].concat(args));
}, { dependencies: { customMethod, selector: this.getFunction() } }, { instantiation: prop })
).getFunction();
});
}
}

return snapshot;
Expand Down
55 changes: 52 additions & 3 deletions test/functional/fixtures/api/es-next/selector/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ describe('[API] Selector', function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Snapshot `hasClass` method');
});

it('Should provide "extend" method in node snapshot', function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Selector `extend` method');
it('Should provide "addCustomDOMProperties" method in node snapshot', function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Selector `addCustomDOMProperties` method');
});

it('Should provide "addCustomMethods" method in node snapshot', function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Selector `addCustomMethods` method');
});

it('Should wait for element to appear on new page', function () {
Expand Down Expand Up @@ -250,7 +254,7 @@ describe('[API] Selector', function () {
})
.catch(function (errs) {
expect(errs[0]).contains(
"Custom DOM properties method \'prop1\' is expected to be a function, but it was number"
"Custom DOM properties method 'prop1' is expected to be a function, but it was number"
);
expect(errs[0]).contains("> 789 | await Selector('rect').addCustomDOMProperties({ prop1: 1, prop2: () => 42 });");
});
Expand All @@ -271,6 +275,51 @@ describe('[API] Selector', function () {
});
}
);

it('Should raise error if addCustomMethods method argument is not object',
function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Add custom method - argument is not object', {
shouldFail: true,
only: 'chrome'
})
.catch(function (errs) {
expect(errs[0]).contains(
'"addCustomMethods" option is expected to be a non-null object, but it was number.'
);
expect(errs[0]).contains("> 887 | await Selector('rect').addCustomMethods(42);");
});
}
);

it('Should raise error if at least one of custom methods is not function',
function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Add custom method - method is not function', {
shouldFail: true,
only: 'chrome'
})
.catch(function (errs) {
expect(errs[0]).contains(
"Custom method 'prop1' is expected to be a function, but it was number"
);
expect(errs[0]).contains("> 891 | await Selector('rect').addCustomMethods({ prop1: 1, prop2: () => 42 });");
});
}
);

it('Should raise error if custom method throws an error',
function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Add custom method - method throws an error', {
shouldFail: true,
only: 'chrome'
})
.catch(function (errs) {
expect(errs[0]).contains(
'An error occurred in customMethod code: Error: test'
);
expect(errs[0]).contains('> 901 | await el.customMethod();');
});
}
);
});

describe('Regression', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ test('Selector filter origin node argument', async t => {
}).id).eql('el3');
});

test('Selector `extend` method', async t => {
test('Selector `addCustomDOMProperties` method', async t => {
let el = Selector('rect')
.addCustomDOMProperties({
prop1: () => 42,
Expand Down Expand Up @@ -846,3 +846,57 @@ test('Selector "prevSibling" method', async t => {
// With filters
await t.expect(Selector('#el4').prevSibling().withText('Hey?!').nth(0).id).eql('el2');
});

test('Selector `addCustomMethods` method', async t => {
let el = Selector('rect').addCustomMethods({
prop1: (node, str) => str + '42',
prop2: (node, str) => str + node.tagName
});

await t
.expect(await el.prop1('value: ')).eql('value: 42')
.expect(await el().prop1('value: ')).eql('value: 42')
.expect(await el.prop2('tagName: ')).eql('tagName: rect')

.expect(await el.parent().filter(() => true).tagName).eql('svg')
.expect(await el.exists).ok()
.expect(await el.count).eql(1);

const snapshot = await el();

await t
.expect(snapshot.prop1('value: ')).eql('value: 42')
.expect(await snapshot.prop1('value: ')).eql('value: 42');

el = el.addCustomMethods({
prop1: (node, str) => str + '!!!'
});

await t
.expect(el.prop1('Hi')).eql('Hi!!!')
.expect(el.prop2('tagName: ')).eql('tagName: rect');

const nonExistingElement = await Selector('nonExistingElement').addCustomMethods({
prop: () => 'value'
})();

await t.expect(nonExistingElement).eql(null);
});

test('Add custom method - argument is not object', async () => {
await Selector('rect').addCustomMethods(42);
});

test('Add custom method - method is not function', async () => {
await Selector('rect').addCustomMethods({ prop1: 1, prop2: () => 42 });
});

test('Add custom method - method throws an error', async () => {
const el = Selector('rect').addCustomMethods({
customMethod: () => {
throw new Error('test');
}
});

await el.customMethod();
});

0 comments on commit 98adf58

Please sign in to comment.