Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Hargne/jest-html-reporter
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3.9.0
Choose a base ref
...
head repository: Hargne/jest-html-reporter
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3.10.0
Choose a head ref
  • 8 commits
  • 6 files changed
  • 1 contributor

Commits on May 20, 2023

  1. Copy the full SHA
    030501e View commit details
  2. Copy the full SHA
    fe38c2e View commit details
  3. Minor refactoring

    Hargne committed May 20, 2023
    Copy the full SHA
    c37a207 View commit details
  4. Fixed typo

    Hargne committed May 20, 2023
    Copy the full SHA
    2fc9255 View commit details

Commits on May 25, 2023

  1. Merge pull request #164 from Hargne/bugfix/strip-terminal-codes

    Strip terminal color codes
    Hargne authored May 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8276cb8 View commit details
  2. Merge pull request #165 from Hargne/feature/collapsible-tests

    Collapsible test suites
    Hargne authored May 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0462290 View commit details
  3. Bump version

    Hargne committed May 25, 2023
    Copy the full SHA
    5370b24 View commit details
  4. Merge pull request #166 from Hargne/dev

    Release 3.10.0
    Hargne authored May 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3a1afec View commit details
Showing with 141 additions and 102 deletions.
  1. +1 −0 README.md
  2. +1 −1 package.json
  3. +83 −81 src/htmlreporter.ts
  4. +1 −0 src/types/index.d.ts
  5. +28 −0 style/defaultTheme.css
  6. +27 −20 test/htmlreporter.spec.ts
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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` |
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",
164 changes: 83 additions & 81 deletions src/htmlreporter.ts
Original file line number Diff line number Diff line change
@@ -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`
);
}

@@ -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" },
@@ -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`
);
@@ -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`
);
@@ -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
@@ -520,6 +514,7 @@ class HTMLReporter {
const {
append,
boilerplate,
collapseSuitesByDefault,
customScriptPath,
dateFormat,
executionTimeWarningThreshold,
@@ -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",
@@ -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,
""
)
);
}
}
1 change: 1 addition & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ export interface JestHTMLReporterProps {
export type IJestHTMLReporterConfigOptions = {
append?: boolean;
boilerplate?: string;
collapseSuitesByDefault?: boolean;
customScriptPath?: string;
dateFormat?: string;
executionTimeWarningThreshold?: number;
28 changes: 28 additions & 0 deletions style/defaultTheme.css
Original file line number Diff line number Diff line change
@@ -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;
@@ -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;
@@ -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 {
47 changes: 27 additions & 20 deletions test/htmlreporter.spec.ts
Original file line number Diff line number Diff line change
@@ -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({
@@ -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
);
});
});
});