codeceptjs 4.0.0-beta.4 → 4.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +139 -87
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +262 -220
- package/lib/container.js +386 -238
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +3 -6
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +262 -267
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +246 -260
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +151 -170
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +82 -62
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -19
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -1
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -276
- package/package.json +76 -70
- package/translations/de-DE.js +4 -3
- package/translations/fr-FR.js +4 -3
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +188 -186
- package/typings/promiseBasedTypes.d.ts +18 -705
- package/typings/types.d.ts +301 -804
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
package/lib/utils.js
CHANGED
|
@@ -1,139 +1,131 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const os = require('os')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
const getFunctionArguments = require('fn-args')
|
|
6
|
+
const deepClone = require('lodash.clonedeep')
|
|
7
|
+
const { convertColorToRGBA, isColorProperty } = require('./colorUtils')
|
|
8
|
+
const Fuse = require('fuse.js')
|
|
9
|
+
const { spawnSync } = require('child_process')
|
|
7
10
|
|
|
8
11
|
function deepMerge(target, source) {
|
|
9
|
-
const merge = require('lodash.merge')
|
|
10
|
-
return merge(target, source)
|
|
12
|
+
const merge = require('lodash.merge')
|
|
13
|
+
return merge(target, source)
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
module.exports.genTestId =
|
|
14
|
-
return require('crypto').createHash('sha256').update(test.fullTitle()).digest('base64')
|
|
15
|
-
|
|
16
|
-
};
|
|
16
|
+
module.exports.genTestId = test => {
|
|
17
|
+
return this.clearString(require('crypto').createHash('sha256').update(test.fullTitle()).digest('base64').slice(0, -2))
|
|
18
|
+
}
|
|
17
19
|
|
|
18
|
-
module.exports.deepMerge = deepMerge
|
|
20
|
+
module.exports.deepMerge = deepMerge
|
|
19
21
|
|
|
20
|
-
module.exports.deepClone = deepClone
|
|
22
|
+
module.exports.deepClone = deepClone
|
|
21
23
|
|
|
22
24
|
module.exports.isGenerator = function (fn) {
|
|
23
|
-
return fn.constructor.name === 'GeneratorFunction'
|
|
24
|
-
}
|
|
25
|
+
return fn.constructor.name === 'GeneratorFunction'
|
|
26
|
+
}
|
|
25
27
|
|
|
26
|
-
const isFunction = module.exports.isFunction = function (fn) {
|
|
27
|
-
return typeof fn === 'function'
|
|
28
|
-
}
|
|
28
|
+
const isFunction = (module.exports.isFunction = function (fn) {
|
|
29
|
+
return typeof fn === 'function'
|
|
30
|
+
})
|
|
29
31
|
|
|
30
|
-
const isAsyncFunction = module.exports.isAsyncFunction = function (fn) {
|
|
31
|
-
if (!fn) return false
|
|
32
|
-
return fn[Symbol.toStringTag] === 'AsyncFunction'
|
|
33
|
-
}
|
|
32
|
+
const isAsyncFunction = (module.exports.isAsyncFunction = function (fn) {
|
|
33
|
+
if (!fn) return false
|
|
34
|
+
return fn[Symbol.toStringTag] === 'AsyncFunction'
|
|
35
|
+
})
|
|
34
36
|
|
|
35
37
|
module.exports.fileExists = function (filePath) {
|
|
36
|
-
return fs.existsSync(filePath)
|
|
37
|
-
}
|
|
38
|
+
return fs.existsSync(filePath)
|
|
39
|
+
}
|
|
38
40
|
|
|
39
41
|
module.exports.isFile = function (filePath) {
|
|
40
|
-
let filestat
|
|
42
|
+
let filestat
|
|
41
43
|
try {
|
|
42
|
-
filestat = fs.statSync(filePath)
|
|
44
|
+
filestat = fs.statSync(filePath)
|
|
43
45
|
} catch (err) {
|
|
44
|
-
if (err.code === 'ENOENT') return false
|
|
46
|
+
if (err.code === 'ENOENT') return false
|
|
45
47
|
}
|
|
46
|
-
if (!filestat) return false
|
|
47
|
-
return filestat.isFile()
|
|
48
|
-
}
|
|
48
|
+
if (!filestat) return false
|
|
49
|
+
return filestat.isFile()
|
|
50
|
+
}
|
|
49
51
|
|
|
50
52
|
module.exports.getParamNames = function (fn) {
|
|
51
|
-
if (fn.isSinonProxy) return []
|
|
52
|
-
return getFunctionArguments(fn)
|
|
53
|
-
}
|
|
53
|
+
if (fn.isSinonProxy) return []
|
|
54
|
+
return getFunctionArguments(fn)
|
|
55
|
+
}
|
|
54
56
|
|
|
55
57
|
module.exports.installedLocally = function () {
|
|
56
|
-
return path.resolve(`${__dirname}/../`).indexOf(process.cwd()) === 0
|
|
57
|
-
}
|
|
58
|
+
return path.resolve(`${__dirname}/../`).indexOf(process.cwd()) === 0
|
|
59
|
+
}
|
|
58
60
|
|
|
59
61
|
module.exports.methodsOfObject = function (obj, className) {
|
|
60
|
-
const methods = []
|
|
61
|
-
|
|
62
|
-
const standard = [
|
|
63
|
-
'constructor',
|
|
64
|
-
'toString',
|
|
65
|
-
'toLocaleString',
|
|
66
|
-
'valueOf',
|
|
67
|
-
'hasOwnProperty',
|
|
68
|
-
'bind',
|
|
69
|
-
'apply',
|
|
70
|
-
'call',
|
|
71
|
-
'isPrototypeOf',
|
|
72
|
-
'propertyIsEnumerable',
|
|
73
|
-
];
|
|
62
|
+
const methods = []
|
|
63
|
+
|
|
64
|
+
const standard = ['constructor', 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'bind', 'apply', 'call', 'isPrototypeOf', 'propertyIsEnumerable']
|
|
74
65
|
|
|
75
66
|
function pushToMethods(prop) {
|
|
76
67
|
try {
|
|
77
|
-
if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return
|
|
78
|
-
} catch (err) {
|
|
79
|
-
|
|
68
|
+
if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// can't access property
|
|
71
|
+
return
|
|
80
72
|
}
|
|
81
|
-
if (standard.indexOf(prop) >= 0) return
|
|
82
|
-
if (prop.indexOf('_') === 0) return
|
|
83
|
-
methods.push(prop)
|
|
73
|
+
if (standard.indexOf(prop) >= 0) return
|
|
74
|
+
if (prop.indexOf('_') === 0) return
|
|
75
|
+
methods.push(prop)
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
while (obj.constructor.name !== className) {
|
|
87
|
-
Object.getOwnPropertyNames(obj).forEach(pushToMethods)
|
|
88
|
-
obj = Object.getPrototypeOf(obj)
|
|
79
|
+
Object.getOwnPropertyNames(obj).forEach(pushToMethods)
|
|
80
|
+
obj = Object.getPrototypeOf(obj)
|
|
89
81
|
|
|
90
|
-
if (!obj || !obj.constructor) break
|
|
82
|
+
if (!obj || !obj.constructor) break
|
|
91
83
|
}
|
|
92
|
-
return methods
|
|
93
|
-
}
|
|
84
|
+
return methods
|
|
85
|
+
}
|
|
94
86
|
|
|
95
87
|
module.exports.template = function (template, data) {
|
|
96
88
|
return template.replace(/{{([^{}]*)}}/g, (a, b) => {
|
|
97
|
-
const r = data[b]
|
|
98
|
-
if (r === undefined) return ''
|
|
99
|
-
return r.toString()
|
|
100
|
-
})
|
|
101
|
-
}
|
|
89
|
+
const r = data[b]
|
|
90
|
+
if (r === undefined) return ''
|
|
91
|
+
return r.toString()
|
|
92
|
+
})
|
|
93
|
+
}
|
|
102
94
|
|
|
103
95
|
/**
|
|
104
96
|
* Make first char uppercase.
|
|
105
97
|
* @param {string} str
|
|
106
|
-
* @returns {string}
|
|
98
|
+
* @returns {string | undefined}
|
|
107
99
|
*/
|
|
108
100
|
module.exports.ucfirst = function (str) {
|
|
109
|
-
return str.charAt(0).toUpperCase() + str.substr(1)
|
|
110
|
-
}
|
|
101
|
+
if (str) return str.charAt(0).toUpperCase() + str.substr(1)
|
|
102
|
+
}
|
|
111
103
|
|
|
112
104
|
/**
|
|
113
105
|
* Make first char lowercase.
|
|
114
106
|
* @param {string} str
|
|
115
|
-
* @returns {string}
|
|
107
|
+
* @returns {string | undefined}
|
|
116
108
|
*/
|
|
117
109
|
module.exports.lcfirst = function (str) {
|
|
118
|
-
return str.charAt(0).toLowerCase() + str.substr(1)
|
|
119
|
-
}
|
|
110
|
+
if (str) return str.charAt(0).toLowerCase() + str.substr(1)
|
|
111
|
+
}
|
|
120
112
|
|
|
121
113
|
module.exports.chunkArray = function (arr, chunk) {
|
|
122
|
-
let i
|
|
123
|
-
let j
|
|
124
|
-
const tmp = []
|
|
114
|
+
let i
|
|
115
|
+
let j
|
|
116
|
+
const tmp = []
|
|
125
117
|
for (i = 0, j = arr.length; i < j; i += chunk) {
|
|
126
|
-
tmp.push(arr.slice(i, i + chunk))
|
|
118
|
+
tmp.push(arr.slice(i, i + chunk))
|
|
127
119
|
}
|
|
128
|
-
return tmp
|
|
129
|
-
}
|
|
120
|
+
return tmp
|
|
121
|
+
}
|
|
130
122
|
|
|
131
123
|
module.exports.clearString = function (str) {
|
|
132
|
-
if (!str) return ''
|
|
124
|
+
if (!str) return ''
|
|
133
125
|
/* Replace forbidden symbols in string
|
|
134
126
|
*/
|
|
135
127
|
if (str.endsWith('.')) {
|
|
136
|
-
str = str.slice(0, -1)
|
|
128
|
+
str = str.slice(0, -1)
|
|
137
129
|
}
|
|
138
130
|
return str
|
|
139
131
|
.replace(/ /g, '_')
|
|
@@ -146,26 +138,29 @@ module.exports.clearString = function (str) {
|
|
|
146
138
|
.replace(/\|/g, '_')
|
|
147
139
|
.replace(/\?/g, '.')
|
|
148
140
|
.replace(/\*/g, '^')
|
|
149
|
-
.replace(/'/g, '')
|
|
150
|
-
}
|
|
141
|
+
.replace(/'/g, '')
|
|
142
|
+
}
|
|
151
143
|
|
|
152
144
|
module.exports.decodeUrl = function (url) {
|
|
153
145
|
/* Replace forbidden symbols in string
|
|
154
146
|
*/
|
|
155
|
-
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
|
|
156
|
-
}
|
|
147
|
+
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
|
|
148
|
+
}
|
|
157
149
|
|
|
158
150
|
module.exports.xpathLocator = {
|
|
159
151
|
/**
|
|
160
152
|
* @param {string} string
|
|
161
153
|
* @returns {string}
|
|
162
154
|
*/
|
|
163
|
-
literal:
|
|
155
|
+
literal: string => {
|
|
164
156
|
if (string.indexOf("'") > -1) {
|
|
165
|
-
string = string
|
|
166
|
-
|
|
157
|
+
string = string
|
|
158
|
+
.split("'", -1)
|
|
159
|
+
.map(substr => `'${substr}'`)
|
|
160
|
+
.join(',"\'",')
|
|
161
|
+
return `concat(${string})`
|
|
167
162
|
}
|
|
168
|
-
return `'${string}'
|
|
163
|
+
return `'${string}'`
|
|
169
164
|
},
|
|
170
165
|
|
|
171
166
|
/**
|
|
@@ -174,55 +169,84 @@ module.exports.xpathLocator = {
|
|
|
174
169
|
* @returns {string}
|
|
175
170
|
*/
|
|
176
171
|
combine: locators => locators.join(' | '),
|
|
177
|
-
}
|
|
172
|
+
}
|
|
178
173
|
|
|
179
174
|
module.exports.test = {
|
|
180
|
-
|
|
181
175
|
grepLines(array, startString, endString) {
|
|
182
|
-
let startIndex = 0
|
|
183
|
-
let endIndex
|
|
176
|
+
let startIndex = 0
|
|
177
|
+
let endIndex
|
|
184
178
|
array.every((elem, index) => {
|
|
185
179
|
if (elem === startString) {
|
|
186
|
-
startIndex = index
|
|
187
|
-
return true
|
|
180
|
+
startIndex = index
|
|
181
|
+
return true
|
|
188
182
|
}
|
|
189
183
|
if (elem === endString) {
|
|
190
|
-
endIndex = index
|
|
191
|
-
return false
|
|
184
|
+
endIndex = index
|
|
185
|
+
return false
|
|
192
186
|
}
|
|
193
|
-
return true
|
|
194
|
-
})
|
|
195
|
-
return array.slice(startIndex + 1, endIndex)
|
|
187
|
+
return true
|
|
188
|
+
})
|
|
189
|
+
return array.slice(startIndex + 1, endIndex)
|
|
196
190
|
},
|
|
197
191
|
|
|
198
192
|
submittedData(dataFile) {
|
|
199
193
|
return function (key) {
|
|
200
194
|
if (!fs.existsSync(dataFile)) {
|
|
201
|
-
|
|
202
|
-
|
|
195
|
+
// Extended timeout for CI environments to handle slower processing
|
|
196
|
+
const waitTime = process.env.CI ? 60 * 1000 : 2 * 1000 // 60 seconds in CI, 2 seconds otherwise
|
|
197
|
+
let pollInterval = 100 // Start with 100ms polling interval
|
|
198
|
+
const maxPollInterval = 2000 // Max 2 second intervals
|
|
199
|
+
const startTime = new Date().getTime()
|
|
200
|
+
|
|
201
|
+
// Synchronous polling with exponential backoff to reduce CPU usage
|
|
202
|
+
while (new Date().getTime() - startTime < waitTime) {
|
|
203
|
+
if (fs.existsSync(dataFile)) {
|
|
204
|
+
break
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Use Node.js child_process.spawnSync with platform-specific sleep commands
|
|
208
|
+
// This avoids busy waiting and allows other processes to run
|
|
209
|
+
try {
|
|
210
|
+
if (os.platform() === 'win32') {
|
|
211
|
+
// Windows: use ping with precise timing (ping waits exactly the specified ms)
|
|
212
|
+
spawnSync('ping', ['-n', '1', '-w', pollInterval.toString(), '127.0.0.1'], { stdio: 'ignore' })
|
|
213
|
+
} else {
|
|
214
|
+
// Unix/Linux/macOS: use sleep with fractional seconds
|
|
215
|
+
spawnSync('sleep', [(pollInterval / 1000).toString()], { stdio: 'ignore' })
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
// If system commands fail, use a simple busy wait with minimal CPU usage
|
|
219
|
+
const end = new Date().getTime() + pollInterval
|
|
220
|
+
while (new Date().getTime() < end) {
|
|
221
|
+
// No-op loop - much lighter than previous approaches
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Exponential backoff: gradually increase polling interval to reduce resource usage
|
|
226
|
+
pollInterval = Math.min(pollInterval * 1.2, maxPollInterval)
|
|
227
|
+
}
|
|
203
228
|
}
|
|
204
229
|
if (!fs.existsSync(dataFile)) {
|
|
205
|
-
throw new Error('Data file was not created in time')
|
|
230
|
+
throw new Error('Data file was not created in time')
|
|
206
231
|
}
|
|
207
|
-
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
|
|
232
|
+
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
|
|
208
233
|
if (key) {
|
|
209
|
-
return data.form[key]
|
|
234
|
+
return data.form[key]
|
|
210
235
|
}
|
|
211
|
-
return data
|
|
212
|
-
}
|
|
236
|
+
return data
|
|
237
|
+
}
|
|
213
238
|
},
|
|
214
|
-
|
|
215
|
-
};
|
|
239
|
+
}
|
|
216
240
|
|
|
217
241
|
function toCamelCase(name) {
|
|
218
242
|
if (typeof name !== 'string') {
|
|
219
|
-
return name
|
|
243
|
+
return name
|
|
220
244
|
}
|
|
221
245
|
return name.replace(/-(\w)/gi, (_word, letter) => {
|
|
222
|
-
return letter.toUpperCase()
|
|
223
|
-
})
|
|
246
|
+
return letter.toUpperCase()
|
|
247
|
+
})
|
|
224
248
|
}
|
|
225
|
-
module.exports.toCamelCase = toCamelCase
|
|
249
|
+
module.exports.toCamelCase = toCamelCase
|
|
226
250
|
|
|
227
251
|
function convertFontWeightToNumber(name) {
|
|
228
252
|
const fontWeightPatterns = [
|
|
@@ -235,106 +259,110 @@ function convertFontWeightToNumber(name) {
|
|
|
235
259
|
{ num: 700, pattern: /^Bold$/i },
|
|
236
260
|
{ num: 800, pattern: /^(Extra|Ultra)-?bold$/i },
|
|
237
261
|
{ num: 900, pattern: /^(Black|Heavy)$/i },
|
|
238
|
-
]
|
|
262
|
+
]
|
|
239
263
|
|
|
240
264
|
if (/^[1-9]00$/.test(name)) {
|
|
241
|
-
return Number(name)
|
|
265
|
+
return Number(name)
|
|
242
266
|
}
|
|
243
267
|
|
|
244
|
-
const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name))
|
|
268
|
+
const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name))
|
|
245
269
|
|
|
246
270
|
if (matches.length) {
|
|
247
|
-
return String(matches[0].num)
|
|
271
|
+
return String(matches[0].num)
|
|
248
272
|
}
|
|
249
|
-
return name
|
|
273
|
+
return name
|
|
250
274
|
}
|
|
251
275
|
|
|
252
276
|
function isFontWeightProperty(prop) {
|
|
253
|
-
return prop === 'fontWeight'
|
|
277
|
+
return prop === 'fontWeight'
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
module.exports.convertCssPropertiesToCamelCase = function (props) {
|
|
257
|
-
const output = {}
|
|
258
|
-
Object.keys(props).forEach(
|
|
259
|
-
const keyCamel = toCamelCase(key)
|
|
281
|
+
const output = {}
|
|
282
|
+
Object.keys(props).forEach(key => {
|
|
283
|
+
const keyCamel = toCamelCase(key)
|
|
260
284
|
|
|
261
285
|
if (isFontWeightProperty(keyCamel)) {
|
|
262
|
-
output[keyCamel] = convertFontWeightToNumber(props[key])
|
|
286
|
+
output[keyCamel] = convertFontWeightToNumber(props[key])
|
|
263
287
|
} else if (isColorProperty(keyCamel)) {
|
|
264
|
-
output[keyCamel] = convertColorToRGBA(props[key])
|
|
288
|
+
output[keyCamel] = convertColorToRGBA(props[key])
|
|
265
289
|
} else {
|
|
266
|
-
output[keyCamel] = props[key]
|
|
290
|
+
output[keyCamel] = props[key]
|
|
267
291
|
}
|
|
268
|
-
})
|
|
269
|
-
return output
|
|
270
|
-
}
|
|
292
|
+
})
|
|
293
|
+
return output
|
|
294
|
+
}
|
|
271
295
|
|
|
272
296
|
module.exports.deleteDir = function (dir_path) {
|
|
273
297
|
if (fs.existsSync(dir_path)) {
|
|
274
298
|
fs.readdirSync(dir_path).forEach(function (entry) {
|
|
275
|
-
const entry_path = path.join(dir_path, entry)
|
|
299
|
+
const entry_path = path.join(dir_path, entry)
|
|
276
300
|
if (fs.lstatSync(entry_path).isDirectory()) {
|
|
277
|
-
this.deleteDir(entry_path)
|
|
301
|
+
this.deleteDir(entry_path)
|
|
278
302
|
} else {
|
|
279
|
-
fs.unlinkSync(entry_path)
|
|
303
|
+
fs.unlinkSync(entry_path)
|
|
280
304
|
}
|
|
281
|
-
})
|
|
282
|
-
fs.rmdirSync(dir_path)
|
|
305
|
+
})
|
|
306
|
+
fs.rmdirSync(dir_path)
|
|
283
307
|
}
|
|
284
|
-
}
|
|
308
|
+
}
|
|
285
309
|
|
|
286
310
|
/**
|
|
287
311
|
* Returns absolute filename to save screenshot.
|
|
288
312
|
* @param fileName {string} - filename.
|
|
289
313
|
*/
|
|
290
314
|
module.exports.screenshotOutputFolder = function (fileName) {
|
|
291
|
-
const fileSep = path.sep
|
|
315
|
+
const fileSep = path.sep
|
|
292
316
|
|
|
293
317
|
if (!fileName.includes(fileSep) || fileName.includes('record_')) {
|
|
294
|
-
return path.resolve(global.output_dir, fileName)
|
|
318
|
+
return path.resolve(global.output_dir, fileName)
|
|
295
319
|
}
|
|
296
|
-
return path.resolve(global.codecept_dir, fileName)
|
|
297
|
-
}
|
|
320
|
+
return path.resolve(global.codecept_dir, fileName)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports.relativeDir = function (fileName) {
|
|
324
|
+
return fileName.replace(global.codecept_dir, '').replace(/^\//, '')
|
|
325
|
+
}
|
|
298
326
|
|
|
299
327
|
module.exports.beautify = function (code) {
|
|
300
|
-
const format = require('js-beautify').js
|
|
301
|
-
return format(code, { indent_size: 2, space_in_empty_paren: true })
|
|
302
|
-
}
|
|
328
|
+
const format = require('js-beautify').js
|
|
329
|
+
return format(code, { indent_size: 2, space_in_empty_paren: true })
|
|
330
|
+
}
|
|
303
331
|
|
|
304
332
|
function shouldAppendBaseUrl(url) {
|
|
305
|
-
return !/^\w+\:\/\//.test(url)
|
|
333
|
+
return !/^\w+\:\/\//.test(url)
|
|
306
334
|
}
|
|
307
335
|
|
|
308
336
|
function trimUrl(url) {
|
|
309
|
-
const firstChar = url.substr(1)
|
|
337
|
+
const firstChar = url.substr(1)
|
|
310
338
|
if (firstChar === '/') {
|
|
311
|
-
url = url.slice(1)
|
|
339
|
+
url = url.slice(1)
|
|
312
340
|
}
|
|
313
|
-
return url
|
|
341
|
+
return url
|
|
314
342
|
}
|
|
315
343
|
|
|
316
344
|
function joinUrl(baseUrl, url) {
|
|
317
|
-
return shouldAppendBaseUrl(url) ? `${baseUrl}/${trimUrl(url)}` : url
|
|
345
|
+
return shouldAppendBaseUrl(url) ? `${baseUrl}/${trimUrl(url)}` : url
|
|
318
346
|
}
|
|
319
347
|
|
|
320
348
|
module.exports.appendBaseUrl = function (baseUrl = '', oneOrMoreUrls) {
|
|
321
349
|
if (typeof baseUrl !== 'string') {
|
|
322
|
-
throw new Error(`Invalid value for baseUrl: ${baseUrl}`)
|
|
350
|
+
throw new Error(`Invalid value for baseUrl: ${baseUrl}`)
|
|
323
351
|
}
|
|
324
352
|
if (!(typeof oneOrMoreUrls === 'string' || Array.isArray(oneOrMoreUrls))) {
|
|
325
|
-
throw new Error(`Expected type of Urls is 'string' or 'array', Found '${typeof oneOrMoreUrls}'.`)
|
|
353
|
+
throw new Error(`Expected type of Urls is 'string' or 'array', Found '${typeof oneOrMoreUrls}'.`)
|
|
326
354
|
}
|
|
327
355
|
// Remove '/' if it's at the end of baseUrl
|
|
328
|
-
const lastChar = baseUrl.substr(-1)
|
|
356
|
+
const lastChar = baseUrl.substr(-1)
|
|
329
357
|
if (lastChar === '/') {
|
|
330
|
-
baseUrl = baseUrl.slice(0, -1)
|
|
358
|
+
baseUrl = baseUrl.slice(0, -1)
|
|
331
359
|
}
|
|
332
360
|
|
|
333
361
|
if (!Array.isArray(oneOrMoreUrls)) {
|
|
334
|
-
return joinUrl(baseUrl, oneOrMoreUrls)
|
|
362
|
+
return joinUrl(baseUrl, oneOrMoreUrls)
|
|
335
363
|
}
|
|
336
|
-
return oneOrMoreUrls.map(url => joinUrl(baseUrl, url))
|
|
337
|
-
}
|
|
364
|
+
return oneOrMoreUrls.map(url => joinUrl(baseUrl, url))
|
|
365
|
+
}
|
|
338
366
|
|
|
339
367
|
/**
|
|
340
368
|
* Recursively search key in object and replace it's value.
|
|
@@ -344,56 +372,53 @@ module.exports.appendBaseUrl = function (baseUrl = '', oneOrMoreUrls) {
|
|
|
344
372
|
* @param {*} value value to set for key
|
|
345
373
|
*/
|
|
346
374
|
module.exports.replaceValueDeep = function replaceValueDeep(obj, key, value) {
|
|
347
|
-
if (!obj) return
|
|
375
|
+
if (!obj) return
|
|
348
376
|
|
|
349
377
|
if (obj instanceof Array) {
|
|
350
378
|
for (const i in obj) {
|
|
351
|
-
replaceValueDeep(obj[i], key, value)
|
|
379
|
+
replaceValueDeep(obj[i], key, value)
|
|
352
380
|
}
|
|
353
381
|
}
|
|
354
382
|
|
|
355
383
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
356
|
-
obj[key] = value
|
|
384
|
+
obj[key] = value
|
|
357
385
|
}
|
|
358
386
|
|
|
359
387
|
if (typeof obj === 'object' && obj !== null) {
|
|
360
|
-
const children = Object.values(obj)
|
|
388
|
+
const children = Object.values(obj)
|
|
361
389
|
for (const child of children) {
|
|
362
|
-
replaceValueDeep(child, key, value)
|
|
390
|
+
replaceValueDeep(child, key, value)
|
|
363
391
|
}
|
|
364
392
|
}
|
|
365
|
-
return obj
|
|
366
|
-
}
|
|
393
|
+
return obj
|
|
394
|
+
}
|
|
367
395
|
|
|
368
396
|
module.exports.ansiRegExp = function ({ onlyFirst = false } = {}) {
|
|
369
|
-
const pattern = [
|
|
370
|
-
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
371
|
-
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
|
|
372
|
-
].join('|');
|
|
397
|
+
const pattern = ['[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'].join('|')
|
|
373
398
|
|
|
374
|
-
return new RegExp(pattern, onlyFirst ? undefined : 'g')
|
|
375
|
-
}
|
|
399
|
+
return new RegExp(pattern, onlyFirst ? undefined : 'g')
|
|
400
|
+
}
|
|
376
401
|
|
|
377
402
|
module.exports.tryOrDefault = function (fn, defaultValue) {
|
|
378
403
|
try {
|
|
379
|
-
return fn()
|
|
404
|
+
return fn()
|
|
380
405
|
} catch (_) {
|
|
381
|
-
return defaultValue
|
|
406
|
+
return defaultValue
|
|
382
407
|
}
|
|
383
|
-
}
|
|
408
|
+
}
|
|
384
409
|
|
|
385
410
|
function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) {
|
|
386
411
|
if (typeof key !== 'string') {
|
|
387
|
-
return string
|
|
412
|
+
return string
|
|
388
413
|
}
|
|
389
|
-
const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase()
|
|
390
|
-
let position = ''
|
|
414
|
+
const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase()
|
|
415
|
+
let position = ''
|
|
391
416
|
if (typeof prefix === 'string') {
|
|
392
|
-
position = prefix
|
|
417
|
+
position = prefix
|
|
393
418
|
} else if (typeof suffix === 'string') {
|
|
394
|
-
position = suffix
|
|
419
|
+
position = suffix
|
|
395
420
|
}
|
|
396
|
-
return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase()
|
|
421
|
+
return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase()
|
|
397
422
|
}
|
|
398
423
|
|
|
399
424
|
/**
|
|
@@ -403,76 +428,184 @@ function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) {
|
|
|
403
428
|
*/
|
|
404
429
|
module.exports.getNormalizedKeyAttributeValue = function (key) {
|
|
405
430
|
// Use operation modifier key based on operating system
|
|
406
|
-
key = key.replace(/(Ctrl|Control|Cmd|Command)[ _]?Or[ _]?(Ctrl|Control|Cmd|Command)/i, os.platform() === 'darwin' ? 'Meta' : 'Control')
|
|
431
|
+
key = key.replace(/(Ctrl|Control|Cmd|Command)[ _]?Or[ _]?(Ctrl|Control|Cmd|Command)/i, os.platform() === 'darwin' ? 'Meta' : 'Control')
|
|
407
432
|
// Selection of keys (https://www.w3.org/TR/uievents-key/#named-key-attribute-values)
|
|
408
433
|
// which can be written in various ways and should be normalized.
|
|
409
434
|
// For example 'LEFT ALT', 'ALT_Left', 'alt left' or 'LeftAlt' will be normalized as 'AltLeft'.
|
|
410
|
-
key = key.replace(/^\s*(?:(Down|Left|Right|Up)[ _]?)?(Arrow|Alt|Ctrl|Control|Cmd|Command|Meta|Option|OS|Page|Shift|Super)(?:[ _]?(Down|Left|Right|Up|Gr(?:aph)?))?\s*$/i, normalizeKeyReplacer)
|
|
435
|
+
key = key.replace(/^\s*(?:(Down|Left|Right|Up)[ _]?)?(Arrow|Alt|Ctrl|Control|Cmd|Command|Meta|Option|OS|Page|Shift|Super)(?:[ _]?(Down|Left|Right|Up|Gr(?:aph)?))?\s*$/i, normalizeKeyReplacer)
|
|
411
436
|
// Map alias to corresponding key value
|
|
412
|
-
key = key.replace(/^(Add|Divide|Decimal|Multiply|Subtract)$/, 'Numpad$1')
|
|
413
|
-
key = key.replace(/^AltGr$/, 'AltGraph')
|
|
414
|
-
key = key.replace(/^(Cmd|Command|Os|Super)/, 'Meta')
|
|
415
|
-
key = key.replace('Ctrl', 'Control')
|
|
416
|
-
key = key.replace('Option', 'Alt')
|
|
417
|
-
key = key.replace(/^(NumpadComma|Separator)$/, 'Comma')
|
|
418
|
-
return key
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const modifierKeys = [
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
'Meta', 'MetaLeft', 'MetaRight',
|
|
425
|
-
'Shift', 'ShiftLeft', 'ShiftRight',
|
|
426
|
-
];
|
|
427
|
-
|
|
428
|
-
module.exports.modifierKeys = modifierKeys;
|
|
437
|
+
key = key.replace(/^(Add|Divide|Decimal|Multiply|Subtract)$/, 'Numpad$1')
|
|
438
|
+
key = key.replace(/^AltGr$/, 'AltGraph')
|
|
439
|
+
key = key.replace(/^(Cmd|Command|Os|Super)/, 'Meta')
|
|
440
|
+
key = key.replace('Ctrl', 'Control')
|
|
441
|
+
key = key.replace('Option', 'Alt')
|
|
442
|
+
key = key.replace(/^(NumpadComma|Separator)$/, 'Comma')
|
|
443
|
+
return key
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const modifierKeys = ['Alt', 'AltGraph', 'AltLeft', 'AltRight', 'Control', 'ControlLeft', 'ControlRight', 'Meta', 'MetaLeft', 'MetaRight', 'Shift', 'ShiftLeft', 'ShiftRight']
|
|
447
|
+
|
|
448
|
+
module.exports.modifierKeys = modifierKeys
|
|
429
449
|
module.exports.isModifierKey = function (key) {
|
|
430
|
-
return modifierKeys.includes(key)
|
|
431
|
-
}
|
|
450
|
+
return modifierKeys.includes(key)
|
|
451
|
+
}
|
|
432
452
|
|
|
433
453
|
module.exports.requireWithFallback = function (...packages) {
|
|
434
454
|
const exists = function (pkg) {
|
|
435
455
|
try {
|
|
436
|
-
require.resolve(pkg)
|
|
456
|
+
require.resolve(pkg)
|
|
437
457
|
} catch (e) {
|
|
438
|
-
return false
|
|
458
|
+
return false
|
|
439
459
|
}
|
|
440
460
|
|
|
441
|
-
return true
|
|
442
|
-
}
|
|
461
|
+
return true
|
|
462
|
+
}
|
|
443
463
|
|
|
444
464
|
for (const pkg of packages) {
|
|
445
465
|
if (exists(pkg)) {
|
|
446
|
-
return require(pkg)
|
|
466
|
+
return require(pkg)
|
|
447
467
|
}
|
|
448
468
|
}
|
|
449
469
|
|
|
450
|
-
throw new Error(`Cannot find modules ${packages.join(',')}`)
|
|
451
|
-
}
|
|
470
|
+
throw new Error(`Cannot find modules ${packages.join(',')}`)
|
|
471
|
+
}
|
|
452
472
|
|
|
453
473
|
module.exports.isNotSet = function (obj) {
|
|
454
|
-
if (obj === null) return true
|
|
455
|
-
if (obj === undefined) return true
|
|
456
|
-
return false
|
|
457
|
-
}
|
|
474
|
+
if (obj === null) return true
|
|
475
|
+
if (obj === undefined) return true
|
|
476
|
+
return false
|
|
477
|
+
}
|
|
458
478
|
|
|
459
|
-
module.exports.emptyFolder = async
|
|
460
|
-
require('child_process').execSync(`rm -rf ${directoryPath}/*`)
|
|
461
|
-
}
|
|
479
|
+
module.exports.emptyFolder = async directoryPath => {
|
|
480
|
+
require('child_process').execSync(`rm -rf ${directoryPath}/*`)
|
|
481
|
+
}
|
|
462
482
|
|
|
463
|
-
module.exports.printObjectProperties =
|
|
483
|
+
module.exports.printObjectProperties = obj => {
|
|
464
484
|
if (typeof obj !== 'object' || obj === null) {
|
|
465
|
-
return obj
|
|
485
|
+
return obj
|
|
466
486
|
}
|
|
467
487
|
|
|
468
|
-
let result = ''
|
|
488
|
+
let result = ''
|
|
469
489
|
for (const [key, value] of Object.entries(obj)) {
|
|
470
|
-
result += `${key}: "${value}";
|
|
490
|
+
result += `${key}: "${value}"; `
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return `{${result}}`
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
module.exports.normalizeSpacesInString = string => {
|
|
497
|
+
return string.replace(/\s+/g, ' ')
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
module.exports.humanizeFunction = function (fn) {
|
|
501
|
+
const fnStr = fn.toString().trim()
|
|
502
|
+
// Remove arrow function syntax, async, and parentheses
|
|
503
|
+
let simplified = fnStr
|
|
504
|
+
.replace(/^async\s*/, '')
|
|
505
|
+
.replace(/^\([^)]*\)\s*=>/, '')
|
|
506
|
+
.replace(/^function\s*\([^)]*\)/, '')
|
|
507
|
+
// Remove curly braces and any whitespace around them
|
|
508
|
+
.replace(/{\s*(.*)\s*}/, '$1')
|
|
509
|
+
// Remove return statement
|
|
510
|
+
.replace(/return\s+/, '')
|
|
511
|
+
// Remove trailing semicolon
|
|
512
|
+
.replace(/;$/, '')
|
|
513
|
+
.trim()
|
|
514
|
+
|
|
515
|
+
if (simplified.length > 100) {
|
|
516
|
+
simplified = simplified.slice(0, 97) + '...'
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return simplified
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Searches through a given data source using the Fuse.js library for fuzzy searching.
|
|
524
|
+
*
|
|
525
|
+
* @function searchWithFusejs
|
|
526
|
+
* @param {Array|Object} source - The data source to search through. This can be an array of objects or strings.
|
|
527
|
+
* @param {string} searchString - The search query string to match against the source.
|
|
528
|
+
* @param {Object} [opts] - Optional configuration object for Fuse.js.
|
|
529
|
+
* @param {boolean} [opts.includeScore=true] - Whether to include the score of the match in the results.
|
|
530
|
+
* @param {number} [opts.threshold=0.6] - Determines the match threshold; lower values mean stricter matching.
|
|
531
|
+
* @param {boolean} [opts.caseSensitive=false] - Whether the search should be case-sensitive.
|
|
532
|
+
* @param {number} [opts.distance=100] - Determines how far apart the search term is allowed to be from the target.
|
|
533
|
+
* @param {number} [opts.maxPatternLength=32] - The maximum length of the search pattern. Patterns longer than this are ignored.
|
|
534
|
+
* @param {boolean} [opts.ignoreLocation=false] - Whether the location of the match is ignored when scoring.
|
|
535
|
+
* @param {boolean} [opts.ignoreFieldNorm=false] - When true, the field's length is not considered when scoring.
|
|
536
|
+
* @param {Array<string>} [opts.keys=[]] - List of keys to search in the objects of the source array.
|
|
537
|
+
* @param {boolean} [opts.shouldSort=true] - Whether the results should be sorted by score.
|
|
538
|
+
* @param {string} [opts.sortFn] - A custom sorting function for sorting results.
|
|
539
|
+
* @param {number} [opts.minMatchCharLength=1] - The minimum number of characters that must match.
|
|
540
|
+
* @param {boolean} [opts.useExtendedSearch=false] - Enables extended search capabilities.
|
|
541
|
+
*
|
|
542
|
+
* @returns {Array<Object>} - An array of search results. Each result contains an item and, if `includeScore` is true, a score.
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* const data = [
|
|
546
|
+
* { title: "Old Man's War", author: "John Scalzi" },
|
|
547
|
+
* { title: "The Lock Artist", author: "Steve Hamilton" },
|
|
548
|
+
* ];
|
|
549
|
+
*
|
|
550
|
+
* const options = {
|
|
551
|
+
* keys: ['title', 'author'],
|
|
552
|
+
* includeScore: true,
|
|
553
|
+
* threshold: 0.4,
|
|
554
|
+
* caseSensitive: false,
|
|
555
|
+
* distance: 50,
|
|
556
|
+
* ignoreLocation: true,
|
|
557
|
+
* };
|
|
558
|
+
*
|
|
559
|
+
* const results = searchWithFusejs(data, 'lock', options);
|
|
560
|
+
* console.log(results);
|
|
561
|
+
*/
|
|
562
|
+
module.exports.searchWithFusejs = function (source, searchString, opts) {
|
|
563
|
+
const fuse = new Fuse(source, opts)
|
|
564
|
+
|
|
565
|
+
return fuse.search(searchString)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
module.exports.humanizeString = function (string) {
|
|
569
|
+
// split strings by words, then make them all lowercase
|
|
570
|
+
const _result = string
|
|
571
|
+
.replace(/([a-z](?=[A-Z]))/g, '$1 ')
|
|
572
|
+
.split(' ')
|
|
573
|
+
.map(word => word.toLowerCase())
|
|
574
|
+
|
|
575
|
+
_result[0] = _result[0] === 'i' ? this.ucfirst(_result[0]) : _result[0]
|
|
576
|
+
return _result.join(' ').trim()
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
module.exports.serializeError = function (error) {
|
|
580
|
+
if (error) {
|
|
581
|
+
const { stack, uncaught, message, actual, expected } = error
|
|
582
|
+
return { stack, uncaught, message, actual, expected }
|
|
471
583
|
}
|
|
584
|
+
return null
|
|
585
|
+
}
|
|
472
586
|
|
|
473
|
-
|
|
474
|
-
|
|
587
|
+
module.exports.base64EncodeFile = function (filePath) {
|
|
588
|
+
return Buffer.from(fs.readFileSync(filePath)).toString('base64')
|
|
589
|
+
}
|
|
475
590
|
|
|
476
|
-
module.exports.
|
|
477
|
-
return
|
|
478
|
-
|
|
591
|
+
module.exports.markdownToAnsi = function (markdown) {
|
|
592
|
+
return (
|
|
593
|
+
markdown
|
|
594
|
+
// Headers (# Text) - make blue and bold
|
|
595
|
+
.replace(/^(#{1,6})\s+(.+)$/gm, (_, hashes, text) => {
|
|
596
|
+
return chalk.bold.blue(`${hashes} ${text}`)
|
|
597
|
+
})
|
|
598
|
+
// Bullet points - replace with yellow bullet character
|
|
599
|
+
.replace(/^[-*]\s+(.+)$/gm, (_, text) => {
|
|
600
|
+
return `${chalk.yellow('•')} ${text}`
|
|
601
|
+
})
|
|
602
|
+
// Bold (**text**) - make bold
|
|
603
|
+
.replace(/\*\*(.+?)\*\*/g, (_, text) => {
|
|
604
|
+
return chalk.bold(text)
|
|
605
|
+
})
|
|
606
|
+
// Italic (*text*) - make italic (dim in terminals)
|
|
607
|
+
.replace(/\*(.+?)\*/g, (_, text) => {
|
|
608
|
+
return chalk.italic(text)
|
|
609
|
+
})
|
|
610
|
+
)
|
|
611
|
+
}
|