Skip to content

Commit

Permalink
Merge pull request #166 from Hargne/dev
Browse files Browse the repository at this point in the history
Release 3.10.0
  • Loading branch information
Hargne authored May 25, 2023
2 parents 87d9d4f + 5370b24 commit 3a1afec
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 102 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Please note that all configuration properties are optional.
| ------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `append` | `BOOLEAN` | If set to true, new test results will be appended to the existing test report | `false` |
| `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` |
| `collapseSuitesByDefault` | `BOOLEAN` | Whether to collapse test suites by default or not | `false` |
| `customScriptPath` | `STRING` | Path to a javascript file that should be injected into the test report | `null` |
| `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"` |
| `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` |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jest-html-reporter",
"version": "3.9.0",
"version": "3.10.0",
"description": "Jest test results processor for generating a summary in HTML",
"main": "dist/index.js",
"unpkg": "dist/index.js",
Expand Down
164 changes: 83 additions & 81 deletions src/htmlreporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,55 +127,40 @@ class HTMLReporter {
};
}

public renderTestSuiteInfo(parent: XMLElement, suite: TestResult) {
const suiteInfo = parent.ele("div", { class: "suite-info" });
// Suite Path
suiteInfo.ele("div", { class: "suite-path" }, suite.testFilePath);
// Suite execution time
const executionTime = (suite.perfStats.end - suite.perfStats.start) / 1000;
suiteInfo.ele(
"div",
{
class: `suite-time${
executionTime >
(this.getConfigValue("executionTimeWarningThreshold") as number)
? " warn"
: ""
}`,
},
`${executionTime}s`
);
}

