Skip to content

Commit

Permalink
Merge pull request #20704 from sainthkh/issue-19116
Browse files Browse the repository at this point in the history
fix: `cy.contains` and `should('contain')` handle backslash correctly.
  • Loading branch information
Blue F committed Apr 26, 2022
2 parents 4972872 + 81f8a85 commit 30a9a27
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 17 deletions.
39 changes: 39 additions & 0 deletions packages/driver/cypress/integration/commands/assertions_spec.js
Expand Up @@ -1269,6 +1269,36 @@ describe('src/cy/commands/assertions', () => {
})
})

// TODO: this suite should be merged with the suite above
describe('message formatting', () => {
const expectMarkdown = (test, message, done) => {
cy.then(() => {
test()
})

cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')

expect(log.get('message')).to.eq(message)

done()
}
})
}

// https://github.com/cypress-io/cypress/issues/19116
it('text with backslashes', (done) => {
const text = '"<OE_D]dQ\\'

expectMarkdown(
() => expect(text).to.equal(text),
`expected **"<OE_D]dQ\\\\** to equal **"<OE_D]dQ\\\\**`,
done,
)
})
})

context('chai overrides', () => {
beforeEach(function () {
this.$body = cy.$$('body')
Expand Down Expand Up @@ -1319,6 +1349,15 @@ describe('src/cy/commands/assertions', () => {

cy.get('#escape-quotes').should('contain', 'shouldn\'t')
})

// https://github.com/cypress-io/cypress/issues/19116
it('escapes backslashes', () => {
const $span = '<span id="escape-backslashes">"&lt;OE_D]dQ\\</span>'

cy.$$($span).appendTo(cy.$$('body'))

cy.get('#escape-backslashes').should('contain', '"<OE_D]dQ\\')
})
})

describe('#match', () => {
Expand Down
Expand Up @@ -1224,6 +1224,12 @@ describe('src/cy/commands/querying', () => {
cy.contains(/\'/)
})

// https://github.com/cypress-io/cypress/issues/19116
it('handles backslashes', () => {
$('<div id="backslashes">"&lt;OE_D]dQ\\</div>').appendTo(cy.$$('body'))
cy.get('#backslashes').contains('"<OE_D]dQ\\')
})

describe('should(\'not.exist\')', () => {
it('returns null when no content exists', () => {
cy.contains('alksjdflkasjdflkajsdf').should('not.exist').then(($el) => {
Expand Down
12 changes: 11 additions & 1 deletion packages/driver/src/cy/chai.ts
Expand Up @@ -8,6 +8,7 @@ import sinonChai from '@cypress/sinon-chai'

import $dom from '../dom'
import $utils from '../cypress/utils'
import { escapeBackslashes, escapeQuotes } from '../util/escape'
import $errUtils from '../cypress/error_utils'
import $stackUtils from '../cypress/stack_utils'
import $chaiJquery from '../cypress/chai_jquery'
Expand All @@ -29,6 +30,8 @@ const trailingWhitespaces = /\s*'\*\*/g
const whitespace = /\s/g
const valueHasLeadingOrTrailingWhitespaces = /\*\*'\s+|\s+'\*\*/g
const imageMarkdown = /!\[.*?\]\(.*?\)/g
const doubleslashRe = /\\\\/g
const escapedDoubleslashRe = /__double_slash__/g

type CreateFunc = ((specWindow, state, assertFn) => ({
chai: Chai.ChaiStatic
Expand Down Expand Up @@ -103,6 +106,9 @@ chai.use((chai, u) => {
return
})

const escapeDoubleSlash = (str: string) => str.replace(doubleslashRe, '__double_slash__')
const restoreDoubleSlash = (str: string) => str.replace(escapedDoubleslashRe, '\\\\')

// remove any single quotes between our **,
// except escaped quotes, empty strings and number strings.
const removeOrKeepSingleQuotesBetweenStars = (message) => {
Expand Down Expand Up @@ -277,7 +283,9 @@ chai.use((chai, u) => {
return _super.apply(this, arguments)
}

const escText = $utils.escapeQuotes(text)
const escText = escapeQuotes(
escapeBackslashes(text),
)

const selector = `:contains('${escText}'), [type='submit'][value~='${escText}']`

Expand Down Expand Up @@ -457,7 +465,9 @@ chai.use((chai, u) => {
let message = chaiUtils.getMessage(this, customArgs as Chai.AssertionArgs)
const actual = chaiUtils.getActual(this, customArgs as Chai.AssertionArgs)

message = escapeDoubleSlash(message)
message = removeOrKeepSingleQuotesBetweenStars(message)
message = restoreDoubleSlash(message)
message = escapeMarkdown(message)

try {
Expand Down
7 changes: 0 additions & 7 deletions packages/driver/src/cypress/utils.ts
Expand Up @@ -10,7 +10,6 @@ import { $Location } from './location'

const tagOpen = /\[([a-z\s='"-]+)\]/g
const tagClosed = /\[\/([a-z]+)\]/g
const quotesRe = /('|")/g

const defaultOptions = {
delay: 10,
Expand Down Expand Up @@ -267,12 +266,6 @@ export default {
}
},

escapeQuotes (text) {
// convert to str and escape any single
// or double quotes
return (`${text}`).replace(quotesRe, '\\$1')
},

normalizeNumber (num) {
const parsed = Number(num)

Expand Down
7 changes: 5 additions & 2 deletions packages/driver/src/dom/elements/find.ts
Expand Up @@ -4,7 +4,8 @@ import $document from '../document'
import $jquery from '../jquery'
import { getTagName } from './elementHelpers'
import { isWithinShadowRoot, getShadowElementFromPoint } from './shadow'
import { normalizeWhitespaces, escapeQuotes } from './utils'
import { normalizeWhitespaces } from './utils'
import { escapeQuotes, escapeBackslashes } from '../../util/escape'

/**
* Find Parents relative to an initial element
Expand Down Expand Up @@ -228,7 +229,9 @@ export const getContainsSelector = (text, filter = '', options: {
} = {}) => {
const $expr = $.expr[':']

const escapedText = escapeQuotes(text)
const escapedText = escapeQuotes(
escapeBackslashes(text),
)

// they may have written the filter as
// comma separated dom els, so we want to search all
Expand Down
7 changes: 0 additions & 7 deletions packages/driver/src/dom/elements/utils.ts
Expand Up @@ -5,7 +5,6 @@ import $window from '../window'
import $document from '../document'

const whitespaces = /\s+/g
const quotesRe = /('|")/g

// When multiple space characters are considered as a single whitespace in all tags except <pre>.
export const normalizeWhitespaces = (elem) => {
Expand All @@ -29,12 +28,6 @@ export const isSelector = ($el: JQuery<HTMLElement>, selector) => {
return $el.is(selector)
}

export function escapeQuotes (text) {
// convert to str and escape any single
// or double quotes
return (`${text}`).replace(quotesRe, '\\$1')
}

export function switchCase (value, casesObj, defaultKey = 'default') {
if (_.has(casesObj, value)) {
return _.result(casesObj, value)
Expand Down
13 changes: 13 additions & 0 deletions packages/driver/src/util/escape.ts
@@ -0,0 +1,13 @@
const quotesRe = /('|")/g
const backslashRe = /\\/g

export function escapeQuotes (text) {
// convert to str and escape any single
// or double quotes
return (`${text}`).replace(quotesRe, '\\$1')
}

export function escapeBackslashes (text) {
// convert to str and escape any backslashes
return (`${text}`).replace(backslashRe, '\\\\')
}

3 comments on commit 30a9a27

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 30a9a27 Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/9.6.1/linux-x64/develop-30a9a2796e6e6cbafd36a5a0b0741ffb86541c08/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 30a9a27 Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/9.6.1/darwin-x64/develop-30a9a2796e6e6cbafd36a5a0b0741ffb86541c08/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 30a9a27 Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/9.6.1/win32-x64/develop-30a9a2796e6e6cbafd36a5a0b0741ffb86541c08/cypress.tgz

Please sign in to comment.