diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js
index 5c341d38..22c5d65a 100644
--- a/lib/jasmine-core/jasmine-html.js
+++ b/lib/jasmine-core/jasmine-html.js
@@ -1010,7 +1010,10 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
return window.location;
}
});
- this.#urlBuilder = new UrlBuilder(this.#queryString);
+ this.#urlBuilder = new UrlBuilder({
+ queryString: this.#queryString,
+ getSuiteById: id => this.#stateBuilder.suitesById[id]
+ });
this.#filterSpecs = options.urls.filteringSpecs();
}
@@ -1147,34 +1150,48 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
class UrlBuilder {
#queryString;
+ #getSuiteById;
- constructor(queryString) {
- this.#queryString = queryString;
+ constructor(options) {
+ this.#queryString = options.queryString;
+ this.#getSuiteById = options.getSuiteById;
}
suiteHref(suite) {
- const els = [];
-
- while (suite && suite.parent) {
- els.unshift(suite.result.description);
- suite = suite.parent;
- }
-
- return this.#addToExistingQueryString('spec', els.join(' '));
+ const path = this.#suitePath(suite);
+ return this.#specPathHref(path);
}
- specHref(result) {
- return this.#addToExistingQueryString('spec', result.fullName);
+ specHref(specResult) {
+ const suite = this.#getSuiteById(specResult.parentSuiteId);
+ const path = this.#suitePath(suite);
+ path.push(specResult.description);
+ return this.#specPathHref(path);
}
runAllHref() {
- return this.#addToExistingQueryString('spec', '');
+ return this.#addToExistingQueryString('path', '');
}
seedHref(seed) {
return this.#addToExistingQueryString('seed', seed);
}
+ #suitePath(suite) {
+ const path = [];
+
+ while (suite && suite.parent) {
+ path.unshift(suite.result.description);
+ suite = suite.parent;
+ }
+
+ return path;
+ }
+
+ #specPathHref(specPath) {
+ return this.#addToExistingQueryString('path', JSON.stringify(specPath));
+ }
+
#addToExistingQueryString(k, v) {
// include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
return (
@@ -1190,6 +1207,7 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
jasmineRequire.HtmlReporterV2Urls = function(j$) {
'use strict';
+ // TODO unify with V2 UrlBuilder?
// TODO jsdoc
class HtmlReporterV2Urls {
constructor(options = {}) {
@@ -1227,19 +1245,19 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
const specFilter = new j$.private.HtmlSpecFilterV2({
filterString: () => {
- return this.queryString.getParam('spec');
+ return this.queryString.getParam('path');
}
});
config.specFilter = function(spec) {
- return specFilter.matches(spec.getFullName());
+ return specFilter.matches(spec);
};
return config;
}
filteringSpecs() {
- return !!this.queryString.getParam('spec');
+ return !!this.queryString.getParam('path');
}
}
@@ -1247,25 +1265,42 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
};
jasmineRequire.HtmlSpecFilterV2 = function() {
- 'use strict';
+ class HtmlSpecFilterV2 {
+ #getFilterString;
- function HtmlSpecFilterV2(options) {
- const filterString =
- options &&
- options.filterString() &&
- options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
- const filterPattern = new RegExp(filterString);
+ constructor(options) {
+ this.#getFilterString = options.filterString;
+ }
/**
* Determines whether the spec with the specified name should be executed.
- * @name HtmlSpecFilter#matches
+ * @name HtmlSpecFilterV2#matches
* @function
- * @param {string} specName The full name of the spec
+ * @param {Spec} spec
* @returns {boolean}
*/
- this.matches = function(specName) {
- return filterPattern.test(specName);
- };
+ matches(spec) {
+ const filterString = this.#getFilterString();
+
+ if (!filterString) {
+ return true;
+ }
+
+ const filterPath = JSON.parse(this.#getFilterString());
+ const specPath = spec.getPath();
+
+ if (filterPath.length > specPath.length) {
+ return false;
+ }
+
+ for (let i = 0; i < filterPath.length; i++) {
+ if (specPath[i] !== filterPath[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
return HtmlSpecFilterV2;
@@ -1278,6 +1313,7 @@ jasmineRequire.ResultsStateBuilder = function(j$) {
constructor() {
this.topResults = new j$.private.ResultsNode({}, '', null);
this.currentParent = this.topResults;
+ this.suitesById = {};
this.totalSpecsDefined = 0;
this.specsExecuted = 0;
this.failureCount = 0;
@@ -1288,6 +1324,7 @@ jasmineRequire.ResultsStateBuilder = function(j$) {
suiteStarted(result) {
this.currentParent.addChild(result, 'suite');
this.currentParent = this.currentParent.last();
+ this.suitesById[result.id] = this.currentParent;
}
suiteDone(result) {
diff --git a/spec/html/HtmlReporterV2Spec.js b/spec/html/HtmlReporterV2Spec.js
index 05d1b4af..63be22da 100644
--- a/spec/html/HtmlReporterV2Spec.js
+++ b/spec/html/HtmlReporterV2Spec.js
@@ -349,6 +349,7 @@ describe('HtmlReporterV2', function() {
let specResult = {
id: 123,
+ parentSuiteId: 1,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'passed',
@@ -423,7 +424,9 @@ describe('HtmlReporterV2', function() {
const suiteDetail = outerSuite.childNodes[0];
const suiteLink = suiteDetail.childNodes[0];
expect(suiteLink.innerHTML).toEqual('A Suite');
- expect(suiteLink.getAttribute('href')).toEqual('/?spec=A%20Suite');
+ expect(suiteLink.getAttribute('href')).toEqual(
+ `/?path=${encodeURIComponent('["A Suite"]')}`
+ );
const specs = outerSuite.childNodes[1];
const spec = specs.childNodes[0];
@@ -433,7 +436,7 @@ describe('HtmlReporterV2', function() {
const specLink = spec.childNodes[0];
expect(specLink.innerHTML).toEqual('with a spec');
expect(specLink.getAttribute('href')).toEqual(
- '/?spec=A%20Suite%20with%20a%20spec'
+ `/?path=${encodeURIComponent('["A Suite","with a spec"]')}`
);
const specDuration = spec.childNodes[1];
@@ -779,7 +782,7 @@ describe('HtmlReporterV2', function() {
reporter.jasmineDone({ order: { random: true } });
const skippedLink = container.querySelector('.jasmine-skipped a');
- expect(skippedLink.getAttribute('href')).toEqual('/?spec=');
+ expect(skippedLink.getAttribute('href')).toEqual('/?path=');
});
});
@@ -985,6 +988,7 @@ describe('HtmlReporterV2', function() {
const failingSpecResult = {
id: 124,
+ parentSuiteId: 2,
status: 'failed',
description: 'a failing spec',
fullName: 'a suite inner suite a failing spec',
@@ -1106,16 +1110,20 @@ describe('HtmlReporterV2', function() {
expect(links.length).toEqual(3);
expect(links[0].textContent).toEqual('A suite');
- expect(links[0].getAttribute('href')).toMatch(/\?spec=A%20suite/);
+ expect(links[0].getAttribute('href')).toEqual(
+ `/?path=${encodeURIComponent('["A suite"]')}`
+ );
expect(links[1].textContent).toEqual('inner suite');
- expect(links[1].getAttribute('href')).toMatch(
- /\?spec=A%20suite%20inner%20suite/
+ expect(links[1].getAttribute('href')).toEqual(
+ `/?path=${encodeURIComponent('["A suite","inner suite"]')}`
);
expect(links[2].textContent).toEqual('a failing spec');
- expect(links[2].getAttribute('href')).toMatch(
- /\?spec=a%20suite%20inner%20suite%20a%20failing%20spec/
+ expect(links[2].getAttribute('href')).toEqual(
+ `/?path=${encodeURIComponent(
+ '["A suite","inner suite","a failing spec"]'
+ )}`
);
});
diff --git a/spec/html/HtmlReporterV2UrlsSpec.js b/spec/html/HtmlReporterV2UrlsSpec.js
index fc462fa2..145cec60 100644
--- a/spec/html/HtmlReporterV2UrlsSpec.js
+++ b/spec/html/HtmlReporterV2UrlsSpec.js
@@ -10,17 +10,17 @@ describe('HtmlReporterV2Urls', function() {
it('configures a matching spec filter', function() {
const queryString = mockQueryString();
- queryString.getParam.withArgs('spec').and.returnValue('foo');
+ queryString.getParam.withArgs('path').and.returnValue('["foo","bar"]');
const subject = new jasmineUnderTest.HtmlReporterV2Urls({ queryString });
const config = subject.configFromCurrentUrl();
const matching = {
- getFullName() {
- return 'foobar';
+ getPath() {
+ return ['foo', 'bar'];
}
};
const nonMatching = {
- getFullName() {
- return 'baz';
+ getPath() {
+ return ['foobar'];
}
};
expect(config.specFilter(matching)).toEqual(true);
diff --git a/spec/html/HtmlSpecFilterV2Spec.js b/spec/html/HtmlSpecFilterV2Spec.js
index bc3dd19f..16c175d7 100644
--- a/spec/html/HtmlSpecFilterV2Spec.js
+++ b/spec/html/HtmlSpecFilterV2Spec.js
@@ -1,19 +1,49 @@
describe('HtmlSpecFilterV2', function() {
- it('should match when no string is provided', function() {
- const specFilter = new privateUnderTest.HtmlSpecFilterV2();
-
- expect(specFilter.matches('foo')).toBe(true);
- expect(specFilter.matches('*bar')).toBe(true);
- });
-
- it('should only match the provided string', function() {
+ it('matches everything when no string is provided', function() {
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
- filterString: function() {
- return 'foo';
+ filterString() {
+ return '';
}
});
- expect(specFilter.matches('foo')).toBe(true);
- expect(specFilter.matches('bar')).toBe(false);
+ expect(specFilter.matches({})).toBeTrue();
});
+
+ it('matches a spec with the exact same path', function() {
+ const specFilter = new privateUnderTest.HtmlSpecFilterV2({
+ filterString() {
+ return '["a","b","c"]';
+ }
+ });
+
+ expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
+ });
+
+ it('matches a spec whose path has the filter path as a prefix', function() {
+ const specFilter = new privateUnderTest.HtmlSpecFilterV2({
+ filterString() {
+ return '["a","b"]';
+ }
+ });
+
+ expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
+ });
+
+ it('does not match a spec with a different path', function() {
+ const specFilter = new privateUnderTest.HtmlSpecFilterV2({
+ filterString() {
+ return '["a","b","c"]';
+ }
+ });
+
+ expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse();
+ });
+
+ function stubSpec(path) {
+ return {
+ getPath() {
+ return path;
+ }
+ };
+ }
});
diff --git a/src/html/HtmlReporterV2.js b/src/html/HtmlReporterV2.js
index 111a31a0..0afc7cec 100644
--- a/src/html/HtmlReporterV2.js
+++ b/src/html/HtmlReporterV2.js
@@ -37,7 +37,10 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
return window.location;
}
});
- this.#urlBuilder = new UrlBuilder(this.#queryString);
+ this.#urlBuilder = new UrlBuilder({
+ queryString: this.#queryString,
+ getSuiteById: id => this.#stateBuilder.suitesById[id]
+ });
this.#filterSpecs = options.urls.filteringSpecs();
}
@@ -174,34 +177,48 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
class UrlBuilder {
#queryString;
+ #getSuiteById;
- constructor(queryString) {
- this.#queryString = queryString;
+ constructor(options) {
+ this.#queryString = options.queryString;
+ this.#getSuiteById = options.getSuiteById;
}
suiteHref(suite) {
- const els = [];
-
- while (suite && suite.parent) {
- els.unshift(suite.result.description);
- suite = suite.parent;
- }
-
- return this.#addToExistingQueryString('spec', els.join(' '));
+ const path = this.#suitePath(suite);
+ return this.#specPathHref(path);
}
- specHref(result) {
- return this.#addToExistingQueryString('spec', result.fullName);
+ specHref(specResult) {
+ const suite = this.#getSuiteById(specResult.parentSuiteId);
+ const path = this.#suitePath(suite);
+ path.push(specResult.description);
+ return this.#specPathHref(path);
}
runAllHref() {
- return this.#addToExistingQueryString('spec', '');
+ return this.#addToExistingQueryString('path', '');
}
seedHref(seed) {
return this.#addToExistingQueryString('seed', seed);
}
+ #suitePath(suite) {
+ const path = [];
+
+ while (suite && suite.parent) {
+ path.unshift(suite.result.description);
+ suite = suite.parent;
+ }
+
+ return path;
+ }
+
+ #specPathHref(specPath) {
+ return this.#addToExistingQueryString('path', JSON.stringify(specPath));
+ }
+
#addToExistingQueryString(k, v) {
// include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
return (
diff --git a/src/html/HtmlReporterV2Urls.js b/src/html/HtmlReporterV2Urls.js
index 5b490d55..9d5a4961 100644
--- a/src/html/HtmlReporterV2Urls.js
+++ b/src/html/HtmlReporterV2Urls.js
@@ -1,6 +1,7 @@
jasmineRequire.HtmlReporterV2Urls = function(j$) {
'use strict';
+ // TODO unify with V2 UrlBuilder?
// TODO jsdoc
class HtmlReporterV2Urls {
constructor(options = {}) {
@@ -38,19 +39,19 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
const specFilter = new j$.private.HtmlSpecFilterV2({
filterString: () => {
- return this.queryString.getParam('spec');
+ return this.queryString.getParam('path');
}
});
config.specFilter = function(spec) {
- return specFilter.matches(spec.getFullName());
+ return specFilter.matches(spec);
};
return config;
}
filteringSpecs() {
- return !!this.queryString.getParam('spec');
+ return !!this.queryString.getParam('path');
}
}
diff --git a/src/html/HtmlSpecFilterv2.js b/src/html/HtmlSpecFilterv2.js
index 8ad66d68..a875b132 100644
--- a/src/html/HtmlSpecFilterv2.js
+++ b/src/html/HtmlSpecFilterv2.js
@@ -1,23 +1,40 @@
jasmineRequire.HtmlSpecFilterV2 = function() {
- 'use strict';
+ class HtmlSpecFilterV2 {
+ #getFilterString;
- function HtmlSpecFilterV2(options) {
- const filterString =
- options &&
- options.filterString() &&
- options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
- const filterPattern = new RegExp(filterString);
+ constructor(options) {
+ this.#getFilterString = options.filterString;
+ }
/**
* Determines whether the spec with the specified name should be executed.
- * @name HtmlSpecFilter#matches
+ * @name HtmlSpecFilterV2#matches
* @function
- * @param {string} specName The full name of the spec
+ * @param {Spec} spec
* @returns {boolean}
*/
- this.matches = function(specName) {
- return filterPattern.test(specName);
- };
+ matches(spec) {
+ const filterString = this.#getFilterString();
+
+ if (!filterString) {
+ return true;
+ }
+
+ const filterPath = JSON.parse(this.#getFilterString());
+ const specPath = spec.getPath();
+
+ if (filterPath.length > specPath.length) {
+ return false;
+ }
+
+ for (let i = 0; i < filterPath.length; i++) {
+ if (specPath[i] !== filterPath[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
return HtmlSpecFilterV2;
diff --git a/src/html/ResultsStateBuilder.js b/src/html/ResultsStateBuilder.js
index ab1390c9..a5cc0f76 100644
--- a/src/html/ResultsStateBuilder.js
+++ b/src/html/ResultsStateBuilder.js
@@ -5,6 +5,7 @@ jasmineRequire.ResultsStateBuilder = function(j$) {
constructor() {
this.topResults = new j$.private.ResultsNode({}, '', null);
this.currentParent = this.topResults;
+ this.suitesById = {};
this.totalSpecsDefined = 0;
this.specsExecuted = 0;
this.failureCount = 0;
@@ -15,6 +16,7 @@ jasmineRequire.ResultsStateBuilder = function(j$) {
suiteStarted(result) {
this.currentParent.addChild(result, 'suite');
this.currentParent = this.currentParent.last();
+ this.suitesById[result.id] = this.currentParent;
}
suiteDone(result) {