jest-html-reporter 3.10.2 โ†’ 4.0.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
@@ -3,13 +3,11 @@
3
3
  <h3 align="center">jest-html-reporter</h3>
4
4
  <p align="center">
5
5
  A <a href="https://github.com/facebook/jest">Jest</a> test results processor for generating a summary in HTML.
6
- <br>
7
- <a href="https://github.com/Hargne/jest-html-reporter/wiki"><strong>Documentation ยป</strong></a>
8
6
  <br />
9
7
  <br />
10
8
  <img src="https://img.shields.io/npm/v/jest-html-reporter?style=flat-square">
9
+ <img src="https://img.shields.io/node/v/jest-html-reporter?style=flat-square">
11
10
  <img src="https://img.shields.io/npm/dm/jest-html-reporter?style=flat-square">
12
- <img src="https://img.shields.io/github/actions/workflow/status/Hargne/jest-html-reporter/main.yml?branch=master&style=flat-square">
13
11
  <br />
14
12
  <br />
15
13
  <small>Inspired by <a href="https://github.com/matthias-schuetz/karma-htmlfile-reporter">karma-htmlfile-reporter</a></small>
@@ -52,7 +50,7 @@ Configure Jest to process the test results by adding the following entry to the
52
50
 
53
51
  As you run Jest from within the terminal, a file called _test-report.html_ will be created within your root folder containing information about your tests.
54
52
 
