jest-html-reporter 4.0.0 → 4.1.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 CHANGED
@@ -62,30 +62,48 @@ To run the reporter as a test results processor (after Jest is complete instead
62
62
  }
63
63
  ```
64
64
 
65
- ***Note:** When running as a testResultsProcessor, the configuration needs be placed within a new file named `jesthtmlreporter.config.json` residing in the root folder.*
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
66
+
67
+ ```JSON
68
+ {
69
+ "pageTitle": "Test Report",
70
+ }
71
+ ```
72
+
73
+ or via adding a key to `package.json` named "jest-html-reporter":
74
+
75
+ ```JSON
76
+ {
77
+ ...
78
+ "jest-html-reporter": {
79
+ "pageTitle": "Test Report",
80
+ }
81
+ }
82
+ ```
66
83
 
67
84
  ## 📌 Configuration Options (All Optional)
68
85
 
69
- | Option | Type | Default | Description |
70
- | ----------------------------------- | --------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
71
- | **`append`** | `boolean` | `false` | Append test results to an existing report. |
72
- | **`boilerplate`** | `string` | `null` | Path to an HTML boilerplate file. The `{jesthtmlreporter-content}` variable will be replaced with test results. |
73
- | **`collapseSuitesByDefault`** | `boolean` | `false` | Collapse test suites (accordions) by default. |
74
- | **`customScriptPath`** | `string` | `null` | Path to an external script file injected into the report. |
75
- | **`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. |
76
- | **`executionTimeWarningThreshold`** | `number` | `5` | Warn if a test suite exceeds this execution time (in seconds). |
77
- | **`includeConsoleLog`** | `boolean` | `false` | Include `console.log` outputs in the report (**requires** `--verbose=false`). |
78
- | **`includeFailureMsg`** | `boolean` | `false` | Show detailed error messages for failed tests. |
79
- | **`includeStackTrace`** | `boolean` | `true` | Show stack traces for failed tests. |
80
- | **`includeSuiteFailure`** | `boolean` | `false` | Show detailed errors for entire failed test suites. |
81
- | **`includeObsoleteSnapshots`** | `boolean` | `false` | Show obsolete snapshot names. |
82
- | **`logo`** | `string` | `null` | Path to an image file to display in the report header. |
83
- | **`outputPath`** | `string` | `./test-report.html` | Full path for the output report file (**must end in `.html`**). |
84
- | **`pageTitle`** | `string` | `"Test Report"` | Title of the document and top-level heading. |
85
- | **`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**. |
86
- | **`statusIgnoreFilter`** | `string` | `null` | **Comma-separated** list of statuses to exclude: `"passed"`, `"pending"`, `"failed"`. |
87
- | **`styleOverridePath`** | `string` | `null` | Path to a CSS file to override default styles. |
88
- | **`useCssFile`** | `boolean` | `false` | Link to the CSS file instead of inlining styles. |
86
+ | Option | Type | Default | Description |
87
+ | ----------------------------------- | ------------------------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88
+ | **`additionalInformation`** | `Array<{ label: string; value: string; }>` | `null` | A list of additional information to be added to the top of the report. |
89
+ | **`append`** | `boolean` | `false` | Append test results to an existing report. |
90
+ | **`boilerplate`** | `string` | `null` | Path to an HTML boilerplate file. The `{jesthtmlreporter-content}` variable will be replaced with test results. |
91
+ | **`collapseSuitesByDefault`** | `boolean` | `false` | Collapse test suites (accordions) by default. |
92
+ | **`customScriptPath`** | `string` | `null` | Path to an external script file injected into the report. |
93
+ | **`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. |
94
+ | **`executionTimeWarningThreshold`** | `number` | `5` | Warn if a test suite exceeds this execution time (in seconds). |
95
+ | **`includeConsoleLog`** | `boolean` | `false` | Include `console.log` outputs in the report (**requires** `--verbose=false`). |
96
+ | **`includeFailureMsg`** | `boolean` | `false` | Show detailed error messages for failed tests. |
97
+ | **`includeStackTrace`** | `boolean` | `true` | Show stack traces for failed tests. |
98
+ | **`includeSuiteFailure`** | `boolean` | `false` | Show detailed errors for entire failed test suites. |
99
+ | **`includeObsoleteSnapshots`** | `boolean` | `false` | Show obsolete snapshot names. |
100
+ | **`logo`** | `string` | `null` | Path to an image file to display in the report header. |
101
+ | **`outputPath`** | `string` | `./test-report.html` | Full path for the output report file (**must end in `.html`**). |
102
+ | **`pageTitle`** | `string` | `"Test Report"` | Title of the document and top-level heading. |
103
+ | **`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**. |
104
+ | **`statusIgnoreFilter`** | `string` | `null` | **Comma-separated** list of statuses to exclude: `"passed"`, `"pending"`, `"failed"`. |
105
+ | **`styleOverridePath`** | `string` | `null` | Path to a CSS file to override default styles. |
106
+ | **`useCssFile`** | `boolean` | `false` | Link to the CSS file instead of inlining styles. |
89
107
 
90
108
  ## Continuous Integration
91
109
 
@@ -93,7 +111,7 @@ All the configuration options provided in the table above are available via envi
93
111
 
94
112
  **Example:** `customScriptPath` -> `JEST_HTML_REPORTER_CUSTOM_SCRIPT_PATH`
95
113
 
96
- ***NOTE:** Environment variables will take precedence over configurations set in jesthtmlreporter.config.json and package.json*
114
+ **\*NOTE:** Environment variables will take precedence over configurations set in jesthtmlreporter.config.json and package.json\*
97
115
 
98
116
  ### CI Example
99
117
 
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("dateformat"),t=require("fs"),s=require("mkdirp"),i=require("path"),n=require("xmlbuilder"),a=require("strip-ansi");function o(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 o("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 a(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=[],a=[],o=[];e.testResults.forEach((e=>{"pending"===e.status?t.push(e):"failed"===e.status?n.push(e):"passed"===e.status?a.push(e):o.push(e)})),t.length>0&&s.pending.push({...e,testResults:t}),n.length>0&&s.failed.push({...e,testResults:n}),a.length>0&&s.passed.push({...e,testResults:a}),o.length>0&&i.push({...e,testResults:o})}));const n=[...t||[],...["pending","failed","passed"].filter((e=>!(t||[]).includes(e)))],a=Object.entries(s).sort((([e],[t])=>n.indexOf(e)-n.indexOf(t))).flatMap((([,e])=>e));return[...a,...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||{};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 a=!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),a=n?n[1]:s,o=/<\/body>/i.exec(i),r=o?o.index:0,l=[i.slice(0,r),a,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()),a=!1)}return a&&t.writeFileSync(n,e.fullHtml.toString()),o("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:{}}),a=s.ele("head");a.ele("meta",{charset:"utf-8"}),a.ele("title",{},this.getConfigValue("pageTitle"));let o=i.join(__dirname,`../style/${this.getConfigValue("theme")}.css`);this.getConfigValue("styleOverridePath")&&(o=this.getConfigValue("styleOverridePath"));if(!this.getConfigValue("useCssFile")&&!this.getConfigValue("styleOverridePath")){const e=t.readFileSync(o,"utf8");a.raw(`<style type="text/css">${e}</style>`)}else a.ele("link",{rel:"stylesheet",type:"text/css",href:o});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 a=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"));a.ele("div",{id:"timestamp"},`Started: ${s}`)}}const o=a.ele("div",{id:"summary"}),r=o.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=o.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})};
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 f=(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]},p=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},v=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},S={additionalInformation:[],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 T(e){try{if(t.existsSync(e))return JSON.parse(t.readFileSync(e,"utf8"))}catch{return{}}return{}}const C={additionalInformation:(F=function(e){return"object"==typeof e&&null!==e&&"string"==typeof e.label&&"string"==typeof e.value},e=>Array.isArray(e)?e.filter(F):[]),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};var F;function D(e){return"object"!=typeof e||null===e?{}:Object.keys(S).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 b{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(S).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=T(i.join(process.cwd(),"jesthtmlreporter.config.json")),n=T(i.join(process.cwd(),"package.json"))["jest-html-reporter"]||{};return{...S,...D(n),...D(s),...D(e),...D(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}`)}}this.renderAdditionalInformation(o);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 f(e,t)}case"executiondesc":return p(e);case"executionasc":return m(e);case"titledesc":return y(e);case"titleasc":return v(e);default:return e}})(this.testData.testResults,this.getConfigValue("sort")),h=this.getConfigValue("statusIgnoreFilter");let S=[];return h&&(S=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=>!S.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"))}renderAdditionalInformation(e){const t=this.getConfigValue("additionalInformation");if(t&&Array.isArray(t)&&t.length>0){const s=e.ele("div",{class:"additional-information-container"});for(const e of t)s.ele("div",{class:"additional-information"},`${e.label}: ${e.value}`);return s}}getConfigValue(e){return this.config[e]}}const j=e=>new b(e).generate();module.exports=function(e,t){const s=[];if(Object.prototype.hasOwnProperty.call(e,"testResults")){const s=e;return j({testData:s,options:t}),s}this.onTestResult=(e,t)=>{t.console&&s.push({filePath:t.testFilePath,logs:t.console})},this.onRunComplete=(i,n)=>j({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": "4.0.0",
3
+ "version": "4.1.0",
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",
@@ -1,15 +1,15 @@
1
1
  :root {
2
2
  --text-primary: #111;
3
- --text-secondary: #4F4F4F;
3
+ --text-secondary: #4f4f4f;
4
4
  --success: #006633;
5
- --success-bright: #80FFBF;
6
- --danger: #CC071E;
7
- --danger-bright: #FBDFE0;
8
- --warning: #995C00;
9
- --warning-bright: #FFEEA8;
5
+ --success-bright: #80ffbf;
6
+ --danger: #cc071e;
7
+ --danger-bright: #fbdfe0;
8
+ --warning: #995c00;
9
+ --warning-bright: #ffeea8;
10
10
  --panel: #eee;
11
11
  --border: #949494;
12
- --disabled: #6B6B6B;
12
+ --disabled: #6b6b6b;
13
13
  }
14
14
 
15
15
  html,
@@ -43,10 +43,23 @@ header {
43
43
  margin-top: 0.5rem;
44
44
  }
45
45
 
46
+ #metadata-container {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: 2rem;
50
+ margin-bottom: 2rem;
51
+ }
52
+
53
+ .additional-information-container {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 0.5rem;
57
+ color: var(--text-secondary);
58
+ }
59
+
46
60
  /** SUMMARY */
47
61
  #summary {
48
62
  color: var(--text-primary);
49
- margin: 2rem 0;
50
63
  display: flex;
51
64
  font-family: monospace;
52
65
  font-size: 1rem;