public renderSuiteFailure(parent: XMLElement, suite: TestResult, i: number) {
const suiteContainer = parent.ele("div", {
id: `suite-${i + 1}`,
class: "suite-container",
public renderTestSuiteHeader(
parent: XMLElement,
suite: TestResult,
suiteIndex: number
) {
const collapsibleBtnId = `collapsible-${suiteIndex}`;
parent.ele("input", {
id: collapsibleBtnId,
type: "checkbox",
class: "toggle",
checked: !this.getConfigValue("collapseSuitesByDefault")
? "checked"
: null,
});

// Suite Information
this.renderTestSuiteInfo(suiteContainer, suite);

// Test Container
const suiteTests = suiteContainer.ele("div", {
class: "suite-tests",
const collapsibleBtnContainer = parent.ele("label", {
for: collapsibleBtnId,
});

const testResult = suiteTests.ele("div", {
class: "test-result failed",
const suiteInfo = collapsibleBtnContainer.ele("div", {
class: "suite-info",
});

const failureMsgDiv = testResult.ele(
suiteInfo.ele("div", { class: "suite-path" }, suite.testFilePath);
const executionTime = (suite.perfStats.end - suite.perfStats.start) / 1000;
const suiteExecutionTimeClass = ["suite-time"];
if (
executionTime >
(this.getConfigValue("executionTimeWarningThreshold") as number)
) {
suiteExecutionTimeClass.push("warn");
}
suiteInfo.ele(
"div",
{
class: "failureMessages suiteFailure",
},
" "
);
failureMsgDiv.ele(
"pre",
{ class: "failureMsg" },
this.sanitizeOutput(suite.failureMessage)
{ class: suiteExecutionTimeClass.join(" ") },
`${executionTime}s`
);
}

Expand Down Expand Up @@ -229,6 +214,14 @@ class HTMLReporter {
const summaryContainer = metaDataContainer.ele("div", { id: "summary" });
// Suite Summary
const suiteSummary = summaryContainer.ele("div", { id: "suite-summary" });

const getSummaryClass = (
type: "passed" | "failed" | "pending",
numberOfSuites: number
) =>
[`summary-${type}`, numberOfSuites > 0 ? "" : " summary-empty"].join(
" "
);
suiteSummary.ele(
"div",
{ class: "summary-total" },
Expand All @@ -237,27 +230,21 @@ class HTMLReporter {
suiteSummary.ele(
"div",
{
class: `summary-passed${
this.testData.numPassedTestSuites === 0 ? " summary-empty" : ""
}`,
class: getSummaryClass("passed", this.testData.numPassedTestSuites),
},
`${this.testData.numPassedTestSuites} passed`
);
suiteSummary.ele(
"div",
{
class: `summary-failed${
this.testData.numFailedTestSuites === 0 ? " summary-empty" : ""
}`,
class: getSummaryClass("failed", this.testData.numFailedTestSuites),
},
`${this.testData.numFailedTestSuites} failed`
);
suiteSummary.ele(
"div",
{
class: `summary-pending${
this.testData.numPendingTestSuites === 0 ? " summary-empty" : ""
}`,
class: getSummaryClass("pending", this.testData.numPendingTestSuites),
},
`${this.testData.numPendingTestSuites} pending`
);
Expand Down Expand Up @@ -285,27 +272,21 @@ class HTMLReporter {
testSummary.ele(
"div",
{
class: `summary-passed${
this.testData.numPassedTests === 0 ? " summary-empty" : ""
}`,
class: getSummaryClass("passed", this.testData.numPassedTests),
},
`${this.testData.numPassedTests} passed`
);
testSummary.ele(
"div",
{
class: `summary-failed${
this.testData.numFailedTests === 0 ? " summary-empty" : ""
}`,
class: getSummaryClass("failed", this.testData.numFailedTests),
},
`${this.testData.numFailedTests} failed`
);
testSummary.ele(
"div",
{
class: `summary-pending${
this.testData.numPendingTests === 0 ? " summary-empty" : ""
}`,
class: getSummaryClass("pending", this.testData.numPendingTests),
},
`${this.testData.numPendingTests} pending`
);
Expand Down Expand Up @@ -336,31 +317,44 @@ class HTMLReporter {
* Test Suites
*/
if (sortedTestResults) {
sortedTestResults.forEach((suite, i) => {
sortedTestResults.forEach((suite, suiteIndex) => {
const suiteContainer = reportBody.ele("div", {
id: `suite-${suiteIndex + 1}`,
class: "suite-container",
});
// Suite Information
this.renderTestSuiteHeader(suiteContainer, suite, suiteIndex);
// Test Container
const suiteTests = suiteContainer.ele("div", {
class: "suite-tests",
});

// Ignore this suite if there are no results
if (!suite.testResults || suite.testResults.length <= 0) {
// Include the suite failure message if it exists
if (
suite.failureMessage &&
this.getConfigValue("includeSuiteFailure")
) {
this.renderSuiteFailure(reportBody, suite, i);
const testResult = suiteTests.ele("div", {
class: "test-result failed",
});
const failureMsgDiv = testResult.ele(
"div",
{
class: "failureMessages suiteFailure",
},
" "
);
failureMsgDiv.ele(
"pre",
{ class: "failureMsg" },
this.sanitizeOutput(suite.failureMessage)
);
}
return;
}

const suiteContainer = reportBody.ele("div", {
id: `suite-${i + 1}`,
class: "suite-container",
});

// Suite Information
this.renderTestSuiteInfo(suiteContainer, suite);

// Test Container
const suiteTests = suiteContainer.ele("div", {
class: "suite-tests",
});

// Test Results
suite.testResults
// Filter out the test results with statuses that equals the statusIgnoreFilter
Expand Down Expand Up @@ -520,6 +514,7 @@ class HTMLReporter {
const {
append,
boilerplate,
collapseSuitesByDefault,
customScriptPath,
dateFormat,
executionTimeWarningThreshold,
Expand Down Expand Up @@ -549,6 +544,11 @@ class HTMLReporter {
environmentVariable: "JEST_HTML_REPORTER_BOILERPLATE",
configValue: boilerplate,
},
collapseSuitesByDefault: {
defaultValue: false,
environmentVariable: "JEST_HTML_REPORTER_COLLAPSE_SUITES_BY_DEFAULT",
configValue: collapseSuitesByDefault,
},
customScriptPath: {
defaultValue: null,
environmentVariable: "JEST_HTML_REPORTER_CUSTOM_SCRIPT_PATH",
Expand Down Expand Up @@ -760,10 +760,12 @@ class HTMLReporter {
*/
private sanitizeOutput(input: string) {
return stripAnsi(
input.replace(
/([^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFC\u{10000}-\u{10FFFF}])/gu,
""
)
input
.replace(/(\x1b\[\d*m)/g, "")
.replace(
/([^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFC\u{10000}-\u{10FFFF}])/gu,
""
)
);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface JestHTMLReporterProps {
export type IJestHTMLReporterConfigOptions = {
append?: boolean;
boilerplate?: string;
collapseSuitesByDefault?: boolean;
customScriptPath?: string;
dateFormat?: string;
executionTimeWarningThreshold?: number;
Expand Down
28 changes: 28 additions & 0 deletions style/defaultTheme.css
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,21 @@ header {
.suite-container {
margin-bottom: 2rem;
}
.suite-container > input[type="checkbox"] {
position: absolute;
left: -100vw;
}
.suite-container label {
display: block;
}
.suite-container .suite-tests {
overflow-y: hidden;
height: 0;
}
.suite-container > input[type="checkbox"]:checked ~ .suite-tests {
height: auto;
overflow: visible;
}
.suite-info {
padding: 1rem;
background-color: #eee;
Expand All @@ -157,6 +172,10 @@ header {
align-items: center;
margin-bottom: 0.25rem;
}
.suite-info:hover {
background-color: #ddd;
cursor: pointer;
}
.suite-info .suite-path {
word-break: break-all;
flex-grow: 1;
Expand All @@ -172,6 +191,15 @@ header {
background-color: #d8000c;
color: #fff;
}
.suite-info:before {
content: "\2303";
display: inline-block;
margin-right: 0.5rem;
transform: rotate(0deg);
}
.suite-container > input[type="checkbox"]:checked ~ label .suite-info:before {
transform: rotate(180deg);
}

/* CONSOLE LOGS */
.suite-consolelog {
Expand Down
47 changes: 27 additions & 20 deletions test/htmlreporter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,6 @@ describe("HTMLReporter", () => {
});

describe("config options", () => {
/* TODO: The following test runs locally, but fails in Travis CI
describe("boilerplate", () => {
it("should insert the test report HTML into the given file", async () => {
const mockedFS = jest.spyOn(fs, "readFileSync");
mockedFS.mockImplementation(
() => "<div>{jesthtmlreporter-content}</div>"
);
const reporter = new HTMLReporter(mockedJestResponseSingleTestResult, {
boilerplate: path.join(process.cwd(), "/path/to/boilerplate.html")
});
const report = await reporter.renderTestReport();
expect(report).toEqual(
`<div>${mockedSingleTestResultReportHTML}</div>`
);
mockedFS.mockRestore();
});
});
*/

describe("styleOverridePath", () => {
it("should insert a link to the overriding stylesheet path", async () => {
const reporter = new HTMLReporter({
Expand Down Expand Up @@ -406,4 +386,31 @@ describe("HTMLReporter", () => {
expect(result).toBe("test/reporter.html");
});
});

describe("collapseSuitesByDefault", () => {
it("should show the contents of test suites by default", async () => {
const reporter = new HTMLReporter({
testData: mockedJestResponseSingleTestResult,
options: {},
});
const report = await reporter.renderTestReport();
console.log(report.fullHtml);
expect(
report.fullHtml.indexOf('class="toggle" checked="checked"')
).toBeGreaterThan(-1);
});

it("should hide the contents of test suites", async () => {
const reporter = new HTMLReporter({
testData: mockedJestResponseSingleTestResult,
options: {
collapseSuitesByDefault: true,
},
});
const report = await reporter.renderTestReport();
expect(report.fullHtml.indexOf('class="toggle" checked="checked"')).toBe(
-1
);
});
});
});

0 comments on commit 3a1afec

Please sign in to comment.