1
1
import {configure as configureDTL, queries} from '@testing-library/dom'
2
- import {getContainer } from './utils'
2
+ import {getFirstElement } from './utils'
3
3
4
4
function configure({fallbackRetryWithoutPreviousSubject, ...config}) {
5
5
return configureDTL(config)
@@ -9,66 +9,79 @@ const findRegex = /^find/
9
9
const queryNames = Object.keys(queries).filter(q => findRegex.test(q))
10
10
11
11
const commands = queryNames.map(queryName => {
12
- return createCommand (queryName, queryName.replace(findRegex, 'get'))
12
+ return createQuery (queryName, queryName.replace(findRegex, 'get'))
13
13
})
14
14
15
- function createCommand (queryName, implementationName) {
15
+ function createQuery (queryName, implementationName) {
16
16
return {
17
17
name: queryName,
18
- options: {prevSubject: ['optional']},
19
- command: (prevSubject, ...args) => {
18
+ command(...args) {
20
19
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} : {}
27
21
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)
33
23
24
+ const queryImpl = queries[implementationName]
34
25
const inputArr = args.filter(filterInputs)
35
26
36
- const getSelector = () => `${queryName}(${queryArgument(args)})`
37
-
38
- const win = cy.state('window')
27
+ const selector = `${queryName}(${queryArgument(args)})`
39
28
40
29
const consoleProps = {
41
30
// TODO: Would be good to completely separate out the types of input into their own properties
42
31
input: inputArr,
43
- Selector: getSelector(),
44
- 'Applied To': getContainer(
45
- options.container || prevSubject || win.document,
46
- ),
32
+ Selector: selector,
47
33
}
48
34
49
- if (options. log) {
50
- options._log = Cypress.log({
51
- type: prevSubject ? 'child' : 'parent',
35
+ const log =
36
+ options.log !== false &&
37
+ Cypress.log({
52
38
name: queryName,
39
+ type:
40
+ this.get('prev').get('chainerId') === this.get('chainerId')
41
+ ? 'child'
42
+ : 'parent',
53
43
message: inputArr,
54
44
timeout: options.timeout,
55
45
consoleProps: () => consoleProps,
56
46
})
57
- }
58
47
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
+ }
63
75
64
76
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)
67
80
}
68
81
69
82
// Overriding the selector of the jquery object because it's displayed in the long message of .should('exist') failure message
70
83
// 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
72
85
73
86
consoleProps.elements = result.length
74
87
if (result.length === 1) {
@@ -86,54 +99,6 @@ function createCommand(queryName, implementationName) {
86
99
87
100
return result
88
101
}
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
- })
137
102
},
138
103
}
139
104
}
0 commit comments