@@ -100,9 +100,47 @@ export default function (Commands, Cypress, cy, state) {
100100
101101 cy . once ( 'command:enqueued' , enqueuedCommand )
102102
103+ // this code helps juggle subjects forward
104+ // the same way that promises work
105+ const current = state ( 'current' )
106+ const next = current . get ( 'next' )
107+
108+ // TODO: this code may no longer be necessary
109+ // if the next command is chained to us then when it eventually
110+ // runs we need to reset the subject to be the return value of the
111+ // previous command so the subject is continuously juggled forward
112+ if ( next && next . get ( 'chainerId' ) === current . get ( 'chainerId' ) ) {
113+ const checkSubject = ( newSubject , args ) => {
114+ if ( state ( 'current' ) !== next ) {
115+ return
116+ }
117+
118+ // get whatever the previous commands return
119+ // value is. this likely does not match the 'var current'
120+ // command in the case of nested cy commands
121+ const s = next . get ( 'prev' ) . get ( 'subject' )
122+
123+ // find the new subject and splice it out
124+ // with our existing subject
125+ const index = _ . indexOf ( args , newSubject )
126+
127+ if ( index > - 1 ) {
128+ args . splice ( index , 1 , s )
129+ }
130+
131+ return cy . removeListener ( 'next:subject:prepared' , checkSubject )
132+ }
133+
134+ cy . on ( 'next:subject:prepared' , checkSubject )
135+ }
136+
103137 const getRet = ( ) => {
104138 let ret = fn . apply ( ctx , args )
105139
140+ if ( cy . isCy ( ret ) ) {
141+ ret = undefined
142+ }
143+
106144 if ( ret && invokedCyCommand && ! ret . then ) {
107145 $errUtils . throwErrByPath ( 'then.callback_mixes_sync_and_async' , {
108146 onFail : options . _log ,
@@ -123,11 +161,6 @@ export default function (Commands, Cypress, cy, state) {
123161 return subject
124162 }
125163
126- // If the user callback returned a non-null value, we break cypress' subject chaining
127- // logic, so that we can use this subject as-is rather than the subject generated by
128- // any chainers inside the callback (if any exist).
129- cy . breakSubjectLinksToCurrentChainer ( )
130-
131164 return ret
132165 } ) . catch ( Promise . TimeoutError , ( ) => {
133166 return $errUtils . throwErrByPath ( 'invoke_its.timed_out' , {
@@ -465,16 +498,54 @@ export default function (Commands, Cypress, cy, state) {
465498 $errUtils . throwErrByPath ( 'each.invalid_argument' )
466499 }
467500
468- if ( subject ?. length === undefined ) {
501+ const nonArray = ( ) => {
469502 return $errUtils . throwErrByPath ( 'each.non_array' , {
470503 args : { subject : $utils . stringify ( subject ) } ,
471504 } )
472505 }
473506
507+ try {
508+ if ( ! ( 'length' in subject ) ) {
509+ nonArray ( )
510+ }
511+ } catch ( e ) {
512+ nonArray ( )
513+ }
514+
474515 if ( subject . length === 0 ) {
475516 return subject
476517 }
477518
519+ // if we have a next command then we need to
520+ // slice in this existing subject as its subject
521+ // due to the way we queue promises
522+ const next = state ( 'current' ) . get ( 'next' )
523+
524+ if ( next ) {
525+ const checkSubject = ( newSubject , args , firstCall ) => {
526+ if ( state ( 'current' ) !== next ) {
527+ return
528+ }
529+
530+ // https://github.com/cypress-io/cypress/issues/4921
531+ // When dual commands like contains() is used as the firstCall (cy.contains() style),
532+ // we should not prepend subject.
533+ if ( ! firstCall ) {
534+ // find the new subject and splice it out
535+ // with our existing subject
536+ const index = _ . indexOf ( args , newSubject )
537+
538+ if ( index > - 1 ) {
539+ args . splice ( index , 1 , subject )
540+ }
541+ }
542+
543+ return cy . removeListener ( 'next:subject:prepared' , checkSubject )
544+ }
545+
546+ cy . on ( 'next:subject:prepared' , checkSubject )
547+ }
548+
478549 let endEarly = false
479550
480551 const yieldItem = ( el , index ) => {
@@ -502,16 +573,11 @@ export default function (Commands, Cypress, cy, state) {
502573
503574 // generate a real array since bluebird is finicky and
504575 // doesnt want an 'array-like' structure like jquery instances
576+ // need to take into account regular arrays here by first checking
577+ // if its an array instance
505578 return Promise
506579 . each ( _ . toArray ( subject ) , yieldItem )
507- . then ( ( ) => {
508- // cy.each does *not* want to use any subjects that the user's callback generated - therefore we break
509- // cypress' subject chaining logic, which by default would override this with any subjects generated by
510- // the callback function.
511- cy . breakSubjectLinksToCurrentChainer ( )
512-
513- return subject
514- } )
580+ . return ( subject )
515581 } ,
516582 } )
517583
0 commit comments