Skip to content

Commit

Permalink
Revert "chore: reverting #22742 (#23047)"
Browse files Browse the repository at this point in the history
This reverts commit 51ef99a.
  • Loading branch information
BlueWinds committed Aug 2, 2022
1 parent ee17719 commit 1f07e9f
Show file tree
Hide file tree
Showing 19 changed files with 218 additions and 234 deletions.
4 changes: 2 additions & 2 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mainBuildFilters: &mainBuildFilters
branches:
only:
- develop
- revert-22742
- tbiethman/electron-19

# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
Expand Down Expand Up @@ -881,7 +881,7 @@ commands:
fi
curl -L https://raw.githubusercontent.com/cypress-io/cypress/$branch/scripts/ensure-node.sh --output ci-ensure-node.sh
else
else
# if no .node-version file exists, we no-op the node script and use the global yarn
echo '' > ci-ensure-node.sh
fi
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/cypress/e2e/commands/assertions.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ describe('src/cy/commands/assertions', () => {
})

cy.get('body').then(() => {
expect(cy.state('subject')).to.match('body')
expect(cy.currentSubject()).to.match('body')
})
})

Expand Down
3 changes: 2 additions & 1 deletion packages/driver/cypress/e2e/commands/commands.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ describe('src/cy/commands/commands', () => {
cy
.get('input:first')
.parent()
.command('login', '[email protected]').then(($input) => {
.command('login', '[email protected]')
.then(($input) => {
expect($input.get(0)).to.eq(input.get(0))
})
})
Expand Down
8 changes: 4 additions & 4 deletions packages/driver/cypress/e2e/commands/connectors.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,16 @@ describe('src/cy/commands/connectors', () => {
})
})

it('should pass the eventual resolved thenable value downstream', () => {
it('should pass the eventually resolved thenable value downstream', () => {
cy
.wrap({ foo: 'bar' })
.then((obj) => {
return cy
.wait(10)
.then(() => {
return obj.foo
}).then((value) => {
})
.then((value) => {
expect(value).to.eq('bar')

return value
Expand Down Expand Up @@ -309,7 +310,7 @@ describe('src/cy/commands/connectors', () => {
return $div
})
.then(function () {
expect(cy.state('subject')).not.to.be.instanceof(this.remoteWindow.$)
expect(cy.currentSubject()).not.to.be.instanceof(this.remoteWindow.$)
})
})
})
Expand Down Expand Up @@ -423,7 +424,6 @@ describe('src/cy/commands/connectors', () => {

cy.get('div:first').invoke('parent').then(function ($parent) {
expect($parent).to.be.instanceof(this.remoteWindow.$)
expect(cy.state('subject')).to.match(parent)
})
})
})
Expand Down
10 changes: 10 additions & 0 deletions packages/driver/cypress/e2e/cypress/cy.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ describe('driver/src/cypress/cy', () => {
expect(userInvocationStack).to.include('.cy.js')
})
})

it('supports cy.state(\'subject\') for backwards compatability', () => {
cy.stub(Cypress.utils, 'warning')
const a = {}

cy.wrap(a).then(() => {
expect(cy.state('subject')).to.equal(a)
expect(Cypress.utils.warning).to.be.calledWith('`cy.state(\'subject\')` has been deprecated and will be removed in a future release. Consider migrating to `cy.currentSubject()` instead.')
})
})
})