55
- There are multiple configuration options available. To read more about these, please refer to the [documentation](https://github.com/Hargne/jest-html-reporter/wiki/configuration).
53
+ There are multiple configuration options available. Read more about these further down in this document.
56
54
 
57
55
  #### Alternative Usage as a Test Results Processor
58
56
 
@@ -64,47 +62,57 @@ To run the reporter as a test results processor (after Jest is complete instead
64
62
  }
65
63
  ```
66
64
 
67
- **Note:** When running as a testResultsProcessor, the configuration needs be placed within a new file named `jesthtmlreporter.config.json` residing in the root folder.
68
- More information about this can be found in the [documentation](https://github.com/Hargne/jest-html-reporter/wiki/configuration).
65
+ **Note:** When running as a testResultsProcessor, the configuration needs either to be placed within a new file named `jesthtmlreporter.config.json` residing in the root folder
69
66
 
70
- ### Node Compatibility
71
-
72
- <img src="https://img.shields.io/node/v/jest-html-reporter?style=flat-square">
67
+ ```JSON
68
+ {
69
+ "pageTitle": "Test Report",
70
+ }
71
+ ```
73
72
 
74
- This plugin is compatible with Node version `^4.8.3`
73
+ or via adding a key to `package.json` named "jest-html-reporter":
75
74
 
76
- ## Configuration
75
+ ```JSON
76
+ {
77
+ ...
78
+ "jest-html-reporter": {
79
+ "pageTitle": "Test Report",
80
+ }
81
+ }
82
+ ```
77
83
 
78
- Please note that all configuration properties are optional.
84
+ ## ๐Ÿ“Œ Configuration Options (All Optional)
85
+
86
+ | Option | Type | Default | Description |
87
+ | ----------------------------------- | --------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88
+ | **`append`** | `boolean` | `false` | Append test results to an existing report. |
89
+ | **`boilerplate`** | `string` | `null` | Path to an HTML boilerplate file. The `{jesthtmlreporter-content}` variable will be replaced with test results. |
90
+ | **`collapseSuitesByDefault`** | `boolean` | `false` | Collapse test suites (accordions) by default. |
91
+ | **`customScriptPath`** | `string` | `null` | Path to an external script file injected into the report. |
92
+ | **`dateFormat`** | `string` | `yyyy-mm-dd HH:MM:ss` | Date format for timestamps. See [documentation](https://github.com/Hargne/jest-html-reporter/wiki/Date-Format) for available formats. |
93
+ | **`executionTimeWarningThreshold`** | `number` | `5` | Warn if a test suite exceeds this execution time (in seconds). |
94
+ | **`includeConsoleLog`** | `boolean` | `false` | Include `console.log` outputs in the report (**requires** `--verbose=false`). |
95
+ | **`includeFailureMsg`** | `boolean` | `false` | Show detailed error messages for failed tests. |
96
+ | **`includeStackTrace`** | `boolean` | `true` | Show stack traces for failed tests. |
97
+ | **`includeSuiteFailure`** | `boolean` | `false` | Show detailed errors for entire failed test suites. |
98
+ | **`includeObsoleteSnapshots`** | `boolean` | `false` | Show obsolete snapshot names. |
99
+ | **`logo`** | `string` | `null` | Path to an image file to display in the report header. |
100
+ | **`outputPath`** | `string` | `./test-report.html` | Full path for the output report file (**must end in `.html`**). |
101
+ | **`pageTitle`** | `string` | `"Test Report"` | Title of the document and top-level heading. |
102
+ | **`sort`** | `string` | `null` | Sort test results by a specific method. Available values:<br> โžค **`status`** โ†’ Sorts by test status (**pending โ†’ failed โ†’ passed**).<br> โžค **`status:{custom-order}`** โ†’ Custom status order (e.g., `"status:failed,passed,pending"`).<br> โžค **`executionasc`** โ†’ Sorts by execution time **ascending**.<br> โžค **`executiondesc`** โ†’ Sorts by execution time **descending**.<br> โžค **`titleasc`** โ†’ Sorts by suite filename/test name **ascending**.<br> โžค **`titledesc`** โ†’ Sorts by suite filename/test name **descending**. |
103
+ | **`statusIgnoreFilter`** | `string` | `null` | **Comma-separated** list of statuses to exclude: `"passed"`, `"pending"`, `"failed"`. |
104
+ | **`styleOverridePath`** | `string` | `null` | Path to a CSS file to override default styles. |
105
+ | **`useCssFile`** | `boolean` | `false` | Link to the CSS file instead of inlining styles. |
79
106
 
80
- | Property | Type | Description | Default |
81
- | ------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
82
- | `append` | `BOOLEAN` | If set to true, new test results will be appended to the existing test report | `false` |
83
- | `boilerplate` | `STRING` | The path to a boilerplate file that should be used to render the body of the test results into. `{jesthtmlreporter-content}` within the boilerplate will be replaced with the test results | `null` |
84
- | `collapseSuitesByDefault` | `BOOLEAN` | Whether to collapse test suites by default or not | `false` |
85
- | `customScriptPath` | `STRING` | Path to a javascript file that should be injected into the test report | `null` |
86
- | `dateFormat` | `STRING` | The format in which date/time should be formatted in the test report. Have a look in the [documentation](https://github.com/Hargne/jest-html-reporter/wiki/Date-Format) for the available date format variables. | `"yyyy-mm-dd HH:MM:ss"` |
87
- | `executionTimeWarningThreshold` | `NUMBER` | The threshold for test execution time (in seconds) in each test suite that will render a warning on the report page. 5 seconds is the default timeout in Jest. | `5` |
88
- | `includeConsoleLog` | `BOOLEAN` | If set to true, this will output all triggered console logs for each test suite. Please note that you have to run Jest together with `--verbose=false` in order to have Jest catch any logs during the tests. | `false` |
89
- | `includeFailureMsg` | `BOOLEAN` | If this setting is set to true, this will output the detailed failure message for each failed test. | `false` |
90
- | `includeStackTrace` | `BOOLEAN` | Turning this option off will cut the stack trace from the failure messages. | `true` |
91
- | `includeSuiteFailure` | `BOOLEAN` | If set to true, this will output the detailed failure message for complete suite failures. | `false` |
92
- | `includeObsoleteSnapshots` | `BOOLEAN` | If set to true, this will output obsolete snapshot names. | `false` |
93
- | `logo` | `STRING` | Path to a logo that will be included in the header of the report | `null` |
94
- | `outputPath` | `STRING` | The path to where the plugin will output the HTML report. The path must include the filename and end with .html | `"./test-report.html"` |
95
- | `pageTitle` | `STRING` | The title of the document. This string will also be outputted on the top of the page. | `"Test Suite"` |
96
- | `sort` | `STRING` | Sorts the test results using the given method. Available sorting methods can be found in the [documentation](https://github.com/Hargne/jest-html-reporter/wiki/Sorting-Methods). | `"default"` |
97
- | `statusIgnoreFilter` | `STRING` | A comma-separated string of the test result statuses that should be ignored when rendering the report. Available statuses are: `"passed"`, `"pending"`, `"failed"` | `null` |
98
- | `styleOverridePath` | `STRING` | The path to a file containing CSS styles that should override the default styling.\* | `null` |
99
- | `useCssFile` | `BOOLEAN` | If set to true, the CSS styles will link in the current theme's .css file instead of inlining its content on the page | `false` |
107
+ ## Continuous Integration
100
108
 
101
- > *The plugin will search for the *styleOverridePath\* from the root directory, therefore there is no need to prepend the string with `./` or `../` - You can read more about the themes in the [documentation](https://github.com/Hargne/jest-html-reporter/wiki/Test-Report-Themes).
109
+ All the configuration options provided in the table above are available via environment variables and follows the pattern of snake case in uppercase prepended with `JEST_HTML_REPORTER_`
102
110
 
103
- ## Continuous Integration
111
+ **Example:** `customScriptPath` -> `JEST_HTML_REPORTER_CUSTOM_SCRIPT_PATH`
104
112
 
105
- Configuration may also be performed with environment variables for dynamic file saving paths in different environments. **\*NOTE:** Environment variables will take precedence over configurations set in jesthtmlreporter.config.json and package.json\*
113
+ **\*NOTE:** Environment variables will take precedence over configurations set in jesthtmlreporter.config.json and package.json\*
106
114
 
107
- #### Example
115
+ ### CI Example
108
116
 
109
117
  Here is an example of dynamically naming your output file and test report title to match your current branch that one might see in a automated deployment pipeline before running their tests.
110
118
 
@@ -113,7 +121,3 @@ export BRANCH_NAME=`git symbolic-ref HEAD 2>/dev/null | cut -d"/" -f 3`
113
121
  export JEST_HTML_REPORTER_OUTPUT_PATH=/home/username/jest-test-output/test-reports/"$BRANCH_NAME".html
114
122
  export JEST_HTML_REPORTER_PAGE_TITLE="$BRANCH_NAME"\ Test\ Report
115
123
  ```
116
-
117
- #### Configuration Environment Variables
118
-
119
- The environment variables reflect the configuration options available in JSON format. Please read the [documentation](https://github.com/Hargne/jest-html-reporter/wiki/configuration#configuration-environment-variables) for more information on these variables.
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("dateformat"),t=require("fs"),s=require("mkdirp"),a=require("path"),i=require("strip-ansi"),l=require("xmlbuilder");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e),r=n(t),u=n(s),c=n(a),d=n(i),g=n(l);const h=e=>{const t=[],s=[],a=[];return e.forEach((e=>{const i=[],l=[],n=[];e.testResults.forEach((e=>{"pending"===e.status?i.push(e):"failed"===e.status?l.push(e):n.push(e)})),i.length>0&&t.push({...e,testResults:i}),l.length>0&&s.push({...e,testResults:l}),n.length>0&&a.push({...e,testResults:n})})),[].concat(t,s,a)},f=e=>(e&&e.sort(((e,t)=>t.perfStats.end-t.perfStats.start-(e.perfStats.end-e.perfStats.start))),e),p=e=>(e&&e.sort(((e,t)=>e.perfStats.end-e.perfStats.start-(t.perfStats.end-t.perfStats.start))),e),T=e=>{if(e){const t=e.sort(((e,t)=>E(e.testFilePath,t.testFilePath,!0)));return t.forEach((e=>{e.testResults.sort(((e,t)=>E(e.ancestorTitles.join(" "),t.ancestorTitles.join(" "),!0))),e.testResults.sort(((e,t)=>E(e.title,t.title,!0)))})),t}return e},m=e=>{if(e){const t=e.sort(((e,t)=>E(e.testFilePath,t.testFilePath)));return t.forEach((e=>{e.testResults.sort(((e,t)=>E(e.ancestorTitles.join(" "),t.ancestorTitles.join(" ")))),e.testResults.sort(((e,t)=>E(e.title,t.title)))})),t}return e},E=(e,t,s=!1)=>!s&&e<t||s&&e>t?-1:!s&&e>t||s&&e<t?1:0;class S{constructor(e){this.testData=e.testData,this.jestConfig=e.jestConfig,this.consoleLogList=e.consoleLogs,this.setupConfig(e.options)}async generate(){try{const e=await this.renderTestReport(),t=this.replaceRootDirInPath(this.jestConfig?this.jestConfig.rootDir:"",this.getConfigValue("outputPath"));await u.default(c.default.dirname(t));let s=!0;if(this.getConfigValue("append")){r.default.existsSync(t)&&(await this.appendToFile(t,e.content.toString()),s=!1)}return s&&r.default.writeFileSync(t,e.fullHtml.toString()),this.logMessage("success",`Report generated (${t})`),e.fullHtml}catch(e){this.logMessage("error",e)}}async renderTestReport(){const e=await this.renderTestReportContent();if(this.getConfigValue("boilerplate")){const t=this.replaceRootDirInPath(this.jestConfig?this.jestConfig.rootDir:"",this.getConfigValue("boilerplate")),s=r.default.readFileSync(t,"utf8");return{content:e.toString(),fullHtml:s.replace("{jesthtmlreporter-content}",e&&e.toString())}}const t=g.default.create({html:{}}),s=t.ele("head");s.ele("meta",{charset:"utf-8"}),s.ele("title",{},this.getConfigValue("pageTitle"));let a=c.default.join(__dirname,`../style/${this.getConfigValue("theme")}.css`);this.getConfigValue("styleOverridePath")&&(a=this.getConfigValue("styleOverridePath"));if(!this.getConfigValue("useCssFile")&&!this.getConfigValue("styleOverridePath")){const e=r.default.readFileSync(a,"utf8");s.raw(`<style type="text/css">${e}</style>`)}else s.ele("link",{rel:"stylesheet",type:"text/css",href:a});const i=t.ele("body");return e&&i.raw(e.toString()),this.getConfigValue("customScriptPath")&&i.raw(`<script src="${this.getConfigValue("customScriptPath")}"><\/script>`),{fullHtml:t.toString(),content:e.toString()}}renderTestSuiteHeader(e,t,s){const a=`collapsible-${s}`;e.ele("input",{id:a,type:"checkbox",class:"toggle",checked:this.getConfigValue("collapseSuitesByDefault")?null:"checked"});const i=e.ele("label",{for:a}).ele("div",{class:"suite-info"});i.ele("div",{class:"suite-path"},t.testFilePath);const l=(t.perfStats.end-t.perfStats.start)/1e3,n=["suite-time"];l>this.getConfigValue("executionTimeWarningThreshold")&&n.push("warn"),i.ele("div",{class:n.join(" ")},`${l}s`)}async renderTestReportContent(){try{if(!this.testData||0===Object.entries(this.testData).length)throw Error("No test data provided");const e=g.default.begin().element("div",{class:"jesthtml-content"}),t=e.ele("header");t.ele("h1",{id:"title"},this.getConfigValue("pageTitle"));const s=this.getConfigValue("logo");s&&t.ele("img",{id:"logo",src:s});const a=e.ele("div",{id:"metadata-container"});if(this.testData.startTime&&!isNaN(this.testData.startTime)){const e=new Date(this.testData.startTime);if(e){const t=o.default(e,this.getConfigValue("dateFormat"));a.ele("div",{id:"timestamp"},`Started: ${t}`)}}const i=a.ele("div",{id:"summary"}),l=i.ele("div",{id:"suite-summary"}),n=(e,t)=>[`summary-${e}`,t>0?"":" summary-empty"].join(" ");l.ele("div",{class:"summary-total"},`Suites (${this.testData.numTotalTestSuites})`),l.ele("div",{class:n("passed",this.testData.numPassedTestSuites)},`${this.testData.numPassedTestSuites} passed`),l.ele("div",{class:n("failed",this.testData.numFailedTestSuites)},`${this.testData.numFailedTestSuites} failed`),l.ele("div",{class:n("pending",this.testData.numPendingTestSuites)},`${this.testData.numPendingTestSuites} pending`),this.testData.snapshot&&this.testData.snapshot.unchecked>0&&this.getConfigValue("includeObsoleteSnapshots")&&l.ele("div",{class:"summary-obsolete-snapshots"},`${this.testData.snapshot.unchecked} obsolete snapshots`);const r=i.ele("div",{id:"test-summary"});r.ele("div",{class:"summary-total"},`Tests (${this.testData.numTotalTests})`),r.ele("div",{class:n("passed",this.testData.numPassedTests)},`${this.testData.numPassedTests} passed`),r.ele("div",{class:n("failed",this.testData.numFailedTests)},`${this.testData.numFailedTests} failed`),r.ele("div",{class:n("pending",this.testData.numPendingTests)},`${this.testData.numPendingTests} pending`);const u=((e,t)=>{switch(t&&t.toLowerCase()){case"status":return h(e);case"executiondesc":return f(e);case"executionasc":return p(e);case"titledesc":return T(e);case"titleasc":return m(e);default:return e}})(this.testData.testResults,this.getConfigValue("sort")),c=this.getConfigValue("statusIgnoreFilter");let d=[];return c&&(d=c.replace(/\s/g,"").toLowerCase().split(",")),u&&u.forEach(((t,s)=>{const a=e.ele("div",{id:`suite-${s+1}`,class:"suite-container"});this.renderTestSuiteHeader(a,t,s);const i=a.ele("div",{class:"suite-tests"});if(!t.testResults||t.testResults.length<=0){if(t.failureMessage&&this.getConfigValue("includeSuiteFailure")){i.ele("div",{class:"test-result failed"}).ele("div",{class:"failureMessages suiteFailure"}," ").ele("pre",{class:"failureMsg"},this.sanitizeOutput(t.failureMessage))}}else t.testResults.filter((e=>!d.includes(e.status))).forEach((async e=>{const t=i.ele("div",{class:`test-result ${e.status}`}),s=t.ele("div",{class:"test-info"});if(s.ele("div",{class:"test-suitename"},e.ancestorTitles&&e.ancestorTitles.length>0?e.ancestorTitles.join(" > "):" "),s.ele("div",{class:"test-title"},e.title),s.ele("div",{class:"test-status"},e.status),s.ele("div",{class:"test-duration"},e.duration/1e3+"s"),e.failureMessages&&e.failureMessages.length>0&&this.getConfigValue("includeFailureMsg")){const s=t.ele("div",{class:"failureMessages"}," ");e.failureMessages.map((e=>this.getConfigValue("includeStackTrace")?e:e.split(/\n\s+at/)[0].trim().replace(/\n+$/,""))).forEach((e=>{s.ele("pre",{class:"failureMsg"},this.sanitizeOutput(e))}))}})),this.consoleLogList&&this.consoleLogList.length>0&&this.getConfigValue("includeConsoleLog")&&this.renderSuiteConsoleLogs(t,a),t.snapshot&&t.snapshot.unchecked>0&&this.getConfigValue("includeObsoleteSnapshots")&&this.renderSuiteObsoleteSnapshots(a,t)})),e}catch(e){this.logMessage("error",e)}}renderSuiteConsoleLogs(e,t){const s=this.consoleLogList.find((t=>t.filePath===e.testFilePath));if(s&&s.logs.length>0){const e=t.ele("div",{class:"suite-consolelog"});e.ele("div",{class:"suite-consolelog-header"},"Console Log"),s.logs.forEach((t=>{const s=e.ele("div",{class:"suite-consolelog-item"});s.ele("pre",{class:"suite-consolelog-item-origin"},this.sanitizeOutput(t.origin)),s.ele("pre",{class:"suite-consolelog-item-message"},this.sanitizeOutput(t.message))}))}}renderSuiteObsoleteSnapshots(e,t){const s=e.ele("div",{class:"suite-obsolete-snapshots"});s.ele("div",{class:"suite-obsolete-snapshots-header"},"Obsolete snapshots");s.ele("div",{class:"suite-obsolete-snapshots-item"}).ele("pre",{class:"suite-obsolete-snapshots-item-message"},t.snapshot.uncheckedKeys.join("\n"))}setupConfig(e){const{append:t,boilerplate:s,collapseSuitesByDefault:a,customScriptPath:i,dateFormat:l,executionTimeWarningThreshold:n,logo:o,includeConsoleLog:u,includeFailureMsg:d,includeStackTrace:g,includeSuiteFailure:h,includeObsoleteSnapshots:f,outputPath:p,pageTitle:T,theme:m,sort:E,statusIgnoreFilter:S,styleOverridePath:R,useCssFile:V}=e||{};this.config={append:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_APPEND",configValue:t},boilerplate:{defaultValue:null,environmentVariable:"JEST_HTML_REPORTER_BOILERPLATE",configValue:s},collapseSuitesByDefault:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_COLLAPSE_SUITES_BY_DEFAULT",configValue:a},customScriptPath:{defaultValue:null,environmentVariable:"JEST_HTML_REPORTER_CUSTOM_SCRIPT_PATH",configValue:i},dateFormat:{defaultValue:"yyyy-mm-dd HH:MM:ss",environmentVariable:"JEST_HTML_REPORTER_DATE_FORMAT",configValue:l},executionTimeWarningThreshold:{defaultValue:5,environmentVariable:"JEST_HTML_REPORTER_EXECUTION_TIME_WARNING_THRESHOLD",configValue:n},logo:{defaultValue:null,environmentVariable:"JEST_HTML_REPORTER_LOGO",configValue:o},includeFailureMsg:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_INCLUDE_FAILURE_MSG",configValue:d},includeStackTrace:{defaultValue:!0,environmentVariable:"JEST_HTML_REPORTER_INCLUDE_STACK_TRACE",configValue:g},includeSuiteFailure:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_INCLUDE_SUITE_FAILURE",configValue:h},includeObsoleteSnapshots:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_INCLUDE_OBSOLETE_SNAPSHOTS",configValue:f},includeConsoleLog:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_INCLUDE_CONSOLE_LOG",configValue:u},outputPath:{defaultValue:c.default.join(process.cwd(),"test-report.html"),environmentVariable:"JEST_HTML_REPORTER_OUTPUT_PATH",configValue:p},pageTitle:{defaultValue:"Test Report",environmentVariable:"JEST_HTML_REPORTER_PAGE_TITLE",configValue:T},theme:{defaultValue:"defaultTheme",environmentVariable:"JEST_HTML_REPORTER_THEME",configValue:m},sort:{defaultValue:null,environmentVariable:"JEST_HTML_REPORTER_SORT",configValue:E},statusIgnoreFilter:{defaultValue:null,environmentVariable:"JEST_HTML_REPORTER_STATUS_FILTER",configValue:S},styleOverridePath:{defaultValue:null,environmentVariable:"JEST_HTML_REPORTER_STYLE_OVERRIDE_PATH",configValue:R},useCssFile:{defaultValue:!1,environmentVariable:"JEST_HTML_REPORTER_USE_CSS_FILE",configValue:V}};try{const e=r.default.readFileSync(c.default.join(process.cwd(),"jesthtmlreporter.config.json"),"utf8");if(e){const t=JSON.parse(e);for(const e of Object.keys(t))e in this.config&&(this.config[e].configValue=t[e]);return this.config}}catch(e){}try{const e=r.default.readFileSync(c.default.join(process.cwd(),"package.json"),"utf8");if(e){const t=JSON.parse(e)["jest-html-reporter"];for(const e of Object.keys(t))e in this.config&&(this.config[e].configValue=t[e]);return this.config}}catch(e){}}getConfigValue(e){const t=this.config[e];if(t)return process.env[t.environmentVariable]?process.env[t.environmentVariable]:t.configValue??t.defaultValue}async appendToFile(e,t){let s=t;const a=r.default.readFileSync(e,"utf8"),i=/<body>(.*?)<\/body>/gm.exec(t);if(i){const[e]=i;s=e}let l=a;const n=/<\/body>/gm.exec(a),o=n?n.index:0;return l=[a.slice(0,o),s,a.slice(o)].join(""),r.default.writeFileSync(e,l)}replaceRootDirInPath(e,t){return/^<rootDir>/.test(t)?c.default.resolve(e,c.default.normalize("./"+t.substring(9))):t}logMessage(e,t){const s={default:"%s",success:"%s",error:"%s"},a=s[e]?s[e]:s.default,i=`jest-html-reporter >> ${t}`;return void 0===process.env.JEST_WORKER_ID&&console.log(a,i),{logColor:a,logMsg:i}}sanitizeOutput(e){return d.default(e.replace(/(\x1b\[\d*m)/g,"").replace(/([^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFC\u{10000}-\u{10FFFF}])/gu,""))}}const R=e=>new S(e).generate();module.exports=function(e,t){const s=[];if(Object.prototype.hasOwnProperty.call(e,"testResults")){const s=e;return R({testData:s,options:t}),s}this.onTestResult=(e,t)=>{t.console&&s.push({filePath:t.testFilePath,logs:t.console})},this.onRunComplete=(a,i)=>R({testData:i,options:t,jestConfig:e,consoleLogs:s})};
1
+ "use strict";var e=require("dateformat"),t=require("fs"),s=require("mkdirp"),i=require("path"),n=require("xmlbuilder"),o=require("strip-ansi");function a(e,t){const s={default:"%s",success:"%s",error:"%s"},i=s[e]?s[e]:s.default,n=`jest-html-reporter >> ${t}`;return void 0===process.env.JEST_WORKER_ID&&console.log(i,n),{logColor:i,logMsg:n}}function r(e){return a("error",e instanceof Error?e.message:"An error occurred")}function l(e){if("string"!=typeof e)throw new TypeError("Input must be a string");return o(e).replace(/[^\t\n\r\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu,"")}function u(e,t){if("string"!=typeof e||"string"!=typeof t)throw new TypeError("Both rootDir and filePath must be strings.");return t.startsWith("<rootDir>")?i.resolve(e,i.normalize(`./${t.slice(9)}`)):t}function c(e,t,s=!1){return!s&&e<t||s&&e>t?-1:!s&&e>t||s&&e<t?1:0}function d(e){return"boolean"==typeof e?e:"string"==typeof e&&"true"===e}function h(e){if("string"==typeof e)return e}const g=["pending","failed","passed"];const p=(e,t)=>{const s={pending:[],failed:[],passed:[]},i=[];e.forEach((e=>{const t=[],n=[],o=[],a=[];e.testResults.forEach((e=>{"pending"===e.status?t.push(e):"failed"===e.status?n.push(e):"passed"===e.status?o.push(e):a.push(e)})),t.length>0&&s.pending.push({...e,testResults:t}),n.length>0&&s.failed.push({...e,testResults:n}),o.length>0&&s.passed.push({...e,testResults:o}),a.length>0&&i.push({...e,testResults:a})}));const n=[...t||[],...["pending","failed","passed"].filter((e=>!(t||[]).includes(e)))],o=Object.entries(s).sort((([e],[t])=>n.indexOf(e)-n.indexOf(t))).flatMap((([,e])=>e));return[...o,...i]},f=e=>(e&&e.sort(((e,t)=>t.perfStats.end-t.perfStats.start-(e.perfStats.end-e.perfStats.start))),e),m=e=>(e&&e.sort(((e,t)=>e.perfStats.end-e.perfStats.start-(t.perfStats.end-t.perfStats.start))),e),y=e=>{if(e){const t=e.sort(((e,t)=>c(e.testFilePath,t.testFilePath,!0)));return t.forEach((e=>{e.testResults.sort(((e,t)=>c(e.ancestorTitles.join(" "),t.ancestorTitles.join(" "),!0))),e.testResults.sort(((e,t)=>c(e.title,t.title,!0)))})),t}return e},S=e=>{if(e){const t=e.sort(((e,t)=>c(e.testFilePath,t.testFilePath)));return t.forEach((e=>{e.testResults.sort(((e,t)=>c(e.ancestorTitles.join(" "),t.ancestorTitles.join(" ")))),e.testResults.sort(((e,t)=>c(e.title,t.title)))})),t}return e},T={append:!1,boilerplate:void 0,collapseSuitesByDefault:!1,customScriptPath:void 0,dateFormat:"yyyy-mm-dd HH:MM:ss",executionTimeWarningThreshold:5,includeConsoleLog:!1,includeFailureMsg:!1,includeStackTrace:!0,includeSuiteFailure:!1,includeObsoleteSnapshots:!1,logo:void 0,outputPath:i.join(process.cwd(),"test-report.html"),pageTitle:"Test Report",sort:void 0,statusIgnoreFilter:void 0,styleOverridePath:void 0,theme:"defaultTheme",useCssFile:!1};function v(e){try{if(t.existsSync(e))return JSON.parse(t.readFileSync(e,"utf8"))}catch{return{}}return{}}const C={append:d,boilerplate:h,collapseSuitesByDefault:d,customScriptPath:h,dateFormat:h,executionTimeWarningThreshold:function(e){return"number"==typeof e?e:"string"==typeof e?Number(e):NaN},includeConsoleLog:d,includeFailureMsg:d,includeStackTrace:d,includeSuiteFailure:d,includeObsoleteSnapshots:d,logo:h,outputPath:h,pageTitle:h,sort:h,statusIgnoreFilter:h,styleOverridePath:h,theme:h,useCssFile:d};function F(e){return"object"!=typeof e||null===e?{}:Object.keys(T).reduce(((t,s)=>{const i=C[s];if(i&&"function"==typeof i&&s in e){const n=i(e[s]);if(void 0!==n)return{...t,[s]:n}}return t}),{})}class D{testData;consoleLogList;jestConfig;config;constructor(e){this.testData=e.testData,this.jestConfig=e.jestConfig,this.consoleLogList=e.consoleLogs||[],this.config=function(e){const t=Object.keys(T).reduce(((e,t)=>{const s=function(e){return`JEST_HTML_REPORTER_${e.replace(/([a-z])([A-Z])/g,"$1_$2").toUpperCase()}`}(t);return void 0!==process.env[s]?{...e,[t]:process.env[s]}:e}),{}),s=v(i.join(process.cwd(),"jesthtmlreporter.config.json")),n=v(i.join(process.cwd(),"package.json"))["jest-html-reporter"]||{};return{...T,...F(n),...F(s),...F(e),...F(t)}}(e.options)}async generate(){try{const e=await this.renderTestReport(),n=u(this.jestConfig?this.jestConfig.rootDir:"",this.getConfigValue("outputPath"));await s(i.dirname(n));let o=!0;if(this.getConfigValue("append")){t.existsSync(n)&&(await async function(e,s){try{await t.promises.access(e);const i=await t.promises.readFile(e,"utf8"),n=/<body>(.*?)<\/body>/s.exec(s),o=n?n[1]:s,a=/<\/body>/i.exec(i),r=a?a.index:0,l=[i.slice(0,r),o,i.slice(r)].join("");await t.promises.writeFile(e,l,"utf8")}catch(e){let t="An unknown error occurred while appending to the file";throw e instanceof Error&&(t=e.message),new Error(t)}}(n,e.content.toString()),o=!1)}return o&&t.writeFileSync(n,e.fullHtml.toString()),a("success",`Report generated (${n})`),e.fullHtml}catch(e){r(e)}}async renderTestReport(){const e=await this.renderTestReportContent();if(this.getConfigValue("boilerplate")){const s=u(this.jestConfig?this.jestConfig.rootDir:"",this.getConfigValue("boilerplate")),i=t.readFileSync(s,"utf8"),n=e?e.toString():"";return{content:n,fullHtml:i.replace("{jesthtmlreporter-content}",n)}}const s=n.create({html:{}}),o=s.ele("head");o.ele("meta",{charset:"utf-8"}),o.ele("title",{},this.getConfigValue("pageTitle"));let a=i.join(__dirname,`../style/${this.getConfigValue("theme")}.css`);this.getConfigValue("styleOverridePath")&&(a=this.getConfigValue("styleOverridePath"));if(!this.getConfigValue("useCssFile")&&!this.getConfigValue("styleOverridePath")){const e=t.readFileSync(a,"utf8");o.raw(`<style type="text/css">${e}</style>`)}else o.ele("link",{rel:"stylesheet",type:"text/css",href:a});const r=s.ele("body");return e&&r.raw(e.toString()),this.getConfigValue("customScriptPath")&&r.raw(`<script src="${this.getConfigValue("customScriptPath")}"><\/script>`),{fullHtml:s.toString(),content:e?e.toString():""}}renderTestSuiteHeader(e,t){const s=e.ele("summary",{class:"suite-info"});s.ele("div",{class:"suite-path"},t.testFilePath);const i=(t.perfStats.end-t.perfStats.start)/1e3,n=["suite-time"];i>this.getConfigValue("executionTimeWarningThreshold")&&n.push("warn"),s.ele("div",{class:n.join(" ")},`${i}s`)}async renderTestReportContent(){try{if(!this.testData||0===Object.entries(this.testData).length)throw Error("No test data provided");const t=n.begin().element("main",{class:"jesthtml-content"}),s=t.ele("header");s.ele("h1",{id:"title"},this.getConfigValue("pageTitle"));const i=this.getConfigValue("logo");i&&s.ele("img",{id:"logo",src:i});const o=t.ele("section",{id:"metadata-container"});if(this.testData.startTime&&!isNaN(this.testData.startTime)){const t=new Date(this.testData.startTime);if(t){const s=e(t,this.getConfigValue("dateFormat"));o.ele("div",{id:"timestamp"},`Started: ${s}`)}}const a=o.ele("div",{id:"summary"}),r=a.ele("div",{id:"suite-summary"}),u=(e,t)=>[`summary-${e}`,t>0?"":" summary-empty"].join(" ");r.ele("div",{class:"summary-total"},`Suites (${this.testData.numTotalTestSuites})`),r.ele("div",{class:u("passed",this.testData.numPassedTestSuites)},`${this.testData.numPassedTestSuites} passed`),r.ele("div",{class:u("failed",this.testData.numFailedTestSuites)},`${this.testData.numFailedTestSuites} failed`),r.ele("div",{class:u("pending",this.testData.numPendingTestSuites)},`${this.testData.numPendingTestSuites} pending`),this.testData.snapshot&&this.testData.snapshot.unchecked>0&&this.getConfigValue("includeObsoleteSnapshots")&&r.ele("div",{class:"summary-obsolete-snapshots"},`${this.testData.snapshot.unchecked} obsolete snapshots`);const c=a.ele("div",{id:"test-summary"});c.ele("div",{class:"summary-total"},`Tests (${this.testData.numTotalTests})`),c.ele("div",{class:u("passed",this.testData.numPassedTests)},`${this.testData.numPassedTests} passed`),c.ele("div",{class:u("failed",this.testData.numFailedTests)},`${this.testData.numFailedTests} failed`),c.ele("div",{class:u("pending",this.testData.numPendingTests)},`${this.testData.numPendingTests} pending`);const d=((e,t)=>{const{sortType:s,params:i}=function(e){if(!e)return{sortType:null};const[t,s]=e.split(":").map((e=>e.trim())),i=s?.split(",").map((e=>e.trim()));if(!["status","executiondesc","executionasc","titledesc","titleasc"].includes(t.toLowerCase()))return{sortType:null};return{sortType:t.toLowerCase(),params:i}}(t);switch(s){case"status":{const t=i?.every((e=>g.includes(e)))?i:void 0;return p(e,t)}case"executiondesc":return f(e);case"executionasc":return m(e);case"titledesc":return y(e);case"titleasc":return S(e);default:return e}})(this.testData.testResults,this.getConfigValue("sort")),h=this.getConfigValue("statusIgnoreFilter");let T=[];return h&&(T=h.replace(/\s/g,"").toLowerCase().split(",")),d&&d.forEach(((e,s)=>{const i=t.ele("details",{id:`suite-${s+1}`,class:"suite-container",open:this.getConfigValue("collapseSuitesByDefault")?void 0:""});this.renderTestSuiteHeader(i,e);const n=i.ele("div",{class:"suite-tests"});if(!e.testResults||e.testResults.length<=0){if(e.failureMessage&&this.getConfigValue("includeSuiteFailure")){n.ele("div",{class:"test-result failed"}).ele("div",{class:"failureMessages suiteFailure"}," ").ele("pre",{class:"failureMsg"},l(e.failureMessage))}}else e.testResults.filter((e=>!T.includes(e.status))).forEach((async e=>{const t=n.ele("div",{class:`test-result ${e.status}`}),s=t.ele("div",{class:"test-info"});if(s.ele("div",{class:"test-suitename"},e.ancestorTitles&&e.ancestorTitles.length>0?e.ancestorTitles.join(" > "):" "),s.ele("div",{class:"test-title"},e.title),s.ele("div",{class:"test-status"},e.status),s.ele("div",{class:"test-duration"},e.duration?e.duration/1e3+"s":" "),e.failureMessages&&e.failureMessages.length>0&&this.getConfigValue("includeFailureMsg")){const s=t.ele("div",{class:"failureMessages"}," ");e.failureMessages.map((e=>this.getConfigValue("includeStackTrace")?e:e.split(/\n\s+at/)[0].trim().replace(/\n+$/,""))).forEach((e=>{s.ele("pre",{class:"failureMsg"},l(e))}))}})),this.consoleLogList&&this.consoleLogList.length>0&&this.getConfigValue("includeConsoleLog")&&this.renderSuiteConsoleLogs(e,i),e.snapshot&&e.snapshot.unchecked>0&&this.getConfigValue("includeObsoleteSnapshots")&&this.renderSuiteObsoleteSnapshots(i,e)})),t}catch(e){r(e)}}renderSuiteConsoleLogs(e,t){const s=this.consoleLogList.find((t=>t.filePath===e.testFilePath));if(s&&s.logs.length>0){const e=t.ele("div",{class:"suite-consolelog"});e.ele("div",{class:"suite-consolelog-header"},"Console Log"),s.logs.forEach((t=>{const s=e.ele("div",{class:"suite-consolelog-item"});s.ele("pre",{class:"suite-consolelog-item-origin"},l(t.origin)),s.ele("pre",{class:"suite-consolelog-item-message"},l(t.message))}))}}renderSuiteObsoleteSnapshots(e,t){const s=e.ele("div",{class:"suite-obsolete-snapshots"});s.ele("div",{class:"suite-obsolete-snapshots-header"},"Obsolete snapshots");s.ele("div",{class:"suite-obsolete-snapshots-item"}).ele("pre",{class:"suite-obsolete-snapshots-item-message"},t.snapshot.uncheckedKeys.join("\n"))}getConfigValue(e){return this.config[e]}}const b=e=>new D(e).generate();module.exports=function(e,t){const s=[];if(Object.prototype.hasOwnProperty.call(e,"testResults")){const s=e;return b({testData:s,options:t}),s}this.onTestResult=(e,t)=>{t.console&&s.push({filePath:t.testFilePath,logs:t.console})},this.onRunComplete=(i,n)=>b({testData:n,options:t,jestConfig:e,consoleLogs:s})};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jest-html-reporter",
3
- "version": "3.10.2",
3
+ "version": "4.0.1",
4
4
  "description": "Jest test results processor for generating a summary in HTML",
5
5
  "main": "dist/index.js",
6
6
  "unpkg": "dist/index.js",
@@ -10,9 +10,10 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "tsc": "tsc",
13
- "lint": "tslint --project . -e '**/*.js' -e '**/*.json'",
13
+ "lint": "eslint src/",
14
14
  "jest": "jest --no-cache --config=jest.config.json",
15
- "test": "npm run lint && npm run bundle && npm run jest",
15
+ "test": "npm run lint && npm run jest",
16
+ "test:watch": "npm run jest -- --watch --no-coverage",
16
17
  "bundle": "rollup -c",
17
18
  "build": "npm run test && npm run bundle"
18
19
  },
