diff --git a/packages/driver/src/cy/chai.coffee b/packages/driver/src/cy/chai.coffee deleted file mode 100644 index a9f0cb7e7c8..00000000000 --- a/packages/driver/src/cy/chai.coffee +++ /dev/null @@ -1,338 +0,0 @@ -## tests in driver/test/cypress/integration/commands/assertions_spec.coffee - -_ = require("lodash") -$ = require("jquery") -chai = require("chai") -sinonChai = require("@cypress/sinon-chai") - -$dom = require("../dom") -$utils = require("../cypress/utils") -$chaiJquery = require("../cypress/chai_jquery") - -## all words between single quotes which are at -## the end of the string -allPropertyWordsBetweenSingleQuotes = /('.*?')$/g - -## grab all words between single quotes except -## when the single quote word is the LAST word -allButLastWordsBetweenSingleQuotes = /('.*?')(.+)/g - -allBetweenFourStars = /\*\*.*\*\*/ -allSingleQuotes = /'/g -allEscapedSingleQuotes = /\\'/g -allQuoteMarkers = /__quote__/g -allWordsBetweenCurlyBraces = /(#{.+?})/g -allQuadStars = /\*\*\*\*/g - -assertProto = null -matchProto = null -lengthProto = null -containProto = null -existProto = null -getMessage = null -chaiUtils = null - -chai.use(sinonChai) - -chai.use (chai, u) -> - chaiUtils = u - - $chaiJquery(chai, chaiUtils, { - onInvalid: (method, obj) -> - err = $utils.cypressErr( - $utils.errMessageByPath( - "chai.invalid_jquery_obj", { - assertion: method - subject: $utils.stringifyActual(obj) - } - ) - ) - - throw err - - onError: (err, method, obj, negated) -> - switch method - when "visible" - if not negated - ## add reason hidden unless we expect the element to be hidden - reason = $dom.getReasonIsHidden(obj) - err.message += "\n\n" + reason - - ## always rethrow the error! - throw err - }) - - assertProto = chai.Assertion::assert - matchProto = chai.Assertion::match - lengthProto = chai.Assertion::__methods.length.method - containProto = chai.Assertion::__methods.contain.method - existProto = Object.getOwnPropertyDescriptor(chai.Assertion::, "exist").get - getMessage = chaiUtils.getMessage - - removeOrKeepSingleQuotesBetweenStars = (message) -> - ## remove any single quotes between our **, preserving escaped quotes - ## and if an empty string, put the quotes back - message.replace allBetweenFourStars, (match) -> - match - .replace(allEscapedSingleQuotes, "__quote__") # preserve escaped quotes - .replace(allSingleQuotes, "") - .replace(allQuoteMarkers, "'") ## put escaped quotes back - .replace(allQuadStars, "**''**") ## fix empty strings that end up as **** - - replaceArgMessages = (args, str) -> - _.reduce args, (memo, value, index) => - if _.isString(value) - value = value - .replace(allWordsBetweenCurlyBraces, "**$1**") - .replace(allEscapedSingleQuotes, "__quote__") - .replace(allButLastWordsBetweenSingleQuotes, "**$1**$2") - .replace(allPropertyWordsBetweenSingleQuotes, "**$1**") - memo.push value - else - memo.push value - - memo - , [] - - restoreAsserts = -> - chaiUtils.getMessage = getMessage - - chai.Assertion::assert = assertProto - chai.Assertion::match = matchProto - chai.Assertion::__methods.length.method = lengthProto - chai.Assertion::__methods.contain.method = containProto - - Object.defineProperty(chai.Assertion::, "exist", {get: existProto}) - - overrideChaiAsserts = (assertFn) -> - _this = @ - - chai.Assertion.prototype.assert = createPatchedAssert(assertFn) - - chaiUtils.getMessage = (assert, args) -> - obj = assert._obj - - ## if we are formatting a DOM object - if $dom.isDom(obj) - ## replace object with our formatted one - assert._obj = $dom.stringify(obj, "short") - - msg = getMessage.call(@, assert, args) - - ## restore the real obj if we changed it - if obj isnt assert._obj - assert._obj = obj - - return msg - - chai.Assertion.overwriteMethod "match", (_super) -> - return (regExp) -> - if _.isRegExp(regExp) or $dom.isDom(@_obj) - _super.apply(@, arguments) - else - err = $utils.cypressErr($utils.errMessageByPath("chai.match_invalid_argument", { regExp })) - err.retry = false - throw err - - containFn1 = (_super) -> - return (text) -> - obj = @_obj - - if not ($dom.isJquery(obj) or $dom.isElement(obj)) - return _super.apply(@, arguments) - - escText = $utils.escapeQuotes(text) - - selector = ":contains('#{escText}'), [type='submit'][value~='#{escText}']" - - ## the assert checks below only work if $dom.isJquery(obj) - ## https://github.com/cypress-io/cypress/issues/3549 - if not ($dom.isJquery(obj)) - obj = $(obj) - - @assert( - obj.is(selector) or !!obj.find(selector).length - 'expected #{this} to contain #{exp}' - 'expected #{this} not to contain #{exp}' - text - ) - - containFn2 = (_super) -> - return -> - _super.apply(@, arguments) - - chai.Assertion.overwriteChainableMethod("contain", containFn1, containFn2) - - chai.Assertion.overwriteChainableMethod "length", - fn1 = (_super) -> - return (length) -> - obj = @_obj - - if not ($dom.isJquery(obj) or $dom.isElement(obj)) - return _super.apply(@, arguments) - - length = $utils.normalizeNumber(length) - - ## filter out anything not currently in our document - if $dom.isDetached(obj) - obj = @_obj = obj.filter (index, el) -> - $dom.isAttached(el) - - node = if obj and obj.length then $dom.stringify(obj, "short") else obj.selector - - ## if our length assertion fails we need to check to - ## ensure that the length argument is a finite number - ## because if its not, we need to bail on retrying - try - @assert( - obj.length is length, - "expected '#{node}' to have a length of \#{exp} but got \#{act}", - "expected '#{node}' to not have a length of \#{act}", - length, - obj.length - ) - - catch e1 - e1.node = node - e1.negated = chaiUtils.flag(@, "negate") - e1.type = "length" - - if _.isFinite(length) - getLongLengthMessage = (len1, len2) -> - if len1 > len2 - "Too many elements found. Found '#{len1}', expected '#{len2}'." - else - "Not enough elements found. Found '#{len1}', expected '#{len2}'." - - e1.displayMessage = getLongLengthMessage(obj.length, length) - throw e1 - - e2 = $utils.cypressErr($utils.errMessageByPath("chai.length_invalid_argument", { length })) - e2.retry = false - throw e2 - - fn2 = (_super) -> - return -> - _super.apply(@, arguments) - - chai.Assertion.overwriteProperty "exist", (_super) -> - return -> - obj = @_obj - - if not ($dom.isJquery(obj) or $dom.isElement(obj)) - try - _super.apply(@, arguments) - catch e - e.type = "existence" - throw e - else - if not obj.length - @_obj = null - - node = if obj and obj.length then $dom.stringify(obj, "short") else obj.selector - - try - @assert( - isAttached = $dom.isAttached(obj), - "expected \#{act} to exist in the DOM", - "expected \#{act} not to exist in the DOM", - node, - node - ) - catch e1 - e1.node = node - e1.negated = chaiUtils.flag(@, "negate") - e1.type = "existence" - - getLongExistsMessage = (obj) -> - ## if we expected not for an element to exist - if isAttached - "Expected #{node} not to exist in the DOM, but it was continuously found." - else - "Expected to find element: '#{obj.selector}', but never found it." - - e1.displayMessage = getLongExistsMessage(obj) - throw e1 - - createPatchedAssert = (assertFn) -> - return (args...) -> - passed = chaiUtils.test(@, args) - value = chaiUtils.flag(@, "object") - expected = args[3] - - customArgs = replaceArgMessages(args, @_obj) - - message = chaiUtils.getMessage(@, customArgs) - actual = chaiUtils.getActual(@, customArgs) - - message = removeOrKeepSingleQuotesBetweenStars(message) - - try - assertProto.apply(@, args) - catch e - err = e - - assertFn(passed, message, value, actual, expected, err) - - throw err if err - - overrideExpect = -> - ## only override assertions for this specific - ## expect function instance so we do not affect - ## the outside world - return (val, message) -> - ## make the assertion - return new chai.Assertion(val, message) - - overrideAssert = -> - fn = (express, errmsg) -> - chai.assert(express, errmsg) - - fns = _.functions(chai.assert) - - _.each fns, (name) -> - fn[name] = -> - chai.assert[name].apply(@, arguments) - - return fn - - setSpecWindowGlobals = (specWindow, assertFn) -> - expect = overrideExpect() - assert = overrideAssert() - - specWindow.chai = chai - specWindow.expect = expect - specWindow.assert = assert - - return { - chai - expect - assert - } - - create = (specWindow, assertFn) -> - # restoreOverrides() - restoreAsserts() - - # overrideChai() - overrideChaiAsserts(assertFn) - - return setSpecWindowGlobals(specWindow) - - module.exports = { - replaceArgMessages - - removeOrKeepSingleQuotesBetweenStars - - setSpecWindowGlobals - - # overrideChai: overrideChai - - restoreAsserts - - overrideExpect - - overrideChaiAsserts - - create - } diff --git a/packages/driver/src/cy/chai.js b/packages/driver/src/cy/chai.js new file mode 100644 index 00000000000..ea018736522 --- /dev/null +++ b/packages/driver/src/cy/chai.js @@ -0,0 +1,400 @@ +// tests in driver/test/cypress/integration/commands/assertions_spec.coffee + +const _ = require('lodash') +const $ = require('jquery') +const chai = require('chai') +const sinonChai = require('@cypress/sinon-chai') + +const $dom = require('../dom') +const $utils = require('../cypress/utils') +const $chaiJquery = require('../cypress/chai_jquery') + +// all words between single quotes which are at +// the end of the string +const allPropertyWordsBetweenSingleQuotes = /('.*?')$/g + +// grab all words between single quotes except +// when the single quote word is the LAST word +const allButLastWordsBetweenSingleQuotes = /('.*?')(.+)/g + +const allBetweenFourStars = /\*\*.*\*\*/ +const allSingleQuotes = /'/g +const allEscapedSingleQuotes = /\\'/g +const allQuoteMarkers = /__quote__/g +const allWordsBetweenCurlyBraces = /(#{.+?})/g +const allQuadStars = /\*\*\*\*/g + +let assertProto = null +let matchProto = null +let lengthProto = null +let containProto = null +let existProto = null +let getMessage = null +let chaiUtils = null + +chai.use(sinonChai) + +chai.use((chai, u) => { + chaiUtils = u + + $chaiJquery(chai, chaiUtils, { + onInvalid (method, obj) { + const err = $utils.cypressErr( + $utils.errMessageByPath( + 'chai.invalid_jquery_obj', { + assertion: method, + subject: $utils.stringifyActual(obj), + } + ) + ) + + throw err + }, + + onError (err, method, obj, negated) { + switch (method) { + case 'visible': + if (!negated) { + // add reason hidden unless we expect the element to be hidden + const reason = $dom.getReasonIsHidden(obj) + + err.message += `\n\n${reason}` + } + + break + default: + break + } + + // always rethrow the error! + throw err + }, + }) + + assertProto = chai.Assertion.prototype.assert + matchProto = chai.Assertion.prototype.match + lengthProto = chai.Assertion.prototype.__methods.length.method + containProto = chai.Assertion.prototype.__methods.contain.method + existProto = Object.getOwnPropertyDescriptor(chai.Assertion.prototype, 'exist').get; + ({ getMessage } = chaiUtils) + + // remove any single quotes between our **, preserving escaped quotes + // and if an empty string, put the quotes back + const removeOrKeepSingleQuotesBetweenStars = (message) => { + return message.replace(allBetweenFourStars, (match) => { + return match + .replace(allEscapedSingleQuotes, '__quote__') // preserve escaped quotes + .replace(allSingleQuotes, '') + .replace(allQuoteMarkers, '\'') // put escaped quotes back + .replace(allQuadStars, '**\'\'**') + }) + } // fix empty strings that end up as **** + + const replaceArgMessages = (args, str) => { + return _.reduce(args, (memo, value, index) => { + if (_.isString(value)) { + value = value + .replace(allWordsBetweenCurlyBraces, '**$1**') + .replace(allEscapedSingleQuotes, '__quote__') + .replace(allButLastWordsBetweenSingleQuotes, '**$1**$2') + .replace(allPropertyWordsBetweenSingleQuotes, '**$1**') + + memo.push(value) + } else { + memo.push(value) + } + + return memo + }, []) + } + + const restoreAsserts = function () { + chaiUtils.getMessage = getMessage + + chai.Assertion.prototype.assert = assertProto + chai.Assertion.prototype.match = matchProto + chai.Assertion.prototype.__methods.length.method = lengthProto + chai.Assertion.prototype.__methods.contain.method = containProto + + return Object.defineProperty(chai.Assertion.prototype, 'exist', { get: existProto }) + } + + const overrideChaiAsserts = function (assertFn) { + chai.Assertion.prototype.assert = createPatchedAssert(assertFn) + + chaiUtils.getMessage = function (assert, args) { + const obj = assert._obj + + // if we are formatting a DOM object + if ($dom.isDom(obj)) { + // replace object with our formatted one + assert._obj = $dom.stringify(obj, 'short') + } + + const msg = getMessage.call(this, assert, args) + + // restore the real obj if we changed it + if (obj !== assert._obj) { + assert._obj = obj + } + + return msg + } + + chai.Assertion.overwriteMethod('match', (_super) => { + return (function (regExp, ...args) { + if (_.isRegExp(regExp) || $dom.isDom(this._obj)) { + return _super.apply(this, [regExp, ...args]) + } + + const err = $utils.cypressErr($utils.errMessageByPath('chai.match_invalid_argument', { regExp })) + + err.retry = false + throw err + }) + }) + + const containFn1 = (_super) => { + return (function (text, ...args) { + let obj = this._obj + + if (!($dom.isJquery(obj) || $dom.isElement(obj))) { + return _super.apply(this, [text, ...args]) + } + + const escText = $utils.escapeQuotes(text) + + const selector = `:contains('${escText}'), [type='submit'][value~='${escText}']` + + // the assert checks below only work if $dom.isJquery(obj) + // https://github.com/cypress-io/cypress/issues/3549 + if (!($dom.isJquery(obj))) { + obj = $(obj) + } + + this.assert( + obj.is(selector) || !!obj.find(selector).length, + 'expected #{this} to contain #{exp}', + 'expected #{this} not to contain #{exp}', + text + ) + }) + } + + const containFn2 = (_super) => { + return (function (...args) { + _super.apply(this, args) + }) + } + + chai.Assertion.overwriteChainableMethod('contain', containFn1, containFn2) + + chai.Assertion.overwriteChainableMethod('length', + (_super) => { + return (function (length, ...args) { + let obj = this._obj + + if (!($dom.isJquery(obj) || $dom.isElement(obj))) { + return _super.apply(this, [length, ...args]) + } + + length = $utils.normalizeNumber(length) + + // filter out anything not currently in our document + if ($dom.isDetached(obj)) { + obj = (this._obj = obj.filter((index, el) => { + return $dom.isAttached(el) + })) + } + + const node = obj && obj.length ? $dom.stringify(obj, 'short') : obj.selector + + // if our length assertion fails we need to check to + // ensure that the length argument is a finite number + // because if its not, we need to bail on retrying + try { + return this.assert( + obj.length === length, + `expected '${node}' to have a length of \#{exp} but got \#{act}`, + `expected '${node}' to not have a length of \#{act}`, + length, + obj.length + ) + } catch (e1) { + e1.node = node + e1.negated = chaiUtils.flag(this, 'negate') + e1.type = 'length' + + if (_.isFinite(length)) { + const getLongLengthMessage = function (len1, len2) { + if (len1 > len2) { + return `Too many elements found. Found '${len1}', expected '${len2}'.` + } + + return `Not enough elements found. Found '${len1}', expected '${len2}'.` + } + + e1.displayMessage = getLongLengthMessage(obj.length, length) + throw e1 + } + + const e2 = $utils.cypressErr($utils.errMessageByPath('chai.length_invalid_argument', { length })) + + e2.retry = false + throw e2 + } + }) + }, + (_super) => { + return (function (...args) { + return _super.apply(this, args) + }) + }) + + return chai.Assertion.overwriteProperty('exist', (_super) => { + return (function (...args) { + const obj = this._obj + + if (!($dom.isJquery(obj) || $dom.isElement(obj))) { + try { + return _super.apply(this, args) + } catch (e) { + e.type = 'existence' + throw e + } + } else { + let isAttached + + if (!obj.length) { + this._obj = null + } + + const node = obj && obj.length ? $dom.stringify(obj, 'short') : obj.selector + + try { + return this.assert( + (isAttached = $dom.isAttached(obj)), + 'expected \#{act} to exist in the DOM', + 'expected \#{act} not to exist in the DOM', + node, + node + ) + } catch (e1) { + e1.node = node + e1.negated = chaiUtils.flag(this, 'negate') + e1.type = 'existence' + + const getLongExistsMessage = function (obj) { + // if we expected not for an element to exist + if (isAttached) { + return `Expected ${node} not to exist in the DOM, but it was continuously found.` + } + + return `Expected to find element: '${obj.selector}', but never found it.` + } + + e1.displayMessage = getLongExistsMessage(obj) + throw e1 + } + } + }) + }) + } + + const createPatchedAssert = (assertFn) => { + return (function (...args) { + let err + const passed = chaiUtils.test(this, args) + const value = chaiUtils.flag(this, 'object') + const expected = args[3] + + const customArgs = replaceArgMessages(args, this._obj) + + let message = chaiUtils.getMessage(this, customArgs) + const actual = chaiUtils.getActual(this, customArgs) + + message = removeOrKeepSingleQuotesBetweenStars(message) + + try { + assertProto.apply(this, args) + } catch (e) { + err = e + } + + assertFn(passed, message, value, actual, expected, err) + + if (err) { + throw err + } + }) + } + + // only override assertions for this specific + // expect function instance so we do not affect + // the outside world + const overrideExpect = () => { + // make the assertion + return (val, message) => { + return new chai.Assertion(val, message) + } + } + + const overrideAssert = function () { + const fn = (express, errmsg) => { + return chai.assert(express, errmsg) + } + + const fns = _.functions(chai.assert) + + _.each(fns, (name) => { + return fn[name] = function (...args) { + return chai.assert[name].apply(this, args) + } + }) + + return fn + } + + const setSpecWindowGlobals = function (specWindow, assertFn) { + const expect = overrideExpect() + const assert = overrideAssert() + + specWindow.chai = chai + specWindow.expect = expect + specWindow.assert = assert + + return { + chai, + expect, + assert, + } + } + + const create = function (specWindow, assertFn) { + // restoreOverrides() + restoreAsserts() + + // overrideChai() + overrideChaiAsserts(assertFn) + + return setSpecWindowGlobals(specWindow) + } + + module.exports = { + replaceArgMessages, + + removeOrKeepSingleQuotesBetweenStars, + + setSpecWindowGlobals, + + // overrideChai: overrideChai + + restoreAsserts, + + overrideExpect, + + overrideChaiAsserts, + + create, + } +}) diff --git a/packages/driver/test/cypress/integration/commands/assertions_spec.coffee b/packages/driver/test/cypress/integration/commands/assertions_spec.coffee deleted file mode 100644 index 6a0b3db5da0..00000000000 --- a/packages/driver/test/cypress/integration/commands/assertions_spec.coffee +++ /dev/null @@ -1,1987 +0,0 @@ -$ = Cypress.$ -_ = Cypress._ - -helpers = require("../../support/helpers") - -describe "src/cy/commands/assertions", -> - before -> - cy - .visit("/fixtures/jquery.html") - .then (win) -> - @body = win.document.body.outerHTML - - beforeEach -> - doc = cy.state("document") - - $(doc.body).empty().html(@body) - - context "#should", -> - beforeEach -> - @logs = [] - - cy.on "log:added", (attrs, log) => - @logs.push(log) - @lastLog = log - - return null - - it "returns the subject for chainability", -> - cy.noop({foo: "bar"}).should("deep.eq", {foo: "bar"}).then (obj) -> - expect(obj).to.deep.eq {foo: "bar"} - - it "can use negation", -> - cy.noop(false).should("not.be.true") - - it "works with jquery chai", -> - div = $("
asdf
") - - cy.$$("body").append(div) - - cy - .get("div.foo").should("have.class", "foo").then ($div) -> - expect($div).to.match div - $div.remove() - - it "can chain multiple assertions", -> - cy - .get("body") - .should("contain", "div") - .should("have.property", "length", 1) - - it "skips over utility commands", -> - cy.on "command:retry", _.after 2, => - cy.$$("div:first").addClass("foo") - - cy.on "command:retry", _.after 4, => - cy.$$("div:first").attr("id", "bar") - - cy.get("div:first").should("have.class", "foo").debug().and("have.id", "bar") - - it "skips over aliasing", -> - cy.on "command:retry", _.after 2, => - cy.$$("div:first").addClass("foo") - - cy.on "command:retry", _.after 4, => - cy.$$("div:first").attr("id", "bar") - - cy.get("div:first").as("div").should("have.class", "foo").debug().and("have.id", "bar") - - it "can change the subject", -> - cy.get("input:first").should("have.property", "length").should("eq", 1).then (num) -> - expect(num).to.eq(1) - - it "changes the subject with chai-jquery", -> - cy.$$("input:first").attr("id", "input") - - cy.get("input:first").should("have.attr", "id").should("eq", "input") - - it "changes the subject with JSON", -> - obj = {requestJSON: {teamIds: [2]}} - cy.noop(obj).its("requestJSON").should("have.property", "teamIds").should("deep.eq", [2]) - - ## TODO: make cy.then retry - ## https://github.com/cypress-io/cypress/issues/627 - it.skip "outer assertions retry on cy.then", -> - obj = {foo: "bar"} - - cy.wrap(obj).then -> - setTimeout -> - obj.foo = "baz" - , 1000 - - return obj - .should("deep.eq", {foo: "baz"}) - - it "does it retry when wrapped", -> - obj = { foo: "bar" } - - cy.wrap(obj).then -> - setTimeout -> - obj.foo = "baz" - , 100 - - return cy.wrap(obj) - .should("deep.eq", { foo: "baz" }) - - describe "function argument", -> - it "waits until function is true", -> - button = cy.$$("button:first") - - cy.on "command:retry", _.after 2, => - button.addClass("ready") - - cy.get("button:first").should ($button) -> - expect($button).to.have.class("ready") - - it "works with regular objects", -> - obj = {} - - cy.on "command:retry", _.after 2, => - obj.foo = "bar" - - cy.wrap(obj).should (o) -> - expect(o).to.have.property("foo").and.eq("bar") - .then -> - ## wrap + have property + and eq - expect(@logs.length).to.eq(3) - - it "logs two assertions", -> - _.delay => - cy.$$("body").addClass("foo") - , Math.random() * 300 - - _.delay => - cy.$$("body").prop("id", "bar") - , Math.random() * 300 - - cy - .get("body").should ($body) -> - expect($body).to.have.class("foo") - expect($body).to.have.id("bar") - .then -> - cy.$$("body").removeClass("foo").removeAttr("id") - - expect(@logs.length).to.eq(3) - - ## the messages should have been updated to reflect - ## the current state of the element - expect(@logs[1].get("message")).to.eq("expected **** to have class **foo**") - expect(@logs[2].get("message")).to.eq("expected **** to have id **bar**") - - it "logs assertions as children even if subject is different", -> - _.delay => - cy.$$("body").addClass("foo") - , Math.random() * 300 - - _.delay => - cy.$$("body").prop("id", "bar") - , Math.random() * 300 - - cy - .get("body").should ($body) -> - expect($body.attr("class")).to.match(/foo/) - expect($body.attr("id")).to.include("bar") - .then -> - cy.$$("body").removeClass("foo").removeAttr("id") - - types = _.map @logs, (l) -> l.get("type") - expect(types).to.deep.eq(["parent", "child", "child"]) - - expect(@logs.length).to.eq(4) - - context "remote jQuery instances", -> - beforeEach -> - @remoteWindow = cy.state("window") - - it "yields the remote jQuery instance", -> - @remoteWindow.$.fn.__foobar = fn = -> - - cy - .get("input:first").should ($input) -> - isInstanceOf = Cypress.utils.isInstanceOf($input, @remoteWindow.$) - hasProp = $input.__foobar is fn - - expect(isInstanceOf).to.be.true - expect(hasProp).to.to.true - - describe "not.exist", -> - it "resolves eventually not exist", -> - button = cy.$$("button:first") - - cy.on "command:retry", _.after 2, _.once -> - button.remove() - - cy.get("button:first").click().should("not.exist") - - it "resolves all 3 assertions", (done) -> - logs = [] - - cy.on "log:added", (attrs, log) -> - if log.get("name") is "assert" - logs.push(log) - - if logs.length is 3 - done() - - cy - .get("#does-not-exist1").should("not.exist") - .get("#does-not-exist2").should("not.exist") - .get("#does-not-exist3").should("not.exist") - - describe "have.text", -> - it "resolves the assertion", -> - cy.get("#list li").eq(0).should("have.text", "li 0").then -> - lastLog = @lastLog - - expect(lastLog.get("name")).to.eq("assert") - expect(lastLog.get("state")).to.eq("passed") - expect(lastLog.get("ended")).to.be.true - - describe "have.length", -> - it "allows valid string numbers", -> - length = cy.$$("button").length - - cy.get("button").should("have.length", ""+length) - - it "throws when should('have.length') isnt a number", (done) -> - cy.on "fail", (err) -> - expect(err.message).to.eq "You must provide a valid number to a length assertion. You passed: 'asdf'" - done() - - cy.get("button").should("have.length", "asdf") - - it "does not log extra commands on fail and properly fails command + assertions", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(6) - - expect(@logs[3].get("name")).to.eq("get") - expect(@logs[3].get("state")).to.eq("failed") - expect(@logs[3].get("error")).to.eq(err) - - expect(@logs[4].get("name")).to.eq("assert") - expect(@logs[4].get("state")).to.eq("failed") - expect(@logs[4].get("error").name).to.eq("AssertionError") - - done() - - cy - .root().should("exist").and("contain", "foo") - .get("button").should("have.length", "asdf") - - it "finishes failed assertions and does not log extra commands when cy.contains fails", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(2) - - expect(@logs[0].get("name")).to.eq("contains") - expect(@logs[0].get("state")).to.eq("failed") - expect(@logs[0].get("error")).to.eq(err) - - expect(@logs[1].get("name")).to.eq("assert") - expect(@logs[1].get("state")).to.eq("failed") - expect(@logs[1].get("error").name).to.eq("AssertionError") - - done() - - cy.contains("Nested Find").should("have.length", 2) - - describe "have.class", -> - it "snapshots and ends the assertion after retrying", -> - cy.on "command:retry", _.after 3, => - cy.$$("#foo").addClass("active") - - cy.contains("foo").should("have.class", "active").then -> - lastLog = @lastLog - - expect(lastLog.get("name")).to.eq("assert") - expect(lastLog.get("ended")).to.be.true - expect(lastLog.get("state")).to.eq("passed") - expect(lastLog.get("snapshots").length).to.eq(1) - expect(lastLog.get("snapshots")[0]).to.be.an("object") - - it "retries assertion until true", -> - button = cy.$$("button:first") - - retry = _.after 3, -> - button.addClass("new-class") - - cy.on "command:retry", retry - - cy.get("button:first").should("have.class", "new-class") - - describe "errors", -> - beforeEach -> - Cypress.config("defaultCommandTimeout", 50) - - it "should not be true", (done) -> - cy.on "fail", (err) -> - expect(err.message).to.eq "expected false to be true" - done() - - cy.noop(false).should("be.true") - - it "throws err when not available chainable", (done) -> - cy.on "fail", (err) -> - expect(err.message).to.eq "The chainer: 'dee' was not found. Could not build assertion." - done() - - cy.noop({}).should("dee.eq", {}) - - it "throws err when ends with a non available chainable", (done) -> - cy.on "fail", (err) -> - expect(err.message).to.eq "The chainer: 'eq2' was not found. Could not build assertion." - done() - - cy.noop({}).should("deep.eq2", {}) - - it "logs 'should' when non available chainer", (done) -> - cy.on "fail", (err) => - lastLog = @lastLog - - expect(@logs.length).to.eq(2) - expect(lastLog.get("name")).to.eq("should") - expect(lastLog.get("error")).to.eq(err) - expect(lastLog.get("state")).to.eq("failed") - expect(lastLog.get("snapshots").length).to.eq(1) - expect(lastLog.get("snapshots")[0]).to.be.an("object") - expect(lastLog.get("message")).to.eq("not.contain2, does-not-exist-foo-bar") - done() - - cy.get("div:first").should("not.contain2", "does-not-exist-foo-bar") - - it "throws when eventually times out", (done) -> - cy.on "fail", (err) -> - expect(err.message).to.eq "Timed out retrying: expected '") - @$div.html = -> throw new Error("html called") - - it "html, not html, contain html", -> - expect(@$div).to.have.html("") ## 1 - expect(@$div).not.to.have.html("foo") ## 2 - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **
** to have HTML ****" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to have HTML **foo**" - ) - - @clearLogs() - expect(@$div).to.contain.html("**" - ) - - @clearLogs() - try - expect(@$div).to.contain.html("span") - catch err - expect(@logs[0].get("message")).to.eq( - "expected **
** to contain HTML **span**, but the HTML was ****" - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected null to have HTML 'foo'" - ) - expect(err.message).to.include("> html") - expect(err.message).to.include("> null") - - done() - - expect(null).to.have.html("foo") - - it "partial match", -> - expect(@$div).to.contain.html('button') - expect(@$div).to.include.html('button') - expect(@$div).to.not.contain.html('span') - cy.get('button').should('contain.html', 'button') - - context "text", -> - beforeEach -> - @$div = $("
foo
") - @$div.text = -> throw new Error("text called") - - it "text, not text, contain text", -> - expect(@$div).to.have.text("foo") ## 1 - expect(@$div).not.to.have.text("bar") ## 2 - - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **
** to have text **foo**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to have text **bar**" - ) - - @clearLogs() - expect(@$div).to.contain.text("f") - expect(@logs[0].get("message")).to.eq( - "expected **
** to contain text **f**" - ) - - @clearLogs() - expect(@$div).to.not.contain.text("foob") - expect(@logs[0].get("message")).to.eq( - "expected **
** not to contain text **foob**" - ) - - @clearLogs() - try - expect(@$div).to.have.text("bar") - catch err - expect(@logs[0].get("message")).to.eq( - "expected **
** to have text **bar**, but the text was **foo**" - ) - - @clearLogs() - try - expect(@$div).to.contain.text("bar") - catch err - expect(@logs[0].get("message")).to.eq( - "expected **
** to contain text **bar**, but the text was **foo**" - ) - - it "partial match", -> - expect(@$div).to.have.text('foo') - expect(@$div).to.contain.text('o') - expect(@$div).to.include.text('o') - cy.get('div').should('contain.text', 'iv').should('contain.text', 'd') - cy.get('div').should('not.contain.text', 'fizzbuzz').should('contain.text', 'Nest') - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected undefined to have text 'foo'" - ) - expect(err.message).to.include("> text") - expect(err.message).to.include("> undefined") - - done() - - expect(undefined).to.have.text("foo") - - context "value", -> - beforeEach -> - @$input = $("") - @$input.val = -> throw new Error("val called") - - it "value, not value, contain value", -> - expect(@$input).to.have.value("foo") ## 1 - expect(@$input).not.to.have.value("bar") ## 2 - - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **** to have value **foo**" - ) - - expect(l2.get("message")).to.eq( - "expected **** not to have value **bar**" - ) - - @clearLogs() - expect(@$input).to.contain.value("foo") - expect(@logs[0].get("message")).to.eq( - "expected **** to contain value **foo**" - ) - - @clearLogs() - expect(@$input).not.to.contain.value("bar") - expect(@logs[0].get("message")).to.eq( - "expected **** not to contain value **bar**" - ) - - @clearLogs() - try - expect(@$input).to.have.value("bar") - catch err - expect(@logs[0].get("message")).to.eq( - "expected **** to have value **bar**, but the value was **foo**" - ) - - @clearLogs() - try - expect(@$input).to.contain.value("bar") - catch err - expect(@logs[0].get("message")).to.eq( - "expected **** to contain value **bar**, but the value was **foo**" - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to have value 'foo'" - ) - expect(err.message).to.include("> value") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.have.value("foo") - - it "partial match", -> - expect(@$input).to.contain.value('oo') - expect(@$input).to.not.contain.value('oof') - ## make sure "includes" is an alias of "include" - expect(@$input).to.includes.value('oo') - cy.get('input') - .invoke('val','foobar') - .should('contain.value', 'bar') - .should('contain.value', 'foo') - .should('include.value', 'foo') - cy.wrap(null).then -> - cy.$$('').prependTo(cy.$$('body')) - cy.$$('').prependTo(cy.$$('body')) - cy.get('input').should ($els) -> - expect($els).to.have.value('foo2') - expect($els).to.contain.value('foo') - expect($els).to.include.value('foo') - .should('contain.value', 'oo2') - - context "descendants", -> - beforeEach -> - @$div = $("
") - @$div.has = -> throw new Error("has called") - - it "descendants, not descendants", -> - expect(@$div).to.have.descendants("button") ## 1 - expect(@$div).not.to.have.descendants("input") ## 2 - - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **
** to have descendants **button**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to have descendants **input**" - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to have descendants 'foo'" - ) - expect(err.message).to.include("> descendants") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.have.descendants("foo") - - context "visible", -> - beforeEach -> - @$div = $("
div
").appendTo($("body")) - @$div.is = -> throw new Error("is called") - - @$div2 = $("
div
").appendTo($("body")) - @$div2.is = -> throw new Error("is called") - - afterEach -> - @$div.remove() - @$div2.remove() - - it "visible, not visible, adds to error", -> - expect(@$div).to.be.visible ## 1 - expect(@$div2).not.to.be.visible ## 2 - - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **
** to be **visible**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to be **visible**" - ) - - try - expect(@$div2).to.be.visible - catch err - l6 = @logs[5] - - ## the error on this log should have this message appended to it - expect(l6.get("error").message).to.eq( - """ - expected '
' to be 'visible' - - This element '
' is not visible because it has CSS property: 'display: none' - """ - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to be 'visible'" - ) - expect(err.message).to.include("> visible") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.be.visible - - context "hidden", -> - beforeEach -> - @$div = $("
div
").appendTo($("body")) - @$div.is = -> throw new Error("is called") - - @$div2 = $("
div
").appendTo($("body")) - @$div2.is = -> throw new Error("is called") - - afterEach -> - @$div.remove() - @$div2.remove() - - it "hidden, not hidden, adds to error", -> - expect(@$div).to.be.hidden ## 1 - expect(@$div2).not.to.be.hidden ## 2 - - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **
** to be **hidden**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to be **hidden**" - ) - - try - expect(@$div2).to.be.hidden - catch err - l6 = @logs[5] - - ## the error on this log should have this message appended to it - expect(l6.get("error").message).to.eq("expected '
' to be 'hidden'") - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to be 'hidden'" - ) - expect(err.message).to.include("> hidden") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.be.hidden - - context "selected", -> - beforeEach -> - @$option = $("") - @$option.is = -> throw new Error("is called") - - @$option2 = $("") - @$option2.is = -> throw new Error("is called") - - it "selected, not selected", -> - expect(@$option).to.be.selected ## 1 - expect(@$option2).not.to.be.selected ## 2 - - expect(@logs.length).to.eq(2) - - l1 = @logs[0] - l2 = @logs[1] - - expect(l1.get("message")).to.eq( - "expected **
** to be **empty**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to be **empty**" - ) - - expect(l3.get("message")).to.eq( - "expected **
** to be **empty**" - ) - - expect(l4.get("message")).to.eq( - "expected **
** not to be **empty**" - ) - - context "focused", -> - beforeEach -> - @div = $("
").appendTo($('body')) - @div.is = -> throw new Error("is called") - - @div2 = $("
").appendTo($('body')) - @div2.is = -> throw new Error("is called") - - it "focus, not focus, raw dom documents", -> - expect(@div).to.not.be.focused - expect(@div[0]).to.not.be.focused - @div.focus() - expect(@div).to.be.focused - expect(@div[0]).to.be.focused - - @div.blur() - expect(@div).to.not.be.focused - expect(@div[0]).to.not.be.focused - - - expect(@div2).not.to.be.focused - expect(@div2[0]).not.to.be.focused - @div.focus() - expect(@div2).not.to.be.focused - @div2.focus() - expect(@div2).to.be.focused - - expect(@logs.length).to.eq(10) - - l1 = @logs[0] - l2 = @logs[1] - l3 = @logs[2] - l4 = @logs[3] - - expect(l1.get("message")).to.eq( - "expected **** not to be **focused**" - ) - - expect(l2.get("message")).to.eq( - "expected **** not to be **focused**" - ) - - expect(l3.get("message")).to.eq( - "expected **** to be **focused**" - ) - - expect(l4.get("message")).to.eq( - "expected **** to be **focused**" - ) - - it "works with focused or focus", -> - expect(@div).to.not.have.focus - expect(@div).to.not.have.focused - expect(@div).to.not.be.focus - expect(@div).to.not.be.focused - - cy.get('#div').should('not.be.focused') - cy.get('#div').should('not.have.focus') - - it "works with multiple elements", -> - cy.get('div:last').focus() - cy.get('div').should('have.focus') - cy.get('div:last').blur() - cy.get('div').should('not.have.focus') - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.contain( - "expected {} to be 'focused'" - ) - expect(err.message).to.include("> focus") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.have.focus - - it "calls into custom focus pseudos", -> - cy.$$('button:first').focus() - stub = cy.spy($.expr.pseudos, 'focus').as('focus') - expect(cy.$$('button:first')).to.have.focus - cy.get('button:first').should('have.focus') - .then -> - expect(stub).to.be.calledTwice - - context "match", -> - beforeEach -> - @div = $("
") - @div.is = -> throw new Error("is called") - - it "passes thru non DOM", -> - expect('foo').to.match(/f/) - - expect(@logs.length).to.eq(1) - - l1 = @logs[0] - - expect(l1.get("message")).to.eq( - "expected **foo** to match /f/" - ) - - it "match, not match, raw dom documents", -> - expect(@div).to.match("div") ## 1 - expect(@div).not.to.match("button") ## 2 - - expect(@div.get(0)).to.match("div") ## 3 - expect(@div.get(0)).not.to.match("button") ## 4 - - expect(@logs.length).to.eq(4) - - l1 = @logs[0] - l2 = @logs[1] - l3 = @logs[2] - l4 = @logs[3] - - expect(l1.get("message")).to.eq( - "expected **
** to match **div**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** not to match **button**" - ) - - expect(l3.get("message")).to.eq( - "expected **
** to match **div**" - ) - - expect(l4.get("message")).to.eq( - "expected **
** not to match **button**" - ) - - context "contain", -> - it "passes thru non DOM", -> - expect(['foo']).to.contain('foo') ## 1 - expect({foo: 'bar', baz: "quux"}).to.contain({foo: "bar"}) ## 2, 3 - expect('foo').to.contain('fo') ## 4 - - expect(@logs.length).to.eq(4) - - l1 = @logs[0] - l2 = @logs[1] - l3 = @logs[2] - l4 = @logs[3] - - expect(l1.get("message")).to.eq( - "expected **[ foo ]** to include **foo**" - ) - - expect(l2.get("message")).to.eq( - "expected **{ foo: bar, baz: quux }** to have a property **foo**" - ) - - expect(l3.get("message")).to.eq( - "expected **{ foo: bar, baz: quux }** to have a property **foo** of **bar**" - ) - - expect(l4.get("message")).to.eq( - "expected **foo** to include **fo**" - ) - - context "attr", -> - beforeEach -> - @$div = $("
foo
") - @$div.attr = -> throw new Error("attr called") - - @$a = $("google") - @$a.attr = -> throw new Error("attr called") - - it "attr, not attr", -> - expect(@$div).to.have.attr("foo") ## 1 - expect(@$div).to.have.attr("foo", "bar") ## 2 - expect(@$div).not.to.have.attr("bar") ## 3 - expect(@$div).not.to.have.attr("bar", "baz") ## 4 - expect(@$div).not.to.have.attr("foo", "baz") ## 5 - - expect(@$a).to.have.attr("href").and.match(/google/) ## 6, 7 - expect(@$a) - .to.have.attr("href", "https://google.com") ## 8 - .and.have.text("google") ## 9 - - try - expect(@$a).not.to.have.attr("href", "https://google.com") ## 10 - catch error - - expect(@logs.length).to.eq(10) - - l1 = @logs[0] - l2 = @logs[1] - l3 = @logs[2] - l4 = @logs[3] - l5 = @logs[4] - l6 = @logs[5] - l7 = @logs[6] - l8 = @logs[7] - l9 = @logs[8] - l10 = @logs[9] - - expect(l1.get("message")).to.eq( - "expected **
** to have attribute **foo**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** to have attribute **foo** with the value **bar**" - ) - - expect(l3.get("message")).to.eq( - "expected **
** not to have attribute **bar**" - ) - - expect(l4.get("message")).to.eq( - "expected **
** not to have attribute **bar**" - ) - - expect(l5.get("message")).to.eq( - "expected **
** not to have attribute **foo** with the value **baz**" - ) - - expect(l6.get("message")).to.eq( - "expected **** to have attribute **href**" - ) - - expect(l7.get("message")).to.eq( - "expected **https://google.com** to match /google/" - ) - - expect(l8.get("message")).to.eq( - "expected **** to have attribute **href** with the value **https://google.com**" - ) - - expect(l9.get("message")).to.eq( - "expected **** to have text **google**" - ) - - expect(l10.get("message")).to.eq( - "expected **** not to have attribute **href** with the value **https://google.com**, but the value was **https://google.com**" - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to have attribute 'foo'" - ) - expect(err.message).to.include("> attr") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.have.attr("foo") - - context "prop", -> - beforeEach -> - @$input = $("") - @$input.prop("checked", true) - @$input.prop = -> throw new Error("prop called") - - @$a = $("google") - @$a.prop = -> throw new Error("prop called") - - it "prop, not prop", -> - expect(@$input).to.have.prop("checked") ## 1 - expect(@$input).to.have.prop("checked", true) ## 2 - expect(@$input).not.to.have.prop("bar") ## 3 - expect(@$input).not.to.have.prop("bar", "baz") ## 4 - expect(@$input).not.to.have.prop("checked", "baz") ## 5 - - href = window.location.origin + "/foo" - - expect(@$a).to.have.prop("href").and.match(/foo/) ## 6, 7 - expect(@$a) - .to.have.prop("href", href) ## 8 - .and.have.text("google") ## 9 - - try - expect(@$a).not.to.have.prop("href", href) ## 10 - catch error - - expect(@logs.length).to.eq(10) - - l1 = @logs[0] - l2 = @logs[1] - l3 = @logs[2] - l4 = @logs[3] - l5 = @logs[4] - l6 = @logs[5] - l7 = @logs[6] - l8 = @logs[7] - l9 = @logs[8] - l10 = @logs[9] - - expect(l1.get("message")).to.eq( - "expected **** to have property **checked**" - ) - - expect(l2.get("message")).to.eq( - "expected **** to have property **checked** with the value **true**" - ) - - expect(l3.get("message")).to.eq( - "expected **** not to have property **bar**" - ) - - expect(l4.get("message")).to.eq( - "expected **** not to have property **bar**" - ) - - expect(l5.get("message")).to.eq( - "expected **** not to have property **checked** with the value **baz**" - ) - - expect(l6.get("message")).to.eq( - "expected **** to have property **href**" - ) - - expect(l7.get("message")).to.eq( - "expected **#{href}** to match /foo/" - ) - - expect(l8.get("message")).to.eq( - "expected **** to have property **href** with the value **#{href}**" - ) - - expect(l9.get("message")).to.eq( - "expected **** to have text **google**" - ) - - expect(l10.get("message")).to.eq( - "expected **** not to have property **href** with the value **#{href}**, but the value was **#{href}**" - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to have property 'foo'" - ) - expect(err.message).to.include("> prop") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.have.prop("foo") - - context "css", -> - beforeEach -> - @$div = $("
div
") - @$div.css = -> throw new Error("css called") - - it "css, not css", -> - expect(@$div).to.have.css("display") ## 1 - expect(@$div).to.have.css("display", "none") ## 2 - expect(@$div).not.to.have.css("bar") ## 3 - expect(@$div).not.to.have.css("bar", "baz") ## 4 - expect(@$div).not.to.have.css("display", "inline") ## 5 - - try - expect(@$div).not.to.have.css("display", "none") ## 6 - catch error - - expect(@logs.length).to.eq(6) - - l1 = @logs[0] - l2 = @logs[1] - l3 = @logs[2] - l4 = @logs[3] - l5 = @logs[4] - l6 = @logs[5] - - expect(l1.get("message")).to.eq( - "expected **
** to have CSS property **display**" - ) - - expect(l2.get("message")).to.eq( - "expected **
** to have CSS property **display** with the value **none**" - ) - - expect(l3.get("message")).to.eq( - "expected **
** not to have CSS property **bar**" - ) - - expect(l4.get("message")).to.eq( - "expected **
** not to have CSS property **bar**" - ) - - expect(l5.get("message")).to.eq( - "expected **
** not to have CSS property **display** with the value **inline**" - ) - - expect(l6.get("message")).to.eq( - "expected **
** not to have CSS property **display** with the value **none**, but the value was **none**" - ) - - it "throws when obj is not DOM", (done) -> - cy.on "fail", (err) => - expect(@logs.length).to.eq(1) - expect(@logs[0].get("error").message).to.eq( - "expected {} to have CSS property 'foo'" - ) - expect(err.message).to.include("> css") - expect(err.message).to.include("> {}") - - done() - - expect({}).to.have.css("foo") diff --git a/packages/driver/test/cypress/integration/commands/assertions_spec.js b/packages/driver/test/cypress/integration/commands/assertions_spec.js new file mode 100644 index 00000000000..c34d5c63a6e --- /dev/null +++ b/packages/driver/test/cypress/integration/commands/assertions_spec.js @@ -0,0 +1,2482 @@ +const { $, _ } = Cypress + +describe('src/cy/commands/assertions', () => { + before(() => { + cy + .visit('/fixtures/jquery.html') + .then(function (win) { + this.body = win.document.body.outerHTML + }) + }) + + beforeEach(function () { + const doc = cy.state('document') + + $(doc.body).empty().html(this.body) + }) + + context('#should', () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + this.logs.push(log) + this.lastLog = log + }) + + return null + }) + + it('returns the subject for chainability', () => { + cy.noop({ foo: 'bar' }).should('deep.eq', { foo: 'bar' }).then((obj) => { + expect(obj).to.deep.eq({ foo: 'bar' }) + }) + }) + + it('can use negation', () => { + cy.noop(false).should('not.be.true') + }) + + it('works with jquery chai', () => { + const div = $('
asdf
') + + cy.$$('body').append(div) + + cy + .get('div.foo').should('have.class', 'foo').then(($div) => { + expect($div).to.match(div) + + $div.remove() + }) + }) + + it('can chain multiple assertions', () => { + cy + .get('body') + .should('contain', 'div') + .should('have.property', 'length', 1) + }) + + it('skips over utility commands', () => { + cy.on('command:retry', _.after(2, () => { + cy.$$('div:first').addClass('foo') + })) + + cy.on('command:retry', _.after(4, () => { + cy.$$('div:first').attr('id', 'bar') + })) + + cy.get('div:first').should('have.class', 'foo').debug().and('have.id', 'bar') + }) + + it('skips over aliasing', () => { + cy.on('command:retry', _.after(2, () => { + cy.$$('div:first').addClass('foo') + })) + + cy.on('command:retry', _.after(4, () => { + cy.$$('div:first').attr('id', 'bar') + })) + + cy.get('div:first').as('div').should('have.class', 'foo').debug().and('have.id', 'bar') + }) + + it('can change the subject', () => { + cy.get('input:first').should('have.property', 'length').should('eq', 1).then((num) => { + expect(num).to.eq(1) + }) + }) + + it('changes the subject with chai-jquery', () => { + cy.$$('input:first').attr('id', 'input') + + cy.get('input:first').should('have.attr', 'id').should('eq', 'input') + }) + + it('changes the subject with JSON', () => { + const obj = { requestJSON: { teamIds: [2] } } + + cy.noop(obj).its('requestJSON').should('have.property', 'teamIds').should('deep.eq', [2]) + }) + + //# TODO: make cy.then retry + //# https://github.com/cypress-io/cypress/issues/627 + it.skip('outer assertions retry on cy.then', () => { + const obj = { foo: 'bar' } + + cy.wrap(obj).then(() => { + setTimeout(() => { + obj.foo = 'baz' + } + , 1000) + + return obj + }).should('deep.eq', { foo: 'baz' }) + }) + + it('does it retry when wrapped', () => { + const obj = { foo: 'bar' } + + cy.wrap(obj).then(() => { + setTimeout(() => { + obj.foo = 'baz' + } + , 100) + + return cy.wrap(obj) + }).should('deep.eq', { foo: 'baz' }) + }) + + describe('function argument', () => { + it('waits until function is true', () => { + const button = cy.$$('button:first') + + cy.on('command:retry', _.after(2, () => { + button.addClass('ready') + })) + + cy.get('button:first').should(($button) => { + expect($button).to.have.class('ready') + }) + }) + + it('works with regular objects', () => { + const obj = {} + + cy.on('command:retry', _.after(2, () => { + obj.foo = 'bar' + })) + + cy.wrap(obj).should((o) => { + expect(o).to.have.property('foo').and.eq('bar') + }).then(function () { + //# wrap + have property + and eq + expect(this.logs.length).to.eq(3) + }) + }) + + it('logs two assertions', () => { + _.delay(() => { + cy.$$('body').addClass('foo') + } + , Math.random() * 300) + + _.delay(() => { + cy.$$('body').prop('id', 'bar') + } + , Math.random() * 300) + + cy + .get('body').should(($body) => { + expect($body).to.have.class('foo') + + expect($body).to.have.id('bar') + }).then(function () { + cy.$$('body').removeClass('foo').removeAttr('id') + + expect(this.logs.length).to.eq(3) + + //# the messages should have been updated to reflect + //# the current state of the element + expect(this.logs[1].get('message')).to.eq('expected **** to have class **foo**') + + expect(this.logs[2].get('message')).to.eq('expected **** to have id **bar**') + }) + }) + + it('logs assertions as children even if subject is different', () => { + _.delay(() => { + cy.$$('body').addClass('foo') + } + , Math.random() * 300) + + _.delay(() => { + cy.$$('body').prop('id', 'bar') + } + , Math.random() * 300) + + cy + .get('body').should(($body) => { + expect($body.attr('class')).to.match(/foo/) + + expect($body.attr('id')).to.include('bar') + }).then(function () { + cy.$$('body').removeClass('foo').removeAttr('id') + + const types = _.map(this.logs, (l) => l.get('type')) + + expect(types).to.deep.eq(['parent', 'child', 'child']) + + expect(this.logs.length).to.eq(4) + }) + }) + + context('remote jQuery instances', () => { + beforeEach(function () { + this.remoteWindow = cy.state('window') + }) + + it('yields the remote jQuery instance', function () { + let fn + + this.remoteWindow.$.fn.__foobar = (fn = function () {}) + + cy + .get('input:first').should(function ($input) { + const isInstanceOf = Cypress.utils.isInstanceOf($input, this.remoteWindow.$) + const hasProp = $input.__foobar === fn + + expect(isInstanceOf).to.be.true + + expect(hasProp).to.to.true + }) + }) + }) + }) + + describe('not.exist', () => { + it('resolves eventually not exist', () => { + const button = cy.$$('button:first') + + cy.on('command:retry', _.after(2, _.once(() => { + button.remove() + }))) + + cy.get('button:first').click().should('not.exist') + }) + + it('resolves all 3 assertions', (done) => { + const logs = [] + + cy.on('log:added', (attrs, log) => { + if (log.get('name') === 'assert') { + logs.push(log) + + if (logs.length === 3) { + done() + } + } + }) + + cy + .get('#does-not-exist1').should('not.exist') + .get('#does-not-exist2').should('not.exist') + .get('#does-not-exist3').should('not.exist') + }) + }) + + describe('have.text', () => { + it('resolves the assertion', () => { + cy.get('#list li').eq(0).should('have.text', 'li 0').then(function () { + const { lastLog } = this + + expect(lastLog.get('name')).to.eq('assert') + expect(lastLog.get('state')).to.eq('passed') + + expect(lastLog.get('ended')).to.be.true + }) + }) + }) + + describe('have.length', () => { + it('allows valid string numbers', () => { + const { length } = cy.$$('button') + + cy.get('button').should('have.length', `${length}`) + }) + + it('throws when should(\'have.length\') isnt a number', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('You must provide a valid number to a length assertion. You passed: \'asdf\'') + + done() + }) + + cy.get('button').should('have.length', 'asdf') + }) + + it('does not log extra commands on fail and properly fails command + assertions', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(6) + + expect(this.logs[3].get('name')).to.eq('get') + expect(this.logs[3].get('state')).to.eq('failed') + expect(this.logs[3].get('error')).to.eq(err) + + expect(this.logs[4].get('name')).to.eq('assert') + expect(this.logs[4].get('state')).to.eq('failed') + expect(this.logs[4].get('error').name).to.eq('AssertionError') + + done() + }) + + cy + .root().should('exist').and('contain', 'foo') + .get('button').should('have.length', 'asdf') + }) + + it('finishes failed assertions and does not log extra commands when cy.contains fails', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(2) + + expect(this.logs[0].get('name')).to.eq('contains') + expect(this.logs[0].get('state')).to.eq('failed') + expect(this.logs[0].get('error')).to.eq(err) + + expect(this.logs[1].get('name')).to.eq('assert') + expect(this.logs[1].get('state')).to.eq('failed') + expect(this.logs[1].get('error').name).to.eq('AssertionError') + + done() + }) + + cy.contains('Nested Find').should('have.length', 2) + }) + }) + + describe('have.class', () => { + it('snapshots and ends the assertion after retrying', () => { + cy.on('command:retry', _.after(3, () => { + cy.$$('#foo').addClass('active') + })) + + cy.contains('foo').should('have.class', 'active').then(function () { + const { lastLog } = this + + expect(lastLog.get('name')).to.eq('assert') + expect(lastLog.get('ended')).to.be.true + expect(lastLog.get('state')).to.eq('passed') + expect(lastLog.get('snapshots').length).to.eq(1) + + expect(lastLog.get('snapshots')[0]).to.be.an('object') + }) + }) + + it('retries assertion until true', () => { + const button = cy.$$('button:first') + + const retry = _.after(3, () => { + button.addClass('new-class') + }) + + cy.on('command:retry', retry) + + cy.get('button:first').should('have.class', 'new-class') + }) + }) + + describe('errors', () => { + beforeEach(() => { + Cypress.config('defaultCommandTimeout', 50) + }) + + it('should not be true', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('expected false to be true') + + done() + }) + + cy.noop(false).should('be.true') + }) + + it('throws err when not available chainable', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('The chainer: \'dee\' was not found. Could not build assertion.') + + done() + }) + + cy.noop({}).should('dee.eq', {}) + }) + + it('throws err when ends with a non available chainable', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('The chainer: \'eq2\' was not found. Could not build assertion.') + + done() + }) + + cy.noop({}).should('deep.eq2', {}) + }) + + it('logs \'should\' when non available chainer', function (done) { + cy.on('fail', (err) => { + const { lastLog } = this + + expect(this.logs.length).to.eq(2) + expect(lastLog.get('name')).to.eq('should') + expect(lastLog.get('error')).to.eq(err) + expect(lastLog.get('state')).to.eq('failed') + expect(lastLog.get('snapshots').length).to.eq(1) + expect(lastLog.get('snapshots')[0]).to.be.an('object') + expect(lastLog.get('message')).to.eq('not.contain2, does-not-exist-foo-bar') + + done() + }) + + cy.get('div:first').should('not.contain2', 'does-not-exist-foo-bar') + }) + + it('throws when eventually times out', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('Timed out retrying: expected \'
') + this.$div.html = function () { + throw new Error('html called') + } + }) + + it('html, not html, contain html', function () { + expect(this.$div).to.have.html('') //# 1 + expect(this.$div).not.to.have.html('foo') //# 2 + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **
** to have HTML ****' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to have HTML **foo**' + ) + + this.clearLogs() + expect(this.$div).to.contain.html('**' + ) + } + + this.clearLogs() + try { + expect(this.$div).to.contain.html('span') + } catch (error1) { + expect(this.logs[0].get('message')).to.eq( + 'expected **
** to contain HTML **span**, but the HTML was ****' + ) + } + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected null to have HTML \'foo\'' + ) + + expect(err.message).to.include('> html') + expect(err.message).to.include('> null') + + done() + }) + + expect(null).to.have.html('foo') + }) + + it('partial match', function () { + expect(this.$div).to.contain.html('button') + expect(this.$div).to.include.html('button') + expect(this.$div).to.not.contain.html('span') + + cy.get('button').should('contain.html', 'button') + }) + }) + + context('text', () => { + beforeEach(function () { + this.$div = $('
foo
') + this.$div.text = function () { + throw new Error('text called') + } + }) + + it('text, not text, contain text', function () { + expect(this.$div).to.have.text('foo') //# 1 + expect(this.$div).not.to.have.text('bar') //# 2 + + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **
** to have text **foo**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to have text **bar**' + ) + + this.clearLogs() + expect(this.$div).to.contain.text('f') + expect(this.logs[0].get('message')).to.eq( + 'expected **
** to contain text **f**' + ) + + this.clearLogs() + expect(this.$div).to.not.contain.text('foob') + expect(this.logs[0].get('message')).to.eq( + 'expected **
** not to contain text **foob**' + ) + + this.clearLogs() + try { + expect(this.$div).to.have.text('bar') + } catch (error) { + expect(this.logs[0].get('message')).to.eq( + 'expected **
** to have text **bar**, but the text was **foo**' + ) + } + + this.clearLogs() + try { + expect(this.$div).to.contain.text('bar') + } catch (error1) { + expect(this.logs[0].get('message')).to.eq( + 'expected **
** to contain text **bar**, but the text was **foo**' + ) + } + }) + + it('partial match', function () { + expect(this.$div).to.have.text('foo') + expect(this.$div).to.contain.text('o') + expect(this.$div).to.include.text('o') + cy.get('div').should('contain.text', 'iv').should('contain.text', 'd') + + cy.get('div').should('not.contain.text', 'fizzbuzz').should('contain.text', 'Nest') + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected undefined to have text \'foo\'' + ) + + expect(err.message).to.include('> text') + expect(err.message).to.include('> undefined') + + done() + }) + + expect(undefined).to.have.text('foo') + }) + }) + + context('value', () => { + beforeEach(function () { + this.$input = $('') + this.$input.val = function () { + throw new Error('val called') + } + }) + + it('value, not value, contain value', function () { + expect(this.$input).to.have.value('foo') //# 1 + expect(this.$input).not.to.have.value('bar') //# 2 + + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **** to have value **foo**' + ) + + expect(l2.get('message')).to.eq( + 'expected **** not to have value **bar**' + ) + + this.clearLogs() + expect(this.$input).to.contain.value('foo') + expect(this.logs[0].get('message')).to.eq( + 'expected **** to contain value **foo**' + ) + + this.clearLogs() + expect(this.$input).not.to.contain.value('bar') + expect(this.logs[0].get('message')).to.eq( + 'expected **** not to contain value **bar**' + ) + + this.clearLogs() + try { + expect(this.$input).to.have.value('bar') + } catch (error) { + expect(this.logs[0].get('message')).to.eq( + 'expected **** to have value **bar**, but the value was **foo**' + ) + } + + this.clearLogs() + try { + expect(this.$input).to.contain.value('bar') + } catch (error1) { + expect(this.logs[0].get('message')).to.eq( + 'expected **** to contain value **bar**, but the value was **foo**' + ) + } + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to have value \'foo\'' + ) + + expect(err.message).to.include('> value') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.have.value('foo') + }) + + it('partial match', function () { + expect(this.$input).to.contain.value('oo') + expect(this.$input).to.not.contain.value('oof') + //# make sure "includes" is an alias of "include" + expect(this.$input).to.includes.value('oo') + cy.get('input') + .invoke('val', 'foobar') + .should('contain.value', 'bar') + .should('contain.value', 'foo') + .should('include.value', 'foo') + + cy.wrap(null).then(() => { + cy.$$('').prependTo(cy.$$('body')) + + cy.$$('').prependTo(cy.$$('body')) + }) + + cy.get('input').should(($els) => { + expect($els).to.have.value('foo2') + expect($els).to.contain.value('foo') + + expect($els).to.include.value('foo') + }).should('contain.value', 'oo2') + }) + }) + + context('descendants', () => { + beforeEach(function () { + this.$div = $('
') + this.$div.has = function () { + throw new Error('has called') + } + }) + + it('descendants, not descendants', function () { + expect(this.$div).to.have.descendants('button') //# 1 + expect(this.$div).not.to.have.descendants('input') //# 2 + + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **
** to have descendants **button**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to have descendants **input**' + ) + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to have descendants \'foo\'' + ) + + expect(err.message).to.include('> descendants') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.have.descendants('foo') + }) + }) + + context('visible', () => { + beforeEach(function () { + this.$div = $('
div
').appendTo($('body')) + this.$div.is = function () { + throw new Error('is called') + } + + this.$div2 = $('
div
').appendTo($('body')) + this.$div2.is = function () { + throw new Error('is called') + } + }) + + afterEach(function () { + this.$div.remove() + + this.$div2.remove() + }) + + it('visible, not visible, adds to error', function () { + expect(this.$div).to.be.visible //# 1 + expect(this.$div2).not.to.be.visible //# 2 + + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **
** to be **visible**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to be **visible**' + ) + + try { + expect(this.$div2).to.be.visible + } catch (err) { + const l6 = this.logs[5] + + //# the error on this log should have this message appended to it + expect(l6.get('error').message).to.eq( + `\ +expected '
' to be 'visible' + +This element '
' is not visible because it has CSS property: 'display: none'\ +` + ) + } + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to be \'visible\'' + ) + + expect(err.message).to.include('> visible') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.be.visible + }) + }) + + context('hidden', () => { + beforeEach(function () { + this.$div = $('
div
').appendTo($('body')) + this.$div.is = function () { + throw new Error('is called') + } + + this.$div2 = $('
div
').appendTo($('body')) + this.$div2.is = function () { + throw new Error('is called') + } + }) + + afterEach(function () { + this.$div.remove() + + this.$div2.remove() + }) + + it('hidden, not hidden, adds to error', function () { + expect(this.$div).to.be.hidden //# 1 + expect(this.$div2).not.to.be.hidden //# 2 + + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **
** to be **hidden**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to be **hidden**' + ) + + try { + expect(this.$div2).to.be.hidden + } catch (err) { + const l6 = this.logs[5] + + //# the error on this log should have this message appended to it + expect(l6.get('error').message).to.eq('expected \'
\' to be \'hidden\'') + } + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to be \'hidden\'' + ) + + expect(err.message).to.include('> hidden') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.be.hidden + }) + }) + + context('selected', () => { + beforeEach(function () { + this.$option = $('') + this.$option.is = function () { + throw new Error('is called') + } + + this.$option2 = $('') + this.$option2.is = function () { + throw new Error('is called') + } + }) + + it('selected, not selected', function () { + expect(this.$option).to.be.selected //# 1 + expect(this.$option2).not.to.be.selected //# 2 + + expect(this.logs.length).to.eq(2) + + const l1 = this.logs[0] + const l2 = this.logs[1] + + expect(l1.get('message')).to.eq( + 'expected **
** to be **empty**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to be **empty**' + ) + + expect(l3.get('message')).to.eq( + 'expected **
** to be **empty**' + ) + + expect(l4.get('message')).to.eq( + 'expected **
** not to be **empty**' + ) + }) + }) + + context('focused', () => { + beforeEach(function () { + this.div = $('
').appendTo($('body')) + this.div.is = function () { + throw new Error('is called') + } + + this.div2 = $('
').appendTo($('body')) + this.div2.is = function () { + throw new Error('is called') + } + }) + + it('focus, not focus, raw dom documents', function () { + expect(this.div).to.not.be.focused + expect(this.div[0]).to.not.be.focused + this.div.focus() + expect(this.div).to.be.focused + expect(this.div[0]).to.be.focused + + this.div.blur() + expect(this.div).to.not.be.focused + expect(this.div[0]).to.not.be.focused + + expect(this.div2).not.to.be.focused + expect(this.div2[0]).not.to.be.focused + this.div.focus() + expect(this.div2).not.to.be.focused + this.div2.focus() + expect(this.div2).to.be.focused + + expect(this.logs.length).to.eq(10) + + const l1 = this.logs[0] + const l2 = this.logs[1] + const l3 = this.logs[2] + const l4 = this.logs[3] + + expect(l1.get('message')).to.eq( + 'expected **** not to be **focused**' + ) + + expect(l2.get('message')).to.eq( + 'expected **** not to be **focused**' + ) + + expect(l3.get('message')).to.eq( + 'expected **** to be **focused**' + ) + + expect(l4.get('message')).to.eq( + 'expected **** to be **focused**' + ) + }) + + it('works with focused or focus', function () { + expect(this.div).to.not.have.focus + expect(this.div).to.not.have.focused + expect(this.div).to.not.be.focus + expect(this.div).to.not.be.focused + + cy.get('#div').should('not.be.focused') + + cy.get('#div').should('not.have.focus') + }) + + it('works with multiple elements', () => { + cy.get('div:last').focus() + cy.get('div').should('have.focus') + cy.get('div:last').blur() + + cy.get('div').should('not.have.focus') + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.contain( + 'expected {} to be \'focused\'' + ) + + expect(err.message).to.include('> focus') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.have.focus + }) + + it('calls into custom focus pseudos', () => { + cy.$$('button:first').focus() + const stub = cy.spy($.expr.pseudos, 'focus').as('focus') + + expect(cy.$$('button:first')).to.have.focus + + cy.get('button:first').should('have.focus') + .then(() => { + expect(stub).to.be.calledTwice + }) + }) + }) + + context('match', () => { + beforeEach(function () { + this.div = $('
') + this.div.is = function () { + throw new Error('is called') + } + }) + + it('passes thru non DOM', function () { + expect('foo').to.match(/f/) + + expect(this.logs.length).to.eq(1) + + const l1 = this.logs[0] + + expect(l1.get('message')).to.eq( + 'expected **foo** to match /f/' + ) + }) + + it('match, not match, raw dom documents', function () { + expect(this.div).to.match('div') //# 1 + expect(this.div).not.to.match('button') //# 2 + + expect(this.div.get(0)).to.match('div') //# 3 + expect(this.div.get(0)).not.to.match('button') //# 4 + + expect(this.logs.length).to.eq(4) + + const l1 = this.logs[0] + const l2 = this.logs[1] + const l3 = this.logs[2] + const l4 = this.logs[3] + + expect(l1.get('message')).to.eq( + 'expected **
** to match **div**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** not to match **button**' + ) + + expect(l3.get('message')).to.eq( + 'expected **
** to match **div**' + ) + + expect(l4.get('message')).to.eq( + 'expected **
** not to match **button**' + ) + }) + }) + + context('contain', () => { + it('passes thru non DOM', function () { + expect(['foo']).to.contain('foo') //# 1 + expect({ foo: 'bar', baz: 'quux' }).to.contain({ foo: 'bar' }) //# 2, 3 + expect('foo').to.contain('fo') //# 4 + + expect(this.logs.length).to.eq(4) + + const l1 = this.logs[0] + const l2 = this.logs[1] + const l3 = this.logs[2] + const l4 = this.logs[3] + + expect(l1.get('message')).to.eq( + 'expected **[ foo ]** to include **foo**' + ) + + expect(l2.get('message')).to.eq( + 'expected **{ foo: bar, baz: quux }** to have a property **foo**' + ) + + expect(l3.get('message')).to.eq( + 'expected **{ foo: bar, baz: quux }** to have a property **foo** of **bar**' + ) + + expect(l4.get('message')).to.eq( + 'expected **foo** to include **fo**' + ) + }) + }) + + context('attr', () => { + beforeEach(function () { + this.$div = $('
foo
') + this.$div.attr = function () { + throw new Error('attr called') + } + + this.$a = $('google') + this.$a.attr = function () { + throw new Error('attr called') + } + }) + + it('attr, not attr', function () { + expect(this.$div).to.have.attr('foo') //# 1 + expect(this.$div).to.have.attr('foo', 'bar') //# 2 + expect(this.$div).not.to.have.attr('bar') //# 3 + expect(this.$div).not.to.have.attr('bar', 'baz') //# 4 + expect(this.$div).not.to.have.attr('foo', 'baz') //# 5 + + expect(this.$a).to.have.attr('href').and.match(/google/) //# 6, 7 + expect(this.$a) + .to.have.attr('href', 'https://google.com') //# 8 + .and.have.text('google') //# 9 + + try { + expect(this.$a).not.to.have.attr('href', 'https://google.com') //# 10 + } catch (error) {} // eslint-disable-line no-empty + + expect(this.logs.length).to.eq(10) + + const l1 = this.logs[0] + const l2 = this.logs[1] + const l3 = this.logs[2] + const l4 = this.logs[3] + const l5 = this.logs[4] + const l6 = this.logs[5] + const l7 = this.logs[6] + const l8 = this.logs[7] + const l9 = this.logs[8] + const l10 = this.logs[9] + + expect(l1.get('message')).to.eq( + 'expected **
** to have attribute **foo**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** to have attribute **foo** with the value **bar**' + ) + + expect(l3.get('message')).to.eq( + 'expected **
** not to have attribute **bar**' + ) + + expect(l4.get('message')).to.eq( + 'expected **
** not to have attribute **bar**' + ) + + expect(l5.get('message')).to.eq( + 'expected **
** not to have attribute **foo** with the value **baz**' + ) + + expect(l6.get('message')).to.eq( + 'expected **** to have attribute **href**' + ) + + expect(l7.get('message')).to.eq( + 'expected **https://google.com** to match /google/' + ) + + expect(l8.get('message')).to.eq( + 'expected **** to have attribute **href** with the value **https://google.com**' + ) + + expect(l9.get('message')).to.eq( + 'expected **** to have text **google**' + ) + + expect(l10.get('message')).to.eq( + 'expected **** not to have attribute **href** with the value **https://google.com**, but the value was **https://google.com**' + ) + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to have attribute \'foo\'' + ) + + expect(err.message).to.include('> attr') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.have.attr('foo') + }) + }) + + context('prop', () => { + beforeEach(function () { + this.$input = $('') + this.$input.prop('checked', true) + this.$input.prop = function () { + throw new Error('prop called') + } + + this.$a = $('google') + this.$a.prop = function () { + throw new Error('prop called') + } + }) + + it('prop, not prop', function () { + expect(this.$input).to.have.prop('checked') //# 1 + expect(this.$input).to.have.prop('checked', true) //# 2 + expect(this.$input).not.to.have.prop('bar') //# 3 + expect(this.$input).not.to.have.prop('bar', 'baz') //# 4 + expect(this.$input).not.to.have.prop('checked', 'baz') //# 5 + + const href = `${window.location.origin}/foo` + + expect(this.$a).to.have.prop('href').and.match(/foo/) //# 6, 7 + expect(this.$a) + .to.have.prop('href', href) //# 8 + .and.have.text('google') //# 9 + + try { + expect(this.$a).not.to.have.prop('href', href) //# 10 + } catch (error) {} // eslint-disable-line no-empty + + expect(this.logs.length).to.eq(10) + + const l1 = this.logs[0] + const l2 = this.logs[1] + const l3 = this.logs[2] + const l4 = this.logs[3] + const l5 = this.logs[4] + const l6 = this.logs[5] + const l7 = this.logs[6] + const l8 = this.logs[7] + const l9 = this.logs[8] + const l10 = this.logs[9] + + expect(l1.get('message')).to.eq( + 'expected **** to have property **checked**' + ) + + expect(l2.get('message')).to.eq( + 'expected **** to have property **checked** with the value **true**' + ) + + expect(l3.get('message')).to.eq( + 'expected **** not to have property **bar**' + ) + + expect(l4.get('message')).to.eq( + 'expected **** not to have property **bar**' + ) + + expect(l5.get('message')).to.eq( + 'expected **** not to have property **checked** with the value **baz**' + ) + + expect(l6.get('message')).to.eq( + 'expected **** to have property **href**' + ) + + expect(l7.get('message')).to.eq( + `expected **${href}** to match /foo/` + ) + + expect(l8.get('message')).to.eq( + `expected **** to have property **href** with the value **${href}**` + ) + + expect(l9.get('message')).to.eq( + 'expected **** to have text **google**' + ) + + expect(l10.get('message')).to.eq( + `expected **** not to have property **href** with the value **${href}**, but the value was **${href}**` + ) + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to have property \'foo\'' + ) + + expect(err.message).to.include('> prop') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.have.prop('foo') + }) + }) + + context('css', () => { + beforeEach(function () { + this.$div = $('
div
') + this.$div.css = function () { + throw new Error('css called') + } + }) + + it('css, not css', function () { + expect(this.$div).to.have.css('display') //# 1 + expect(this.$div).to.have.css('display', 'none') //# 2 + expect(this.$div).not.to.have.css('bar') //# 3 + expect(this.$div).not.to.have.css('bar', 'baz') //# 4 + expect(this.$div).not.to.have.css('display', 'inline') //# 5 + + try { + expect(this.$div).not.to.have.css('display', 'none') //# 6 + } catch (error) {} // eslint-disable-line no-empty + + expect(this.logs.length).to.eq(6) + + const l1 = this.logs[0] + const l2 = this.logs[1] + const l3 = this.logs[2] + const l4 = this.logs[3] + const l5 = this.logs[4] + const l6 = this.logs[5] + + expect(l1.get('message')).to.eq( + 'expected **
** to have CSS property **display**' + ) + + expect(l2.get('message')).to.eq( + 'expected **
** to have CSS property **display** with the value **none**' + ) + + expect(l3.get('message')).to.eq( + 'expected **
** not to have CSS property **bar**' + ) + + expect(l4.get('message')).to.eq( + 'expected **
** not to have CSS property **bar**' + ) + + expect(l5.get('message')).to.eq( + 'expected **
** not to have CSS property **display** with the value **inline**' + ) + + expect(l6.get('message')).to.eq( + 'expected **
** not to have CSS property **display** with the value **none**, but the value was **none**' + ) + }) + + it('throws when obj is not DOM', function (done) { + cy.on('fail', (err) => { + expect(this.logs.length).to.eq(1) + expect(this.logs[0].get('error').message).to.eq( + 'expected {} to have CSS property \'foo\'' + ) + + expect(err.message).to.include('> css') + expect(err.message).to.include('> {}') + + done() + }) + + expect({}).to.have.css('foo') + }) + }) + }) +})