11import { configure as configureDTL , queries } from '@testing-library/dom'
2- import { getContainer } from './utils'
2+ import { getFirstElement } from './utils'
33
44function configure ( { fallbackRetryWithoutPreviousSubject, ...config } ) {
55 return configureDTL ( config )
@@ -9,66 +9,79 @@ const findRegex = /^find/
99const queryNames = Object . keys ( queries ) . filter ( q => findRegex . test ( q ) )
1010
1111const commands = queryNames . map ( queryName => {
12- return createCommand ( queryName , queryName . replace ( findRegex , 'get' ) )
12+ return createQuery ( queryName , queryName . replace ( findRegex , 'get' ) )
1313} )
1414
15- function createCommand ( queryName , implementationName ) {
15+ function createQuery ( queryName , implementationName ) {
1616 return {
1717 name : queryName ,
18- options : { prevSubject : [ 'optional' ] } ,
19- command : ( prevSubject , ...args ) => {
18+ command ( ...args ) {
2019 const lastArg = args [ args . length - 1 ]
21- const defaults = {
22- timeout : Cypress . config ( ) . defaultCommandTimeout ,
23- log : true ,
24- }
25- const options =
26- typeof lastArg === 'object' ? { ...defaults , ...lastArg } : defaults
20+ const options = typeof lastArg === 'object' ? { ...lastArg } : { }
2721
28- const queryImpl = queries [ implementationName ]
29- const baseCommandImpl = container => {
30- return queryImpl ( getContainer ( container ) , ...args )
31- }
32- const commandImpl = container => baseCommandImpl ( container )
22+ this . set ( 'timeout' , options . timeout )
3323
24+ const queryImpl = queries [ implementationName ]
3425 const inputArr = args . filter ( filterInputs )
3526
36- const getSelector = ( ) => `${ queryName } (${ queryArgument ( args ) } )`
37-
38- const win = cy . state ( 'window' )
27+ const selector = `${ queryName } (${ queryArgument ( args ) } )`
3928
4029 const consoleProps = {
4130 // TODO: Would be good to completely separate out the types of input into their own properties
4231 input : inputArr ,
43- Selector : getSelector ( ) ,
44- 'Applied To' : getContainer (
45- options . container || prevSubject || win . document ,
46- ) ,
32+ Selector : selector ,
4733 }
4834
49- if ( options . log ) {
50- options . _log = Cypress . log ( {
51- type : prevSubject ? 'child' : 'parent' ,
35+ const log =
36+ options . log !== false &&
37+ Cypress . log ( {
5238 name : queryName ,
39+ type :
40+ this . get ( 'prev' ) . get ( 'chainerId' ) === this . get ( 'chainerId' )
41+ ? 'child'
42+ : 'parent' ,
5343 message : inputArr ,
5444 timeout : options . timeout ,
5545 consoleProps : ( ) => consoleProps ,
5646 } )
57- }
5847
59- const getValue = (
60- container = options . container || prevSubject || win . document ,
61- ) => {
62- const value = commandImpl ( container )
48+ const withinSubject = cy . state ( 'withinSubjectChain' )
49+
50+ let error
51+ this . set ( 'onFail' , err => {
52+ if ( error ) {
53+ err . message = error . message
54+ }
55+ } )
56+
57+ return subject => {
58+ const container = getFirstElement (
59+ options . container ||
60+ subject ||
61+ cy . getSubjectFromChain ( withinSubject ) ||
62+ cy . state ( 'window' ) . document ,
63+ )
64+ consoleProps [ 'Applied To' ] = container
65+
66+ let value
67+
68+ try {
69+ value = queryImpl ( container , ...args )
70+ } catch ( e ) {
71+ error = e
72+ value = Cypress . $ ( )
73+ value . selector = selector
74+ }
6375
6476 const result = Cypress . $ ( value )
65- if ( value && options . _log ) {
66- options . _log . set ( '$el' , result )
77+
78+ if ( value && log ) {
79+ log . set ( '$el' , result )
6780 }
6881
6982 // Overriding the selector of the jquery object because it's displayed in the long message of .should('exist') failure message
7083 // Hopefully it makes it clearer, because I find the normal response of "Expected to find element '', but never found it" confusing
71- result . selector = getSelector ( )
84+ result . selector = selector
7285
7386 consoleProps . elements = result . length
7487 if ( result . length === 1 ) {
@@ -86,54 +99,6 @@ function createCommand(queryName, implementationName) {
8699
87100 return result
88101 }
89-
90- let error
91-
92- // Errors will be thrown by @testing -library/dom, but a query might be followed by `.should('not.exist')`
93- // We just need to capture the error thrown by @testing -library/dom and return an empty jQuery NodeList
94- // to allow Cypress assertions errors to happen naturally. If an assertion fails, we'll have a helpful
95- // error message handy to pass on to the user
96- const catchQueryError = err => {
97- error = err
98- const result = Cypress . $ ( )
99- result . selector = getSelector ( )
100- return result
101- }
102-
103- const resolveValue = ( ) => {
104- // retry calling "getValue" until following assertions pass or this command times out
105- return Cypress . Promise . try ( getValue )
106- . catch ( catchQueryError )
107- . then ( value => {
108- return cy . verifyUpcomingAssertions ( value , options , {
109- onRetry : resolveValue ,
110- onFail : ( ) => {
111- // We want to override Cypress's normal non-existence message with @testing-library/dom's more helpful ones
112- if ( error ) {
113- options . error . message = error . message
114- }
115- } ,
116- } )
117- } )
118- }
119-
120- return resolveValue ( )
121- . then ( subject => {
122- // Remove the error that occurred because it is irrelevant now
123- if ( consoleProps . error ) {
124- delete consoleProps . error
125- }
126- if ( options . _log ) {
127- options . _log . snapshot ( )
128- }
129-
130- return subject
131- } )
132- . finally ( ( ) => {
133- if ( options . _log ) {
134- options . _log . end ( )
135- }
136- } )
137102 } ,
138103 }
139104}
0 commit comments