context('custom commands', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/cross-origin/origin_fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const handleOriginFn = (Cypress: Cypress.Cypress, cy: $Cy) => {
queueFinished = true
setRunnableStateToPassed()
Cypress.specBridgeCommunicator.toPrimary('queue:finished', {
subject: cy.state('subject'),
subject: cy.currentSubject(),
}, {
syncGlobals: true,
})
Expand Down
94 changes: 14 additions & 80 deletions packages/driver/src/cy/commands/connectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,47 +100,9 @@ export default function (Commands, Cypress, cy, state) {

cy.once('command:enqueued', enqueuedCommand)

// this code helps juggle subjects forward
// the same way that promises work
const current = state('current')
const next = current.get('next')

// TODO: this code may no longer be necessary
// if the next command is chained to us then when it eventually
// runs we need to reset the subject to be the return value of the
// previous command so the subject is continuously juggled forward
if (next && next.get('chainerId') === current.get('chainerId')) {
const checkSubject = (newSubject, args) => {
if (state('current') !== next) {
return
}

// get whatever the previous commands return
// value is. this likely does not match the 'var current'
// command in the case of nested cy commands
const s = next.get('prev').get('subject')

// find the new subject and splice it out
// with our existing subject
const index = _.indexOf(args, newSubject)

if (index > -1) {
args.splice(index, 1, s)
}

return cy.removeListener('next:subject:prepared', checkSubject)
}

cy.on('next:subject:prepared', checkSubject)
}

const getRet = () => {
let ret = fn.apply(ctx, args)

if (cy.isCy(ret)) {
ret = undefined
}

if (ret && invokedCyCommand && !ret.then) {
$errUtils.throwErrByPath('then.callback_mixes_sync_and_async', {
onFail: options._log,
Expand All @@ -161,6 +123,11 @@ export default function (Commands, Cypress, cy, state) {
return subject
}

// If the user callback returned a non-null value, we break cypress' subject chaining
// logic, so that we can use this subject as-is rather than the subject generated by
// any chainers inside the callback (if any exist).
cy.breakSubjectLinksToCurrentChainer()

return ret
}).catch(Promise.TimeoutError, () => {
return $errUtils.throwErrByPath('invoke_its.timed_out', {
Expand Down Expand Up @@ -498,54 +465,16 @@ export default function (Commands, Cypress, cy, state) {
$errUtils.throwErrByPath('each.invalid_argument')
}

const nonArray = () => {
if (subject?.length === undefined) {
return $errUtils.throwErrByPath('each.non_array', {
args: { subject: $utils.stringify(subject) },
})
}

try {
if (!('length' in subject)) {
nonArray()
}
} catch (e) {
nonArray()
}

if (subject.length === 0) {
return subject
}

// if we have a next command then we need to
// slice in this existing subject as its subject
// due to the way we queue promises
const next = state('current').get('next')

if (next) {
const checkSubject = (newSubject, args, firstCall) => {
if (state('current') !== next) {
return
}

// https://github.com/cypress-io/cypress/issues/4921
// When dual commands like contains() is used as the firstCall (cy.contains() style),
// we should not prepend subject.
if (!firstCall) {
// find the new subject and splice it out
// with our existing subject
const index = _.indexOf(args, newSubject)

if (index > -1) {
args.splice(index, 1, subject)
}
}

return cy.removeListener('next:subject:prepared', checkSubject)
}

cy.on('next:subject:prepared', checkSubject)
}

let endEarly = false

const yieldItem = (el, index) => {
Expand Down Expand Up @@ -573,11 +502,16 @@ export default function (Commands, Cypress, cy, state) {

// generate a real array since bluebird is finicky and
// doesnt want an 'array-like' structure like jquery instances
// need to take into account regular arrays here by first checking
// if its an array instance
return Promise
.each(_.toArray(subject), yieldItem)
.return(subject)
.then(() => {
// cy.each does *not* want to use any subjects that the user's callback generated - therefore we break
// cypress' subject chaining logic, which by default would override this with any subjects generated by
// the callback function.
cy.breakSubjectLinksToCurrentChainer()

return subject
})
},
})

Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/cy/commands/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default (Commands, Cypress, cy, state) => {
const restoreCmdIndex = state('index') + 1

cy.queue.insert(restoreCmdIndex, $Command.create({
args: [state('subject')],
args: [cy.currentSubject()],
name: 'log-restore',
fn: (subject) => subject,
}))
Expand Down
8 changes: 7 additions & 1 deletion packages/driver/src/cy/commands/querying/within.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export default (Commands, Cypress, cy, state) => {

fn.call(cy.state('ctx'), subject)

const cleanup = () => cy.removeListener('command:start', setWithinSubject)
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
Expand Down Expand Up @@ -76,6 +78,10 @@ export default (Commands, Cypress, cy, state) => {
})
}

// TODO: Rework cy.within to use chainer-based subject chaining, rather than its custom withinSubject state.
// For now, we leave this logic in place and just ensure that the new rules don't interfere with it.
cy.breakSubjectLinksToCurrentChainer()

return subject
}

Expand Down
1 change: 1 addition & 0 deletions packages/driver/src/cy/logGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default (Cypress, userOptions: Cypress.LogGroup.Config, fn: Cypress.LogGr
const endLogGroupCmd = $Command.create({
name: 'end-logGroup',
injected: true,
args: [],
})

const forwardYieldedSubject = () => {
Expand Down
21 changes: 18 additions & 3 deletions packages/driver/src/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,24 @@ class $Cypress {
_.extend(this, browserInfo(config))

this.state = $SetterGetter.create({}) as unknown as StateFunc

/*
* As part of the Detached DOM effort, we're changing the way subjects are determined in Cypress.
* While we usually consider cy.state() to be internal, in the case of cy.state('subject'),
* cypress-testing-library, one of our most popular plugins, relies on it.
* https://github.com/testing-library/cypress-testing-library/blob/1af9f2f28b2ca62936da8a8acca81fc87e2192f7/src/utils.js#L9
*
* Therefore, we've added this shim to continue to support them. The library is actively maintained, so this
* shouldn't need to stick around too long (written 07/22).
*/
Object.defineProperty(this.state(), 'subject', {
get: () => {
$errUtils.warnByPath('subject.state_subject_deprecated')

return cy.currentSubject()
},
})

this.originalConfig = _.cloneDeep(config)
this.config = $SetterGetter.create(config, (config) => {
if (this.isCrossOriginSpecBridge ? !window.__cySkipValidateConfig : !window.top!.__cySkipValidateConfig) {
Expand Down Expand Up @@ -640,9 +658,6 @@ class $Cypress {
case 'cy:url:changed':
return this.emit('url:changed', args[0])

case 'cy:next:subject:prepared':
return this.emit('next:subject:prepared', ...args)

case 'cy:collect:run:state':
return this.emitThen('collect:run:state')

Expand Down
9 changes: 0 additions & 9 deletions packages/driver/src/cypress/chainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@ import $stackUtils from './stack_utils'
export class $Chainer {
specWindow: Window
chainerId: string
firstCall: boolean

constructor (specWindow) {
this.specWindow = specWindow
// The id prefix needs to be unique per origin, so there are not
// collisions when chainers created in a secondary origin are passed
// to the primary origin for the command log, etc.
this.chainerId = _.uniqueId(`ch-${window.location.origin}-`)

// firstCall is used to throw a useful error if the user leads off with a
// parent command.

// TODO: Refactor firstCall out of the chainer and into the command function,
// since cy.ts already has all the necessary information to throw this error
// without an instance variable, in one localized place in the code.
this.firstCall = true
}

static remove (key) {
Expand Down
13 changes: 8 additions & 5 deletions packages/driver/src/cypress/command_queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import type { ITimeouts } from '../cy/timeouts'

const debugErrors = Debug('cypress:driver:errors')

const __stackReplacementMarker = (fn, ctx, args) => {
return fn.apply(ctx, args)
const __stackReplacementMarker = (fn, args) => {
return fn(...args)
}

const commandRunningFailed = (Cypress, state, err) => {
Expand Down Expand Up @@ -165,9 +165,11 @@ export class CommandQueue extends Queue<$Command> {
Cypress.once('command:enqueued', commandEnqueued)
}

args = [command.get('chainerId'), ...args]

// run the command's fn with runnable's context
try {
ret = __stackReplacementMarker(command.get('fn'), this.state('ctx'), args)
ret = __stackReplacementMarker(command.get('fn'), args)
} catch (err) {
throw err
} finally {
Expand Down Expand Up @@ -247,7 +249,7 @@ export class CommandQueue extends Queue<$Command> {
// we're finished with the current command so set it back to null
this.state('current', null)

this.state('subject', subject)
cy.setSubjectForChainer(command.get('chainerId'), subject)

return subject
})
Expand Down Expand Up @@ -278,7 +280,8 @@ export class CommandQueue extends Queue<$Command> {
})

this.state('index', index + 1)
this.state('subject', command.get('subject'))

cy.setSubjectForChainer(command.get('chainerId'), command.get('subject'))

Cypress.action('cy:skipped:command:end', command)

Expand Down
Loading

0 comments on commit 1f07e9f

Please sign in to comment.