@@ -34,41 +35,43 @@
34
35
  },
35
36
  "homepage": "https://github.com/Hargne/jest-html-reporter#readme",
36
37
  "engines": {
37
- "node": ">=4.8.3"
38
+ "node": ">=14.0.0"
38
39
  },
39
40
  "dependencies": {
40
- "mkdirp": "^1.0.3",
41
+ "@jest/reporters": "^29.7.0",
42
+ "@jest/test-result": "^29.7.0",
43
+ "@jest/types": "^29.6.3",
41
44
  "dateformat": "3.0.2",
45
+ "mkdirp": "^1.0.3",
42
46
  "strip-ansi": "6.0.1",
43
- "xmlbuilder": "15.0.0",
44
- "@jest/test-result": "^29.0.2",
45
- "@jest/types": "^29.0.2"
47
+ "xmlbuilder": "15.0.0"
46
48
  },
47
49
  "peerDependencies": {
48
50
  "jest": "19.x - 29.x",
49
51
  "typescript": "^3.7.x || ^4.3.x || ^5.x"
50
52
  },
51
53
  "devDependencies": {
52
- "@babel/core": "^7.9.0",
53
- "@babel/preset-env": "^7.8.7",
54
- "@babel/preset-typescript": "^7.8.3",
55
- "@jest/console": "^29.0.2",
56
- "@rollup/plugin-babel": "^5.3.0",
57
- "@rollup/plugin-node-resolve": "^7.1.1",
54
+ "@eslint/js": "^9.19.0",
55
+ "@jest/console": "^29.7.0",
56
+ "@rollup/plugin-commonjs": "^28.0.2",
57
+ "@rollup/plugin-node-resolve": "^16.0.0",
58
+ "@rollup/plugin-terser": "^0.4.4",
59
+ "@rollup/plugin-typescript": "^12.1.2",
58
60
  "@types/dateformat": "^3.0.X",
59
- "@types/jest": "^29.0.0",
61
+ "@types/jest": "^29.5.14",
60
62
  "@types/mkdirp": "1.0.2",
61
- "@types/node": "12.20.12",
63
+ "@types/node": "^22.13.0",
62
64
  "@types/sinon": "9.0.11",
63
- "babel-jest": "^29.0.2",
64
- "jest": "^29.0.2",
65
- "rollup": "2.47.0",
66
- "rollup-plugin-terser": "7.0.2",
65
+ "eslint": "^9.19.0",
66
+ "jest": "^29.7.0",
67
+ "jest-environment-jsdom": "^29.7.0",
68
+ "rollup": "^4.34.1",
67
69
  "sinon": "^9.0.1",
68
70
  "ts-jest": "^29.0.0",
69
- "tslint": "^5.12.0",
71
+ "tslib": "^2.8.1",
70
72
  "tslint-config-prettier": "^1.18.0",
71
- "typescript": "^4.3.x"
73
+ "typescript": "^5.7.3",
74
+ "typescript-eslint": "^8.22.0"
72
75
  },
