mocha 10.2.0 → 10.4.0
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 +14 -10
- package/lib/cli/cli.js +2 -2
- package/lib/cli/collect-files.js +4 -1
- package/lib/cli/lookup-files.js +7 -2
- package/lib/cli/run.js +1 -1
- package/lib/reporters/base.js +60 -19
- package/lib/reporters/nyan.js +22 -31
- package/lib/reporters/xunit.js +1 -0
- package/lib/runnable.js +1 -1
- package/lib/runner.js +16 -5
- package/lib/suite.js +1 -1
- package/lib/utils.js +12 -3
- package/mocha.js +127 -84
- package/mocha.js.map +1 -1
- package/package.json +13 -22
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://cldup.com/xFVFxOioAU.svg" alt="Mocha test framework"/>
|
|
2
|
+
<img src="https://cldup.com/xFVFxOioAU.svg" alt="Mocha test framework logo"/>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">☕️ Simple, flexible, fun JavaScript test framework for Node.js & The Browser ☕️</p>
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
<a href="https://github.com/mochajs/mocha/actions?query=workflow%3ATests+branch%3Amaster"><img src="https://github.com/mochajs/mocha/workflows/Tests/badge.svg?branch=master" alt="GitHub Actions Build Status"></a>
|
|
9
9
|
<a href="https://coveralls.io/github/mochajs/mocha"><img src="https://coveralls.io/repos/github/mochajs/mocha/badge.svg" alt="Coverage Status"></a>
|
|
10
10
|
<a href="https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha?ref=badge_shield"><img src="https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha.svg?type=shield" alt="FOSSA Status"></a>
|
|
11
|
-
<a href="https://
|
|
12
|
-
<a href="https://github.com/mochajs/mocha#sponsors"><img src="https://opencollective.com/mochajs/tiers/sponsors/badge.svg" alt="OpenCollective"></a>
|
|
13
|
-
<a href="https://github.com/mochajs/mocha#backers"><img src="https://opencollective.com/mochajs/tiers/backers/badge.svg" alt="OpenCollective"></a>
|
|
11
|
+
<a href="https://discord.gg/KeDn2uXhER"><img alt="Chat - Discord" src="https://img.shields.io/badge/chat-Discord-5765F2.svg" /></a>
|
|
12
|
+
<a href="https://github.com/mochajs/mocha#sponsors"><img src="https://opencollective.com/mochajs/tiers/sponsors/badge.svg" alt="OpenCollective Sponsors"></a>
|
|
13
|
+
<a href="https://github.com/mochajs/mocha#backers"><img src="https://opencollective.com/mochajs/tiers/backers/badge.svg" alt="OpenCollective Backers"></a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
## Links
|
|
24
24
|
|
|
25
|
-
- **[Documentation](https://mochajs.org
|
|
25
|
+
- **[Documentation](https://mochajs.org)**
|
|
26
26
|
- **[Release Notes / History / Changes](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)**
|
|
27
27
|
- [Code of Conduct](https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md)
|
|
28
28
|
- [Contributing](https://github.com/mochajs/mocha/blob/master/.github/CONTRIBUTING.md)
|
|
29
|
-
- [
|
|
29
|
+
- [Development](https://github.com/mochajs/mocha/blob/master/.github/DEVELOPMENT.md)
|
|
30
|
+
- [Discord](https://discord.gg/KeDn2uXhER) (ask questions here!)
|
|
30
31
|
- [Issue Tracker](https://github.com/mochajs/mocha/issues)
|
|
31
32
|
|
|
32
33
|
## Backers
|
|
@@ -37,7 +38,10 @@
|
|
|
37
38
|
|
|
38
39
|
## Sponsors
|
|
39
40
|
|
|
40
|
-
Does your company use Mocha? Ask your manager or marketing team if your company would be interested in supporting our project.
|
|
41
|
+
Does your company use Mocha? Ask your manager or marketing team if your company would be interested in supporting our project.
|
|
42
|
+
Support will allow the maintainers to dedicate more time for maintenance and new features for everyone.
|
|
43
|
+
Also, your company's logo will show [on GitHub](https://github.com/mochajs/mocha#readme) and on [our site](https://mochajs.org#sponsors) - who doesn't want a little extra exposure?
|
|
44
|
+
[Here's the info](https://opencollective.com/mochajs).
|
|
41
45
|
|
|
42
46
|
[](https://opencollective.com/mochajs/tiers/sponsors/0/website)
|
|
43
47
|
[](https://opencollective.com/mochajs/tiers/sponsors/1/website)
|
|
@@ -53,11 +57,11 @@ You might want to know that:
|
|
|
53
57
|
|
|
54
58
|
You might want to help:
|
|
55
59
|
|
|
56
|
-
- New to contributing to Mocha? Check out this list of [good first issues](https://github.com/mochajs/mocha/issues?q=is%
|
|
57
|
-
- Mocha could use a hand with [these issues](https://github.com/mochajs/mocha/issues?q=is%
|
|
60
|
+
- New to contributing to Mocha? Check out this list of [good first issues](https://github.com/mochajs/mocha/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
|
61
|
+
- Mocha could use a hand with [these issues](https://github.com/mochajs/mocha/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
|
|
58
62
|
- The [maintainer's handbook](https://github.com/mochajs/mocha/blob/master/MAINTAINERS.md) explains how things get done
|
|
59
63
|
|
|
60
|
-
Finally, come [chat with the maintainers](https://
|
|
64
|
+
Finally, come [chat with the maintainers on Discord](https://discord.gg/KeDn2uXhER) if you want to help with:
|
|
61
65
|
|
|
62
66
|
- Triaging issues, answering questions
|
|
63
67
|
- Review, merging, and closing pull requests
|
package/lib/cli/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
const lookupFiles = require('./lookup-files');
|
|
24
24
|
const commands = require('./commands');
|
|
25
25
|
const ansi = require('ansi-colors');
|
|
26
|
-
const {repository, homepage, version,
|
|
26
|
+
const {repository, homepage, version, discord} = require('../../package.json');
|
|
27
27
|
const {cwd} = require('../utils');
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -68,7 +68,7 @@ exports.main = (argv = process.argv.slice(2), mochaArgs) => {
|
|
|
68
68
|
.wrap(process.stdout.columns ? Math.min(process.stdout.columns, 80) : 80)
|
|
69
69
|
.epilog(
|
|
70
70
|
`Mocha Resources
|
|
71
|
-
Chat: ${ansi.magenta(
|
|
71
|
+
Chat: ${ansi.magenta(discord)}
|
|
72
72
|
GitHub: ${ansi.blue(repository.url)}
|
|
73
73
|
Docs: ${ansi.yellow(homepage)}
|
|
74
74
|
`
|
package/lib/cli/collect-files.js
CHANGED
|
@@ -35,7 +35,10 @@ module.exports = ({
|
|
|
35
35
|
try {
|
|
36
36
|
const moreSpecFiles = castArray(lookupFiles(arg, extension, recursive))
|
|
37
37
|
.filter(filename =>
|
|
38
|
-
ignore.every(
|
|
38
|
+
ignore.every(
|
|
39
|
+
pattern =>
|
|
40
|
+
!minimatch(filename, pattern, {windowsPathsNoEscape: true})
|
|
41
|
+
)
|
|
39
42
|
)
|
|
40
43
|
.map(filename => path.resolve(filename));
|
|
41
44
|
return [...specFiles, ...moreSpecFiles];
|
package/lib/cli/lookup-files.js
CHANGED
|
@@ -75,7 +75,7 @@ module.exports = function lookupFiles(
|
|
|
75
75
|
|
|
76
76
|
if (!fs.existsSync(filepath)) {
|
|
77
77
|
let pattern;
|
|
78
|
-
if (glob.hasMagic(filepath)) {
|
|
78
|
+
if (glob.hasMagic(filepath, {windowsPathsNoEscape: true})) {
|
|
79
79
|
// Handle glob as is without extensions
|
|
80
80
|
pattern = filepath;
|
|
81
81
|
} else {
|
|
@@ -86,7 +86,12 @@ module.exports = function lookupFiles(
|
|
|
86
86
|
pattern = `${filepath}+(${strExtensions})`;
|
|
87
87
|
debug('looking for files using glob pattern: %s', pattern);
|
|
88
88
|
}
|
|
89
|
-
files.push(
|
|
89
|
+
files.push(
|
|
90
|
+
...glob.sync(pattern, {
|
|
91
|
+
nodir: true,
|
|
92
|
+
windowsPathsNoEscape: true
|
|
93
|
+
})
|
|
94
|
+
);
|
|
90
95
|
if (!files.length) {
|
|
91
96
|
throw createNoFilesMatchPatternError(
|
|
92
97
|
`Cannot find any files matching pattern "${filepath}"`,
|
package/lib/cli/run.js
CHANGED
|
@@ -369,7 +369,7 @@ exports.handler = async function (argv) {
|
|
|
369
369
|
try {
|
|
370
370
|
await runMocha(mocha, argv);
|
|
371
371
|
} catch (err) {
|
|
372
|
-
console.error('\n
|
|
372
|
+
console.error('\n Exception during run:', err);
|
|
373
373
|
process.exit(1);
|
|
374
374
|
}
|
|
375
375
|
};
|
package/lib/reporters/base.js
CHANGED
|
@@ -221,6 +221,56 @@ var generateDiff = (exports.generateDiff = function (actual, expected) {
|
|
|
221
221
|
}
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Traverses err.cause and returns all stack traces
|
|
226
|
+
*
|
|
227
|
+
* @private
|
|
228
|
+
* @param {Error} err
|
|
229
|
+
* @param {Set<Error>} [seen]
|
|
230
|
+
* @return {FullErrorStack}
|
|
231
|
+
*/
|
|
232
|
+
var getFullErrorStack = function (err, seen) {
|
|
233
|
+
if (seen && seen.has(err)) {
|
|
234
|
+
return { message: '', msg: '<circular>', stack: '' };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var message;
|
|
238
|
+
|
|
239
|
+
if (typeof err.inspect === 'function') {
|
|
240
|
+
message = err.inspect() + '';
|
|
241
|
+
} else if (err.message && typeof err.message.toString === 'function') {
|
|
242
|
+
message = err.message + '';
|
|
243
|
+
} else {
|
|
244
|
+
message = '';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
var msg;
|
|
248
|
+
var stack = err.stack || message;
|
|
249
|
+
var index = message ? stack.indexOf(message) : -1;
|
|
250
|
+
|
|
251
|
+
if (index === -1) {
|
|
252
|
+
msg = message;
|
|
253
|
+
} else {
|
|
254
|
+
index += message.length;
|
|
255
|
+
msg = stack.slice(0, index);
|
|
256
|
+
// remove msg from stack
|
|
257
|
+
stack = stack.slice(index + 1);
|
|
258
|
+
|
|
259
|
+
if (err.cause) {
|
|
260
|
+
seen = seen || new Set();
|
|
261
|
+
seen.add(err);
|
|
262
|
+
const causeStack = getFullErrorStack(err.cause, seen)
|
|
263
|
+
stack += '\n Caused by: ' + causeStack.msg + (causeStack.stack ? '\n' + causeStack.stack : '');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
message,
|
|
269
|
+
msg,
|
|
270
|
+
stack
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
224
274
|
/**
|
|
225
275
|
* Outputs the given `failures` as a list.
|
|
226
276
|
*
|
|
@@ -241,7 +291,6 @@ exports.list = function (failures) {
|
|
|
241
291
|
color('error stack', '\n%s\n');
|
|
242
292
|
|
|
243
293
|
// msg
|
|
244
|
-
var msg;
|
|
245
294
|
var err;
|
|
246
295
|
if (test.err && test.err.multiple) {
|
|
247
296
|
if (multipleTest !== test) {
|
|
@@ -252,25 +301,8 @@ exports.list = function (failures) {
|
|
|
252
301
|
} else {
|
|
253
302
|
err = test.err;
|
|
254
303
|
}
|
|
255
|
-
var message;
|
|
256
|
-
if (typeof err.inspect === 'function') {
|
|
257
|
-
message = err.inspect() + '';
|
|
258
|
-
} else if (err.message && typeof err.message.toString === 'function') {
|
|
259
|
-
message = err.message + '';
|
|
260
|
-
} else {
|
|
261
|
-
message = '';
|
|
262
|
-
}
|
|
263
|
-
var stack = err.stack || message;
|
|
264
|
-
var index = message ? stack.indexOf(message) : -1;
|
|
265
304
|
|
|
266
|
-
|
|
267
|
-
msg = message;
|
|
268
|
-
} else {
|
|
269
|
-
index += message.length;
|
|
270
|
-
msg = stack.slice(0, index);
|
|
271
|
-
// remove msg from stack
|
|
272
|
-
stack = stack.slice(index + 1);
|
|
273
|
-
}
|
|
305
|
+
var { message, msg, stack } = getFullErrorStack(err);
|
|
274
306
|
|
|
275
307
|
// uncaught
|
|
276
308
|
if (err.uncaught) {
|
|
@@ -548,3 +580,12 @@ function sameType(a, b) {
|
|
|
548
580
|
Base.consoleLog = consoleLog;
|
|
549
581
|
|
|
550
582
|
Base.abstract = true;
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* An object with all stack traces recursively mounted from each err.cause
|
|
586
|
+
* @memberof module:lib/reporters/base
|
|
587
|
+
* @typedef {Object} FullErrorStack
|
|
588
|
+
* @property {string} message
|
|
589
|
+
* @property {string} msg
|
|
590
|
+
* @property {string} stack
|
|
591
|
+
*/
|
package/lib/reporters/nyan.js
CHANGED
|
@@ -66,7 +66,7 @@ function NyanCat(runner, options) {
|
|
|
66
66
|
runner.once(EVENT_RUN_END, function () {
|
|
67
67
|
Base.cursor.show();
|
|
68
68
|
for (var i = 0; i < self.numberOfLines; i++) {
|
|
69
|
-
write('\n');
|
|
69
|
+
process.stdout.write('\n');
|
|
70
70
|
}
|
|
71
71
|
self.epilogue();
|
|
72
72
|
});
|
|
@@ -102,15 +102,15 @@ NyanCat.prototype.drawScoreboard = function () {
|
|
|
102
102
|
var stats = this.stats;
|
|
103
103
|
|
|
104
104
|
function draw(type, n) {
|
|
105
|
-
write(' ');
|
|
106
|
-
write(Base.color(type, n));
|
|
107
|
-
write('\n');
|
|
105
|
+
process.stdout.write(' ');
|
|
106
|
+
process.stdout.write(Base.color(type, n));
|
|
107
|
+
process.stdout.write('\n');
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
draw('green', stats.passes);
|
|
111
111
|
draw('fail', stats.failures);
|
|
112
112
|
draw('pending', stats.pending);
|
|
113
|
-
write('\n');
|
|
113
|
+
process.stdout.write('\n');
|
|
114
114
|
|
|
115
115
|
this.cursorUp(this.numberOfLines);
|
|
116
116
|
};
|
|
@@ -144,9 +144,9 @@ NyanCat.prototype.drawRainbow = function () {
|
|
|
144
144
|
var self = this;
|
|
145
145
|
|
|
146
146
|
this.trajectories.forEach(function (line) {
|
|
147
|
-
write('\u001b[' + self.scoreboardWidth + 'C');
|
|
148
|
-
write(line.join(''));
|
|
149
|
-
write('\n');
|
|
147
|
+
process.stdout.write('\u001b[' + self.scoreboardWidth + 'C');
|
|
148
|
+
process.stdout.write(line.join(''));
|
|
149
|
+
process.stdout.write('\n');
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
this.cursorUp(this.numberOfLines);
|
|
@@ -163,25 +163,25 @@ NyanCat.prototype.drawNyanCat = function () {
|
|
|
163
163
|
var dist = '\u001b[' + startWidth + 'C';
|
|
164
164
|
var padding = '';
|
|
165
165
|
|
|
166
|
-
write(dist);
|
|
167
|
-
write('_,------,');
|
|
168
|
-
write('\n');
|
|
166
|
+
process.stdout.write(dist);
|
|
167
|
+
process.stdout.write('_,------,');
|
|
168
|
+
process.stdout.write('\n');
|
|
169
169
|
|
|
170
|
-
write(dist);
|
|
170
|
+
process.stdout.write(dist);
|
|
171
171
|
padding = self.tick ? ' ' : ' ';
|
|
172
|
-
write('_|' + padding + '/\\_/\\ ');
|
|
173
|
-
write('\n');
|
|
172
|
+
process.stdout.write('_|' + padding + '/\\_/\\ ');
|
|
173
|
+
process.stdout.write('\n');
|
|
174
174
|
|
|
175
|
-
write(dist);
|
|
175
|
+
process.stdout.write(dist);
|
|
176
176
|
padding = self.tick ? '_' : '__';
|
|
177
177
|
var tail = self.tick ? '~' : '^';
|
|
178
|
-
write(tail + '|' + padding + this.face() + ' ');
|
|
179
|
-
write('\n');
|
|
178
|
+
process.stdout.write(tail + '|' + padding + this.face() + ' ');
|
|
179
|
+
process.stdout.write('\n');
|
|
180
180
|
|
|
181
|
-
write(dist);
|
|
181
|
+
process.stdout.write(dist);
|
|
182
182
|
padding = self.tick ? ' ' : ' ';
|
|
183
|
-
write(padding + '"" "" ');
|
|
184
|
-
write('\n');
|
|
183
|
+
process.stdout.write(padding + '"" "" ');
|
|
184
|
+
process.stdout.write('\n');
|
|
185
185
|
|
|
186
186
|
this.cursorUp(this.numberOfLines);
|
|
187
187
|
};
|
|
@@ -213,7 +213,7 @@ NyanCat.prototype.face = function () {
|
|
|
213
213
|
*/
|
|
214
214
|
|
|
215
215
|
NyanCat.prototype.cursorUp = function (n) {
|
|
216
|
-
write('\u001b[' + n + 'A');
|
|
216
|
+
process.stdout.write('\u001b[' + n + 'A');
|
|
217
217
|
};
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -224,7 +224,7 @@ NyanCat.prototype.cursorUp = function (n) {
|
|
|
224
224
|
*/
|
|
225
225
|
|
|
226
226
|
NyanCat.prototype.cursorDown = function (n) {
|
|
227
|
-
write('\u001b[' + n + 'B');
|
|
227
|
+
process.stdout.write('\u001b[' + n + 'B');
|
|
228
228
|
};
|
|
229
229
|
|
|
230
230
|
/**
|
|
@@ -264,13 +264,4 @@ NyanCat.prototype.rainbowify = function (str) {
|
|
|
264
264
|
return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
|
|
265
265
|
};
|
|
266
266
|
|
|
267
|
-
/**
|
|
268
|
-
* Stdout helper.
|
|
269
|
-
*
|
|
270
|
-
* @param {string} string A message to write to stdout.
|
|
271
|
-
*/
|
|
272
|
-
function write(string) {
|
|
273
|
-
process.stdout.write(string);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
267
|
NyanCat.description = '"nyan cat"';
|
package/lib/reporters/xunit.js
CHANGED
package/lib/runnable.js
CHANGED
|
@@ -211,7 +211,7 @@ Runnable.prototype.fullTitle = function () {
|
|
|
211
211
|
*
|
|
212
212
|
* @memberof Mocha.Runnable
|
|
213
213
|
* @public
|
|
214
|
-
* @return {string}
|
|
214
|
+
* @return {string[]}
|
|
215
215
|
*/
|
|
216
216
|
Runnable.prototype.titlePath = function () {
|
|
217
217
|
return this.parent.titlePath().concat([this.title]);
|
package/lib/runner.js
CHANGED
|
@@ -443,11 +443,22 @@ Runner.prototype.fail = function (test, err, force) {
|
|
|
443
443
|
err = thrown2Error(err);
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
446
|
+
// Filter the stack traces
|
|
447
|
+
if (!this.fullStackTrace) {
|
|
448
|
+
const alreadyFiltered = new Set();
|
|
449
|
+
let currentErr = err;
|
|
450
|
+
|
|
451
|
+
while (currentErr && currentErr.stack && !alreadyFiltered.has(currentErr)) {
|
|
452
|
+
alreadyFiltered.add(currentErr);
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
currentErr.stack = stackFilter(currentErr.stack);
|
|
456
|
+
} catch (ignore) {
|
|
457
|
+
// some environments do not take kindly to monkeying with the stack
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
currentErr = currentErr.cause;
|
|
461
|
+
}
|
|
451
462
|
}
|
|
452
463
|
|
|
453
464
|
this.emit(constants.EVENT_TEST_FAIL, test, err);
|
package/lib/suite.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* Module dependencies.
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
const {nanoid} = require('nanoid/non-secure');
|
|
13
11
|
var path = require('path');
|
|
14
12
|
var util = require('util');
|
|
15
13
|
var he = require('he');
|
|
@@ -615,11 +613,22 @@ exports.constants = exports.defineConstants({
|
|
|
615
613
|
MOCHA_ID_PROP_NAME
|
|
616
614
|
});
|
|
617
615
|
|
|
616
|
+
const uniqueIDBase =
|
|
617
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
|
|
618
|
+
|
|
618
619
|
/**
|
|
619
620
|
* Creates a new unique identifier
|
|
621
|
+
* Does not create cryptographically safe ids.
|
|
622
|
+
* Trivial copy of nanoid/non-secure
|
|
620
623
|
* @returns {string} Unique identifier
|
|
621
624
|
*/
|
|
622
|
-
exports.uniqueID = () =>
|
|
625
|
+
exports.uniqueID = () => {
|
|
626
|
+
let id = '';
|
|
627
|
+
for (let i = 0; i < 21; i++) {
|
|
628
|
+
id += uniqueIDBase[(Math.random() * 64) | 0];
|
|
629
|
+
}
|
|
630
|
+
return id;
|
|
631
|
+
};
|
|
623
632
|
|
|
624
633
|
exports.assignNewMochaID = obj => {
|
|
625
634
|
const id = exports.uniqueID();
|