forked from cypress-io/cypress
/
clock.ts
175 lines (131 loc) · 3.98 KB
/
clock.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import _ from 'lodash'
import { create as createClock, Clock } from '../../cypress/clock'
import $errUtils from '../../cypress/error_utils'
type CyClock = Clock & {
tick(ms, options?: any): number
restore(options?: any): void
}
// create a global clock
let clock: CyClock | null = null
export default function (Commands, Cypress, cy, state) {
const reset = () => {
if (clock) {
clock.restore({ log: false })
}
clock = null
}
// reset before a run
reset()
// remove clock before each test run, so a new one is created
// when user calls cy.clock()
//
// this MUST be prepended else if we are stubbing or spying on
// global timers they will be reset in agents before this runs
// its reset function
Cypress.prependListener('test:before:run', reset)
Cypress.on('window:before:load', (contentWindow) => {
// if a clock has been created before this event (likely before
// a cy.visit(), then bind that clock to the new window
if (clock) {
return clock.bind(contentWindow)
}
return
})
return Commands.addAll({ type: 'utility' }, {
clock (subject, now, methods, options: Partial<Cypress.Loggable> = {}) {
let userOptions = options
const ctx = state('ctx')
if (clock) {
return clock
}
if (_.isDate(now)) {
now = now.getTime()
}
if (_.isObject(now)) {
userOptions = now
now = undefined
}
if (_.isObject(methods) && !_.isArray(methods)) {
userOptions = methods
methods = undefined
}
if (now != null && !_.isNumber(now)) {
$errUtils.throwErrByPath('clock.invalid_1st_arg', { args: { arg: JSON.stringify(now) } })
}
if (methods != null && !(_.isArray(methods) && _.every(methods, _.isString))) {
$errUtils.throwErrByPath('clock.invalid_2nd_arg', { args: { arg: JSON.stringify(methods) } })
}
options = _.defaults({}, userOptions, {
log: true,
})
const log = (name, message = '', snapshot = true, consoleProps = {}) => {
if (!options.log) {
return
}
const details = clock!.details()
const logNow = details.now
const logMethods = details.methods.slice()
return Cypress.log({
name,
message: message ? message : '',
type: 'parent',
end: true,
snapshot,
consoleProps () {
return _.extend({
'Now': logNow,
'Methods replaced': logMethods,
}, consoleProps)
},
})
}
clock = createClock(state('window'), now, methods)
const { tick } = clock
clock.tick = function (ms, options: Partial<Cypress.Loggable> = {}) {
if ((ms != null) && !_.isNumber(ms)) {
$errUtils.throwErrByPath('tick.invalid_argument', { args: { arg: JSON.stringify(ms) } })
}
if (ms == null) {
ms = 0
}
let theLog
if (options.log !== false) {
theLog = log('tick', `${ms}ms`, false, {
'Now': clock!.details().now + ms,
'Ticked': `${ms} milliseconds`,
})
}
if (theLog) {
theLog.snapshot('before', { next: 'after' })
}
const ret = tick.apply(this, [ms])
if (theLog) {
theLog.snapshot().end()
}
return ret
}
const { restore } = clock
clock.restore = function (options: Partial<Cypress.Loggable> = {}) {
const ret = restore.apply(this)
if (options.log !== false) {
log('restore')
}
ctx.clock = null
clock = null
state('clock', clock)
return ret
}
log('clock')
state('clock', clock)
ctx.clock = clock
return clock
},
tick (subject, ms, options: Partial<Cypress.Loggable> = {}) {
if (!clock) {
$errUtils.throwErrByPath('tick.no_clock')
}
clock!.tick(ms, options)
return clock
},
})
}