Skip to content

Commit

Permalink
Added .addCustomMethods method (closes DevExpress#1212) (DevExpress#1235
Browse files Browse the repository at this point in the history
)

* Added .addCustomMethods method (closes DevExpress#1212)

* Fixed remarks

* Fixed callsite

* Add custom functions via ClientFunctionBuilder

* Fixed remarks

* Move argument validation tests to unit
  • Loading branch information
kirovboris committed Dec 18, 2019
1 parent 18fe125 commit a7e5f5a
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 48 deletions.
70 changes: 60 additions & 10 deletions src/client-functions/selectors/add-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,15 @@ function assertAddCustomDOMPropertiesOptions (properties) {
});
}

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

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

function addSnapshotProperties (obj, getSelector, properties) {
properties.forEach(prop => {
Object.defineProperty(obj, prop, {
get: () => {
Expand All @@ -113,6 +116,37 @@ function addSnapshotPropertyShorthands (obj, getSelector, customDOMProperties) {
}
});
});
}

export function addCustomMethods (obj, getSelector, customMethods) {
var customMethodProps = customMethods ? Object.keys(customMethods) : [];

customMethodProps.forEach(prop => {
var dependencies = {
customMethod: customMethods[prop],
selector: getSelector()
};

var callsiteNames = { instantiation: prop };

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

return customMethod.apply(customMethod, [node].concat(args));
/* eslint-enable no-undef */
}, { dependencies }, callsiteNames)).getFunction();
});
}

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

if (customDOMProperties)
properties = properties.concat(Object.keys(customDOMProperties));

addSnapshotProperties(obj, getSelector, properties);
addCustomMethods(obj, getSelector, customMethods);

obj.getStyleProperty = prop => {
var callsite = getCallsite('getStyleProperty');
Expand Down Expand Up @@ -205,6 +239,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 @@ -214,7 +249,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 @@ -257,10 +296,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 @@ -465,9 +514,10 @@ function addHierarchicalSelectors (obj, getSelector, SelectorBuilder) {

}

export default function addAPI (obj, getSelector, SelectorBuilder, customDOMProperties) {
addSnapshotPropertyShorthands(obj, getSelector, customDOMProperties);
export 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
13 changes: 9 additions & 4 deletions src/client-functions/selectors/selector-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import MESSAGE from '../../errors/runtime/message';
import { assertType, is } from '../../errors/runtime/type-assertions';
import { ExecuteSelectorCommand } from '../../test-run/commands/observation';
import defineLazyProperty from '../../utils/define-lazy-property';
import addAPI from './add-api';
import { addAPI, addCustomMethods } from './add-api';
import createSnapshotMethods from './create-snapshot-methods';

export default class SelectorBuilder extends ClientFunctionBuilder {
Expand Down 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,9 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
if (snapshot && !this.options.counterMode) {
this._addBoundArgsSelectorGetter(snapshot, selectorArgs);
createSnapshotMethods(snapshot);

if (this.options.customMethods)
addCustomMethods(snapshot, () => snapshot.selector, this.options.customMethods);
}

return snapshot;
Expand Down
39 changes: 14 additions & 25 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 @@ -227,47 +231,32 @@ describe('[API] Selector', function () {
});
});

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

it('Should raise error if at least one of custom DOM properties is not function',
it('Should raise error if custom DOM property throws an error',
function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Add custom DOM properties method - property is not function', {
return runTests('./testcafe-fixtures/selector-test.js', 'Add custom DOM properties method - property throws an error', {
shouldFail: true,
only: 'chrome'
})
.catch(function (errs) {
expect(errs[0]).contains(
"Custom DOM properties method \'prop1\' is expected to be a function, but it was number"
'An error occurred when trying to calculate a custom Selector property "prop": Error: test'
);
expect(errs[0]).contains("> 789 | await Selector('rect').addCustomDOMProperties({ prop1: 1, prop2: () => 42 });");
expect(errs[0]).contains('> 791 | await el();');
});
}
);

it('Should raise error if custom DOM property throws an error',
it('Should raise error if custom method throws an error',
function () {
return runTests('./testcafe-fixtures/selector-test.js', 'Add custom DOM properties method - property throws an error', {
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 when trying to calculate a custom Selector property "prop": Error: test'
'An error occurred in customMethod code: Error: test'
);
expect(errs[0]).contains('> 799 | await el();');
expect(errs[0]).contains('> 885 | await el.customMethod();');
});
}
);
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 @@ -781,14 +781,6 @@ test('Selector `extend` method', async t => {
await t.expect(await doc.prop).eql('documentProp');
});

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

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

test('Add custom DOM properties method - property throws an error', async () => {
const el = Selector('rect').addCustomDOMProperties({
prop: () => {
Expand Down Expand Up @@ -846,3 +838,49 @@ 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, separator) => [str, node.tagName].join(separator)
});

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 - method throws an error', async () => {
const el = Selector('rect').addCustomMethods({
customMethod: () => {
throw new Error('test');
}
});

await el.customMethod();
});
Loading

0 comments on commit a7e5f5a

Please sign in to comment.