feat: update .within to use internal log group api (#20980)

This commit is contained in:
Emily Rohrbough
2022-04-28 14:40:40 -05:00
committed by GitHub
parent d94c586f10
commit c3147df277
3 changed files with 117 additions and 82 deletions

View File

@@ -48,6 +48,23 @@ describe('src/cy/commands/querying/within', () => {
})
})
it('can be chained off an alias', () => {
const form = cy.$$('#by-name')
cy.get('#by-name').as('nameForm')
.within(() => {})
.then(($form) => {
expect($form.get(0)).to.eq(form.get(0))
})
cy.get('#by-name').as('nameForm')
.within(() => {
cy.get('input').should('be.visible')
})
cy.get('@nameForm').should('be.visible')
})
it('can call child commands after within on the same subject', () => {
const input = cy.$$('#by-name input:first')
@@ -199,6 +216,20 @@ describe('src/cy/commands/querying/within', () => {
expect(lastLog.get('snapshots')[0]).to.be.an('object')
})
})
it('provides additional information in console prop', () => {
cy.get('div').within(() => {})
.then(function () {
const { lastLog } = this
const consoleProps = lastLog.get('consoleProps')()
expect(consoleProps).to.be.an('object')
expect(consoleProps.Command).to.eq('within')
expect(consoleProps.Yielded).to.not.be.null
expect(consoleProps.Yielded).to.have.length(55)
})
})
})
describe('errors', {

View File

@@ -2,12 +2,86 @@ import _ from 'lodash'
import { $Command } from '../../../cypress/command'
import $errUtils from '../../../cypress/error_utils'
import group from '../../logGroup'
export default (Commands, Cypress, cy, state) => {
const withinFn = (subject, fn) => {
// reference the next command after this
// within. when that command runs we'll
// know to remove withinSubject
const next = state('current').get('next')
// backup the current withinSubject
// this prevents a bug where we null out
// withinSubject when there are nested .withins()
// we want the inner within to restore the outer
// once its done
const prevWithinSubject = state('withinSubject')
state('withinSubject', subject)
// https://github.com/cypress-io/cypress/pull/8699
// An internal command is inserted to create a divider between
// commands inside within() callback and commands chained to it.
const restoreCmdIndex = state('index') + 1
cy.queue.insert(restoreCmdIndex, $Command.create({
args: [subject],
name: 'within-restore',
fn: (subject) => subject,
}))
state('index', restoreCmdIndex)
fn.call(cy.state('ctx'), subject)
const cleanup = () => cy.removeListener('command:start', setWithinSubject)
// we need a mechanism to know when we should remove
// our withinSubject so we dont accidentally keep it
// around after the within callback is done executing
// 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) => {
if (obj !== next) {
return
}
// okay so what we're doing here is creating a property
// which stores the 'next' command which will reset the
// withinSubject. If two 'within' commands reference the
// exact same 'next' command, then this prevents accidentally
// resetting withinSubject more than once. If they point
// to different 'next's then its okay
if (next !== state('nextWithinSubject')) {
state('withinSubject', prevWithinSubject || null)
state('nextWithinSubject', next)
}
// regardless nuke this listeners
cleanup()
}
// if next is defined then we know we'll eventually
// unbind these listeners
if (next) {
cy.on('command:start', setWithinSubject)
} else {
// remove our listener if we happen to reach the end
// event which will finalize cleanup if there was no next obj
cy.once('command:queue:before:end', () => {
cleanup()
state('withinSubject', null)
})
}
return subject
}
Commands.addAll({ prevSubject: ['element', 'document'] }, {
within (subject, options, fn) {
let userOptions = options
const ctx = this
if (_.isUndefined(fn)) {
fn = userOptions
@@ -16,90 +90,20 @@ export default (Commands, Cypress, cy, state) => {
options = _.defaults({}, userOptions, { log: true })
if (options.log) {
options._log = Cypress.log({
$el: subject,
message: '',
timeout: options.timeout,
})
const groupOptions: Cypress.LogGroup.Config = {
log: options.log,
$el: subject,
message: '',
timeout: options.timeout,
}
if (!_.isFunction(fn)) {
$errUtils.throwErrByPath('within.invalid_argument', { onFail: options._log })
}
// reference the next command after this
// within. when that command runs we'll
// know to remove withinSubject
const next = state('current').get('next')
// backup the current withinSubject
// this prevents a bug where we null out
// withinSubject when there are nested .withins()
// we want the inner within to restore the outer
// once its done
const prevWithinSubject = state('withinSubject')
state('withinSubject', subject)
// https://github.com/cypress-io/cypress/pull/8699
// An internal command is inserted to create a divider between
// commands inside within() callback and commands chained to it.
const restoreCmdIndex = state('index') + 1
cy.queue.insert(restoreCmdIndex, $Command.create({
args: [subject],
name: 'within-restore',
fn: (subject) => subject,
}))
state('index', restoreCmdIndex)
fn.call(ctx, subject)
const cleanup = () => cy.removeListener('command:start', setWithinSubject)
// we need a mechanism to know when we should remove
// our withinSubject so we dont accidentally keep it
// around after the within callback is done executing
// 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) => {
if (obj !== next) {
return
return group(Cypress, groupOptions, (log) => {
if (!_.isFunction(fn)) {
$errUtils.throwErrByPath('within.invalid_argument', { onFail: log })
}
// okay so what we're doing here is creating a property
// which stores the 'next' command which will reset the
// withinSubject. If two 'within' commands reference the
// exact same 'next' command, then this prevents accidentally
// resetting withinSubject more than once. If they point
// to differnet 'next's then its okay
if (next !== state('nextWithinSubject')) {
state('withinSubject', prevWithinSubject || null)
state('nextWithinSubject', next)
}
// regardless nuke this listeners
cleanup()
}
// if next is defined then we know we'll eventually
// unbind these listeners
if (next) {
cy.on('command:start', setWithinSubject)
} else {
// remove our listener if we happen to reach the end
// event which will finalize cleanup if there was no next obj
cy.once('command:queue:before:end', () => {
cleanup()
state('withinSubject', null)
})
}
return subject
return withinFn(subject, fn)
})
},
})
}

View File

@@ -82,7 +82,7 @@ export class $Command {
const arg = args[i]
if (_.isObject(arg)) {
// filter out any properties which arent primitives
// filter out any properties which aren't primitives
// to prevent accidental mutations
const opts = _.omitBy(arg, _.isObject)