73
76
  "browserslist": [
74
77
  "since 2017-06"
@@ -1,14 +1,27 @@
1
+ :root {
2
+ --text-primary: #111;
3
+ --text-secondary: #4F4F4F;
4
+ --success: #006633;
5
+ --success-bright: #80FFBF;
6
+ --danger: #CC071E;
7
+ --danger-bright: #FBDFE0;
8
+ --warning: #995C00;
9
+ --warning-bright: #FFEEA8;
10
+ --panel: #eee;
11
+ --border: #949494;
12
+ --disabled: #6B6B6B;
13
+ }
14
+
1
15
  html,
2
16
  body {
3
17
  font-family: Arial, Helvetica, sans-serif;
4
- font-size: 1rem;
18
+ font-size: 16px;
5
19
  margin: 0;
6
20
  padding: 0;
7
- color: #333;
21
+ color: var(--text-primary);
8
22
  }
9
23
  body {
10
24
  padding: 2rem 1rem;
11
- font-size: 0.85rem;
12
25
  }
13
26
  .jesthtml-content {
14
27
  margin: 0 auto;
@@ -26,21 +39,21 @@ header {
26
39
  height: 4rem;
27
40
  }
28
41
  #timestamp {
29
- color: #777;
42
+ color: var(--text-secondary);
30
43
  margin-top: 0.5rem;
31
44
  }
32
45
 
33
46
  /** SUMMARY */
34
47
  #summary {
35
- color: #333;
48
+ color: var(--text-primary);
36
49
  margin: 2rem 0;
37
50
  display: flex;
38
51
  font-family: monospace;
39
52
  font-size: 1rem;
40
53
  }
