From 105b13cf6874337a40c7415a50224d79fc2b3c3b Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 22 Jan 2020 14:10:37 -0500 Subject: [PATCH 01/13] migrate vscode eslint settings --- .vscode/settings.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f6006f21b08..a08fff44ae4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,5 +37,13 @@ "typescript", "typescriptreact", "json" - ] + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "[coffeescript]": { + "editor.codeActionsOnSave": { + "source.fixAll.eslint": false + } + } } From bd66f3530dcf17a7d8da1f3097aaeb18edf44bd2 Mon Sep 17 00:00:00 2001 From: KHeo Date: Mon, 11 Nov 2019 12:58:43 +0900 Subject: [PATCH 02/13] Handles whitespaces with newlines. --- packages/driver/src/cy/commands/querying.js | 134 +++++++++++------- .../integration/commands/querying_spec.js | 58 +++++++- 2 files changed, 139 insertions(+), 53 deletions(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index c7f405e02782..fe0350cd91e7 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -13,7 +13,9 @@ const restoreContains = () => { return $expr.contains = $contains } -module.exports = (Commands, Cypress, cy) => { +const whitespaces = /\s+/g + +module.exports = function (Commands, Cypress, cy, state, config) { // restore initially when a run starts restoreContains() @@ -32,7 +34,7 @@ module.exports = (Commands, Cypress, cy) => { options._log = Cypress.log() } - const log = ($el) => { + const log = function ($el) { if (options.log === false) { return } @@ -40,17 +42,20 @@ module.exports = (Commands, Cypress, cy) => { options._log.set({ $el, consoleProps () { - const ret = $el ? $dom.getElements($el) : '--nothing--' + const ret = $el ? + $dom.getElements($el) + : + '--nothing--' return { Yielded: ret, - Elements: $el != null ? $el.length : 0, + Elements: ($el != null ? $el.length : undefined) != null ? ($el != null ? $el.length : undefined) : 0, } }, }) } - const getFocused = () => { + const getFocused = function () { const focused = cy.getFocused() log(focused) @@ -58,7 +63,7 @@ module.exports = (Commands, Cypress, cy) => { return focused } - const resolveFocused = () => { + const resolveFocused = (failedByNonAssertion) => { return Promise .try(getFocused) .then(($el) => { @@ -78,10 +83,11 @@ module.exports = (Commands, Cypress, cy) => { }) } - return resolveFocused() + return resolveFocused(false) }, get (selector, options = {}) { + let aliasObj; let allParts; let resolveElements; let toSelect const ctx = this if ((options === null) || Array.isArray(options) || (typeof options !== 'object')) { @@ -98,26 +104,23 @@ module.exports = (Commands, Cypress, cy) => { verify: true, }) - let aliasObj const consoleProps = {} - const start = (aliasType) => { + const start = function (aliasType) { if (options.log === false) { return } - if (options._log == null) { - options._log = Cypress.log({ - message: selector, - referencesAlias: (aliasObj != null && aliasObj.alias) ? { name: aliasObj.alias } : undefined, - aliasType, - consoleProps: () => { - return consoleProps - }, - }) - } + options._log = options._log != null ? options._log : Cypress.log({ + message: selector, + referencesAlias: (aliasObj != null ? aliasObj.alias : undefined) ? { name: aliasObj.alias } : undefined, + aliasType, + consoleProps () { + return consoleProps + }, + }) } - const log = (value, aliasType = 'dom') => { + const log = function (value, aliasType = 'dom') { if (options.log === false) { return } @@ -135,7 +138,7 @@ module.exports = (Commands, Cypress, cy) => { }) } - obj.consoleProps = () => { + obj.consoleProps = function () { const key = aliasObj ? 'Alias' : 'Selector' consoleProps[key] = selector @@ -170,9 +173,6 @@ module.exports = (Commands, Cypress, cy) => { options._log.set(obj) } - let allParts - let toSelect - // We want to strip everything after the last '.' // only when it is potentially a number or 'all' if ((_.indexOf(selector, '.') === -1) || @@ -187,12 +187,12 @@ module.exports = (Commands, Cypress, cy) => { if (aliasObj) { let { subject, alias, command } = aliasObj - const resolveAlias = () => { + const resolveAlias = function () { // if this is a DOM element if ($dom.isElement(subject)) { let replayFrom = false - const replay = () => { + const replay = function () { cy.replayCommandsFrom(command) // its important to return undefined @@ -272,7 +272,7 @@ module.exports = (Commands, Cypress, cy) => { start('dom') - const setEl = ($el) => { + const setEl = function ($el) { if (options.log === false) { return } @@ -283,7 +283,7 @@ module.exports = (Commands, Cypress, cy) => { options._log.set({ $el }) } - const getElements = () => { + const getElements = function () { // attempt to query for the elements by withinSubject context // and catch any sizzle errors! let $el @@ -298,7 +298,7 @@ module.exports = (Commands, Cypress, cy) => { $el.selector = selector } } catch (e) { - e.onFail = () => { + e.onFail = function () { if (options.log === false) { return e } @@ -341,7 +341,7 @@ module.exports = (Commands, Cypress, cy) => { } } - const resolveElements = () => { + resolveElements = () => { return Promise.try(getElements).then(($el) => { if (options.verify === false) { return $el @@ -363,7 +363,7 @@ module.exports = (Commands, Cypress, cy) => { options._log = Cypress.log({ message: '' }) } - const log = ($el) => { + const log = function ($el) { if (options.log) { options._log.set({ $el }) } @@ -393,16 +393,13 @@ module.exports = (Commands, Cypress, cy) => { subject = null } - if (_.isRegExp(text)) { - // .contains(filter, text) + if (_.isRegExp(text)) { // .contains(filter, text) // Do nothing - } else if (_.isObject(text)) { - // .contains(text, options) + } else if (_.isObject(text)) { // .contains(text, options) options = text text = filter filter = '' - } else if (_.isUndefined(text)) { - // .contains(text) + } else if (_.isUndefined(text)) { // .contains(text) text = filter filter = '' } @@ -417,7 +414,7 @@ module.exports = (Commands, Cypress, cy) => { $utils.throwErrByPath('contains.empty_string') } - const getPhrase = () => { + const getPhrase = function (type, negated) { if (filter && subject) { const node = $dom.stringify(subject, 'short') @@ -437,15 +434,18 @@ module.exports = (Commands, Cypress, cy) => { return '' } - const getErr = (err) => { + const getErr = function (err) { const { type, negated } = err - if (type === 'existence') { - if (negated) { - return `Expected not to find content: '${text}' ${getPhrase()}but continuously found it.` - } + switch (type) { + case 'existence': + if (negated) { + return `Expected not to find content: '${text}' ${getPhrase(type, negated)}but continuously found it.` + } - return `Expected to find content: '${text}' ${getPhrase()}but never did.` + return `Expected to find content: '${text}' ${getPhrase(type, negated)}but never did.` + default: + break } } @@ -460,13 +460,13 @@ module.exports = (Commands, Cypress, cy) => { options._log = Cypress.log({ message: _.compact([filter, text]), type: subject ? 'child' : 'parent', - consoleProps: () => { + consoleProps () { return consoleProps }, }) } - const setEl = ($el) => { + const setEl = function ($el) { if (options.log === false) { return } @@ -477,10 +477,31 @@ module.exports = (Commands, Cypress, cy) => { options._log.set({ $el }) } + // When multiple space characters are considered as a single whitespace in all tags except
.
+      const normalizeWhitespaces = (elem) => {
+        let testText = elem.textContent || elem.innerText || $.text(elem)
+
+        if (elem.tagName === 'PRE') {
+          return testText
+        }
+
+        return testText.replace(whitespaces, ' ')
+      }
+
       if (_.isRegExp(text)) {
         // taken from jquery's normal contains method
         $expr.contains = (elem) => {
-          return text.test(elem.textContent || elem.innerText || $.text(elem))
+          const testText = normalizeWhitespaces(elem)
+
+          return text.test(testText)
+        }
+      }
+
+      if (_.isString(text)) {
+        $expr.contains = (elem) => {
+          const testText = normalizeWhitespaces(elem)
+
+          return testText.includes(text)
         }
       }
 
@@ -488,7 +509,22 @@ module.exports = (Commands, Cypress, cy) => {
       // and any submit inputs with the attributeContainsWord selector
       const selector = $dom.getContainsSelector(text, filter)
 
-      const resolveElements = () => {
+      // eslint-disable-next-line no-unused-vars
+      const checkToAutomaticallyRetry = function (count, $el) {
+        // we should automatically retry querying
+        // if we did not have any upcoming assertions
+        // and our $el's length was 0, because that means
+        // the element didnt exist in the DOM and the user
+        // did not explicitly request that it does not exist
+        if ((count !== 0) || ($el && $el.length)) {
+          return
+        }
+
+        // throw here to cause the .catch to trigger
+        throw new Error()
+      }
+
+      const resolveElements = function () {
         const getOpts = _.extend(_.clone(options), {
           // error: getErr(text, phrase)
           withinSubject: subject || cy.state('withinSubject') || cy.$$('body'),
@@ -581,7 +617,7 @@ module.exports = (Commands, Cypress, cy) => {
       // so when each command starts, check to see if this
       // is the command which references our 'next' and
       // if so, remove the within subject
-      const setWithinSubject = (obj) => {
+      const setWithinSubject = function (obj) {
         if (obj !== next) {
           return
         }
diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js
index 78cfc78109da..cb4433ea2dfc 100644
--- a/packages/driver/test/cypress/integration/commands/querying_spec.js
+++ b/packages/driver/test/cypress/integration/commands/querying_spec.js
@@ -1817,21 +1817,71 @@ describe('src/cy/commands/querying', () => {
       })
     })
 
-    // NOTE: not sure why this is skipped... last edit was 3 years ago...
-    // @bkucera maybe take a look at this
-    describe.skip('handles whitespace', () => {
+    describe('handles whitespace', () => {
       it('finds el with new lines', () => {
         const btn = $(`\
-\
 `).appendTo(cy.$$('body'))
 
+        cy.get('#whitespace1').contains('White space')
         cy.contains('White space').then(($btn) => {
           expect($btn.get(0)).to.eq(btn.get(0))
         })
       })
+
+      it('finds el with new lines + spaces', () => {
+        const btn = $(`\
+\
+`).appendTo(cy.$$('body'))
+
+        cy.get('#whitespace2').contains('White space')
+        cy.contains('White space').then(($btn) => {
+          expect($btn.get(0)).to.eq(btn.get(0))
+        })
+      })
+
+      it('finds el with multiple spaces', () => {
+        const btn = $(`\
+\
+`).appendTo(cy.$$('body'))
+
+        cy.get('#whitespace3').contains('White space')
+        cy.contains('White space').then(($btn) => {
+          expect($btn.get(0)).to.eq(btn.get(0))
+        })
+      })
+
+      it('finds el with regex', () => {
+        const btn = $(`\
+\
+`).appendTo(cy.$$('body'))
+
+        cy.get('#whitespace4').contains('White space')
+        cy.contains(/White space/).then(($btn) => {
+          expect($btn.get(0)).to.eq(btn.get(0))
+        })
+      })
+
+      it('does not normalize text in pre tag', () => {
+        $(`\
+
+White
+space
+
\ +`).appendTo(cy.$$('body')) + + cy.contains('White space').should('not.match', 'pre') + }) }) describe('subject contains text nodes', () => { From 7408ec31aec88f2fdbe14f5b0e369cca7b473519 Mon Sep 17 00:00:00 2001 From: KHeo Date: Mon, 11 Nov 2019 16:59:05 +0900 Subject: [PATCH 03/13] Feature: contains() matches case insensitivity --- packages/driver/src/cy/commands/querying.js | 17 +++++++++--- .../integration/commands/querying_spec.js | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index fe0350cd91e7..f01caa92bc6c 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -404,7 +404,7 @@ module.exports = function (Commands, Cypress, cy, state, config) { filter = '' } - _.defaults(options, { log: true }) + _.defaults(options, { log: true, matchCase: true }) if (!(_.isString(text) || _.isFinite(text) || _.isRegExp(text))) { $utils.throwErrByPath('contains.invalid_argument') @@ -489,9 +489,15 @@ module.exports = function (Commands, Cypress, cy, state, config) { } if (_.isRegExp(text)) { + if (!options.matchCase) { + if (!text.flags.includes('i')) { + text = new RegExp(text.source, text.flags + 'i') // eslint-disable-line prefer-template + } + } + // taken from jquery's normal contains method $expr.contains = (elem) => { - const testText = normalizeWhitespaces(elem) + let testText = normalizeWhitespaces(elem) return text.test(testText) } @@ -499,7 +505,12 @@ module.exports = function (Commands, Cypress, cy, state, config) { if (_.isString(text)) { $expr.contains = (elem) => { - const testText = normalizeWhitespaces(elem) + let testText = normalizeWhitespaces(elem) + + if (!options.matchCase) { + testText = testText.toLowerCase() + text = text.toLowerCase() + } return testText.includes(text) } diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js index cb4433ea2dfc..ea2c4e41d1b7 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.js +++ b/packages/driver/test/cypress/integration/commands/querying_spec.js @@ -1884,6 +1884,32 @@ space }) }) + describe('case sensitivity', () => { + beforeEach(() => { + $('').appendTo(cy.$$('body')) + }) + + it('is case sensitive when matchCase is undefined', () => { + cy.get('#test-button').contains('Test') + }) + + it('is case insensitive when matchCase is false', () => { + cy.get('#test-button').contains('test', { + matchCase: false, + }) + + cy.get('#test-button').contains(/Test/, { + matchCase: false, + }) + }) + + it('does not crash when matchCase: false is used with regex flag, i', () => { + cy.get('#test-button').contains(/Test/i, { + matchCase: false, + }) + }) + }) + describe('subject contains text nodes', () => { it('searches for content within subject', () => { const badge = cy.$$('#edge-case-contains .badge:contains(5)') From 5acb51b368abd0aa5b5707f6570e50373d009f68 Mon Sep 17 00:00:00 2001 From: KHeo Date: Tue, 3 Dec 2019 12:46:30 +0900 Subject: [PATCH 04/13] Fixed merge conflicts. --- packages/driver/src/cy/commands/querying.js | 2 +- .../test/cypress/integration/commands/querying_spec.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index f01caa92bc6c..0a64f1d79d75 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -15,7 +15,7 @@ const restoreContains = () => { const whitespaces = /\s+/g -module.exports = function (Commands, Cypress, cy, state, config) { +module.exports = (Commands, Cypress, cy) => { // restore initially when a run starts restoreContains() diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js index ea2c4e41d1b7..5d65816f7b06 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.js +++ b/packages/driver/test/cypress/integration/commands/querying_spec.js @@ -1893,6 +1893,12 @@ space cy.get('#test-button').contains('Test') }) + it('is case sensitive when matchCase is true', () => { + cy.get('#test-button').contains('Test', { + matchCase: true, + }) + }) + it('is case insensitive when matchCase is false', () => { cy.get('#test-button').contains('test', { matchCase: false, From b31a7dd4ac266769bf4fa6a3a0c10da89a538890 Mon Sep 17 00:00:00 2001 From: KHeo Date: Tue, 3 Dec 2019 14:46:31 +0900 Subject: [PATCH 05/13] Fixed conflicts. --- packages/driver/src/cy/commands/querying.js | 107 +++++++++----------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index 0a64f1d79d75..89cbc5d56964 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -34,7 +34,7 @@ module.exports = (Commands, Cypress, cy) => { options._log = Cypress.log() } - const log = function ($el) { + const log = ($el) => { if (options.log === false) { return } @@ -42,20 +42,17 @@ module.exports = (Commands, Cypress, cy) => { options._log.set({ $el, consoleProps () { - const ret = $el ? - $dom.getElements($el) - : - '--nothing--' + const ret = $el ? $dom.getElements($el) : '--nothing--' return { Yielded: ret, - Elements: ($el != null ? $el.length : undefined) != null ? ($el != null ? $el.length : undefined) : 0, + Elements: $el != null ? $el.length : 0, } }, }) } - const getFocused = function () { + const getFocused = () => { const focused = cy.getFocused() log(focused) @@ -63,7 +60,7 @@ module.exports = (Commands, Cypress, cy) => { return focused } - const resolveFocused = (failedByNonAssertion) => { + const resolveFocused = () => { return Promise .try(getFocused) .then(($el) => { @@ -83,11 +80,10 @@ module.exports = (Commands, Cypress, cy) => { }) } - return resolveFocused(false) + return resolveFocused() }, get (selector, options = {}) { - let aliasObj; let allParts; let resolveElements; let toSelect const ctx = this if ((options === null) || Array.isArray(options) || (typeof options !== 'object')) { @@ -104,23 +100,26 @@ module.exports = (Commands, Cypress, cy) => { verify: true, }) + let aliasObj const consoleProps = {} - const start = function (aliasType) { + const start = (aliasType) => { if (options.log === false) { return } - options._log = options._log != null ? options._log : Cypress.log({ - message: selector, - referencesAlias: (aliasObj != null ? aliasObj.alias : undefined) ? { name: aliasObj.alias } : undefined, - aliasType, - consoleProps () { - return consoleProps - }, - }) + if (options._log == null) { + options._log = Cypress.log({ + message: selector, + referencesAlias: (aliasObj != null && aliasObj.alias) ? { name: aliasObj.alias } : undefined, + aliasType, + consoleProps: () => { + return consoleProps + }, + }) + } } - const log = function (value, aliasType = 'dom') { + const log = (value, aliasType = 'dom') => { if (options.log === false) { return } @@ -138,7 +137,7 @@ module.exports = (Commands, Cypress, cy) => { }) } - obj.consoleProps = function () { + obj.consoleProps = () => { const key = aliasObj ? 'Alias' : 'Selector' consoleProps[key] = selector @@ -173,6 +172,9 @@ module.exports = (Commands, Cypress, cy) => { options._log.set(obj) } + let allParts + let toSelect + // We want to strip everything after the last '.' // only when it is potentially a number or 'all' if ((_.indexOf(selector, '.') === -1) || @@ -187,12 +189,12 @@ module.exports = (Commands, Cypress, cy) => { if (aliasObj) { let { subject, alias, command } = aliasObj - const resolveAlias = function () { + const resolveAlias = () => { // if this is a DOM element if ($dom.isElement(subject)) { let replayFrom = false - const replay = function () { + const replay = () => { cy.replayCommandsFrom(command) // its important to return undefined @@ -272,7 +274,7 @@ module.exports = (Commands, Cypress, cy) => { start('dom') - const setEl = function ($el) { + const setEl = ($el) => { if (options.log === false) { return } @@ -283,7 +285,7 @@ module.exports = (Commands, Cypress, cy) => { options._log.set({ $el }) } - const getElements = function () { + const getElements = () => { // attempt to query for the elements by withinSubject context // and catch any sizzle errors! let $el @@ -298,7 +300,7 @@ module.exports = (Commands, Cypress, cy) => { $el.selector = selector } } catch (e) { - e.onFail = function () { + e.onFail = () => { if (options.log === false) { return e } @@ -341,7 +343,7 @@ module.exports = (Commands, Cypress, cy) => { } } - resolveElements = () => { + const resolveElements = () => { return Promise.try(getElements).then(($el) => { if (options.verify === false) { return $el @@ -363,7 +365,7 @@ module.exports = (Commands, Cypress, cy) => { options._log = Cypress.log({ message: '' }) } - const log = function ($el) { + const log = ($el) => { if (options.log) { options._log.set({ $el }) } @@ -393,13 +395,16 @@ module.exports = (Commands, Cypress, cy) => { subject = null } - if (_.isRegExp(text)) { // .contains(filter, text) + if (_.isRegExp(text)) { + // .contains(filter, text) // Do nothing - } else if (_.isObject(text)) { // .contains(text, options) + } else if (_.isObject(text)) { + // .contains(text, options) options = text text = filter filter = '' - } else if (_.isUndefined(text)) { // .contains(text) + } else if (_.isUndefined(text)) { + // .contains(text) text = filter filter = '' } @@ -414,7 +419,7 @@ module.exports = (Commands, Cypress, cy) => { $utils.throwErrByPath('contains.empty_string') } - const getPhrase = function (type, negated) { + const getPhrase = () => { if (filter && subject) { const node = $dom.stringify(subject, 'short') @@ -434,18 +439,15 @@ module.exports = (Commands, Cypress, cy) => { return '' } - const getErr = function (err) { + const getErr = (err) => { const { type, negated } = err - switch (type) { - case 'existence': - if (negated) { - return `Expected not to find content: '${text}' ${getPhrase(type, negated)}but continuously found it.` - } + if (type === 'existence') { + if (negated) { + return `Expected not to find content: '${text}' ${getPhrase()}but continuously found it.` + } - return `Expected to find content: '${text}' ${getPhrase(type, negated)}but never did.` - default: - break + return `Expected to find content: '${text}' ${getPhrase()}but never did.` } } @@ -460,13 +462,13 @@ module.exports = (Commands, Cypress, cy) => { options._log = Cypress.log({ message: _.compact([filter, text]), type: subject ? 'child' : 'parent', - consoleProps () { + consoleProps: () => { return consoleProps }, }) } - const setEl = function ($el) { + const setEl = ($el) => { if (options.log === false) { return } @@ -520,22 +522,7 @@ module.exports = (Commands, Cypress, cy) => { // and any submit inputs with the attributeContainsWord selector const selector = $dom.getContainsSelector(text, filter) - // eslint-disable-next-line no-unused-vars - const checkToAutomaticallyRetry = function (count, $el) { - // we should automatically retry querying - // if we did not have any upcoming assertions - // and our $el's length was 0, because that means - // the element didnt exist in the DOM and the user - // did not explicitly request that it does not exist - if ((count !== 0) || ($el && $el.length)) { - return - } - - // throw here to cause the .catch to trigger - throw new Error() - } - - const resolveElements = function () { + const resolveElements = () => { const getOpts = _.extend(_.clone(options), { // error: getErr(text, phrase) withinSubject: subject || cy.state('withinSubject') || cy.$$('body'), @@ -628,7 +615,7 @@ module.exports = (Commands, Cypress, cy) => { // so when each command starts, check to see if this // is the command which references our 'next' and // if so, remove the within subject - const setWithinSubject = function (obj) { + const setWithinSubject = (obj) => { if (obj !== next) { return } From 12c79f000d6219af63a2fc15b450910acd5cbc2e Mon Sep 17 00:00:00 2001 From: KHeo Date: Wed, 15 Jan 2020 09:27:31 +0900 Subject: [PATCH 06/13] Added option type, CaseMatchable. --- cli/types/index.d.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index b8d618c2c5ad..5981effb2cf1 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -594,7 +594,7 @@ declare namespace Cypress { * // tries to find the given text for up to 1 second * cy.contains('my text to find', {timeout: 1000}) */ - contains(content: string | number | RegExp, options?: Partial): Chainable + contains(content: string | number | RegExp, options?: Partial): Chainable /** * Get the child DOM element that contains given text. * @@ -612,7 +612,7 @@ declare namespace Cypress { * // yields
    ...
* cy.contains('ul', 'apples') */ - contains(selector: K, text: string | number | RegExp, options?: Partial): Chainable> + contains(selector: K, text: string | number | RegExp, options?: Partial): Chainable> /** * Get the DOM element using CSS "selector" containing the text or regular expression. * @@ -621,7 +621,7 @@ declare namespace Cypress { * // yields <... class="foo">... apples ... * cy.contains('.foo', 'apples') */ - contains(selector: string, text: string | number | RegExp, options?: Partial): Chainable> + contains(selector: string, text: string | number | RegExp, options?: Partial): Chainable> /** * Double-click a DOM element. @@ -1995,6 +1995,18 @@ declare namespace Cypress { timeout: number } + /** + * Options that check case sensitivity + */ + interface CaseMatchable { + /** + * Check case sensitivity + * + * @default true + */ + matchCase: boolean + } + /** * Options that control how long the Test Runner waits for an XHR request and response to succeed */ From 53b3365438a070d4a6ce3747ea78db8bbae3a12d Mon Sep 17 00:00:00 2001 From: KHeo Date: Wed, 15 Jan 2020 10:06:18 +0900 Subject: [PATCH 07/13] Fixed lint error. --- cli/types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index 5981effb2cf1..e3a19378fc59 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -2001,7 +2001,7 @@ declare namespace Cypress { interface CaseMatchable { /** * Check case sensitivity - * + * * @default true */ matchCase: boolean From fc2c1d03e10def5ac682b3699ebb1f07c9a4a930 Mon Sep 17 00:00:00 2001 From: KHeo Date: Wed, 15 Jan 2020 10:06:40 +0900 Subject: [PATCH 08/13] Added test for leading/trailing spaces. --- .../test/cypress/integration/commands/querying_spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js index 5d65816f7b06..d361b9247607 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.js +++ b/packages/driver/test/cypress/integration/commands/querying_spec.js @@ -1881,6 +1881,16 @@ space `).appendTo(cy.$$('body')) cy.contains('White space').should('not.match', 'pre') + cy.get('#whitespace5').contains('White\nspace') + }) + + it('finds el with leading/trailing spaces', () => { + const btn = $(``).appendTo(cy.$$('body')) + + cy.get('#whitespace6').contains('White space') + cy.contains('White space').then(($btn) => { + expect($btn.get(0)).to.eq(btn.get(0)) + }) }) }) From ff5b9c542fa2336773882e2e065aecfc3f684611 Mon Sep 17 00:00:00 2001 From: KHeo Date: Thu, 16 Jan 2020 10:59:12 +0900 Subject: [PATCH 09/13] Add an error message for regex and matchCase conflict. --- packages/driver/src/cy/commands/querying.js | 10 ++++++---- .../cypress/integration/commands/querying_spec.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index 89cbc5d56964..b88db3a12d47 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -491,10 +491,12 @@ module.exports = (Commands, Cypress, cy) => { } if (_.isRegExp(text)) { - if (!options.matchCase) { - if (!text.flags.includes('i')) { - text = new RegExp(text.source, text.flags + 'i') // eslint-disable-line prefer-template - } + if (options.matchCase === false && !text.flags.includes('i')) { + text = new RegExp(text.source, text.flags + 'i') // eslint-disable-line prefer-template + } + + if (options.matchCase === true && text.flags.includes('i')) { + throw new Error('cy.contains() content has i flag and matchCase is true. What is intended?') } // taken from jquery's normal contains method diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js index d361b9247607..d006dfe5ddd6 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.js +++ b/packages/driver/test/cypress/integration/commands/querying_spec.js @@ -1924,6 +1924,18 @@ space matchCase: false, }) }) + + it('throws when content has "i" flag while matchCase: true', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('cy.contains() content has i flag and matchCase is true. What is intended?') + + done() + }) + + cy.get('#test-button').contains(/Test/i, { + matchCase: true, + }) + }) }) describe('subject contains text nodes', () => { From 89cc3f8173dada0ba2de4c07a9fd836af6161e4d Mon Sep 17 00:00:00 2001 From: KHeo Date: Thu, 16 Jan 2020 12:03:57 +0900 Subject: [PATCH 10/13] Fix the valid case that throws an error. --- packages/driver/src/cy/commands/querying.js | 8 ++++---- .../test/cypress/integration/commands/querying_spec.js | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index b88db3a12d47..36ff834fab27 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -409,6 +409,10 @@ module.exports = (Commands, Cypress, cy) => { filter = '' } + if (options.matchCase === true && _.isRegExp(text) && text.flags.includes('i')) { + throw new Error('cy.contains() content has i flag and matchCase is true. What is intended?') + } + _.defaults(options, { log: true, matchCase: true }) if (!(_.isString(text) || _.isFinite(text) || _.isRegExp(text))) { @@ -495,10 +499,6 @@ module.exports = (Commands, Cypress, cy) => { text = new RegExp(text.source, text.flags + 'i') // eslint-disable-line prefer-template } - if (options.matchCase === true && text.flags.includes('i')) { - throw new Error('cy.contains() content has i flag and matchCase is true. What is intended?') - } - // taken from jquery's normal contains method $expr.contains = (elem) => { let testText = normalizeWhitespaces(elem) diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js index d006dfe5ddd6..572969957b2e 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.js +++ b/packages/driver/test/cypress/integration/commands/querying_spec.js @@ -1936,6 +1936,10 @@ space matchCase: true, }) }) + + it('passes when "i" flag is used with undefined option', () => { + cy.get('#test-button').contains(/Test/i) + }) }) describe('subject contains text nodes', () => { From ec02a09eac70286ef61082eb6b1ad7930ba1aef3 Mon Sep 17 00:00:00 2001 From: KHeo Date: Tue, 28 Jan 2020 10:50:23 +0900 Subject: [PATCH 11/13] Fix how error message is thrown. --- packages/driver/src/cy/commands/querying.js | 2 +- packages/driver/src/cypress/error_messages.coffee | 1 + .../driver/test/cypress/integration/commands/querying_spec.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index 36ff834fab27..16c73230cba3 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -410,7 +410,7 @@ module.exports = (Commands, Cypress, cy) => { } if (options.matchCase === true && _.isRegExp(text) && text.flags.includes('i')) { - throw new Error('cy.contains() content has i flag and matchCase is true. What is intended?') + $utils.throwErrByPath('contains.regex_conflict') } _.defaults(options, { log: true, matchCase: true }) diff --git a/packages/driver/src/cypress/error_messages.coffee b/packages/driver/src/cypress/error_messages.coffee index 38febed47396..8b2517f20f66 100644 --- a/packages/driver/src/cypress/error_messages.coffee +++ b/packages/driver/src/cypress/error_messages.coffee @@ -148,6 +148,7 @@ module.exports = { empty_string: "#{cmd('contains')} cannot be passed an empty string." invalid_argument: "#{cmd('contains')} can only accept a string, number or regular expression." length_option: "#{cmd('contains')} cannot be passed a length option because it will only ever return 1 element." + regex_conflict: "You passed a regular expression with the case-insensitive (i) flag and { matchCase: true } to #{cmd('contains')}. Those options conflict with each other, so please choose one or the other." cookies: backend_error: """ diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.js b/packages/driver/test/cypress/integration/commands/querying_spec.js index 572969957b2e..adaf0a927060 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.js +++ b/packages/driver/test/cypress/integration/commands/querying_spec.js @@ -1927,7 +1927,7 @@ space it('throws when content has "i" flag while matchCase: true', (done) => { cy.on('fail', (err) => { - expect(err.message).to.eq('cy.contains() content has i flag and matchCase is true. What is intended?') + expect(err.message).to.eq('You passed a regular expression with the case-insensitive (i) flag and { matchCase: true } to cy.contains(). Those options conflict with each other, so please choose one or the other.') done() }) From 6e28b78297d999d7e53e797bb30a94a459945b6e Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Wed, 29 Jan 2020 14:20:34 +0630 Subject: [PATCH 12/13] update some cli deps that have fallen out of date since last commit --- cli/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/package.json b/cli/package.json index 6c770ac5d9a8..a12c73513060 100644 --- a/cli/package.json +++ b/cli/package.json @@ -30,11 +30,11 @@ "@cypress/xvfb": "1.2.4", "@types/sizzle": "2.3.2", "arch": "2.1.1", - "bluebird": "3.7.1", + "bluebird": "3.7.2", "cachedir": "2.3.0", "chalk": "3.0.0", "check-more-types": "2.24.0", - "commander": "4.0.1", + "commander": "4.1.0", "common-tags": "1.8.0", "debug": "4.1.1", "eventemitter2": "4.1.2", @@ -42,7 +42,7 @@ "executable": "4.1.1", "extract-zip": "1.6.7", "fs-extra": "8.1.0", - "getos": "3.1.1", + "getos": "3.1.4", "is-ci": "2.0.0", "is-installed-globally": "0.3.1", "lazy-ass": "1.6.0", From 4a4150c497f333e0de781071e9debdfbf8ecb954 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Wed, 29 Jan 2020 15:51:51 -0500 Subject: [PATCH 13/13] update cli snapshot --- cli/__snapshots__/cli_spec.js | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/__snapshots__/cli_spec.js b/cli/__snapshots__/cli_spec.js index 9092186504ba..5eaf5f414e5c 100644 --- a/cli/__snapshots__/cli_spec.js +++ b/cli/__snapshots__/cli_spec.js @@ -16,24 +16,24 @@ exports['shows help for open --foo 1'] = ` Opens Cypress in the interactive GUI. Options: - -b, --browser path to a custom browser to be added to the + -b, --browser path to a custom browser to be added to the list of available browsers in Cypress - -c, --config sets configuration values. separate multiple - values with a comma. overrides any value in + -c, --config sets configuration values. separate multiple + values with a comma. overrides any value in cypress.json. - -C, --config-file path to JSON file where configuration values - are set. defaults to "cypress.json". pass + -C, --config-file path to JSON file where configuration values + are set. defaults to "cypress.json". pass "false" to disable. -d, --detached [bool] runs Cypress application in detached mode - -e, --env sets environment variables. separate - multiple values with a comma. overrides any + -e, --env sets environment variables. separate + multiple values with a comma. overrides any value in cypress.json or cypress.env.json - --global force Cypress into global mode as if its + --global force Cypress into global mode as if its globally installed - -p, --port runs Cypress on a specific port. overrides + -p, --port runs Cypress on a specific port. overrides any value in cypress.json. -P, --project path to the project - --dev runs cypress in development and bypasses + --dev runs cypress in development and bypasses binary check -h, --help output usage information ------- @@ -198,9 +198,9 @@ exports['cli help command shows help 1'] = ` version prints Cypress version run [options] Runs Cypress tests from the CLI without the GUI open [options] Opens Cypress in the interactive GUI. - install [options] Installs the Cypress executable matching this package's + install [options] Installs the Cypress executable matching this package's version - verify [options] Verifies that Cypress is installed correctly and + verify [options] Verifies that Cypress is installed correctly and executable cache [options] Manages the Cypress binary cache ------- @@ -233,9 +233,9 @@ exports['cli help command shows help for -h 1'] = ` version prints Cypress version run [options] Runs Cypress tests from the CLI without the GUI open [options] Opens Cypress in the interactive GUI. - install [options] Installs the Cypress executable matching this package's + install [options] Installs the Cypress executable matching this package's version - verify [options] Verifies that Cypress is installed correctly and + verify [options] Verifies that Cypress is installed correctly and executable cache [options] Manages the Cypress binary cache ------- @@ -268,9 +268,9 @@ exports['cli help command shows help for --help 1'] = ` version prints Cypress version run [options] Runs Cypress tests from the CLI without the GUI open [options] Opens Cypress in the interactive GUI. - install [options] Installs the Cypress executable matching this package's + install [options] Installs the Cypress executable matching this package's version - verify [options] Verifies that Cypress is installed correctly and + verify [options] Verifies that Cypress is installed correctly and executable cache [options] Manages the Cypress binary cache ------- @@ -304,9 +304,9 @@ exports['cli unknown command shows usage and exits 1'] = ` version prints Cypress version run [options] Runs Cypress tests from the CLI without the GUI open [options] Opens Cypress in the interactive GUI. - install [options] Installs the Cypress executable matching this package's + install [options] Installs the Cypress executable matching this package's version - verify [options] Verifies that Cypress is installed correctly and + verify [options] Verifies that Cypress is installed correctly and executable cache [options] Manages the Cypress binary cache -------