Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cy.contains and should('contain') handle backslash correctly. #20704

Merged
merged 5 commits into from Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/driver/cypress/integration/commands/assertions_spec.js
Expand Up @@ -1277,6 +1277,36 @@ describe('src/cy/commands/assertions', () => {
})
})

// TODO: this suite should be merged with the suite above
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO is here because the test codes should be refactored a bit later.

The suite above format quotation marks actually has 3 subcategories:

  • format quotation marks
  • format strings
  • escape markdown

The name doesn't explain well about the tests.

It'll be handled after this PR is merged.

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 @@ -1327,6 +1357,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, '\\\\')
}