41
54
  #summary > div {
42
- margin-right: 2rem;
43
- background: #eee;
55
+ margin-right: 0.5rem;
56
+ background: var(--panel);
44
57
  padding: 1rem;
45
58
  min-width: 15rem;
46
59
  }
@@ -53,7 +66,7 @@ header {
53
66
  }
54
67
  #summary > div {
55
68
  margin-right: 0;
56
- margin-top: 2rem;
69
+ margin-top: 1rem;
57
70
  }
58
71
  #summary > div:first-child {
59
72
  margin-top: 0;
@@ -65,24 +78,28 @@ header {
65
78
  margin-bottom: 0.5rem;
66
79
  }
67
80
  .summary-passed {
68
- color: #4f8a10;
69
- border-left: 0.4rem solid #4f8a10;
81
+ color: var(--success);
82
+ border-left: 0.4rem solid var(--success);
70
83
  padding-left: 0.5rem;
84
+ margin-bottom: 0.15rem;
71
85
  }
72
86
  .summary-failed,
73
87
  .summary-obsolete-snapshots {
74
- color: #d8000c;
75
- border-left: 0.4rem solid #d8000c;
88
+ color: var(--danger);
89
+ border-left: 0.4rem solid var(--danger);
76
90
  padding-left: 0.5rem;
91
+ margin-bottom: 0.15rem;
77
92
  }
