mocha 10.4.0 → 10.5.1

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  <p align="center">☕️ Simple, flexible, fun JavaScript test framework for Node.js & The Browser ☕️</p>
6
6
 
7
7
  <p align="center">
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>
8
+ <a href="https://github.com/mochajs/mocha/actions?query=workflow%3ATests+branch%3Amain"><img src="https://github.com/mochajs/mocha/workflows/Tests/badge.svg?branch=main" 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
11
  <a href="https://discord.gg/KeDn2uXhER"><img alt="Chat - Discord" src="https://img.shields.io/badge/chat-Discord-5765F2.svg" /></a>
@@ -23,10 +23,10 @@
23
23
  ## Links
24
24
 
25
25
  - **[Documentation](https://mochajs.org)**
26
- - **[Release Notes / History / Changes](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)**
27
- - [Code of Conduct](https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md)
28
- - [Contributing](https://github.com/mochajs/mocha/blob/master/.github/CONTRIBUTING.md)
29
- - [Development](https://github.com/mochajs/mocha/blob/master/.github/DEVELOPMENT.md)
26
+ - **[Release Notes / History / Changes](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)**
27
+ - [Code of Conduct](https://github.com/mochajs/mocha/blob/main/.github/CODE_OF_CONDUCT.md)
28
+ - [Contributing](https://github.com/mochajs/mocha/blob/main/.github/CONTRIBUTING.md)
29
+ - [Development](https://github.com/mochajs/mocha/blob/main/.github/DEVELOPMENT.md)
30
30
  - [Discord](https://discord.gg/KeDn2uXhER) (ask questions here!)
31
31
  - [Issue Tracker](https://github.com/mochajs/mocha/issues)
32
32
 
@@ -59,7 +59,7 @@ You might want to help:
59
59
 
60
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
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)
62
- - The [maintainer's handbook](https://github.com/mochajs/mocha/blob/master/MAINTAINERS.md) explains how things get done
62
+ - The [maintainer's handbook](https://github.com/mochajs/mocha/blob/main/MAINTAINERS.md) explains how things get done
63
63
 
64
64
  Finally, come [chat with the maintainers on Discord](https://discord.gg/KeDn2uXhER) if you want to help with:
65
65
 
@@ -69,6 +69,6 @@ Finally, come [chat with the maintainers on Discord](https://discord.gg/KeDn2uXh
69
69
 
70
70
  ## License
71
71
 
72
- Copyright 2011-2022 OpenJS Foundation and contributors. Licensed [MIT](https://github.com/mochajs/mocha/blob/master/LICENSE).
72
+ Copyright 2011-2022 OpenJS Foundation and contributors. Licensed [MIT](https://github.com/mochajs/mocha/blob/main/LICENSE).
73
73
 
74
74
  [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha?ref=badge_large)
package/browser-entry.js CHANGED
@@ -71,8 +71,8 @@ process.listenerCount = function (name) {
71
71
 
72
72
  process.on = function (e, fn) {
73
73
  if (e === 'uncaughtException') {
74
- global.onerror = function (err, url, line) {
75
- fn(new Error(err + ' (' + url + ':' + line + ')'));
74
+ global.onerror = function (msg, url, line, col, err) {
75
+ fn(err || new Error(msg + ' (' + url + ':' + line + ':' + col + ')'));
76
76
  return !mocha.options.allowUncaught;
77
77
  };
78
78
  uncaughtExceptionHandlers.push(fn);
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
5
  const ansi = require('ansi-colors');
5
6
  const debug = require('debug')('mocha:cli:run:helpers');
@@ -19,7 +20,7 @@ const {castArray} = require('../utils');
19
20
  /**
20
21
  * Smash together an array of test files in the correct order
21
22
  * @param {FileCollectionOptions} [opts] - Options
22
- * @returns {string[]} List of files to test
23
+ * @returns {FileCollectionResponse} An object containing a list of files to test and unmatched files.
23
24
  * @private
24
25
  */
25
26
  module.exports = ({
@@ -30,7 +31,7 @@ module.exports = ({
30
31
  sort,
31
32
  spec
32
33
  } = {}) => {
33
- const unmatched = [];
34
+ const unmatchedSpecFiles = [];
34
35
  const specFiles = spec.reduce((specFiles, arg) => {
35
36
  try {
36
37
  const moreSpecFiles = castArray(lookupFiles(arg, extension, recursive))
@@ -44,7 +45,7 @@ module.exports = ({
44
45
  return [...specFiles, ...moreSpecFiles];
45
46
  } catch (err) {
46
47
  if (err.code === NO_FILES_MATCH_PATTERN) {
47
- unmatched.push({message: err.message, pattern: err.pattern});
48
+ unmatchedSpecFiles.push({message: err.message, pattern: err.pattern});
48
49
  return specFiles;
49
50
  }
50
51
 
@@ -52,6 +53,27 @@ module.exports = ({
52
53
  }
53
54
  }, []);
54
55
 
56
+ // check that each file passed in to --file exists
57
+
58
+ const unmatchedFiles = [];
59
+ fileArgs.forEach(file => {
60
+ const fileAbsolutePath = path.resolve(file);
61
+ try {
62
+ // Used instead of fs.existsSync to ensure that file-ending less files are still resolved correctly
63
+ require.resolve(fileAbsolutePath);
64
+ } catch (err) {
65
+ if (err.code === 'MODULE_NOT_FOUND') {
66
+ unmatchedFiles.push({
67
+ pattern: file,
68
+ absolutePath: fileAbsolutePath
69
+ });
70
+ return;
71
+ }
72
+
73
+ throw err;
74
+ }
75
+ });
76
+
55
77
  // ensure we don't sort the stuff from fileArgs; order is important!
56
78
  if (sort) {
57
79
  specFiles.sort();
@@ -67,19 +89,24 @@ module.exports = ({
67
89
  if (!files.length) {
68
90
  // give full message details when only 1 file is missing
69
91
  const noneFoundMsg =
70
- unmatched.length === 1
71
- ? `Error: No test files found: ${JSON.stringify(unmatched[0].pattern)}` // stringify to print escaped characters raw
92
+ unmatchedSpecFiles.length === 1
93
+ ? `Error: No test files found: ${JSON.stringify(
94
+ unmatchedSpecFiles[0].pattern
95
+ )}` // stringify to print escaped characters raw
72
96
  : 'Error: No test files found';
73
97
  console.error(ansi.red(noneFoundMsg));
74
98
  process.exit(1);
75
99
  } else {
76
100
  // print messages as a warning
77
- unmatched.forEach(warning => {
101
+ unmatchedSpecFiles.forEach(warning => {
78
102
  console.warn(ansi.yellow(`Warning: ${warning.message}`));
79
103
  });
80
104
  }
81
105
 
82
- return files;
106
+ return {
107
+ files,
108
+ unmatchedFiles
109
+ };
83
110
  };
84
111
 
85
112
  /**
@@ -93,3 +120,18 @@ module.exports = ({
93
120
  * @property {boolean} recursive - Find files recursively
94
121
  * @property {boolean} sort - Sort test files
95
122
  */
123
+
124
+ /**
125
+ * Diagnostic object containing unmatched files
126
+ * @typedef {Object} UnmatchedFile -
127
+ * @property {string} absolutePath - A list of unmatched files derived from the file arguments passed in.
128
+ * @property {string} pattern - A list of unmatched files derived from the file arguments passed in.
129
+ *
130
+ */
131
+
132
+ /**
133
+ * Response object containing a list of files to test and unmatched files.
134
+ * @typedef {Object} FileCollectionResponse
135
+ * @property {string[]} files - A list of files to test
136
+ * @property {UnmatchedFile[]} unmatchedFiles - A list of unmatched files derived from the file arguments passed in.
137
+ */
@@ -208,9 +208,10 @@ module.exports.loadPkgRc = loadPkgRc;
208
208
  * Priority list:
209
209
  *
210
210
  * 1. Command-line args
211
- * 2. RC file (`.mocharc.c?js`, `.mocharc.ya?ml`, `mocharc.json`)
212
- * 3. `mocha` prop of `package.json`
213
- * 4. default configuration (`lib/mocharc.json`)
211
+ * 2. `MOCHA_OPTIONS` environment variable.
212
+ * 3. RC file (`.mocharc.c?js`, `.mocharc.ya?ml`, `mocharc.json`)
213
+ * 4. `mocha` prop of `package.json`
214
+ * 5. default configuration (`lib/mocharc.json`)
214
215
  *
215
216
  * If a {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS "one-and-done" option} is present in the `argv` array, no external config files will be read.
216
217
  * @summary Parses options read from `.mocharc.*` and `package.json`.
@@ -231,6 +232,7 @@ const loadOptions = (argv = []) => {
231
232
  return args;
232
233
  }
233
234
 
235
+ const envConfig = parse(process.env.MOCHA_OPTIONS || '');
234
236
  const rcConfig = loadRc(args);
235
237
  const pkgConfig = loadPkgRc(args);
236
238
 
@@ -243,7 +245,14 @@ const loadOptions = (argv = []) => {
243
245
  args._ = args._.concat(pkgConfig._ || []);
244
246
  }
245
247
 
246
- args = parse(args._, mocharc, args, rcConfig || {}, pkgConfig || {});
248
+ args = parse(
249
+ args._,
250
+ mocharc,
251
+ args,
252
+ envConfig,
253
+ rcConfig || {},
254
+ pkgConfig || {}
255
+ );
247
256
 
248
257
  // recombine positional arguments and "spec"
249
258
  if (args.spec) {
@@ -9,6 +9,7 @@
9
9
 
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
+ const ansi = require('ansi-colors');
12
13
  const debug = require('debug')('mocha:cli:run:helpers');
13
14
  const {watchRun, watchParallelRun} = require('./watch-run');
14
15
  const collectFiles = require('./collect-files');
@@ -16,6 +17,7 @@ const {format} = require('util');
16
17
  const {createInvalidLegacyPluginError} = require('../errors');
17
18
  const {requireOrImport} = require('../nodejs/esm-utils');
18
19
  const PluginLoader = require('../plugin-loader');
20
+ const {UnmatchedFile} = require('./collect-files');
19
21
 
20
22
  /**
21
23
  * Exits Mocha when tests + code under test has finished execution (default)
@@ -106,6 +108,32 @@ exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => {
106
108
  return plugins;
107
109
  };
108
110
 
111
+ /**
112
+ * Logs errors and exits the app if unmatched files exist
113
+ * @param {Mocha} mocha - Mocha instance
114
+ * @param {UnmatchedFile} unmatchedFiles - object containing unmatched file paths
115
+ * @returns {Promise<Runner>}
116
+ * @private
117
+ */
118
+ const handleUnmatchedFiles = (mocha, unmatchedFiles) => {
119
+ if (unmatchedFiles.length === 0) {
120
+ return;
121
+ }
122
+
123
+ unmatchedFiles.forEach(({pattern, absolutePath}) => {
124
+ console.error(
125
+ ansi.yellow(
126
+ `Warning: Cannot find any files matching pattern "${pattern}" at the absolute path "${absolutePath}"`
127
+ )
128
+ );
129
+ });
130
+ console.log(
131
+ 'No test file(s) found with the given pattern, exiting with code 1'
132
+ );
133
+
134
+ return mocha.run(exitMocha(1));
135
+ };
136
+
109
137
  /**
110
138
  * Collect and load test files, then run mocha instance.
111
139
  * @param {Mocha} mocha - Mocha instance
@@ -117,9 +145,14 @@ exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => {
117
145
  * @private
118
146
  */
119
147
  const singleRun = async (mocha, {exit}, fileCollectParams) => {
120
- const files = collectFiles(fileCollectParams);
121
- debug('single run with %d file(s)', files.length);
122
- mocha.files = files;
148
+ const fileCollectionObj = collectFiles(fileCollectParams);
149
+
150
+ if (fileCollectionObj.unmatchedFiles.length > 0) {
151
+ return handleUnmatchedFiles(mocha, fileCollectionObj.unmatchedFiles);
152
+ }
153
+
154
+ debug('single run with %d file(s)', fileCollectionObj.files.length);
155
+ mocha.files = fileCollectionObj.files;
123
156
 
124
157
  // handles ESM modules
125
158
  await mocha.loadFilesAsync();
@@ -140,9 +173,17 @@ const singleRun = async (mocha, {exit}, fileCollectParams) => {
140
173
  * @private
141
174
  */
142
175
  const parallelRun = async (mocha, options, fileCollectParams) => {
143
- const files = collectFiles(fileCollectParams);
144
- debug('executing %d test file(s) in parallel mode', files.length);
145
- mocha.files = files;
176
+ const fileCollectionObj = collectFiles(fileCollectParams);
177
+
178
+ if (fileCollectionObj.unmatchedFiles.length > 0) {
179
+ return handleUnmatchedFiles(mocha, fileCollectionObj.unmatchedFiles);
180
+ }
181
+
182
+ debug(
183
+ 'executing %d test file(s) in parallel mode',
184
+ fileCollectionObj.files.length
185
+ );
186
+ mocha.files = fileCollectionObj.files;
146
187
 
147
188
  // note that we DO NOT load any files here; this is handled by the worker
148
189
  return mocha.run(options.exit ? exitMocha : exitMochaLater);
@@ -58,7 +58,7 @@ exports.watchParallelRun = (
58
58
  newMocha.suite.ctx = new Context();
59
59
 
60
60
  // reset the list of files
61
- newMocha.files = collectFiles(fileCollectParams);
61
+ newMocha.files = collectFiles(fileCollectParams).files;
62
62
 
63
63
  // because we've swapped out the root suite (see the `run` inner function
64
64
  // in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
@@ -120,7 +120,7 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
120
120
  newMocha.suite.ctx = new Context();
121
121
 
122
122
  // reset the list of files
123
- newMocha.files = collectFiles(fileCollectParams);
123
+ newMocha.files = collectFiles(fileCollectParams).files;
124
124
 
125
125
  // because we've swapped out the root suite (see the `run` inner function
126
126
  // in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
@@ -54,7 +54,7 @@ const ONCE_EVENT_NAMES = [EVENT_DELAY_BEGIN, EVENT_DELAY_END];
54
54
 
55
55
  /**
56
56
  * The `ParallelBuffered` reporter is used by each worker process in "parallel"
57
- * mode, by default. Instead of reporting to to `STDOUT`, etc., it retains a
57
+ * mode, by default. Instead of reporting to `STDOUT`, etc., it retains a
58
58
  * list of events it receives and hands these off to the callback passed into
59
59
  * {@link Mocha#run}. That callback will then return the data to the main
60
60
  * process.
@@ -117,7 +117,7 @@ class SerializableEvent {
117
117
  /**
118
118
  * Constructs a `SerializableEvent`, throwing if we receive unexpected data.
119
119
  *
120
- * Practically, events emitted from `Runner` have a minumum of zero (0)
120
+ * Practically, events emitted from `Runner` have a minimum of zero (0)
121
121
  * arguments-- (for example, {@link Runnable.constants.EVENT_RUN_BEGIN}) and a
122
122
  * maximum of two (2) (for example,
123
123
  * {@link Runnable.constants.EVENT_TEST_FAIL}, where the second argument is an
@@ -10,7 +10,6 @@
10
10
 
11
11
  var Base = require('./base');
12
12
  var utils = require('../utils');
13
- var Progress = require('../browser/progress');
14
13
  var escapeRe = require('escape-string-regexp');
15
14
  var constants = require('../runner').constants;
16
15
  var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
@@ -38,7 +37,7 @@ exports = module.exports = HTML;
38
37
 
39
38
  var statsTemplate =
40
39
  '<ul id="mocha-stats">' +
41
- '<li class="progress"><canvas width="40" height="40"></canvas></li>' +
40
+ '<li class="progress-contain"><progress class="progress-element" max="100" value="0"></progress><svg class="progress-ring"><circle class="ring-flatlight" stroke-dasharray="100%,0%"/><circle class="ring-highlight" stroke-dasharray="0%,100%"/></svg><div class="progress-text">0%</div></li>' +
42
41
  '<li class="passes"><a href="javascript:void(0);">passes:</a> <em>0</em></li>' +
43
42
  '<li class="failures"><a href="javascript:void(0);">failures:</a> <em>0</em></li>' +
44
43
  '<li class="duration">duration: <em>0</em>s</li>' +
@@ -68,24 +67,16 @@ function HTML(runner, options) {
68
67
  var failures = items[2].getElementsByTagName('em')[0];
69
68
  var failuresLink = items[2].getElementsByTagName('a')[0];
70
69
  var duration = items[3].getElementsByTagName('em')[0];
71
- var canvas = stat.getElementsByTagName('canvas')[0];
72
70
  var report = fragment('<ul id="mocha-report"></ul>');
73
71
  var stack = [report];
74
- var progress;
75
- var ctx;
72
+ var progressText = items[0].getElementsByTagName('div')[0];
73
+ var progressBar = items[0].getElementsByTagName('progress')[0];
74
+ var progressRing = [
75
+ items[0].getElementsByClassName('ring-flatlight')[0],
76
+ items[0].getElementsByClassName('ring-highlight')[0]];
77
+ var progressRingRadius = null; // computed CSS unavailable now, so set later
76
78
  var root = document.getElementById('mocha');
77
79
 
78
- if (canvas.getContext) {
79
- var ratio = window.devicePixelRatio || 1;
80
- canvas.style.width = canvas.width;
81
- canvas.style.height = canvas.height;
82
- canvas.width *= ratio;
83
- canvas.height *= ratio;
84
- ctx = canvas.getContext('2d');
85
- ctx.scale(ratio, ratio);
86
- progress = new Progress();
87
- }
88
-
89
80
  if (!root) {
90
81
  return error('#mocha div missing, add it to your document');
91
82
  }
@@ -115,10 +106,6 @@ function HTML(runner, options) {
115
106
  root.appendChild(stat);
116
107
  root.appendChild(report);
117
108
 
118
- if (progress) {
119
- progress.size(40);
120
- }
121
-
122
109
  runner.on(EVENT_SUITE_BEGIN, function (suite) {
123
110
  if (suite.root) {
124
111
  return;
@@ -234,8 +221,26 @@ function HTML(runner, options) {
234
221
  function updateStats() {
235
222
  // TODO: add to stats
236
223
  var percent = ((stats.tests / runner.total) * 100) | 0;
237
- if (progress) {
238
- progress.update(percent).draw(ctx);
224
+ progressBar.value = percent;
225
+ if (progressText) {
226
+ // setting a toFixed that is too low, makes small changes to progress not shown
227
+ // setting it too high, makes the progress text longer then it needs to
228
+ // to address this, calculate the toFixed based on the magnitude of total
229
+ var decimalPlaces = Math.ceil(Math.log10(runner.total / 100));
230
+ text(
231
+ progressText,
232
+ percent.toFixed(Math.min(Math.max(decimalPlaces, 0), 100)) + '%'
233
+ );
234
+ }
235
+ if (progressRing) {
236
+ var radius = parseFloat(getComputedStyle(progressRing[0]).getPropertyValue('r'));
237
+ var wholeArc = Math.PI * 2 * radius;
238
+ var highlightArc = percent * (wholeArc / 100);
239
+ // The progress ring is in 2 parts, the flatlight color and highlight color.
240
+ // Rendering both on top of the other, seems to make a 3rd color on the edges.
241
+ // To create 1 whole ring with 2 colors, both parts are inverse of the other.
242
+ progressRing[0].style['stroke-dasharray'] = `0,${highlightArc}px,${wholeArc}px`;
243
+ progressRing[1].style['stroke-dasharray'] = `${highlightArc}px,${wholeArc}px`;
239
244
  }
240
245
 
241
246
  // update stats
package/mocha.css CHANGED
@@ -22,6 +22,9 @@
22
22
  --mocha-stats-color: #888;
23
23
  --mocha-stats-em-color: #000;
24
24
  --mocha-stats-hover-color: #eee;
25
+ --mocha-progress-ring-color: #eee;
26
+ --mocha-progress-ring-highlight-color: #9f9f9f;
27
+ --mocha-progress-text-color: #000;
25
28
  --mocha-error-color: #c00;
26
29
 
27
30
  --mocha-code-comment: #ddd;
@@ -54,6 +57,9 @@
54
57
  --mocha-stats-color: #aaa;
55
58
  --mocha-stats-em-color: #fff;
56
59
  --mocha-stats-hover-color: #444;
60
+ --mocha-progress-ring-color: #444;
61
+ --mocha-progress-ring-highlight-color: #888;
62
+ --mocha-progress-text-color: #fff;
57
63
  --mocha-error-color: #f44;
58
64
 
59
65
  --mocha-code-comment: #ddd;
@@ -325,6 +331,10 @@ body {
325
331
  }
326
332
 
327
333
  #mocha-stats {
334
+ --ring-container-size: 40px;
335
+ --ring-size: 39px;
336
+ --ring-radius: calc(var(--ring-size) / 2);
337
+
328
338
  position: fixed;
329
339
  top: 15px;
330
340
  right: 10px;
@@ -334,20 +344,52 @@ body {
334
344
  z-index: 1;
335
345
  }
336
346
 
337
- #mocha-stats .progress {
347
+ #mocha-stats .progress-contain {
338
348
  float: right;
339
- padding-top: 0;
349
+ padding: 0;
350
+ }
351
+
352
+ #mocha-stats :is(.progress-element, .progress-text) {
353
+ width: var(--ring-container-size);
354
+ display: block;
355
+ top: 12px;
356
+ position: absolute;
357
+ }
358
+
359
+ #mocha-stats .progress-element {
360
+ visibility: hidden;
361
+ height: calc(var(--ring-container-size) / 2);
362
+ }
363
+
364
+ #mocha-stats .progress-text {
365
+ text-align: center;
366
+ text-overflow: clip;
367
+ overflow: hidden;
368
+ color: var(--mocha-stats-em-color);
369
+ font-size: 11px;
370
+ }
340
371
 
341
- /**
342
- * Set safe initial values, so mochas .progress does not inherit these
343
- * properties from Bootstrap .progress (which causes .progress height to
344
- * equal line height set in Bootstrap).
345
- */
346
- height: auto;
347
- -webkit-box-shadow: none;
348
- -moz-box-shadow: none;
349
- box-shadow: none;
350
- background-color: initial;
372
+ #mocha-stats .progress-ring {
373
+ width: var(--ring-container-size);
374
+ height: var(--ring-container-size);
375
+ }
376
+
377
+ #mocha-stats :is(.ring-flatlight, .ring-highlight) {
378
+ --stroke-thickness: 1.65px;
379
+ --center: calc(var(--ring-container-size) / 2);
380
+ cx: var(--center);
381
+ cy: var(--center);
382
+ r: calc(var(--ring-radius) - calc(var(--stroke-thickness) / 2));
383
+ fill: hsla(0, 0%, 0%, 0);
384
+ stroke-width: var(--stroke-thickness);
385
+ }
386
+
387
+ #mocha-stats .ring-flatlight {
388
+ stroke: var(--mocha-progress-ring-color);
389
+ }
390
+
391
+ #mocha-stats .ring-highlight {
392
+ stroke: var(--mocha-progress-ring-highlight-color);
351
393
  }
352
394
 
353
395
  #mocha-stats em {
@@ -370,11 +412,6 @@ body {
370
412
  padding-top: 11px;
371
413
  }
372
414
 
373
- #mocha-stats canvas {
374
- width: 40px;
375
- height: 40px;
376
- }
377
-
378
415
  #mocha code .comment { color: var(--mocha-code-comment); }
379
416
  #mocha code .init { color: var(--mocha-code-init); }
380
417
  #mocha code .string { color: var(--mocha-code-string); }