@revoloo/cypress6
Version:
Cypress.io end to end testing tool
345 lines (292 loc) • 8.56 kB
JavaScript
const _ = require('lodash')
const $ = require('jquery')
const $dom = require('../dom')
const $elements = require('../dom/elements')
const selectors = {
visible: 'visible',
hidden: 'hidden',
selected: 'selected',
checked: 'checked',
enabled: 'enabled',
disabled: 'disabled',
focus: 'focused',
focused: 'focused',
}
const accessors = {
attr: 'attribute',
css: 'CSS property',
prop: 'property',
}
// reset the obj under test
// to be re-wrapped in our own
// jquery, so we can control
// the methods on it
const wrap = (ctx) => $(ctx._obj)
const maybeCastNumberToString = (num) => {
// if this is a finite number (no Infinity or NaN)
// cast to a string
return _.isFinite(num) ? `${num}` : num
}
const $chaiJquery = (chai, chaiUtils, callbacks = {}) => {
const { inspect, flag } = chaiUtils
const assertDom = (ctx, method, ...args) => {
if (!$dom.isDom(ctx._obj) && !$dom.isJquery(ctx._obj)) {
try {
// always fail the assertion
// if we aren't a DOM like object
// depends on the "negate" flag
return ctx.assert(!!ctx.__flags.negate, ...args)
} catch (err) {
return callbacks.onInvalid(method, ctx._obj)
}
}
}
const assert = (ctx, method, bool, ...args) => {
assertDom(ctx, method, ...args)
try {
// reset obj to wrapped
const orig = ctx._obj
const selector = ctx._obj.selector
ctx._obj = wrap(ctx)
if (ctx._obj.length === 0) {
// From jQuery 3.x .selector API is deprecated. (https://api.jquery.com/selector/)
// Because of that, wrap() above removes selector property.
// That's why we're caching the value of selector above and using it here.
ctx._obj = selector
// if no element found, fail the existence check
// depends on the negate flag
ctx.assert(!!ctx.__flags.negate, ...args)
}
// apply the assertion
const ret = ctx.assert(bool, ...args)
ctx._obj = orig
return ret
} catch (err) {
// send it up with the obj and whether it was negated
return callbacks.onError(err, method, ctx._obj, flag(ctx, 'negate'))
}
}
const assertPartial = (ctx, method, actual, expected, message, notMessage, ...args) => {
if (ctx.__flags.contains || ctx.__flags.includes) {
return assert(
ctx,
method,
_.includes(actual, expected),
`expected #{this} to contain ${message}`,
`expected #{this} not to contain ${notMessage}`,
...args,
)
}
return assert(
ctx,
method,
actual === expected,
`expected #{this} to have ${message}`,
`expected #{this} not to have ${notMessage}`,
...args,
)
}
chai.Assertion.addMethod('data', function (...args) {
assertDom(this, 'data')
let a = new chai.Assertion(wrap(this).data())
if (flag(this, 'negate')) {
a = a.not
}
return a.property.apply(a, args)
})
chai.Assertion.addMethod('class', function (className) {
return assert(
this,
'class',
wrap(this).hasClass(className),
'expected #{this} to have class #{exp}',
'expected #{this} not to have class #{exp}',
className,
)
})
chai.Assertion.addMethod('id', function (id) {
id = maybeCastNumberToString(id)
return assert(
this,
'id',
wrap(this).prop('id') === id,
'expected #{this} to have id #{exp}',
'expected #{this} not to have id #{exp}',
id,
)
})
chai.Assertion.addMethod('html', function (html) {
assertDom(
this,
'html',
'expected #{this} to have HTML #{exp}',
'expected #{this} not to have HTML #{exp}',
html,
)
const actual = wrap(this).html()
return assertPartial(
this,
'html',
actual,
html,
'HTML #{exp}, but the HTML was #{act}',
'HTML #{exp}',
html,
actual,
)
})
chai.Assertion.addMethod('text', function (text) {
text = maybeCastNumberToString(text)
assertDom(
this,
'text',
'expected #{this} to have text #{exp}',
'expected #{this} not to have text #{exp}',
text,
)
const actual = wrap(this).text()
return assertPartial(
this,
'text',
actual,
text,
'text #{exp}, but the text was #{act}',
'text #{exp}',
text,
actual,
)
})
chai.Assertion.addMethod('value', function (value) {
const $el = wrap(this)
// some elements return a number for the .value property
// in this case, we don't want to cast the expected value to a string
if ($el[0] && !$elements.isValueNumberTypeElement($el[0])) {
value = maybeCastNumberToString(value)
}
assertDom(
this,
'value',
'expected #{this} to have value #{exp}',
'expected #{this} not to have value #{exp}',
value,
)
const actual = $el.val()
return assertPartial(
this,
'value',
actual,
value,
'value #{exp}, but the value was #{act}',
'value #{exp}',
value,
actual,
)
})
chai.Assertion.addMethod('descendants', function (selector) {
return assert(
this,
'descendants',
wrap(this).has(selector).length > 0,
'expected #{this} to have descendants #{exp}',
'expected #{this} not to have descendants #{exp}',
selector,
)
})
chai.Assertion.overwriteProperty('empty', (_super) => {
return (function (...args) {
if ($dom.isDom(this._obj)) {
return assert(
this,
'empty',
wrap(this).is(':empty'),
'expected #{this} to be #{exp}',
'expected #{this} not to be #{exp}',
'empty',
)
}
return _super.apply(this, args)
})
})
chai.Assertion.overwriteMethod('match', (_super) => {
return (function (...args) {
const selector = args[0]
if ($dom.isDom(this._obj)) {
return assert(
this,
'match',
wrap(this).is(selector),
'expected #{this} to match #{exp}',
'expected #{this} not to match #{exp}',
selector,
)
}
return _super.apply(this, args)
})
})
_.each(selectors, (selectorName, selector) => {
return chai.Assertion.addProperty(selector, function () {
return assert(
this,
selector,
wrap(this).is(`:${selector}`),
'expected #{this} to be #{exp}',
'expected #{this} not to be #{exp}',
selectorName,
)
})
})
_.each(accessors, (description, accessor) => {
return chai.Assertion.addMethod(accessor, function (name, val) {
assertDom(
this,
accessor,
`expected #{this} to have ${description} #{exp}`,
`expected #{this} not to have ${description} #{exp}`,
name,
)
const actual = wrap(this)[accessor](name)
// when we only have 1 argument dont worry about val
if (arguments.length === 1) {
assert(
this,
accessor,
actual !== undefined,
`expected #{this} to have ${description} #{exp}`,
`expected #{this} not to have ${description} #{exp}`,
name,
)
// change the subject
this._obj = actual
} else {
// if we don't have an accessor here at all we need to
// have a different failure message
let message; let negatedMessage
if (_.isUndefined(actual)) {
message = `expected \#{this} to have ${description} ${inspect(name)}`
negatedMessage = `expected \#{this} not to have ${description} ${inspect(name)}`
} else {
message = `expected \#{this} to have ${description} ${inspect(name)} with the value \#{exp}, but the value was \#{act}`
negatedMessage = `expected \#{this} not to have ${description} ${inspect(name)} with the value \#{exp}, but the value was \#{act}`
}
// only cast .attr() as a string
// since prop stores the native javascript type
// and we don't want to optimistically cast those
// values as a string
if (accessor === 'attr') {
val = maybeCastNumberToString(val)
}
assert(
this,
accessor,
(actual != null) && (actual === val),
message,
negatedMessage,
val,
actual,
)
}
return this
})
})
}
module.exports = $chaiJquery