UNPKG

jasmine-reporters

Version:
498 lines (469 loc) 23.9 kB
/* globals jasmine, describe, afterEach, beforeEach, it, expect */ var jasmineReporters = require("../index"); var DOMParser = require("@xmldom/xmldom").DOMParser; var env, suite, reporter, writeCalls, suiteId=0, specId=0, noop=function(){}; function fakeSpec(ste, name) { var s = new jasmine.Spec({ env: env, id: specId++, description: name, queueableFn: {fn: noop}, }); ste.addChild(s); return s; } function fakeSuite(name, parentSuite) { var s = new jasmine.Suite({ env: env, id: suiteId++, description: name, parentSuite: parentSuite || jasmine.createSpy("pretend top suite") // I'm sure there's a good reason Jasmine does this... }); if (parentSuite) { parentSuite.addChild(s); } else { env._suites = env._suites || []; env._suites.push(s); } return s; } function setupReporterWithOptions(options) { reporter = new jasmineReporters.JUnitXmlReporter(options); reporter.writeFile = jasmine.createSpy(); } // make sure reporter is set before calling this function triggerRunnerEvents(callback) { reporter.jasmineStarted(); for (var i=0; i<env._suites.length; i++) { var s = env._suites[i]; if(callback && typeof(callback) === "function") { callback(); } triggerSuiteEvents(s); } reporter.jasmineDone(); // pre-parse some data to be used by various specs writeCalls = reporter.writeFile.calls.all(); for (i=0; i<writeCalls.length; i++) { writeCalls[i].output = writeCalls[i].args[1]; writeCalls[i].xmldoc = xmlDocumentFromString(writeCalls[i].output); } } function triggerSuiteEvents(ste) { reporter.suiteStarted(ste.result); var thing; for (var i=0; i<ste.children.length; i++) { thing = ste.children[i]; if (thing instanceof jasmine.Suite) { triggerSuiteEvents(thing); } else { reporter.specStarted(thing.result); reporter.specDone(thing.result); } } reporter.suiteDone(ste.result); } function xmlDocumentFromString(str) { return (new DOMParser()).parseFromString(str, "text/xml"); } describe("JUnitXmlReporter", function(){ beforeEach(function(){ env = new jasmine.Env(); suite = fakeSuite("ParentSuite"); fakeSpec(suite, "should be a dummy with invalid characters: & < > \" '"); setupReporterWithOptions(); }); describe("constructor", function(){ it("should default path to an empty string", function(){ expect(reporter.savePath).toEqual(""); }); it("should default consolidateAll to true", function(){ expect(reporter.consolidateAll).toBe(true); }); it("should default consolidate to true", function(){ expect(reporter.consolidate).toBe(true); }); it("should default useDotNotation to true", function(){ expect(reporter.useDotNotation).toBe(true); }); describe("file prefix", function(){ it("should default output file prefix to 'junitresults'", function () { expect(reporter.filePrefix).toBe("junitresults"); }); it("should default output file prefix to 'junitresults-' if consolidateAll is false", function () { setupReporterWithOptions({ consolidateAll: false }); expect(reporter.filePrefix).toBe("junitresults-"); }); it("should prefix suite names if consolidateAll is false", function () { setupReporterWithOptions({ consolidateAll: false, filePrefix: "alt-prefix-" }); triggerRunnerEvents(); expect(reporter.writeFile).toHaveBeenCalledWith("alt-prefix-ParentSuite.xml", jasmine.any(String)); }); }); describe("package", function () { it("should default output package to undefined", function () { expect(reporter.package).toBeUndefined(); }); it("should not set output package if a non-string is provided", function() { setupReporterWithOptions({package:true}); expect(reporter.package).toBeUndefined(); setupReporterWithOptions({package:["test"]}); expect(reporter.package).toBeUndefined(); }); it("should set output package to the provided string", function () { setupReporterWithOptions({package:"testPackage"}); expect(reporter.package).toBe("testPackage"); }); }); describe("stylesheetPath", function () { it("should default stylesheetPath to undefined", function () { expect(reporter.stylesheetPath).toBeUndefined(); }); it("should not set stylesheetPath if an empty or non-string is provided", function() { setupReporterWithOptions({stylesheetPath:true}); expect(reporter.stylesheetPath).toBeUndefined(); setupReporterWithOptions({stylesheetPath:""}); expect(reporter.stylesheetPath).toBeUndefined(); }); it("should set output stylesheetPath to the provided string", function () { setupReporterWithOptions({stylesheetPath:"mystyle.xslt"}); expect(reporter.stylesheetPath).toBe("mystyle.xslt"); }); it("should include the stylesheet in all generated output files", function () { setupReporterWithOptions({consolidate: false, stylesheetPath:"mystyle.xslt"}); triggerRunnerEvents(); writeCalls.forEach(call => { expect(call.output.indexOf('<?xml-stylesheet type="text/xsl" href="mystyle.xslt"')).toBeGreaterThan(-1); }); }); }); }); function assertTestsuitesTagAttributes(testSuitesTag, {disabled, errors, failures, tests} = {}) { if (disabled === void 0) { expect(testSuitesTag.hasAttribute("disabled")).toBe(false); } else { expect(testSuitesTag.getAttribute("disabled")).toBe(disabled); } expect(testSuitesTag.getAttribute("errors")).toBe(errors); expect(testSuitesTag.getAttribute("failures")).toBe(failures); expect(testSuitesTag.getAttribute("tests")).toBe(tests); } it("the testsuites tags should include a time attribute", function() { var testSuitesTags = writeCalls[0].xmldoc.getElementsByTagName("testsuites"); expect(testSuitesTags.length).toBe(1); var testSuitesTag = testSuitesTags[0]; expect(testSuitesTag.getAttribute("time")).not.toBe(""); }); describe("no xml output generation", function() { beforeEach(function() { setupReporterWithOptions({consolidateAll:true}); triggerRunnerEvents(); }); it("testsuites tags should default disabled, errors, failures to 0 when undefined", function() { assertTestsuitesTagAttributes( writeCalls[0].xmldoc.getElementsByTagName("testsuites")[0], {disabled: "0", errors: "0", failures: "0", tests: "1"}); }); }); describe("generated xml output", function(){ var subSuite, subSubSuite, siblingSuite; function itShouldIncludeXmlPreambleInAllFiles() { it("should include xml preamble once in all files", function() { for (var i=0; i<writeCalls.length; i++) { expect(writeCalls[i].output.indexOf("<?xml")).toBe(0); expect(writeCalls[i].output.lastIndexOf("<?xml")).toBe(0); } }); } function itShouldHaveOneTestsuitesElementPerFile() { it("should include xml preamble once in all files", function() { for (var i=0; i<writeCalls.length; i++) { expect(writeCalls[i].xmldoc.getElementsByTagName("testsuites").length).toBe(1); } }); } beforeEach(function(){ subSuite = fakeSuite("SubSuite", suite); subSubSuite = fakeSuite("SubSubSuite", subSuite); siblingSuite = fakeSuite("SiblingSuite With Invalid Chars < & > \" ' | : \\ /"); fakeSpec(subSuite, "should be one level down"); fakeSpec(subSubSuite, "should be two levels down"); var skipped = fakeSpec(subSubSuite, "should be skipped two levels down"); var disabled = fakeSpec(subSubSuite, "should be disabled two levels down"); var failed = fakeSpec(subSubSuite, "should be failed two levels down"); fakeSpec(siblingSuite, "should be a sibling of Parent"); skipped.result.status = "pending"; disabled.result.status = "disabled"; failed.result.status = "failed"; failed.result.failedExpectations.push({ passed: false, message: "Expected true to be false.", expected: false, actual: true, matcherName: "toBe", stack: 'Stack trace! Stack trackes are cool & can have "special" characters <3\n\n Neat: yes.' }); }); describe("consolidateAll=true", function() { beforeEach(function() { setupReporterWithOptions({consolidateAll:true, filePrefix:"results"}); triggerRunnerEvents(); }); it("should only write a single file", function() { expect(writeCalls.length).toBe(1); }); it("should include results for all test suites", function() { expect(writeCalls[0].xmldoc.getElementsByTagName("testsuite").length).toBe(4); }); it("should write a single file using filePrefix as the filename", function() { expect(writeCalls[0].args[0]).toBe("results.xml"); }); it("testsuites tags should include disabled, errors, failures, and tests (count) when defined", function() { assertTestsuitesTagAttributes( writeCalls[0].xmldoc.getElementsByTagName("testsuites")[0], {disabled: "1", errors: "0", failures: "1", tests: "7"}); }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); describe("consolidatedAll=false, consolidate=true", function(){ beforeEach(function(){ setupReporterWithOptions({consolidateAll:false, consolidate:true, filePrefix:"results-"}); triggerRunnerEvents(); }); it("should write one file per parent suite", function(){ expect(writeCalls.length).toEqual(2); }); it("should include results for top-level suite and its descendents", function() { expect(writeCalls[0].xmldoc.getElementsByTagName("testsuite").length).toBe(3); expect(writeCalls[1].xmldoc.getElementsByTagName("testsuite").length).toBe(1); }); it("should construct filenames using filePrefix and suite description, removing bad characters", function() { expect(writeCalls[0].args[0]).toBe("results-ParentSuite.xml"); expect(writeCalls[1].args[0]).toBe("results-SiblingSuiteWithInvalidChars.xml"); }); it("testsuites tags should include disabled, errors, failures, and tests (count) when defined", function() { assertTestsuitesTagAttributes(writeCalls[0].xmldoc.getElementsByTagName("testsuites")[0], {disabled: "1", errors: "0", failures: "1", tests: "6"}); assertTestsuitesTagAttributes(writeCalls[1].xmldoc.getElementsByTagName("testsuites")[0], {disabled: "0", errors: "0", failures: "0", tests: "1"}); }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); describe("consolidated=false", function(){ beforeEach(function(){ // consolidateAll becomes a noop, we include it specifically to passively test that setupReporterWithOptions({consolidateAll:true, consolidate:false, filePrefix:"results-"}); triggerRunnerEvents(); }); it("should write one file per suite", function(){ expect(writeCalls.length).toEqual(4); }); it("should include results for a single suite", function() { for (var i=0; i<writeCalls.length; i++) { expect(writeCalls[i].xmldoc.getElementsByTagName("testsuite").length).toBe(1); } }); it("should construct filenames using filePrefix and suite description, always using dot notation for filenames", function() { expect(writeCalls[0].args[0]).toBe("results-ParentSuite.SubSuite.SubSubSuite.xml"); expect(writeCalls[1].args[0]).toBe("results-ParentSuite.SubSuite.xml"); expect(writeCalls[2].args[0]).toBe("results-ParentSuite.xml"); }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); describe("classname generation", function() { it("should remove invalid xml chars from the classname", function() { setupReporterWithOptions({consolidateAll:true, consolidate:true}); triggerRunnerEvents(); expect(writeCalls[0].output).toContain("SiblingSuite With Invalid Chars &lt; &amp; &gt; &quot; &apos; | : \\ /"); }); describe("useDotNotation=true", function() { beforeEach(function() { setupReporterWithOptions({consolidateAll:true, consolidate:true, useDotNotation:true}); triggerRunnerEvents(); }); it("should use suite descriptions separated by periods", function() { expect(writeCalls[0].xmldoc.getElementsByTagName("testsuite")[2].getAttribute("name")).toBe("ParentSuite.SubSuite.SubSubSuite"); expect(writeCalls[0].xmldoc.getElementsByTagName("testcase")[2].getAttribute("classname")).toBe("ParentSuite.SubSuite.SubSubSuite"); }); }); describe("useDotNotation=false", function() { beforeEach(function() { setupReporterWithOptions({consolidateAll:true, consolidate:true, useDotNotation:false}); triggerRunnerEvents(); }); it("should use suite descriptions separated by spaces", function() { expect(writeCalls[0].xmldoc.getElementsByTagName("testsuite")[2].getAttribute("name")).toBe("ParentSuite SubSuite SubSubSuite"); expect(writeCalls[0].xmldoc.getElementsByTagName("testcase")[2].getAttribute("classname")).toBe("ParentSuite SubSuite SubSubSuite"); }); }); }); describe("suite result generation", function() { var suites; beforeEach(function() { setupReporterWithOptions({consolidateAll:true, consolidate:true}); triggerRunnerEvents(); suites = writeCalls[0].xmldoc.getElementsByTagName("testsuite"); }); it("should include test suites in order", function() { expect(suites[0].getAttribute("name")).toBe("ParentSuite"); expect(suites[1].getAttribute("name")).toContain("SubSuite"); expect(suites[2].getAttribute("name")).toContain("SubSubSuite"); expect(suites[3].getAttribute("name")).toContain("SiblingSuite"); }); it("should include total / failed / skipped counts for each suite (ignoring descendent results)", function() { expect(suites[1].getAttribute("tests")).toBe("1"); expect(suites[2].getAttribute("tests")).toBe("4"); expect(suites[2].getAttribute("skipped")).toBe("1"); expect(suites[2].getAttribute("failures")).toBe("1"); }); it("should calculate duration", function() { expect(Number(suites[0].getAttribute("time"))).not.toEqual(NaN); }); it("should include timestamp as an ISO date string without timezone", function() { expect(suites[0].getAttribute("timestamp")).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); }); it("should include hostname, simply because the JUnit XSD says it is required", function() { expect(suites[0].getAttribute("hostname")).toBe("localhost"); }); describe("package", function() { it("should not include the package attribute if it is not provided", function() { setupReporterWithOptions({}); triggerRunnerEvents(); suites = writeCalls[0].xmldoc.getElementsByTagName("testsuite"); expect(suites[0].getAttribute("package")).toBe(""); }); it("should include the package attribute if a string is provided", function() { setupReporterWithOptions({package:"testPackage"}); triggerRunnerEvents(); suites = writeCalls[0].xmldoc.getElementsByTagName("testsuite"); expect(suites[0].getAttribute("package")).toBe("testPackage"); }); it("should escape the string provided", function() { setupReporterWithOptions({package:"testPackage <3"}); triggerRunnerEvents(); suites = writeCalls[0].xmldoc.getElementsByTagName("testsuite"); expect(suites[0].getAttribute("package")).toBe("testPackage <3"); expect(writeCalls[0].output).toContain('package="testPackage &lt;3"'); }); }); }); describe("spec result generation", function() { var specs; beforeEach(function() { setupReporterWithOptions({consolidateAll:true, consolidate:true}); triggerRunnerEvents(); specs = writeCalls[0].xmldoc.getElementsByTagName("testcase"); }); it("should include specs in order", function() { expect(specs[0].getAttribute("name")).toContain("should be a dummy"); expect(specs[5].getAttribute("name")).toBe("should be failed two levels down"); }); it("should escape bad xml characters in spec description", function() { expect(writeCalls[0].output).toContain("&amp; &lt; &gt; &quot; &apos;"); }); it("should calculate duration", function() { expect(Number(specs[0].getAttribute("time"))).not.toEqual(NaN); }); it("should include failed matcher name as the failure type", function() { var failure = specs[5].getElementsByTagName("failure")[0]; expect(failure.getAttribute("type")).toBe("toBe"); }); it("should include failure messages", function() { var failure = specs[5].getElementsByTagName("failure")[0]; expect(failure.getAttribute("message")).toBe("Expected true to be false."); }); it("should include stack traces for failed specs (using CDATA to preserve special characters)", function() { var failure = specs[5].getElementsByTagName("failure")[0]; expect(failure.textContent).toContain('cool & can have "special" characters <3'); }); it("should include <skipped/> for skipped specs", function() { expect(specs[3].getElementsByTagName("skipped").length).toBe(1); }); }); describe("modifySuiteName", function(){ var suites, modification = "-modified"; beforeEach(function(){ // consolidateAll becomes a noop, we include it specifically to passively test that setupReporterWithOptions({ consolidateAll:true, consolidate:true, modifySuiteName:function(generatedName/*, suite*/) { return generatedName + modification; } }); triggerRunnerEvents(); suites = writeCalls[0].xmldoc.getElementsByTagName("testsuite"); }); it("should construct suitenames that contain modification", function() { for (var i = 0, suite; i < suites.length; i++) { suite = suites[i]; expect(suite.getAttribute("name")).toContain(modification); } }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); describe("modifyReportFileName", function(){ var modification = "-modified"; beforeEach(function(){ // consolidateAll becomes a noop, we include it specifically to passively test that setupReporterWithOptions({ consolidateAll:false, modifyReportFileName:function(generatedName/*, suite*/) { return generatedName + modification; } }); triggerRunnerEvents(); }); it("should construct filenames that contain modification", function() { expect(writeCalls[0].args[0]).toContain(modification); }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); describe("suppressDisabled=true", function() { beforeEach(function() { setupReporterWithOptions({suppressDisabled: true}); triggerRunnerEvents(); }); it("testsuites tags should include errors, failures, and tests (count) when defined, but not disabled", function() { assertTestsuitesTagAttributes( writeCalls[0].xmldoc.getElementsByTagName("testsuites")[0], {disabled: void 0, errors: "0", failures: "1", tests: "7"}); }); }); describe("captures stdout in <xml-output>", function(){ var specOutputs; const testOutput = "I'm generating test output."; var _stdoutWrite; beforeEach(function(){ _stdoutWrite = process.stdout.write; process.stdout.write = noop; setupReporterWithOptions( {consolidateAll:true, consolidate:true, captureStdout: true}); triggerRunnerEvents(function() { console.log(testOutput); }); specOutputs = writeCalls[0].xmldoc.getElementsByTagName("system-out"); }); afterEach(function() { process.stdout.write = _stdoutWrite; }); it("should record stdout", function() { expect(specOutputs[0].textContent).toContain(testOutput); }); it("should discard any stdout for skipped tests", function() { expect(specOutputs[3].textContent).not.toContain(testOutput); }); }); }); });