78
93
  .summary-pending {
79
- color: #9f6000;
80
- border-left: 0.4rem solid #9f6000;
94
+ color: var(--warning);
95
+ border-left: 0.4rem solid var(--warning);
81
96
  padding-left: 0.5rem;
97
+ margin-bottom: 0.15rem;
82
98
  }
83
99
  .summary-empty {
84
- color: #999;
85
- border-left: 0.4rem solid #999;
100
+ color: var(--disabled);
101
+ border-left: 0.4rem solid var(--disabled);
102
+ margin-bottom: 0.15rem;
86
103
  }
87
104
 
88
105
  .test-result {
@@ -93,16 +110,16 @@ header {
93
110
  border: 0;
94
111
  }
95
112
  .test-result.passed {
96
- background-color: #dff2bf;
97
- color: #4f8a10;
113
+ background-color: var(--success-bright);
114
+ color: var(--success);
98
115
  }
99
116
  .test-result.failed {
100
- background-color: #ffbaba;
101
- color: #d8000c;
117
+ background-color: var(--danger-bright);
118
+ color: var(--danger);
102
119
  }
103
120
  .test-result.pending {
104
- background-color: #ffdf61;
105
- color: #9f6000;
121
+ background-color: var(--warning-bright);
122
+ color: var(--warning);
106
123
  }
107
124
 
108
125
  .test-info {
@@ -127,13 +144,13 @@ header {
127
144
  .test-duration {
128
145
  width: 10%;
129
146
  text-align: right;
130
- font-size: 0.75rem;
147
+ font-size: 0.85rem;
131
148
  }
132
149
 
133
150
  .failureMessages {
134
151
  padding: 0 1rem;
135
152
  margin-top: 1rem;
136
- border-top: 1px dashed #d8000c;
153
+ border-top: 1px dashed var(--danger);
137
154
  }
138
155
  .failureMessages.suiteFailure {
139
156
  border-top: none;
@@ -147,33 +164,20 @@ header {
147
164
  }
148
165
 
149
166
  .suite-container {
150
- margin-bottom: 2rem;
151
- }
152
- .suite-container > input[type="checkbox"] {
153
- position: absolute;
154
- left: -100vw;
155
- }
156
- .suite-container label {
157
- display: block;
158
- }
159
- .suite-container .suite-tests {
160
- overflow-y: hidden;
161
- height: 0;
162
- }
163
- .suite-container > input[type="checkbox"]:checked ~ .suite-tests {
164
- height: auto;
165
- overflow: visible;
167
+ margin-bottom: 1rem;
166
168
  }
167
169
  .suite-info {
168
170
  padding: 1rem;
169
- background-color: #eee;
170
- color: #777;
171
+ background-color: var(--panel);
172
+ color: var(--text-secondary);
173
+ border: 0.15rem solid;
174
+ border-color: var(--panel);
171
175
  display: flex;
172
176
  align-items: center;
173
177
  margin-bottom: 0.25rem;
174
178
  }
175
179
  .suite-info:hover {
176
- background-color: #ddd;
180
+ border-color: var(--border);
177
181
  cursor: pointer;
178
182
  }
179
183
  .suite-info .suite-path {
@@ -183,29 +187,29 @@ header {
183
187
  font-size: 1rem;
184
188
  }
185
189
  .suite-info .suite-time {
186
- margin-left: 0.5rem;
190
+ margin-left: 1rem;
187
191
  padding: 0.2rem 0.3rem;
188
- font-size: 0.75rem;
192
+ font-size: 0.85rem;
189
193
  }
190
194
  .suite-info .suite-time.warn {
191
- background-color: #d8000c;
195
+ background-color: var(--danger);
192
196
  color: #fff;
193
197
  }
194
198
  .suite-info:before {
195
199
  content: "\2303";
196
200
  display: inline-block;
197
- margin-right: 0.5rem;
198
- transform: rotate(0deg);
201
+ margin-right: 1rem;
202
+ transform: rotate(180deg) translateY(0.15rem);
199
203
  }
200
- .suite-container > input[type="checkbox"]:checked ~ label .suite-info:before {
201
- transform: rotate(180deg);
204
+ .suite-container[open] .suite-info:before {
205
+ transform: rotate(0deg) translateY(0.15rem);
202
206
  }
203
207
 
204
208
  /* CONSOLE LOGS */
205
209
  .suite-consolelog {
206
210
  margin-bottom: 0.25rem;
207
211
  padding: 1rem;
208
- background-color: #efefef;
212
+ background-color: var(--panel);
209
213
  }
210
214
  .suite-consolelog-header {
211
215
  font-weight: bold;
@@ -222,11 +226,11 @@ header {
222
226
  word-wrap: break-word;
223
227
  }
224
228
  .suite-consolelog-item-origin {
225
- color: #777;
229
+ color: var(--text-secondary);
226
230
  font-weight: bold;
227
231
  }
228
232
  .suite-consolelog-item-message {
229
- color: #000;
233
+ color: var(--text-primary);
230
234
  font-size: 1rem;
231
235
  padding: 0 0.5rem;
232
236
  }
@@ -235,8 +239,8 @@ header {
235
239
  .suite-obsolete-snapshots {
236
240
  margin-bottom: 0.25rem;
237
241
  padding: 1rem;
238
- background-color: #ffbaba;
239
- color: #d8000c;
242
+ background-color: var(--danger-bright);
243
+ color: var(--danger);
240
244
  }
241
245
  .suite-obsolete-snapshots-header {
242
246
  font-weight: bold;
@@ -253,7 +257,7 @@ header {
253
257
  word-wrap: break-word;
254
258
  }
255
259
  .suite-obsolete-snapshots-item-message {
256
- color: #000;
260
+ color: var(--text-primary);
257
261
  font-size: 1rem;
258
262
  padding: 0 0.5rem;
259
263
  }