UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

140 lines (116 loc) 4.27 kB
const _ = require('lodash') const Promise = require('bluebird') const $dom = require('../../dom') const $errUtils = require('../../cypress/error_utils') const reExistence = /exist/ const reHaveLength = /length/ module.exports = function (Commands, Cypress, cy, state) { const shouldFnWithCallback = function (subject, fn) { state('current')?.set('followedByShouldCallback', true) return Promise .try(() => { const remoteSubject = cy.getRemotejQueryInstance(subject) return fn.call(this, remoteSubject ? remoteSubject : subject) }) .tap(() => { state('current')?.set('followedByShouldCallback', false) }) .return(subject) } const shouldFn = function (subject, chainers, ...args) { if (_.isFunction(chainers)) { return shouldFnWithCallback.apply(this, arguments) // eslint-disable-line prefer-rest-params } let exp = cy.expect(subject).to const originalChainers = chainers const throwAndLogErr = (err) => { // since we are throwing our own error // without going through the assertion we need // to ensure our .should command gets logged const log = Cypress.log({ name: 'should', type: 'child', message: [].concat(originalChainers, args), end: true, snapshot: true, error: err, }) return $errUtils.throwErr(err, { onFail: log }) } chainers = chainers.split('.') const lastChainer = _.last(chainers) // backup the original assertion subject const originalObj = exp._obj let err const isCheckingExistence = reExistence.test(chainers) const isCheckingLengthOrExistence = isCheckingExistence || reHaveLength.test(chainers) const applyChainer = function (memo, value) { if (value === lastChainer && !isCheckingExistence) { if (_.isFunction(memo[value])) { try { return memo[value].apply(memo, args) } catch (err) { // if we made it all the way to the actual // assertion but its set to retry false then // we need to log out this .should since there // was a problem with the actual assertion syntax if (err.retry === false) { return throwAndLogErr(err) } throw err } } else { return memo[value] } } else { return memo[value] } } const applyChainers = function () { // if we're not doing existence or length assertions // then check to ensure the subject exists // in the DOM if its a DOM subject // because its possible we're asserting about an // element which has left the DOM and we always // want to auto-fail on those if (!isCheckingLengthOrExistence && $dom.isElement(subject)) { cy.ensureAttached(subject, 'should') } const newExp = _.reduce(chainers, (memo, value) => { if (!(value in memo)) { err = $errUtils.cypressErrByPath('should.chainer_not_found', { args: { chainer: value } }) err.retry = false throwAndLogErr(err) } // https://github.com/cypress-io/cypress/issues/883 // A single chainer used that is not an actual assertion, like '.should('be', 'true')' if (chainers.length < 2 && !isCheckingExistence && !_.isFunction(memo[value])) { err = $errUtils.cypressErrByPath('should.language_chainer', { args: { originalChainers } }) err.retry = false throwAndLogErr(err) } return applyChainer(memo, value) }, exp) exp = newExp ? newExp : exp } return Promise.try(applyChainers).then(() => { // if the _obj has been mutated then we // are chaining assertion properties and // should return this new subject if (originalObj !== exp._obj) { return exp._obj } return subject }) } Commands.addAll({ type: 'assertion', prevSubject: true }, { should () { // eslint-disable-next-line prefer-rest-params return shouldFn.apply(this, arguments) }, and () { // eslint-disable-next-line prefer-rest-params return shouldFn.apply(this, arguments) }, }) }