Compare commits
27 Commits
v6.0.0-alp
...
v6.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
788eba34b6 | ||
|
|
1f31b4b0f6 | ||
|
|
00a8a11904 | ||
|
|
3899d83fb6 | ||
|
|
8f13684a01 | ||
|
|
bdf63f2402 | ||
|
|
9c2ffae2f9 | ||
|
|
7b2807b321 | ||
|
|
e930622548 | ||
|
|
56e2832ebe | ||
|
|
d31d33aeb3 | ||
|
|
e4c69e960e | ||
|
|
a8431f33bd | ||
|
|
4995c967ac | ||
|
|
9a9d3994da | ||
|
|
ff9feb29d3 | ||
|
|
fee7e6e64e | ||
|
|
18d4d38655 | ||
|
|
53e9bc68d2 | ||
|
|
2be50e1b87 | ||
|
|
27a1257b6d | ||
|
|
75658e0566 | ||
|
|
85322d1877 | ||
|
|
6667a42301 | ||
|
|
020dffd504 | ||
|
|
4201fd848f | ||
|
|
9a67c4e24d |
@@ -1,5 +1,5 @@
|
||||
# Run tests against supported Node versions, and (except for pull requests)
|
||||
# against supported browsers.
|
||||
# against supported browsers that are available on Saucelabs.
|
||||
|
||||
version: 2.1
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
export SAUCE_TUNNEL_NAME=$CIRCLE_WORKFLOW_JOB_ID
|
||||
scripts/start-sauce-connect
|
||||
set +o errexit
|
||||
scripts/run-all-browsers
|
||||
scripts/run-sauce-browsers
|
||||
exitcode=$?
|
||||
set -o errexit
|
||||
scripts/stop-sauce-connect
|
||||
|
||||
23
.github/workflows/safari.yml
vendored
Normal file
23
.github/workflows/safari.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Test in latest available Safari
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Report Safari version
|
||||
run: osascript -e 'get version of application "Safari"'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.x
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: JASMINE_BROWSER=safari npm run ci
|
||||
@@ -30,7 +30,7 @@ Microsoft Edge) as well as Node.
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------------|
|
||||
| Node | 20, 22, 24 |
|
||||
| Safari | 16*, 17* |
|
||||
| Safari | 16*, 17*, 26* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128*, 140 |
|
||||
| Edge | Evergreen |
|
||||
|
||||
15
RELEASE.md
15
RELEASE.md
@@ -28,9 +28,18 @@ should also rev to that version.
|
||||
|
||||
When ready to release - specs are all green and the stories are done:
|
||||
|
||||
1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly. Include a list of supported environments.
|
||||
1. Update the version in `package.json`
|
||||
1. Run `npm run build`.
|
||||
1. Update the release notes in `release_notes` - use the Anchorman gem to
|
||||
generate the Markdown file and edit accordingly. Include a list of supported
|
||||
environments. Get that information from these places:
|
||||
* For Node, see .circleci/config.yml or the README.
|
||||
* For Firefox ESR and Safari <=17, see scripts/run-sauce-browsers or the README.
|
||||
* For evergreen browsers, trigger a Circle CI run and check the
|
||||
[Saucelabs dashboard](https://app.saucelabs.com/dashboard/tests?ownerId=90a771d55857492da3bd5251a2d92457&ownerType=user&ownerName=jasmine-js&start=last7days)
|
||||
once it's finished.
|
||||
* For Safari >17, trigger the [Safari action](https://github.com/jasmine/jasmine/actions/workflows/safari.yml)
|
||||
and get the version from the output.
|
||||
2. Update the version in `package.json`
|
||||
3. Run `npm run build`.
|
||||
|
||||
### Commit and push core changes
|
||||
|
||||
|
||||
@@ -58,6 +58,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
env.addReporter(jsApiReporter);
|
||||
env.addReporter(htmlReporter);
|
||||
/**
|
||||
* Configures Jasmine based on the current set of query parameters. This
|
||||
* supports all parameters set by the HTML reporter as well as
|
||||
* spec=partialPath, which filters out specs whose paths don't contain the
|
||||
* parameter.
|
||||
*/
|
||||
env.configure(urls.configFromCurrentUrl());
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,8 @@ jasmineRequire.html = function(j$) {
|
||||
j$.private.SymbolsView = jasmineRequire.SymbolsView(j$);
|
||||
j$.private.SummaryTreeView = jasmineRequire.SummaryTreeView(j$);
|
||||
j$.private.FailuresView = jasmineRequire.FailuresView(j$);
|
||||
j$.private.PerformanceView = jasmineRequire.PerformanceView(j$);
|
||||
j$.private.TabBar = jasmineRequire.TabBar(j$);
|
||||
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
|
||||
j$.HtmlReporterV2Urls = jasmineRequire.HtmlReporterV2Urls(j$);
|
||||
j$.HtmlReporterV2 = jasmineRequire.HtmlReporterV2(j$);
|
||||
@@ -187,16 +189,52 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
results.appendChild(summary.rootEl);
|
||||
|
||||
if (this.#stateBuilder.anyNonTopSuiteFailures) {
|
||||
this.#alerts.addFailureToggle(
|
||||
() => this.#setMenuModeTo('jasmine-failure-list'),
|
||||
() => this.#setMenuModeTo('jasmine-spec-list')
|
||||
);
|
||||
|
||||
this.#addFailureToggle();
|
||||
this.#setMenuModeTo('jasmine-failure-list');
|
||||
this.#failures.show();
|
||||
}
|
||||
}
|
||||
|
||||
#addFailureToggle() {
|
||||
const onClickFailures = () => this.#setMenuModeTo('jasmine-failure-list');
|
||||
const onClickSpecList = () => this.#setMenuModeTo('jasmine-spec-list');
|
||||
const failuresLink = createDom(
|
||||
'a',
|
||||
{ className: 'jasmine-failures-menu', href: '#' },
|
||||
'Failures'
|
||||
);
|
||||
let specListLink = createDom(
|
||||
'a',
|
||||
{ className: 'jasmine-spec-list-menu', href: '#' },
|
||||
'Spec List'
|
||||
);
|
||||
|
||||
failuresLink.onclick = function() {
|
||||
onClickFailures();
|
||||
return false;
|
||||
};
|
||||
|
||||
specListLink.onclick = function() {
|
||||
onClickSpecList();
|
||||
return false;
|
||||
};
|
||||
|
||||
this.#alerts.addBar(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
|
||||
[createDom('span', {}, 'Spec List | '), failuresLink]
|
||||
)
|
||||
);
|
||||
this.#alerts.addBar(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
|
||||
[specListLink, createDom('span', {}, ' | Failures ')]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#find(selector) {
|
||||
return this.#getContainer().querySelector(
|
||||
'.jasmine_html-reporter ' + selector
|
||||
@@ -434,6 +472,7 @@ jasmineRequire.AlertsView = function(j$) {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: remove this once HtmlReporterV2 doesn't use it
|
||||
addFailureToggle(onClickFailures, onClickSpecList) {
|
||||
const failuresLink = createDom(
|
||||
'a',
|
||||
@@ -456,14 +495,20 @@ jasmineRequire.AlertsView = function(j$) {
|
||||
return false;
|
||||
};
|
||||
|
||||
this.#createAndAdd('jasmine-menu jasmine-bar jasmine-spec-list', [
|
||||
createDom('span', {}, 'Spec List | '),
|
||||
failuresLink
|
||||
]);
|
||||
this.#createAndAdd('jasmine-menu jasmine-bar jasmine-failure-list', [
|
||||
specListLink,
|
||||
createDom('span', {}, ' | Failures ')
|
||||
]);
|
||||
this.rootEl.appendChild(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
|
||||
[createDom('span', {}, 'Spec List | '), failuresLink]
|
||||
)
|
||||
);
|
||||
this.rootEl.appendChild(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
|
||||
[specListLink, createDom('span', {}, ' | Failures ')]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addGlobalFailure(failure) {
|
||||
@@ -946,6 +991,10 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
|
||||
const { createDom, noExpectations } = j$.private.htmlReporterUtils;
|
||||
|
||||
const specListTabId = 'jasmine-specListTab';
|
||||
const failuresTabId = 'jasmine-failuresTab';
|
||||
const perfTabId = 'jasmine-perfTab';
|
||||
|
||||
/**
|
||||
* @class HtmlReporterV2
|
||||
* @classdesc Displays results and allows re-running individual specs and suites.
|
||||
@@ -958,7 +1007,7 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
* const reporter = new jasmine.HtmlReporterV2({
|
||||
* env,
|
||||
* urls,
|
||||
* container: document.body
|
||||
* getContainer: () => document.body
|
||||
* });
|
||||
*/
|
||||
class HtmlReporterV2 {
|
||||
@@ -974,6 +1023,7 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
// Sub-views
|
||||
#alerts;
|
||||
#statusBar;
|
||||
#tabBar;
|
||||
#progress;
|
||||
#banner;
|
||||
#failures;
|
||||
@@ -1011,6 +1061,25 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
this.#statusBar = new j$.private.OverallStatusBar(this.#urlBuilder);
|
||||
this.#statusBar.showRunning();
|
||||
this.#alerts.addBar(this.#statusBar.rootEl);
|
||||
|
||||
this.#tabBar = new j$.private.TabBar(
|
||||
[
|
||||
{ id: specListTabId, label: 'Spec List' },
|
||||
{ id: failuresTabId, label: 'Failures' },
|
||||
{ id: perfTabId, label: 'Performance' }
|
||||
],
|
||||
tabId => {
|
||||
if (tabId === specListTabId) {
|
||||
this.#setMenuModeTo('jasmine-spec-list');
|
||||
} else if (tabId === failuresTabId) {
|
||||
this.#setMenuModeTo('jasmine-failure-list');
|
||||
} else {
|
||||
this.#setMenuModeTo('jasmine-performance');
|
||||
}
|
||||
}
|
||||
);
|
||||
this.#alerts.addBar(this.#tabBar.rootEl);
|
||||
|
||||
this.#progress = new ProgressView();
|
||||
this.#banner = new j$.private.Banner(
|
||||
this.#queryString.navigateWithNewParam.bind(this.#queryString),
|
||||
@@ -1101,15 +1170,17 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
);
|
||||
summary.addResults(this.#stateBuilder.topResults);
|
||||
results.appendChild(summary.rootEl);
|
||||
const perf = new j$.private.PerformanceView();
|
||||
perf.addResults(this.#stateBuilder.topResults);
|
||||
results.appendChild(perf.rootEl);
|
||||
this.#tabBar.showTab(specListTabId);
|
||||
this.#tabBar.showTab(perfTabId);
|
||||
|
||||
if (this.#stateBuilder.anyNonTopSuiteFailures) {
|
||||
this.#alerts.addFailureToggle(
|
||||
() => this.#setMenuModeTo('jasmine-failure-list'),
|
||||
() => this.#setMenuModeTo('jasmine-spec-list')
|
||||
);
|
||||
|
||||
this.#setMenuModeTo('jasmine-failure-list');
|
||||
this.#failures.show();
|
||||
this.#tabBar.showTab(failuresTabId);
|
||||
this.#tabBar.selectTab(failuresTabId);
|
||||
} else {
|
||||
this.#tabBar.selectTab(specListTabId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,7 +1219,6 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
this.rootEl.value = this.rootEl.value + 1;
|
||||
|
||||
if (result.status === 'failed') {
|
||||
// TODO: also a non-color indicator
|
||||
this.rootEl.classList.add('failed');
|
||||
}
|
||||
}
|
||||
@@ -1233,7 +1303,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Configuration} from the current page's URL.
|
||||
* Creates a {@link Configuration} from the current page's URL. Supported
|
||||
* query string parameters include all those set by {@link HtmlReporterV2}
|
||||
* as well as spec=partialPath, which filters out specs whose paths don't
|
||||
* contain partialPath.
|
||||
* @returns {Configuration}
|
||||
* @example
|
||||
* const urls = new jasmine.HtmlReporterV2Urls();
|
||||
@@ -1259,9 +1332,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
|
||||
}
|
||||
|
||||
const specFilter = new j$.private.HtmlSpecFilterV2({
|
||||
filterString: () => {
|
||||
return this.queryString.getParam('path');
|
||||
}
|
||||
filterParams: () => ({
|
||||
path: this.queryString.getParam('path'),
|
||||
spec: this.queryString.getParam('spec')
|
||||
})
|
||||
});
|
||||
|
||||
config.specFilter = function(spec) {
|
||||
@@ -1281,35 +1355,35 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
|
||||
|
||||
jasmineRequire.HtmlSpecFilterV2 = function() {
|
||||
class HtmlSpecFilterV2 {
|
||||
#getFilterString;
|
||||
#getFilterParams;
|
||||
|
||||
constructor(options) {
|
||||
this.#getFilterString = options.filterString;
|
||||
this.#getFilterParams = options.filterParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the spec with the specified name should be executed.
|
||||
* @name HtmlSpecFilterV2#matches
|
||||
* @function
|
||||
* @param {Spec} spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
matches(spec) {
|
||||
const filterString = this.#getFilterString();
|
||||
const params = this.#getFilterParams();
|
||||
|
||||
if (!filterString) {
|
||||
return true;
|
||||
if (params.path) {
|
||||
return this.#matchesPath(spec, JSON.parse(params.path));
|
||||
} else if (params.spec) {
|
||||
// Like legacy HtmlSpecFilter, retained because it's convenient for
|
||||
// hand-constructing filter URLs
|
||||
return spec.getFullName().includes(params.spec);
|
||||
}
|
||||
|
||||
const filterPath = JSON.parse(this.#getFilterString());
|
||||
return true;
|
||||
}
|
||||
|
||||
#matchesPath(spec, path) {
|
||||
const specPath = spec.getPath();
|
||||
|
||||
if (filterPath.length > specPath.length) {
|
||||
if (path.length > specPath.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < filterPath.length; i++) {
|
||||
if (specPath[i] !== filterPath[i]) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
if (specPath[i] !== path[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1430,6 +1504,105 @@ jasmineRequire.OverallStatusBar = function(j$) {
|
||||
return OverallStatusBar;
|
||||
};
|
||||
|
||||
jasmineRequire.PerformanceView = function(j$) {
|
||||
const createDom = j$.private.htmlReporterUtils.createDom;
|
||||
const MAX_SLOW_SPECS = 20;
|
||||
|
||||
class PerformanceView {
|
||||
#summary;
|
||||
#tbody;
|
||||
|
||||
constructor() {
|
||||
this.#tbody = document.createElement('tbody');
|
||||
this.#summary = document.createElement('div');
|
||||
this.rootEl = createDom(
|
||||
'div',
|
||||
{ className: 'jasmine-performance-view' },
|
||||
createDom('h2', {}, 'Performance'),
|
||||
this.#summary,
|
||||
createDom('h3', {}, 'Slowest Specs'),
|
||||
createDom(
|
||||
'table',
|
||||
{},
|
||||
createDom(
|
||||
'thead',
|
||||
{},
|
||||
createDom(
|
||||
'tr',
|
||||
{},
|
||||
createDom('th', {}, 'Duration'),
|
||||
createDom('th', {}, 'Spec Name')
|
||||
)
|
||||
),
|
||||
this.#tbody
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addResults(resultsTree) {
|
||||
const specResults = [];
|
||||
getSpecResults(resultsTree, specResults);
|
||||
|
||||
if (specResults.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
specResults.sort(function(a, b) {
|
||||
if (a.duration < b.duration) {
|
||||
return 1;
|
||||
} else if (a.duration > b.duration) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
this.#populateSumary(specResults);
|
||||
this.#populateTable(specResults);
|
||||
}
|
||||
|
||||
#populateSumary(specResults) {
|
||||
const total = specResults.map(r => r.duration).reduce((a, b) => a + b, 0);
|
||||
const mean = total / specResults.length;
|
||||
const median = specResults[Math.floor(specResults.length / 2)].duration;
|
||||
this.#summary.appendChild(
|
||||
document.createTextNode(`Mean spec run time: ${mean.toFixed(0)}ms`)
|
||||
);
|
||||
this.#summary.appendChild(document.createElement('br'));
|
||||
this.#summary.appendChild(
|
||||
document.createTextNode(`Median spec run time: ${median}ms`)
|
||||
);
|
||||
}
|
||||
|
||||
#populateTable(specResults) {
|
||||
specResults = specResults.slice(0, MAX_SLOW_SPECS);
|
||||
|
||||
for (const r of specResults) {
|
||||
this.#tbody.appendChild(
|
||||
createDom(
|
||||
'tr',
|
||||
{},
|
||||
createDom('td', {}, `${r.duration}ms`),
|
||||
createDom('td', {}, r.fullName)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecResults(resultsTree, dest) {
|
||||
for (const node of resultsTree.children) {
|
||||
if (node.type === 'suite') {
|
||||
getSpecResults(node, dest);
|
||||
} else if (node.result.status !== 'excluded') {
|
||||
dest.push(node.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PerformanceView;
|
||||
};
|
||||
|
||||
jasmineRequire.ResultsStateBuilder = function(j$) {
|
||||
'use strict';
|
||||
|
||||
@@ -1661,3 +1834,81 @@ jasmineRequire.SymbolsView = function(j$) {
|
||||
|
||||
return SymbolsView;
|
||||
};
|
||||
|
||||
jasmineRequire.TabBar = function(j$) {
|
||||
const createDom = j$.private.htmlReporterUtils.createDom;
|
||||
|
||||
class TabBar {
|
||||
#tabs;
|
||||
#onSelectTab;
|
||||
|
||||
// tabSpecs should be an array of {id, label}.
|
||||
// All tabs are initially not visible and not selected.
|
||||
constructor(tabSpecs, onSelectTab) {
|
||||
this.#onSelectTab = onSelectTab;
|
||||
this.#tabs = [];
|
||||
this.#tabs = tabSpecs.map(ts => new Tab(ts, () => this.selectTab(ts.id)));
|
||||
|
||||
this.rootEl = createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar' },
|
||||
this.#tabs.map(t => t.rootEl)
|
||||
);
|
||||
}
|
||||
|
||||
showTab(id) {
|
||||
for (const tab of this.#tabs) {
|
||||
if (tab.rootEl.id === id) {
|
||||
tab.setVisibility(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectTab(id) {
|
||||
for (const tab of this.#tabs) {
|
||||
tab.setSelected(tab.rootEl.id === id);
|
||||
}
|
||||
|
||||
this.#onSelectTab(id);
|
||||
}
|
||||
}
|
||||
|
||||
class Tab {
|
||||
#spec;
|
||||
#onClick;
|
||||
|
||||
constructor(spec, onClick) {
|
||||
this.#spec = spec;
|
||||
this.#onClick = onClick;
|
||||
this.rootEl = createDom(
|
||||
'span',
|
||||
{ id: spec.id, className: 'jasmine-tab jasmine-hidden' },
|
||||
this.#createLink()
|
||||
);
|
||||
}
|
||||
|
||||
setVisibility(visible) {
|
||||
this.rootEl.classList.toggle('jasmine-hidden', !visible);
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
if (selected) {
|
||||
this.rootEl.textContent = this.#spec.label;
|
||||
} else {
|
||||
this.rootEl.textContent = '';
|
||||
this.rootEl.appendChild(this.#createLink());
|
||||
}
|
||||
}
|
||||
|
||||
#createLink() {
|
||||
const link = createDom('a', { href: '#' }, this.#spec.label);
|
||||
link.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
this.#onClick();
|
||||
});
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
||||
return TabBar;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ body {
|
||||
background-color: #eee;
|
||||
padding: 5px;
|
||||
margin: -8px;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
font-family: Monaco, "Lucida Console", monospace;
|
||||
line-height: 14px;
|
||||
color: #333;
|
||||
@@ -63,7 +63,7 @@ body {
|
||||
float: right;
|
||||
line-height: 28px;
|
||||
padding-right: 9px;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-symbol-summary {
|
||||
overflow: hidden;
|
||||
@@ -198,11 +198,17 @@ body {
|
||||
color: white;
|
||||
}
|
||||
.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list,
|
||||
.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures {
|
||||
.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures,
|
||||
.jasmine_html-reporter.jasmine-spec-list .jasmine-performance-view {
|
||||
display: none;
|
||||
}
|
||||
.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list,
|
||||
.jasmine_html-reporter.jasmine-failure-list .jasmine-summary {
|
||||
.jasmine_html-reporter.jasmine-failure-list .jasmine-summary,
|
||||
.jasmine_html-reporter.jasmine-failure-list .jasmine-performance-view {
|
||||
display: none;
|
||||
}
|
||||
.jasmine_html-reporter.jasmine-performance .jasmine-results .jasmine-failures,
|
||||
.jasmine_html-reporter.jasmine-performance .jasmine-summary {
|
||||
display: none;
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-results {
|
||||
@@ -323,4 +329,23 @@ body {
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.jasmine-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jasmine-tab + .jasmine-tab:before {
|
||||
content: " | ";
|
||||
}
|
||||
|
||||
.jasmine-performance-view h2, .jasmine-performance-view h3 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.jasmine-performance-view table {
|
||||
border-spacing: 5px;
|
||||
}
|
||||
.jasmine-performance-view th, .jasmine-performance-view td {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -50,11 +50,12 @@ var getJasmineRequireObj = (function() {
|
||||
j$.private.util = jRequire.util(j$);
|
||||
j$.private.errors = jRequire.errors();
|
||||
j$.private.formatErrorMsg = jRequire.formatErrorMsg(j$);
|
||||
j$.private.AllOf = jRequire.AllOf(j$);
|
||||
j$.private.Any = jRequire.Any(j$);
|
||||
j$.private.Anything = jRequire.Anything(j$);
|
||||
j$.private.CallTracker = jRequire.CallTracker(j$);
|
||||
j$.private.MockDate = jRequire.MockDate(j$);
|
||||
j$.private.getClearStack = jRequire.clearStack(j$);
|
||||
j$.private.getStackClearer = jRequire.StackClearer(j$);
|
||||
j$.private.Clock = jRequire.Clock();
|
||||
j$.private.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
|
||||
j$.private.Deprecator = jRequire.Deprecator(j$);
|
||||
@@ -407,6 +408,19 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
|
||||
* value being compared matches every provided equality tester.
|
||||
* @name asymmetricEqualityTesters.allOf
|
||||
* @emittedName jasmine.allOf
|
||||
* @since 5.13.0
|
||||
* @function
|
||||
* @param {...*} arguments - The asymmetric equality checkers to compare.
|
||||
*/
|
||||
j$.allOf = function() {
|
||||
return new j$.AllOf(...arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
|
||||
* value being compared is an instance of the specified class/constructor.
|
||||
@@ -849,8 +863,11 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
// Key and value will eventually be cloned during reporting. The error
|
||||
// thrown at that point if they aren't cloneable isn't very helpful.
|
||||
// Throw a better one now.
|
||||
j$.private.util.assertReporterCloneable(key, 'Key');
|
||||
if (!j$.private.isString(key)) {
|
||||
throw new Error('Key must be a string');
|
||||
}
|
||||
j$.private.util.assertReporterCloneable(value, 'Value');
|
||||
|
||||
this.#executionState.properties = this.#executionState.properties || {};
|
||||
this.#executionState.properties[key] = value;
|
||||
}
|
||||
@@ -930,12 +947,11 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
* @property {String} description - The description passed to the {@link it} that created this spec.
|
||||
* @property {String} fullName - The full description including all ancestors of this spec.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
|
||||
* @property {String} filename - The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. This property may be removed in
|
||||
* a future version unless there is enough user interest in keeping it.
|
||||
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* same call stack height as the originals. You can fix that by setting
|
||||
* {@link Configuration#extraItStackFrames}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
|
||||
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
|
||||
@@ -1117,7 +1133,20 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
* @returns {Array.<string>}
|
||||
* @since 5.7.0
|
||||
*/
|
||||
getPath: this.getPath.bind(this)
|
||||
getPath: this.getPath.bind(this),
|
||||
|
||||
/**
|
||||
* The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. You can fix that by setting
|
||||
* {@link Configuration#extraItStackFrames}.
|
||||
* @name Spec#filename
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 5.13.0
|
||||
*/
|
||||
filename: this.filename
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1196,6 +1225,8 @@ getJasmineRequireObj().Order = function() {
|
||||
getJasmineRequireObj().Env = function(j$) {
|
||||
'use strict';
|
||||
|
||||
const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3;
|
||||
|
||||
/**
|
||||
* @class Env
|
||||
* @since 2.0.0
|
||||
@@ -1213,7 +1244,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
const realSetTimeout = global.setTimeout;
|
||||
const realClearTimeout = global.clearTimeout;
|
||||
const clearStack = j$.private.getClearStack(global);
|
||||
const stackClearer = j$.private.getStackClearer(global);
|
||||
this.clock = new j$.private.Clock(
|
||||
global,
|
||||
function() {
|
||||
@@ -1223,7 +1254,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
);
|
||||
|
||||
const globalErrors = new GlobalErrors(
|
||||
undefined,
|
||||
global,
|
||||
// Configuration is late-bound because GlobalErrors needs to be constructed
|
||||
// before it's set to detect load-time errors in browsers
|
||||
() => this.configuration()
|
||||
@@ -1292,6 +1323,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
config.update(changes);
|
||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||
stackClearer.setSafariYieldStrategy(config.safariYieldStrategy);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1494,7 +1526,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
|
||||
function runQueue(options) {
|
||||
options.clearStack = options.clearStack || clearStack;
|
||||
options.clearStack = options.clearStack || stackClearer;
|
||||
options.timeout = {
|
||||
setTimeout: realSetTimeout,
|
||||
clearTimeout: realClearTimeout
|
||||
@@ -1786,14 +1818,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
this.describe = function(description, definitionFn) {
|
||||
ensureIsNotNested('describe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.describe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
|
||||
this.xdescribe = function(description, definitionFn) {
|
||||
ensureIsNotNested('xdescribe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.xdescribe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
@@ -1801,30 +1833,38 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
this.fdescribe = function(description, definitionFn) {
|
||||
ensureIsNotNested('fdescribe');
|
||||
ensureNonParallel('fdescribe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.fdescribe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
|
||||
this.it = function(description, fn, timeout) {
|
||||
ensureIsNotNested('it');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.it(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
this.xit = function(description, fn, timeout) {
|
||||
ensureIsNotNested('xit');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.xit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
this.fit = function(description, fn, timeout) {
|
||||
ensureIsNotNested('fit');
|
||||
ensureNonParallel('fit');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
function itStackDepth() {
|
||||
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames;
|
||||
}
|
||||
|
||||
function describeStackDepth() {
|
||||
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-defined property as part of the properties field of {@link SpecDoneEvent}
|
||||
* @name Env#getSpecProperty
|
||||
@@ -2010,11 +2050,12 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
}
|
||||
|
||||
function callerCallerFilename() {
|
||||
function indirectCallerFilename(depth) {
|
||||
const frames = new j$.private.StackTrace(new Error()).frames;
|
||||
// frames[3] should always exist except in Jasmine's own tests, which bypass
|
||||
// the global it/describe layer, but don't crash if it doesn't.
|
||||
return frames[3] && frames[3].file;
|
||||
// The specified frame should always exist except in Jasmine's own tests,
|
||||
// which bypass the global it/describe layer, but could be absent in case
|
||||
// of misconfiguration. Don't crash if it's absent.
|
||||
return frames[depth] && frames[depth].file;
|
||||
}
|
||||
|
||||
return Env;
|
||||
@@ -2152,6 +2193,36 @@ getJasmineRequireObj().JsApiReporter = function(j$) {
|
||||
return JsApiReporter;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().AllOf = function(j$) {
|
||||
'use strict';
|
||||
|
||||
function AllOf() {
|
||||
const expectedValues = Array.from(arguments);
|
||||
if (expectedValues.length === 0) {
|
||||
throw new TypeError(
|
||||
'jasmine.allOf() expects at least one argument to be passed.'
|
||||
);
|
||||
}
|
||||
this.expectedValues = expectedValues;
|
||||
}
|
||||
|
||||
AllOf.prototype.asymmetricMatch = function(other, matchersUtil) {
|
||||
for (const expectedValue of this.expectedValues) {
|
||||
if (!matchersUtil.equals(other, expectedValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
AllOf.prototype.jasmineToString = function(pp) {
|
||||
return '<jasmine.allOf(' + pp(this.expectedValues) + ')>';
|
||||
};
|
||||
|
||||
return AllOf;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().Any = function(j$) {
|
||||
'use strict';
|
||||
|
||||
@@ -2849,131 +2920,6 @@ getJasmineRequireObj().CallTracker = function(j$) {
|
||||
return CallTracker;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().clearStack = function(j$) {
|
||||
'use strict';
|
||||
|
||||
const maxInlineCallCount = 10;
|
||||
|
||||
function browserQueueMicrotaskImpl(global) {
|
||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||
const { queueMicrotask } = global;
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
queueMicrotask(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
unclampedSetTimeout(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function nodeQueueMicrotaskImpl(global) {
|
||||
const { queueMicrotask } = global;
|
||||
|
||||
return function(fn) {
|
||||
queueMicrotask(fn);
|
||||
};
|
||||
}
|
||||
|
||||
function messageChannelImpl(global) {
|
||||
const { setTimeout } = global;
|
||||
const postMessage = getPostMessage(global);
|
||||
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
postMessage(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getUnclampedSetTimeout(global) {
|
||||
const { setTimeout } = global;
|
||||
if (!global.MessageChannel) {
|
||||
return setTimeout;
|
||||
}
|
||||
|
||||
const postMessage = getPostMessage(global);
|
||||
return function unclampedSetTimeout(fn) {
|
||||
postMessage(function() {
|
||||
setTimeout(fn);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPostMessage(global) {
|
||||
const { MessageChannel, setTimeout } = global;
|
||||
const channel = new MessageChannel();
|
||||
let head = {};
|
||||
let tail = head;
|
||||
|
||||
let taskRunning = false;
|
||||
channel.port1.onmessage = function() {
|
||||
head = head.next;
|
||||
const task = head.task;
|
||||
delete head.task;
|
||||
|
||||
if (taskRunning) {
|
||||
setTimeout(task, 0);
|
||||
} else {
|
||||
try {
|
||||
taskRunning = true;
|
||||
task();
|
||||
} finally {
|
||||
taskRunning = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return function postMessage(fn) {
|
||||
tail = tail.next = { task: fn };
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
function getClearStack(global) {
|
||||
const NODE_JS =
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
typeof global.process.versions.node === 'string';
|
||||
|
||||
// Windows builds of WebKit have a fairly generic user agent string when no application name is provided:
|
||||
// e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)"
|
||||
const SAFARI_OR_WIN_WEBKIT =
|
||||
global.navigator &&
|
||||
/(^((?!chrome|android).)*safari)|(Win64; x64\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)$)/i.test(
|
||||
global.navigator.userAgent
|
||||
);
|
||||
|
||||
if (NODE_JS) {
|
||||
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
|
||||
// so we avoid the overhead.
|
||||
return nodeQueueMicrotaskImpl(global);
|
||||
} else if (SAFARI_OR_WIN_WEBKIT || !global.MessageChannel /* tests */) {
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari
|
||||
// and other WebKit-based browsers, such as the one distributed by Playwright
|
||||
// to test Safari-like behavior on Windows.
|
||||
// Some of our own integration tests provide a mock queueMicrotask in all
|
||||
// environments because it's simpler to mock than MessageChannel.
|
||||
return browserQueueMicrotaskImpl(global);
|
||||
} else {
|
||||
// MessageChannel is faster than queueMicrotask in supported browsers
|
||||
// other than Safari.
|
||||
return messageChannelImpl(global);
|
||||
}
|
||||
}
|
||||
|
||||
return getClearStack;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().Clock = function() {
|
||||
'use strict';
|
||||
|
||||
@@ -3507,7 +3453,45 @@ getJasmineRequireObj().Configuration = function(j$) {
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
detectLateRejectionHandling: false
|
||||
detectLateRejectionHandling: false,
|
||||
|
||||
/**
|
||||
* The number of extra stack frames inserted by a wrapper around {@link it}
|
||||
* or by some other local modification. Jasmine uses this to determine the
|
||||
* filename for {@link SpecStartedEvent} and {@link SpecDoneEvent}.
|
||||
* @name Configuration#extraItStackFrames
|
||||
* @since 5.13.0
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
extraItStackFrames: 0,
|
||||
|
||||
/**
|
||||
* The number of extra stack frames inserted by a wrapper around
|
||||
* {@link describe} or by some other local modification. Jasmine uses this
|
||||
* to determine the filename for {@link SpecStartedEvent} and
|
||||
* {@link SpecDoneEvent}.
|
||||
* @name Configuration#extraDescribeStackFrames
|
||||
* @since 5.13.0
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
extraDescribeStackFrames: 0,
|
||||
|
||||
/**
|
||||
* The strategy to use in Safari and similar browsers to determine how often
|
||||
* to yield control by calling setTimeout. If set to "count", the default,
|
||||
* the frequency of setTimeout calls is based on the number of relevant
|
||||
* function calls. If set to "time", the frequency of setTimeout calls is
|
||||
* based on elapsed time. Using "time" may provide a significant performance
|
||||
* improvement, but as of 6.0 it hasn't been tested with a wide variety of
|
||||
* workloads and should be considered experimental.
|
||||
* @name Configuration#safariYieldStrategy
|
||||
* @since 6.0.0
|
||||
* @type 'count' | 'time'
|
||||
* @default 'count'
|
||||
*/
|
||||
safariYieldStrategy: 'count'
|
||||
};
|
||||
Object.freeze(defaultConfig);
|
||||
|
||||
@@ -3558,6 +3542,28 @@ getJasmineRequireObj().Configuration = function(j$) {
|
||||
if (typeof changes.seed !== 'undefined') {
|
||||
this.#values.seed = changes.seed;
|
||||
}
|
||||
|
||||
// 0 is a valid value for both of these, so a truthiness check wouldn't work
|
||||
if (typeof changes.extraItStackFrames !== 'undefined') {
|
||||
this.#values.extraItStackFrames = changes.extraItStackFrames;
|
||||
}
|
||||
|
||||
if (typeof changes.extraDescribeStackFrames !== 'undefined') {
|
||||
this.#values.extraDescribeStackFrames =
|
||||
changes.extraDescribeStackFrames;
|
||||
}
|
||||
|
||||
if (typeof changes.safariYieldStrategy !== 'undefined') {
|
||||
const v = changes.safariYieldStrategy;
|
||||
|
||||
if (v === 'count' || v === 'time') {
|
||||
this.#values.safariYieldStrategy = v;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8560,11 +8566,11 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
}
|
||||
|
||||
this.onComplete = attrs.onComplete || emptyFn;
|
||||
this.clearStack =
|
||||
attrs.clearStack ||
|
||||
function(fn) {
|
||||
this.clearStack = attrs.clearStack || {
|
||||
clearStack(fn) {
|
||||
fn();
|
||||
};
|
||||
}
|
||||
};
|
||||
this.onException = attrs.onException || emptyFn;
|
||||
this.onMultipleDone = attrs.onMultipleDone || fallbackOnMultipleDone;
|
||||
this.userContext = attrs.userContext || new j$.private.UserContext();
|
||||
@@ -8744,7 +8750,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
}
|
||||
}
|
||||
|
||||
this.clearStack(() => {
|
||||
this.clearStack.clearStack(() => {
|
||||
this.globalErrors.popListener(this.handleFinalError);
|
||||
|
||||
if (this.errored_) {
|
||||
@@ -8902,6 +8908,7 @@ getJasmineRequireObj().reporterEvents = function(j$) {
|
||||
* {@link ReporterCapabilities} will apply.
|
||||
* @name Reporter#reporterCapabilities
|
||||
* @type ReporterCapabilities | undefined
|
||||
* @optional
|
||||
* @since 5.0
|
||||
*/
|
||||
/**
|
||||
@@ -8968,6 +8975,7 @@ getJasmineRequireObj().reporterEvents = function(j$) {
|
||||
/**
|
||||
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
|
||||
* @function
|
||||
* @optional
|
||||
* @name Reporter#specStarted
|
||||
* @param {SpecStartedEvent} result Information about the individual {@link it} being run
|
||||
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
|
||||
@@ -10678,6 +10686,164 @@ getJasmineRequireObj().SpyStrategy = function(j$) {
|
||||
return SpyStrategy;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().StackClearer = function(j$) {
|
||||
'use strict';
|
||||
|
||||
const maxInlineCallCount = 10;
|
||||
// 25ms gives a good balance of speed and UI responsiveness when running
|
||||
// jasmine-core's own tests in Safari 18. The exact value isn't critical.
|
||||
const safariYieldIntervalMs = 25;
|
||||
|
||||
function browserQueueMicrotaskImpl(global) {
|
||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||
const { queueMicrotask } = global;
|
||||
let yieldStrategy = 'count';
|
||||
let currentCallCount = 0; // for count strategy
|
||||
let nextSetTimeoutTime; // for time strategy
|
||||
|
||||
return {
|
||||
clearStack(fn) {
|
||||
currentCallCount++;
|
||||
let shouldSetTimeout;
|
||||
|
||||
if (yieldStrategy === 'time') {
|
||||
const now = new Date().getTime();
|
||||
shouldSetTimeout = now >= nextSetTimeoutTime;
|
||||
if (shouldSetTimeout) {
|
||||
nextSetTimeoutTime = now + safariYieldIntervalMs;
|
||||
}
|
||||
} else {
|
||||
shouldSetTimeout = currentCallCount >= maxInlineCallCount;
|
||||
if (shouldSetTimeout) {
|
||||
currentCallCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSetTimeout) {
|
||||
unclampedSetTimeout(fn);
|
||||
} else {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
},
|
||||
setSafariYieldStrategy(strategy) {
|
||||
yieldStrategy = strategy;
|
||||
|
||||
if (yieldStrategy === 'time') {
|
||||
nextSetTimeoutTime = new Date().getTime() + safariYieldIntervalMs;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function nodeQueueMicrotaskImpl(global) {
|
||||
const { queueMicrotask } = global;
|
||||
|
||||
return {
|
||||
clearStack(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
setSafariYieldStrategy() {}
|
||||
};
|
||||
}
|
||||
|
||||
function messageChannelImpl(global) {
|
||||
const { setTimeout } = global;
|
||||
const postMessage = getPostMessage(global);
|
||||
|
||||
let currentCallCount = 0;
|
||||
|
||||
return {
|
||||
clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
postMessage(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
},
|
||||
setSafariYieldStrategy() {}
|
||||
};
|
||||
}
|
||||
|
||||
function getUnclampedSetTimeout(global) {
|
||||
const { setTimeout } = global;
|
||||
if (!global.MessageChannel) {
|
||||
return setTimeout;
|
||||
}
|
||||
|
||||
const postMessage = getPostMessage(global);
|
||||
return function unclampedSetTimeout(fn) {
|
||||
postMessage(function() {
|
||||
setTimeout(fn);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPostMessage(global) {
|
||||
const { MessageChannel, setTimeout } = global;
|
||||
const channel = new MessageChannel();
|
||||
let head = {};
|
||||
let tail = head;
|
||||
|
||||
let taskRunning = false;
|
||||
channel.port1.onmessage = function() {
|
||||
head = head.next;
|
||||
const task = head.task;
|
||||
delete head.task;
|
||||
|
||||
if (taskRunning) {
|
||||
setTimeout(task, 0);
|
||||
} else {
|
||||
try {
|
||||
taskRunning = true;
|
||||
task();
|
||||
} finally {
|
||||
taskRunning = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return function postMessage(fn) {
|
||||
tail = tail.next = { task: fn };
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
function getStackClearer(global) {
|
||||
const NODE_JS =
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
typeof global.process.versions.node === 'string';
|
||||
|
||||
// Windows builds of WebKit have a fairly generic user agent string when no application name is provided:
|
||||
// e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)"
|
||||
const SAFARI_OR_WIN_WEBKIT =
|
||||
global.navigator &&
|
||||
/(^((?!chrome|android).)*safari)|(Win64; x64\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)$)/i.test(
|
||||
global.navigator.userAgent
|
||||
);
|
||||
|
||||
if (NODE_JS) {
|
||||
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
|
||||
// so we avoid the overhead.
|
||||
return nodeQueueMicrotaskImpl(global);
|
||||
} else if (SAFARI_OR_WIN_WEBKIT) {
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari
|
||||
// and other WebKit-based browsers, such as the one distributed by Playwright
|
||||
// to test Safari-like behavior on Windows.
|
||||
return browserQueueMicrotaskImpl(global);
|
||||
} else {
|
||||
// MessageChannel is faster than queueMicrotask in supported browsers
|
||||
// other than Safari.
|
||||
return messageChannelImpl(global);
|
||||
}
|
||||
}
|
||||
|
||||
return getStackClearer;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().StackTrace = function(j$) {
|
||||
'use strict';
|
||||
|
||||
@@ -10851,8 +11017,11 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
// Key and value will eventually be cloned during reporting. The error
|
||||
// thrown at that point if they aren't cloneable isn't very helpful.
|
||||
// Throw a better one now.
|
||||
j$.private.util.assertReporterCloneable(key, 'Key');
|
||||
if (!j$.private.isString(key)) {
|
||||
throw new Error('Key must be a string');
|
||||
}
|
||||
j$.private.util.assertReporterCloneable(value, 'Value');
|
||||
|
||||
this.#result.properties = this.#result.properties || {};
|
||||
this.#result.properties[key] = value;
|
||||
}
|
||||
@@ -10960,12 +11129,11 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
* @property {String} description - The description text passed to the {@link describe} that made this suite.
|
||||
* @property {String} fullName - The full description including all ancestors of this suite.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
|
||||
* @property {String} filename - The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
|
||||
* don't maintain the same call stack height as the originals. This property
|
||||
* may be removed in a future version unless there is enough user interest
|
||||
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* don't maintain the same call stack height as the originals. You can fix
|
||||
* that by setting {@link Configuration#extraDescribeStackFrames}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
|
||||
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
|
||||
@@ -11173,6 +11341,19 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.description = suite.description;
|
||||
|
||||
/**
|
||||
* The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions
|
||||
* that don't maintain the same call stack height as the originals. You
|
||||
* can fix that by setting {@link Configuration#extraItStackFrames}.
|
||||
* @name Suite#filename
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 5.13.0
|
||||
*/
|
||||
this.filename = suite.filename;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11852,7 +12033,10 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
_executeSpec(spec, specOverallDone) {
|
||||
const onStart = next => {
|
||||
this.#currentRunableTracker.setCurrentSpec(spec);
|
||||
this.#runableResources.initForRunable(spec.id, spec.parentSuiteId);
|
||||
this.#runableResources.initForRunable(
|
||||
spec.id,
|
||||
spec.parentSuiteId || this.#executionTree.topSuite.id
|
||||
);
|
||||
this.#reportDispatcher.specStarted(spec.startedEvent()).then(next);
|
||||
};
|
||||
const resultCallback = (result, next) => {
|
||||
@@ -12111,5 +12295,5 @@ getJasmineRequireObj().UserContext = function(j$) {
|
||||
};
|
||||
|
||||
getJasmineRequireObj().version = function() {
|
||||
return '6.0.0-alpha.1';
|
||||
return '6.0.0-alpha.2';
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "6.0.0-alpha.1",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine.git"
|
||||
|
||||
29
release_notes/5.12.1.md
Normal file
29
release_notes/5.12.1.md
Normal file
@@ -0,0 +1,29 @@
|
||||
Jasmine Core 5.12.1 Release Notes
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix custom matchers in top-level specs
|
||||
* Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 141* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 144* |
|
||||
| Edge | 141* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
90
release_notes/6.0.0-alpha.2.md
Normal file
90
release_notes/6.0.0-alpha.2.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Jasmine Core 6.0.0-alpha.2 Release Notes
|
||||
|
||||
This is a pre-release, intended to offer a preview of upcoming changes and to
|
||||
solicit feedback.
|
||||
|
||||
## A Note About Pre-Release Compatibility
|
||||
|
||||
There may be additional breaking changes in future 6.0 pre-releases or in the
|
||||
final 6.0 release. That's allowed by the semver specification, but users are
|
||||
sometimes unpleasantly surprised by it.
|
||||
|
||||
NPM's implementation of carat version ranges assumes that subsequent
|
||||
pre-releases and final releases are fully compatible with earlier pre-releases.
|
||||
If your package.json contains `"jasmine-core": "^6.0.0-alpha.2`,
|
||||
NPM might install any later 6.x version even though there is no guarantee of
|
||||
compatibility. If that isn't ok, you should specify an exact pre-release version:
|
||||
`"jasmine-core": "6.0.0-alpha.2`.
|
||||
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
Safari 26 is now supported on a best-effort basis.†
|
||||
|
||||
Due to the limited availability of Safari 18 and later on free CI services,
|
||||
Safari support in future jasmine-core versions will be limited to:
|
||||
|
||||
* Best-effort support for the latest Safari version available on GitHub Actions,
|
||||
which may change at any time.
|
||||
* Best-effort support for Safari 16 and 17 for as long as it remains practical.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Fix custom matchers in top-level specs††
|
||||
* Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin
|
||||
|
||||
## New features
|
||||
|
||||
* Larger body font size in HTML reporters
|
||||
* New Performance tab in HtmlReporterV2 shows metrics and a list of the slowest
|
||||
specs.
|
||||
* Experimental `safariYieldStrategy: "time"` config option, which may make
|
||||
Jasmine run significantly faster in Safari and similar browsers. So far, this
|
||||
option has not been tested on a wide variety of workloads. Feedback is
|
||||
appreciated.
|
||||
* New `extraItStackFrames` and `extraDescribeStackFrames` config options to fix
|
||||
the filename properties of reporter events in configurations that wrap
|
||||
`it`/`describe`, such as zone.js.†
|
||||
* `jasmine.allOf asymmetric` equality tester†
|
||||
* Merges [#2087](https://github.com/jasmine/jasmine/pull/2087) from @jonahd-g
|
||||
* Fixes [#2083](https://github.com/jasmine/jasmine/issues/2083)
|
||||
* Re-add support for partial spec name filtering via `spec` query parameter
|
||||
* Fixes [#2085](https://github.com/jasmine/jasmine/issues/2085).
|
||||
* Require spec/suite property keys to be strings, not just anything that's
|
||||
cloneable and serializable. This matches the existing API reference
|
||||
documentation.
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Fix HtmlReporterV2 ctor example
|
||||
|
||||
## Internal Improvements
|
||||
|
||||
* Remove code to support browsers that don't have MessageChannel. Jasmine hasn't
|
||||
run in any such browsers since 2.x.
|
||||
|
||||
† Also likely to be included in a future 5.x release.<br>
|
||||
†† Also released in 5.12.1.
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 20, 22, 24 |
|
||||
| Safari | 16**, 17**, 26** |
|
||||
| Chrome | 142* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 145* |
|
||||
| Edge | 142* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Run tests in supported browsers that are available on Saucelabs.
|
||||
# Note: The latest Safari version is tested via GitHub Actions because Saucelabs
|
||||
# only makes it available to paid enterprise accounts. See
|
||||
# .github/workflows/safari.yml.
|
||||
|
||||
run_browser() {
|
||||
browser=$1
|
||||
version=$2
|
||||
@@ -10,7 +10,14 @@ describe('Configuration', function() {
|
||||
'detectLateRejectionHandling',
|
||||
'verboseDeprecations'
|
||||
];
|
||||
const allKeys = [...standardBooleanKeys, 'seed', 'specFilter'];
|
||||
const allKeys = [
|
||||
...standardBooleanKeys,
|
||||
'seed',
|
||||
'specFilter',
|
||||
'extraItStackFrames',
|
||||
'extraDescribeStackFrames',
|
||||
'safariYieldStrategy'
|
||||
];
|
||||
Object.freeze(standardBooleanKeys);
|
||||
Object.freeze(allKeys);
|
||||
|
||||
@@ -28,6 +35,9 @@ describe('Configuration', function() {
|
||||
expect(subject.forbidDuplicateNames).toEqual(true);
|
||||
expect(subject.verboseDeprecations).toEqual(false);
|
||||
expect(subject.detectLateRejectionHandling).toEqual(false);
|
||||
expect(subject.extraItStackFrames).toEqual(0);
|
||||
expect(subject.extraDescribeStackFrames).toEqual(0);
|
||||
expect(subject.safariYieldStrategy).toEqual('count');
|
||||
});
|
||||
|
||||
describe('copy()', function() {
|
||||
@@ -109,5 +119,48 @@ describe('Configuration', function() {
|
||||
subject.update({ seed: null });
|
||||
expect(subject.seed).toBeNull();
|
||||
});
|
||||
|
||||
it('sets extraItStackFrames when not undefined', function() {
|
||||
const subject = new privateUnderTest.Configuration();
|
||||
|
||||
subject.update({ extraItStackFrames: undefined });
|
||||
expect(subject.extraItStackFrames).toEqual(0);
|
||||
|
||||
subject.update({ extraItStackFrames: 100000 });
|
||||
expect(subject.extraItStackFrames).toEqual(100000);
|
||||
});
|
||||
|
||||
it('sets extraDescribeStackFrames when not undefined', function() {
|
||||
const subject = new privateUnderTest.Configuration();
|
||||
|
||||
subject.update({ extraDescribeStackFrames: undefined });
|
||||
expect(subject.extraDescribeStackFrames).toEqual(0);
|
||||
|
||||
subject.update({ extraDescribeStackFrames: 100000 });
|
||||
expect(subject.extraDescribeStackFrames).toEqual(100000);
|
||||
});
|
||||
|
||||
it('sets safariYieldStrategy when valid', function() {
|
||||
const subject = new privateUnderTest.Configuration();
|
||||
|
||||
subject.update({ safariYieldStrategy: undefined });
|
||||
expect(subject.safariYieldStrategy).toEqual('count');
|
||||
|
||||
subject.update({ safariYieldStrategy: 'time' });
|
||||
expect(subject.safariYieldStrategy).toEqual('time');
|
||||
|
||||
subject.update({ safariYieldStrategy: 'count' });
|
||||
expect(subject.safariYieldStrategy).toEqual('count');
|
||||
});
|
||||
|
||||
it('rejcts invalid safariYieldStrategy values', function() {
|
||||
const subject = new privateUnderTest.Configuration();
|
||||
|
||||
expect(function() {
|
||||
subject.update({ safariYieldStrategy: 'thyme' });
|
||||
}).toThrowError(
|
||||
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// TODO: Fix these unit tests!
|
||||
describe('Env', function() {
|
||||
let env;
|
||||
beforeEach(function() {
|
||||
@@ -95,7 +94,7 @@ describe('Env', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts its own current configureation', function() {
|
||||
it('accepts its own current configuration', function() {
|
||||
env.configure(env.configuration());
|
||||
});
|
||||
|
||||
@@ -198,6 +197,29 @@ describe('Env', function() {
|
||||
expect(innerSuite.parentSuite).toBe(suite);
|
||||
expect(spec.getFullName()).toEqual('outer suite inner suite a spec');
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraDescribeStackFrames is not set', function() {
|
||||
// IIFE is used to match the stack depth when global describe() is called
|
||||
const suite = (function() {
|
||||
return env[methodName]('a suite', function() {
|
||||
env.it('a spec');
|
||||
});
|
||||
})();
|
||||
expect(suite.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraDescribeStackFrames is set', function() {
|
||||
env.configure({ extraDescribeStackFrames: 2 });
|
||||
// IIFE is used to match the stack depth when global describe() is called
|
||||
const suite = (function() {
|
||||
return specHelpers.callerFilenameShim(function() {
|
||||
return env[methodName]('a suite', function() {
|
||||
env.it('a spec');
|
||||
});
|
||||
});
|
||||
})();
|
||||
expect(suite.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
}
|
||||
|
||||
describe('#describe', function() {
|
||||
@@ -300,6 +322,25 @@ describe('Env', function() {
|
||||
.not.toEqual('');
|
||||
expect(spec.pend).toBeFalsy();
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraItStackFrames is not set', function() {
|
||||
// IIFE is used to match the stack depth when global it() is called
|
||||
const spec = (function() {
|
||||
return env[methodName]('a spec', function() {});
|
||||
})();
|
||||
expect(spec.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraItStackFrames is set', function() {
|
||||
env.configure({ extraItStackFrames: 2 });
|
||||
// IIFE is used to match the stack depth when global it() is called
|
||||
const spec = (function() {
|
||||
return specHelpers.callerFilenameShim(function() {
|
||||
return env[methodName]('a spec', function() {});
|
||||
});
|
||||
})();
|
||||
expect(spec.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
}
|
||||
|
||||
describe('#it', function() {
|
||||
|
||||
@@ -486,7 +486,7 @@ describe('QueueRunner', function() {
|
||||
errorListeners.pop();
|
||||
}
|
||||
},
|
||||
clearStack = jasmine.createSpy('clearStack'),
|
||||
clearStack = jasmine.createSpyObj('clearStack', ['clearStack']),
|
||||
onException = jasmine.createSpy('onException'),
|
||||
queueRunner = new privateUnderTest.QueueRunner({
|
||||
queueableFns: [queueableFn],
|
||||
@@ -498,10 +498,10 @@ describe('QueueRunner', function() {
|
||||
|
||||
queueRunner.execute();
|
||||
jasmine.clock().tick();
|
||||
expect(clearStack).toHaveBeenCalled();
|
||||
expect(clearStack.clearStack).toHaveBeenCalled();
|
||||
expect(errorListeners.length).toEqual(1);
|
||||
errorListeners[0](error);
|
||||
clearStack.calls.argsFor(0)[0]();
|
||||
clearStack.clearStack.calls.argsFor(0)[0]();
|
||||
expect(onException).toHaveBeenCalledWith(error);
|
||||
});
|
||||
});
|
||||
@@ -908,22 +908,22 @@ describe('QueueRunner', function() {
|
||||
},
|
||||
afterFn = { fn: jasmine.createSpy('afterFn') },
|
||||
completeCallback = jasmine.createSpy('completeCallback'),
|
||||
clearStack = jasmine.createSpy('clearStack'),
|
||||
clearStack = jasmine.createSpyObj('clearStack', ['clearStack']),
|
||||
queueRunner = new privateUnderTest.QueueRunner({
|
||||
queueableFns: [asyncFn, afterFn],
|
||||
clearStack: clearStack,
|
||||
onComplete: completeCallback
|
||||
});
|
||||
|
||||
clearStack.and.callFake(function(fn) {
|
||||
clearStack.clearStack.and.callFake(function(fn) {
|
||||
fn();
|
||||
});
|
||||
|
||||
queueRunner.execute();
|
||||
jasmine.clock().tick();
|
||||
expect(afterFn.fn).toHaveBeenCalled();
|
||||
expect(clearStack).toHaveBeenCalled();
|
||||
clearStack.calls.argsFor(0)[0]();
|
||||
expect(clearStack.clearStack).toHaveBeenCalled();
|
||||
clearStack.clearStack.calls.argsFor(0)[0]();
|
||||
expect(completeCallback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,26 +103,14 @@ describe('Spec', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the key is not structured-cloneable', function() {
|
||||
it('throws if the key is not a string', function() {
|
||||
const spec = new privateUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
spec.setSpecProperty(new Promise(() => {}), '');
|
||||
}).toThrowError("Key can't be cloned");
|
||||
});
|
||||
|
||||
it('throws if the key is not JSON-serializable', function() {
|
||||
const spec = new privateUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
const k = {};
|
||||
k.self = k;
|
||||
spec.setSpecProperty(k, '');
|
||||
}).toThrowError("Key can't be cloned");
|
||||
spec.setSpecProperty({}, '');
|
||||
}).toThrowError('Key must be a string');
|
||||
});
|
||||
|
||||
it('throws if the value is not structured-cloneable', function() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
describe('ClearStack', function() {
|
||||
describe('StackClearer', function() {
|
||||
it('works in an integrationy way', function(done) {
|
||||
const clearStack = privateUnderTest.getClearStack(
|
||||
const { clearStack } = privateUnderTest.getStackClearer(
|
||||
jasmineUnderTest.getGlobal()
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('ClearStack', function() {
|
||||
queueMicrotask
|
||||
};
|
||||
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
clearStack(function() {});
|
||||
@@ -73,17 +73,6 @@ describe('ClearStack', function() {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe('when MessageChannel is unavailable', function() {
|
||||
usesQueueMicrotaskWithSetTimeout(function() {
|
||||
return {
|
||||
navigator: {
|
||||
userAgent: 'CERN-LineMode/2.15 libwww/2.17b3',
|
||||
MessageChannel: undefined
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in Node', function() {
|
||||
@@ -104,7 +93,7 @@ describe('ClearStack', function() {
|
||||
...makeGlobal(),
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
let called = false;
|
||||
|
||||
clearStack(function() {
|
||||
@@ -125,7 +114,7 @@ describe('ClearStack', function() {
|
||||
return fakeChannel;
|
||||
}
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
clearStack(function() {});
|
||||
@@ -150,7 +139,7 @@ describe('ClearStack', function() {
|
||||
setTimeout,
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
const fn = jasmine.createSpy('second clearStack function');
|
||||
|
||||
clearStack(function() {
|
||||
@@ -170,7 +159,7 @@ describe('ClearStack', function() {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
let called = false;
|
||||
|
||||
clearStack(function() {
|
||||
@@ -180,30 +169,82 @@ describe('ClearStack', function() {
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('uses setTimeout instead of queueMicrotask every 10 calls to make sure we release the CPU', function() {
|
||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask,
|
||||
setTimeout
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
function hasSetTimeoutBehavior(configure) {
|
||||
it('uses setTimeout instead of queueMicrotask every 10 calls', function() {
|
||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask,
|
||||
setTimeout
|
||||
};
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
clearStack(function() {});
|
||||
}
|
||||
if (configure) {
|
||||
configure(stackClearer);
|
||||
}
|
||||
|
||||
expect(queueMicrotask).toHaveBeenCalled();
|
||||
expect(setTimeout).not.toHaveBeenCalled();
|
||||
for (let i = 0; i < 9; i++) {
|
||||
stackClearer.clearStack(function() {});
|
||||
}
|
||||
|
||||
clearStack(function() {});
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(9);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
expect(queueMicrotask).toHaveBeenCalled();
|
||||
expect(setTimeout).not.toHaveBeenCalled();
|
||||
|
||||
clearStack(function() {});
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
stackClearer.clearStack(function() {});
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(9);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
|
||||
stackClearer.clearStack(function() {});
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}
|
||||
|
||||
hasSetTimeoutBehavior();
|
||||
|
||||
describe('With yield strategy explicitly set to count', function() {
|
||||
hasSetTimeoutBehavior(function(stackClearer) {
|
||||
stackClearer.setSafariYieldStrategy('count');
|
||||
});
|
||||
});
|
||||
|
||||
describe('With yield strategy set to time', function() {
|
||||
beforeEach(function() {
|
||||
jasmine.clock().install();
|
||||
jasmine.clock().mockDate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
it('uses setTimeout instead of queueMicrotask every 25 milliseconds', function() {
|
||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask,
|
||||
setTimeout
|
||||
};
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
stackClearer.setSafariYieldStrategy('time');
|
||||
|
||||
// 10+ counts should not trigger a setTimeout if they happen fast enough
|
||||
jasmine.clock().tick(24);
|
||||
for (let i = 0; i < 11; i++) {
|
||||
stackClearer.clearStack(function() {});
|
||||
}
|
||||
|
||||
expect(queueMicrotask).toHaveBeenCalled();
|
||||
expect(setTimeout).not.toHaveBeenCalled();
|
||||
|
||||
queueMicrotask.calls.reset();
|
||||
jasmine.clock().tick(1);
|
||||
stackClearer.clearStack(function() {});
|
||||
expect(queueMicrotask).not.toHaveBeenCalled();
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,7 +256,7 @@ describe('ClearStack', function() {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
let called = false;
|
||||
|
||||
clearStack(function() {
|
||||
@@ -233,7 +274,7 @@ describe('ClearStack', function() {
|
||||
queueMicrotask,
|
||||
setTimeout
|
||||
};
|
||||
const clearStack = privateUnderTest.getClearStack(global);
|
||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
||||
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
@@ -409,12 +409,12 @@ describe('Suite', function() {
|
||||
});
|
||||
|
||||
describe('#setSuiteProperty', function() {
|
||||
it('throws if the key is not structured-cloneable', function() {
|
||||
it('throws if the key is not a string', function() {
|
||||
const suite = new privateUnderTest.Suite({});
|
||||
|
||||
expect(function() {
|
||||
suite.setSuiteProperty(new Promise(() => {}), '');
|
||||
}).toThrowError("Key can't be cloned");
|
||||
suite.setSuiteProperty({}, '');
|
||||
}).toThrowError('Key must be a string');
|
||||
});
|
||||
|
||||
it('throws if the value is not structured-cloneable', function() {
|
||||
@@ -424,6 +424,16 @@ describe('Suite', function() {
|
||||
suite.setSuiteProperty('k', new Promise(() => {}));
|
||||
}).toThrowError("Value can't be cloned");
|
||||
});
|
||||
|
||||
it('throws if the value is not JSON-serializable', function() {
|
||||
const suite = new privateUnderTest.Suite({});
|
||||
|
||||
expect(function() {
|
||||
const v = {};
|
||||
v.self = v;
|
||||
suite.setSuiteProperty('k', v);
|
||||
}).toThrowError("Value can't be cloned");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#startedEvent', function() {
|
||||
|
||||
63
spec/core/asymmetric_equality/AllOfSpec.js
Normal file
63
spec/core/asymmetric_equality/AllOfSpec.js
Normal file
@@ -0,0 +1,63 @@
|
||||
describe('AllOf', function() {
|
||||
it('matches a single value', function() {
|
||||
const matchersUtil = new privateUnderTest.MatchersUtil();
|
||||
const allOf = new privateUnderTest.AllOf('foo');
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
|
||||
});
|
||||
|
||||
it('matches a single matcher', function() {
|
||||
const matchersUtil = new privateUnderTest.MatchersUtil();
|
||||
const allOf = new privateUnderTest.AllOf(
|
||||
new privateUnderTest.StringContaining('oo')
|
||||
);
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
|
||||
});
|
||||
|
||||
it('matches multiple matchers', function() {
|
||||
const matchersUtil = new privateUnderTest.MatchersUtil();
|
||||
const allOf = new privateUnderTest.AllOf(
|
||||
new privateUnderTest.StringContaining('o'),
|
||||
new privateUnderTest.StringContaining('f')
|
||||
);
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
|
||||
});
|
||||
|
||||
it('does not match when value does not match', function() {
|
||||
const matchersUtil = new privateUnderTest.MatchersUtil();
|
||||
const allOf = new privateUnderTest.AllOf('bar');
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse();
|
||||
});
|
||||
|
||||
it('does not match when any matchers fail', function() {
|
||||
const matchersUtil = new privateUnderTest.MatchersUtil();
|
||||
const allOf = new privateUnderTest.AllOf(
|
||||
new privateUnderTest.StringContaining('o'),
|
||||
new privateUnderTest.StringContaining('x')
|
||||
);
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse();
|
||||
});
|
||||
|
||||
it('jasmineToStrings itself', function() {
|
||||
const matcher = new privateUnderTest.AllOf('o');
|
||||
const pp = jasmine.createSpy('pp').and.returnValue('sample');
|
||||
|
||||
expect(matcher.jasmineToString(pp)).toEqual('<jasmine.allOf(sample)>');
|
||||
expect(pp).toHaveBeenCalledWith(['o']);
|
||||
});
|
||||
|
||||
describe('when called without an argument', function() {
|
||||
it('tells the user to pass a constructor argument', function() {
|
||||
expect(function() {
|
||||
new privateUnderTest.AllOf();
|
||||
}).toThrowError(
|
||||
TypeError,
|
||||
'jasmine.allOf() expects at least one argument to be passed.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1114,8 +1114,17 @@ describe('Env integration', function() {
|
||||
global: {
|
||||
setTimeout: globalSetTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
// Enough Node globals to make getStackClearer() return the microtask
|
||||
// implementation, which is the easiest to mock
|
||||
process: {
|
||||
versions: {
|
||||
node: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1179,7 +1188,7 @@ describe('Env integration', function() {
|
||||
global: {
|
||||
setTimeout: function(cb, t) {
|
||||
const stack = new Error().stack;
|
||||
if (stack.indexOf('ClearStack') >= 0) {
|
||||
if (stack.indexOf('clearStack') >= 0) {
|
||||
return realSetTimeout(cb, t);
|
||||
} else {
|
||||
return setTimeout(cb, t);
|
||||
@@ -1188,8 +1197,17 @@ describe('Env integration', function() {
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval,
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
// Enough Node globals to make getStackClearer() return the microtask
|
||||
// implementation, which is the easiest to mock
|
||||
process: {
|
||||
versions: {
|
||||
node: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2274,6 +2292,34 @@ describe('Env integration', function() {
|
||||
await env.execute();
|
||||
});
|
||||
|
||||
it('Custom matchers set in top-level beforeAll should be available to all specs and suites', async function() {
|
||||
const matchers = {
|
||||
toFoo: function() {}
|
||||
};
|
||||
|
||||
env.beforeAll(function() {
|
||||
env.addMatchers(matchers);
|
||||
});
|
||||
|
||||
env.describe('suite - top-level', function() {
|
||||
env.it('has access to the custom matcher', function() {
|
||||
expect(env.expect().toFoo).toBeDefined();
|
||||
});
|
||||
|
||||
env.describe('suite - nested', function() {
|
||||
env.it('has access to the custom matcher', function() {
|
||||
expect(env.expect().toFoo).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
env.it('spec - top-level - has access to the custom matcher', function() {
|
||||
expect(env.expect().toFoo).toBeDefined();
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
});
|
||||
|
||||
it('throws an exception if you try to create a spy outside of a runnable', async function() {
|
||||
const obj = { fn: function() {} };
|
||||
let exception;
|
||||
@@ -2775,12 +2821,18 @@ describe('Env integration', function() {
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
// Enough Node globals to make getStackClearer() return the microtask
|
||||
// implementation, which is the easiest to mock
|
||||
process: {
|
||||
versions: {
|
||||
node: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
const reporter = jasmine.createSpyObj('reporter', [
|
||||
'jasmineDone',
|
||||
'suiteDone',
|
||||
@@ -3228,13 +3280,11 @@ describe('Env integration', function() {
|
||||
});
|
||||
|
||||
it('is resolved after the stack is cleared', function(done) {
|
||||
const realClearStack = privateUnderTest.getClearStack(
|
||||
jasmineUnderTest.getGlobal()
|
||||
),
|
||||
clearStackSpy = jasmine
|
||||
.createSpy('clearStack')
|
||||
.and.callFake(realClearStack);
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(clearStackSpy);
|
||||
const stackClearer = privateUnderTest.getStackClearer(
|
||||
jasmineUnderTest.getGlobal()
|
||||
);
|
||||
spyOn(stackClearer, 'clearStack').and.callThrough();
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
|
||||
|
||||
// Create a new env that has the clearStack defined above
|
||||
env.cleanup_();
|
||||
@@ -3245,10 +3295,10 @@ describe('Env integration', function() {
|
||||
});
|
||||
|
||||
env.execute(null).then(function() {
|
||||
expect(clearStackSpy).toHaveBeenCalled(); // (many times)
|
||||
clearStackSpy.calls.reset();
|
||||
expect(stackClearer.clearStack).toHaveBeenCalled(); // (many times)
|
||||
stackClearer.clearStack.calls.reset();
|
||||
setTimeout(function() {
|
||||
expect(clearStackSpy).not.toHaveBeenCalled();
|
||||
expect(stackClearer.clearStack).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -3337,13 +3387,11 @@ describe('Env integration', function() {
|
||||
});
|
||||
|
||||
it('is called after the stack is cleared', async function() {
|
||||
const realClearStack = privateUnderTest.getClearStack(
|
||||
jasmineUnderTest.getGlobal()
|
||||
),
|
||||
clearStackSpy = jasmine
|
||||
.createSpy('clearStack')
|
||||
.and.callFake(realClearStack);
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(clearStackSpy);
|
||||
const stackClearer = privateUnderTest.getStackClearer(
|
||||
jasmineUnderTest.getGlobal()
|
||||
);
|
||||
spyOn(stackClearer, 'clearStack').and.callThrough();
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
|
||||
|
||||
// Create a new env that has the clearStack defined above
|
||||
env.cleanup_();
|
||||
@@ -3355,12 +3403,12 @@ describe('Env integration', function() {
|
||||
|
||||
await env.execute();
|
||||
|
||||
expect(clearStackSpy).toHaveBeenCalled(); // (many times)
|
||||
clearStackSpy.calls.reset();
|
||||
expect(stackClearer.clearStack).toHaveBeenCalled(); // (many times)
|
||||
stackClearer.clearStack.calls.reset();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
|
||||
expect(clearStackSpy).not.toHaveBeenCalled();
|
||||
expect(stackClearer.clearStack).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('is called after QueueRunner timeouts are cleared', async function() {
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('Global error handling (integration)', function() {
|
||||
env.cleanup_();
|
||||
});
|
||||
|
||||
it('reports errors that occur during loading', async function() {
|
||||
const global = {
|
||||
function mockGlobal() {
|
||||
return {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
@@ -23,12 +23,21 @@ describe('Global error handling (integration)', function() {
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
onerror: function() {}
|
||||
onerror: function() {},
|
||||
// Enough Node globals to make getStackClearer() return the microtask
|
||||
// implementation, which is the easiest to mock
|
||||
process: {
|
||||
versions: {
|
||||
node: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
}
|
||||
|
||||
it('reports errors that occur during loading', async function() {
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
const reporter = jasmine.createSpyObj('reporter', [
|
||||
'jasmineDone',
|
||||
'suiteDone',
|
||||
@@ -70,21 +79,9 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('If suppressLoadErrors: true was passed', function() {
|
||||
it('does not install a global error handler during loading', async function() {
|
||||
const global = mockGlobal();
|
||||
const originalOnerror = jasmine.createSpy('original onerror');
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
onerror: originalOnerror
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
global.onerror = originalOnerror;
|
||||
const globalErrors = new privateUnderTest.GlobalErrors(global);
|
||||
const onerror = jasmine.createSpy('onerror');
|
||||
globalErrors.pushListener(onerror);
|
||||
@@ -92,6 +89,7 @@ describe('Global error handling (integration)', function() {
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env({
|
||||
suppressLoadErrors: true,
|
||||
global,
|
||||
GlobalErrors: function() {
|
||||
return globalErrors;
|
||||
}
|
||||
@@ -115,21 +113,9 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('Handling unhandled exceptions', function() {
|
||||
it('routes unhandled exceptions to the running spec', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
'suiteDone'
|
||||
@@ -156,24 +142,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the most recently running spec has reported specDone', function() {
|
||||
it('routes unhandled exceptions to an ancestor suite', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn) {
|
||||
clearTimeout(fn);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
const realClearStack = privateUnderTest.getClearStack(global);
|
||||
const global = mockGlobal();
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
const realClearStack = stackClearer.clearStack;
|
||||
const clearStackCallbacks = {};
|
||||
let clearStackCallCount = 0;
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
|
||||
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
|
||||
clearStackCallCount++;
|
||||
|
||||
if (clearStackCallbacks[clearStackCallCount]) {
|
||||
@@ -182,9 +156,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
|
||||
stackClearer
|
||||
);
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
let suiteErrors = [];
|
||||
env.addReporter({
|
||||
@@ -220,21 +197,9 @@ describe('Global error handling (integration)', function() {
|
||||
});
|
||||
|
||||
it('routes unhandled exceptions to the running suite', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
'suiteDone'
|
||||
@@ -273,24 +238,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the most recently suite has reported suiteDone', function() {
|
||||
it('routes unhandled exceptions to an ancestor suite', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
const realClearStack = privateUnderTest.getClearStack(global);
|
||||
const global = mockGlobal();
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
const realClearStack = stackClearer.clearStack;
|
||||
const clearStackCallbacks = {};
|
||||
let clearStackCallCount = 0;
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
|
||||
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
|
||||
clearStackCallCount++;
|
||||
|
||||
if (clearStackCallbacks[clearStackCallCount]) {
|
||||
@@ -299,9 +252,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
|
||||
stackClearer
|
||||
);
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
let suiteErrors = [];
|
||||
env.addReporter({
|
||||
@@ -341,21 +297,9 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the env has started reporting jasmineDone', function() {
|
||||
it('logs the error to the console', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
spyOn(console, 'error');
|
||||
|
||||
@@ -384,26 +328,14 @@ describe('Global error handling (integration)', function() {
|
||||
});
|
||||
|
||||
it('routes all errors that occur during stack clearing somewhere', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn) {
|
||||
clearTimeout(fn);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
const realClearStack = privateUnderTest.getClearStack(global);
|
||||
const global = mockGlobal();
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
const realClearStack = stackClearer.clearStack;
|
||||
let clearStackCallCount = 0;
|
||||
let jasmineDone = false;
|
||||
const expectedErrors = [];
|
||||
const expectedErrorsAfterJasmineDone = [];
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
|
||||
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
|
||||
clearStackCallCount++;
|
||||
const msg = `Error in clearStack #${clearStackCallCount}`;
|
||||
|
||||
@@ -416,10 +348,11 @@ describe('Global error handling (integration)', function() {
|
||||
dispatchErrorEvent(global, 'error', { error: msg });
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
|
||||
spyOn(console, 'error');
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
const receivedErrors = [];
|
||||
function logErrors(event) {
|
||||
@@ -459,21 +392,9 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('Handling unhandled promise rejections', function() {
|
||||
it('routes unhandled promise rejections to the running spec', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
'suiteDone'
|
||||
@@ -502,24 +423,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the most recently running spec has reported specDone', function() {
|
||||
it('routes unhandled promise rejections to an ancestor suite', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn) {
|
||||
clearTimeout(fn);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
const realClearStack = privateUnderTest.getClearStack(global);
|
||||
const global = mockGlobal();
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
const realClearStack = stackClearer.clearStack;
|
||||
const clearStackCallbacks = {};
|
||||
let clearStackCallCount = 0;
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
|
||||
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
|
||||
clearStackCallCount++;
|
||||
|
||||
if (clearStackCallbacks[clearStackCallCount]) {
|
||||
@@ -528,9 +437,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
|
||||
stackClearer
|
||||
);
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
let suiteErrors = [];
|
||||
env.addReporter({
|
||||
@@ -566,21 +478,9 @@ describe('Global error handling (integration)', function() {
|
||||
});
|
||||
|
||||
it('routes unhandled promise rejections to the running suite', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
'suiteDone'
|
||||
@@ -621,24 +521,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the most recently suite has reported suiteDone', function() {
|
||||
it('routes unhandled promise rejections to an ancestor suite', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
const realClearStack = privateUnderTest.getClearStack(global);
|
||||
const global = mockGlobal();
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
const realClearStack = stackClearer.clearStack;
|
||||
const clearStackCallbacks = {};
|
||||
let clearStackCallCount = 0;
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
|
||||
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
|
||||
clearStackCallCount++;
|
||||
|
||||
if (clearStackCallbacks[clearStackCallCount]) {
|
||||
@@ -647,9 +535,12 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
|
||||
stackClearer
|
||||
);
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
let suiteErrors = [];
|
||||
env.addReporter({
|
||||
@@ -689,21 +580,9 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the env has started reporting jasmineDone', function() {
|
||||
it('logs the rejection to the console', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
spyOn(console, 'error');
|
||||
|
||||
@@ -734,26 +613,14 @@ describe('Global error handling (integration)', function() {
|
||||
});
|
||||
|
||||
it('routes all unhandled promise rejections that occur during stack clearing somewhere', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn) {
|
||||
clearTimeout(fn);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
|
||||
const realClearStack = privateUnderTest.getClearStack(global);
|
||||
const global = mockGlobal();
|
||||
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||
const realClearStack = stackClearer.clearStack;
|
||||
let clearStackCallCount = 0;
|
||||
let jasmineDone = false;
|
||||
const expectedErrors = [];
|
||||
const expectedErrorsAfterJasmineDone = [];
|
||||
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
|
||||
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
|
||||
clearStackCallCount++;
|
||||
const reason = `Error in clearStack #${clearStackCallCount}`;
|
||||
const expectedMsg = `Unhandled promise rejection: ${reason} thrown`;
|
||||
@@ -767,10 +634,11 @@ describe('Global error handling (integration)', function() {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', { reason });
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
|
||||
spyOn(console, 'error');
|
||||
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
|
||||
const receivedErrors = [];
|
||||
function logErrors(event) {
|
||||
@@ -819,21 +687,9 @@ describe('Global error handling (integration)', function() {
|
||||
let global, reporter;
|
||||
|
||||
beforeEach(function() {
|
||||
global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
env.configure({ detectLateRejectionHandling: true });
|
||||
|
||||
reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
@@ -966,21 +822,8 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe("When the unhandled rejection event doesn't have a promise", function() {
|
||||
it('reports the rejection', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
const global = mockGlobal();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
env.configure({ detectLateRejectionHandling: true });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
@@ -1012,21 +855,9 @@ describe('Global error handling (integration)', function() {
|
||||
});
|
||||
|
||||
it('works when the suite is run multiple times', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
const global = mockGlobal();
|
||||
env.cleanup_();
|
||||
env = new privateUnderTest.Env();
|
||||
env = new privateUnderTest.Env({ global });
|
||||
env.configure({ autoCleanClosures: false });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', ['specDone']);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ describe('The jasmine namespace', function() {
|
||||
'version',
|
||||
|
||||
// Asymmetric equality testers
|
||||
'allOf',
|
||||
'any',
|
||||
'anything',
|
||||
'arrayContaining',
|
||||
|
||||
5
spec/helpers/callerFilenameShim.js
Normal file
5
spec/helpers/callerFilenameShim.js
Normal file
@@ -0,0 +1,5 @@
|
||||
(function() {
|
||||
specHelpers.callerFilenameShim = function(fn) {
|
||||
return fn();
|
||||
};
|
||||
})();
|
||||
@@ -167,18 +167,18 @@ describe('HtmlReporterV2', function() {
|
||||
'.jasmine-alert .jasmine-bar'
|
||||
);
|
||||
|
||||
expect(alertBars.length).toEqual(4);
|
||||
expect(alertBars[1].innerHTML).toMatch(
|
||||
expect(alertBars.length).toEqual(5);
|
||||
expect(alertBars[2].innerHTML).toMatch(
|
||||
/spec deprecation.*\(in spec: a spec with a deprecation\)/
|
||||
);
|
||||
expect(alertBars[1].getAttribute('class')).toEqual(
|
||||
expect(alertBars[2].getAttribute('class')).toEqual(
|
||||
'jasmine-bar jasmine-warning'
|
||||
);
|
||||
expect(alertBars[2].innerHTML).toMatch(
|
||||
expect(alertBars[3].innerHTML).toMatch(
|
||||
/suite deprecation.*\(in suite: a suite with a deprecation\)/
|
||||
);
|
||||
expect(alertBars[3].innerHTML).toMatch(/global deprecation/);
|
||||
expect(alertBars[3].innerHTML).not.toMatch(/in /);
|
||||
expect(alertBars[4].innerHTML).toMatch(/global deprecation/);
|
||||
expect(alertBars[4].innerHTML).not.toMatch(/in /);
|
||||
});
|
||||
|
||||
it('displays expandable stack traces', function() {
|
||||
@@ -259,6 +259,214 @@ describe('HtmlReporterV2', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('The tab bar', function() {
|
||||
function checkHidden(tabs, expected) {
|
||||
const actual = Array.from(tabs).map(t =>
|
||||
t.classList.contains('jasmine-hidden')
|
||||
);
|
||||
expect(actual)
|
||||
.withContext('tab hiddenness')
|
||||
.toEqual(expected);
|
||||
}
|
||||
|
||||
describe('while Jasmine is running', function() {
|
||||
it('hides all tabs', function() {
|
||||
const reporter = setup();
|
||||
reporter.initialize();
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 0 });
|
||||
const tabs = container.querySelectorAll('.jasmine-tab');
|
||||
expect(tabs.length).toEqual(3);
|
||||
expect(tabs[0].textContent).toEqual('Spec List');
|
||||
expect(tabs[1].textContent).toEqual('Failures');
|
||||
expect(tabs[2].textContent).toEqual('Performance');
|
||||
checkHidden(tabs, [true, true, true]);
|
||||
|
||||
// Results, even failures, should not show any tabs
|
||||
reporter.specDone({
|
||||
id: 1,
|
||||
description: 'a failing spec',
|
||||
fullName: 'a failing spec',
|
||||
status: 'failed',
|
||||
failedExpectations: [{}],
|
||||
passedExpectations: []
|
||||
});
|
||||
checkHidden(tabs, [true, true, true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Jasmine is done', function() {
|
||||
function hasSpecOrSuiteFailureBehavior(reportEvents) {
|
||||
let reporter;
|
||||
|
||||
beforeEach(function() {
|
||||
reporter = setup();
|
||||
reporter.initialize();
|
||||
reportEvents(reporter);
|
||||
});
|
||||
|
||||
it('shows all three tabs', function() {
|
||||
const tabs = container.querySelectorAll('.jasmine-tab');
|
||||
checkHidden(tabs, [false, false, false]);
|
||||
});
|
||||
|
||||
it('selects the Failures tab', function() {
|
||||
const reporterNode = container.querySelector(
|
||||
'.jasmine_html-reporter'
|
||||
);
|
||||
expect(reporterNode).toHaveClass('jasmine-failure-list');
|
||||
});
|
||||
|
||||
it('switches between failure details and the spec summary', function() {
|
||||
const tabs = container.querySelectorAll('.jasmine-tab');
|
||||
let specListLink = () => tabs[0].querySelector('a');
|
||||
let failuresLink = () => tabs[1].querySelector('a');
|
||||
const reporterNode = container.querySelector(
|
||||
'.jasmine_html-reporter'
|
||||
);
|
||||
expect(specListLink().textContent).toEqual('Spec List');
|
||||
expect(failuresLink())
|
||||
.withContext('failures link')
|
||||
.toBeFalsy();
|
||||
|
||||
specListLink().click();
|
||||
expect(reporterNode).toHaveClass('jasmine-spec-list');
|
||||
expect(reporterNode).not.toHaveClass('jasmine-failure-list');
|
||||
expect(specListLink())
|
||||
.withContext('spec list link')
|
||||
.toBeFalsy();
|
||||
expect(failuresLink().textContent).toEqual('Failures');
|
||||
|
||||
failuresLink().click();
|
||||
expect(reporterNode.getAttribute('class')).toMatch(
|
||||
'jasmine-failure-list'
|
||||
);
|
||||
expect(failuresLink())
|
||||
.withContext('failures link')
|
||||
.toBeFalsy();
|
||||
expect(specListLink().textContent).toEqual('Spec List');
|
||||
expect(reporterNode).toHaveClass('jasmine-failure-list');
|
||||
expect(reporterNode).not.toHaveClass('jasmine-spec-list');
|
||||
});
|
||||
}
|
||||
|
||||
function hasSpecAndSuiteSuccessBehavior(reportEvents) {
|
||||
let reporter;
|
||||
|
||||
beforeEach(function() {
|
||||
reporter = setup();
|
||||
reporter.initialize();
|
||||
reportEvents(reporter);
|
||||
});
|
||||
|
||||
it('shows the Spec List and Performance tabs', function() {
|
||||
const tabs = container.querySelectorAll('.jasmine-tab');
|
||||
checkHidden(tabs, [false, true, false]);
|
||||
});
|
||||
|
||||
it('shows the spec list view', function() {
|
||||
const reporterNode = container.querySelector(
|
||||
'.jasmine_html-reporter'
|
||||
);
|
||||
expect(reporterNode).toHaveClass('jasmine-spec-list');
|
||||
expect(reporterNode).not.toHaveClass('jasmine-failure-list');
|
||||
});
|
||||
}
|
||||
|
||||
describe('with spec failures', function() {
|
||||
hasSpecOrSuiteFailureBehavior(function(reporter) {
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 0 });
|
||||
reporter.specDone({
|
||||
id: 1,
|
||||
description: 'a failing spec',
|
||||
fullName: 'a failing spec',
|
||||
status: 'failed',
|
||||
failedExpectations: [{}],
|
||||
passedExpectations: []
|
||||
});
|
||||
reporter.specDone({
|
||||
id: 2,
|
||||
description: 'a passing spec',
|
||||
fullName: 'a passing spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: []
|
||||
});
|
||||
reporter.jasmineDone({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with suite failures', function() {
|
||||
hasSpecOrSuiteFailureBehavior(function(reporter) {
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 0 });
|
||||
reporter.specDone({
|
||||
id: 1,
|
||||
description: 'a failing spec',
|
||||
fullName: 'a failing spec',
|
||||
status: 'failed',
|
||||
failedExpectations: [{}],
|
||||
passedExpectations: []
|
||||
});
|
||||
reporter.specDone({
|
||||
id: 2,
|
||||
description: 'a passing spec',
|
||||
fullName: 'a passing spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: []
|
||||
});
|
||||
reporter.jasmineDone({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without any failures', function() {
|
||||
hasSpecAndSuiteSuccessBehavior(function(reporter) {
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 0 });
|
||||
reporter.specDone({
|
||||
id: 1,
|
||||
description: 'a passing spec',
|
||||
fullName: 'a passing spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: []
|
||||
});
|
||||
reporter.suiteDone({ id: 1 });
|
||||
reporter.jasmineDone({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with only top suite failures', function() {
|
||||
// Top suite failures are displayed in their own alert bars, so they
|
||||
// don't cause the failures tab to be shown.
|
||||
hasSpecAndSuiteSuccessBehavior(function(reporter) {
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 0 });
|
||||
reporter.jasmineDone({
|
||||
failedExpectations: [{}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the slow spec view when the Performance tab is clicked', function() {
|
||||
const reporter = setup();
|
||||
reporter.initialize();
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 0 });
|
||||
reporter.specDone({
|
||||
duration: 1.2,
|
||||
failedExpectations: [],
|
||||
passedExpectations: []
|
||||
});
|
||||
reporter.jasmineDone({});
|
||||
const tabs = container.querySelectorAll('.jasmine-tab');
|
||||
let perfLink = tabs[2].querySelector('a');
|
||||
const reporterNode = container.querySelector('.jasmine_html-reporter');
|
||||
expect(perfLink.textContent).toEqual('Performance');
|
||||
perfLink.click();
|
||||
expect(reporterNode).toHaveClass('jasmine-performance');
|
||||
expect(reporterNode.innerHTML).toContain('<h2>Performance</h2>');
|
||||
expect(reporterNode.innerHTML).toContain('<td>1.2ms</td>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Jasmine is done', function() {
|
||||
it('adds a warning to the link title of specs that have no expectations', function() {
|
||||
const reporter = setup();
|
||||
@@ -449,21 +657,18 @@ describe('HtmlReporterV2', function() {
|
||||
]
|
||||
});
|
||||
|
||||
const alertBars = container.querySelectorAll(
|
||||
'.jasmine-alert .jasmine-bar'
|
||||
const errorBars = container.querySelectorAll(
|
||||
'.jasmine-alert .jasmine-bar.jasmine-errored'
|
||||
);
|
||||
|
||||
expect(alertBars.length).toEqual(3);
|
||||
expect(alertBars[1].getAttribute('class')).toEqual(
|
||||
'jasmine-bar jasmine-errored'
|
||||
);
|
||||
expect(alertBars[1].innerHTML).toMatch(
|
||||
expect(errorBars.length).toEqual(2);
|
||||
expect(errorBars[0].innerHTML).toMatch(
|
||||
/AfterAll Global After All Failure/
|
||||
);
|
||||
expect(alertBars[2].innerHTML).toMatch(
|
||||
expect(errorBars[1].innerHTML).toMatch(
|
||||
/Error during loading: Your JS is borken/
|
||||
);
|
||||
expect(alertBars[2].innerHTML).not.toMatch(/line/);
|
||||
expect(errorBars[1].innerHTML).not.toMatch(/line/);
|
||||
});
|
||||
|
||||
it('does not display the "AfterAll" prefix for other error types', function() {
|
||||
@@ -482,16 +687,16 @@ describe('HtmlReporterV2', function() {
|
||||
]
|
||||
});
|
||||
|
||||
const alertBars = container.querySelectorAll(
|
||||
'.jasmine-alert .jasmine-bar'
|
||||
const errorBars = container.querySelectorAll(
|
||||
'.jasmine-alert .jasmine-bar.jasmine-errored'
|
||||
);
|
||||
|
||||
expect(alertBars.length).toEqual(4);
|
||||
expect(alertBars[1].textContent).toContain('load error');
|
||||
expect(alertBars[2].textContent).toContain('lateExpectation error');
|
||||
expect(alertBars[3].textContent).toContain('lateError error');
|
||||
expect(errorBars.length).toEqual(3);
|
||||
expect(errorBars[0].textContent).toContain('load error');
|
||||
expect(errorBars[1].textContent).toContain('lateExpectation error');
|
||||
expect(errorBars[2].textContent).toContain('lateError error');
|
||||
|
||||
for (let bar of alertBars) {
|
||||
for (let bar of errorBars) {
|
||||
expect(bar.textContent).not.toContain('AfterAll');
|
||||
}
|
||||
});
|
||||
@@ -512,12 +717,12 @@ describe('HtmlReporterV2', function() {
|
||||
]
|
||||
});
|
||||
|
||||
const alertBars = container.querySelectorAll(
|
||||
'.jasmine-alert .jasmine-bar'
|
||||
const alertBar = container.querySelector(
|
||||
'.jasmine-alert .jasmine-bar.jasmine-errored'
|
||||
);
|
||||
|
||||
expect(alertBars.length).toEqual(2);
|
||||
expect(alertBars[1].innerHTML).toMatch(
|
||||
expect(alertBar).toBeTruthy();
|
||||
expect(alertBar.innerHTML).toMatch(
|
||||
/Error during loading: Your JS is borken in some\/file.js line 42/
|
||||
);
|
||||
});
|
||||
@@ -758,12 +963,12 @@ describe('HtmlReporterV2', function() {
|
||||
});
|
||||
|
||||
it('reports the specs counts', function() {
|
||||
const alertBars = container.querySelectorAll(
|
||||
'.jasmine-alert .jasmine-bar'
|
||||
const resultBar = container.querySelector(
|
||||
'.jasmine-alert .jasmine-bar.jasmine-overall-result'
|
||||
);
|
||||
|
||||
expect(alertBars.length).toEqual(1);
|
||||
expect(alertBars[0].innerHTML).toMatch(/2 specs, 0 failures/);
|
||||
expect(resultBar).toBeTruthy();
|
||||
expect(resultBar.innerHTML).toMatch(/2 specs, 0 failures/);
|
||||
});
|
||||
|
||||
it('reports no failure details', function() {
|
||||
@@ -1072,23 +1277,6 @@ describe('HtmlReporterV2', function() {
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('allows switching between failure details and the spec summary', function() {
|
||||
const menuBar = container.querySelectorAll('.jasmine-bar')[1];
|
||||
|
||||
expect(menuBar.getAttribute('class')).not.toMatch(/hidden/);
|
||||
|
||||
const link = menuBar.querySelector('a');
|
||||
expect(link.innerHTML).toEqual('Failures');
|
||||
expect(link.getAttribute('href')).toEqual('#');
|
||||
});
|
||||
|
||||
it("sets the reporter to 'Failures List' mode", function() {
|
||||
const reporterNode = container.querySelector('.jasmine_html-reporter');
|
||||
expect(reporterNode.getAttribute('class')).toMatch(
|
||||
'jasmine-failure-list'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('counts failures that are reported in the jasmineDone event', function() {
|
||||
|
||||
@@ -1,48 +1,69 @@
|
||||
describe('HtmlSpecFilterV2', function() {
|
||||
it('matches everything when no string is provided', function() {
|
||||
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
|
||||
filterString() {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
describe('When both query parameters are falsy', function() {
|
||||
it('matches everything', function() {
|
||||
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
|
||||
filterParams() {
|
||||
return { path: '', spec: '' };
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches({})).toBeTrue();
|
||||
expect(specFilter.matches({})).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
it('matches a spec with the exact same path', function() {
|
||||
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
|
||||
filterString() {
|
||||
return '["a","b","c"]';
|
||||
}
|
||||
describe('When the path parameter is truthy', function() {
|
||||
it('matches a spec with the exact same path', function() {
|
||||
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
|
||||
filterParams() {
|
||||
return { path: '["a","b","c"]', spec: '' };
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
|
||||
});
|
||||
|
||||
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({
|
||||
filterParams() {
|
||||
return { path: '["a","b"]', spec: '' };
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
|
||||
});
|
||||
it('does not match a spec with a different path', function() {
|
||||
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
|
||||
filterParams() {
|
||||
return { path: '["a","b","c"]', spec: '' };
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse();
|
||||
});
|
||||
|
||||
function stubSpec(path) {
|
||||
describe('When the path parameter is falsy and the spec parameter is truthy', function() {
|
||||
it('matches specs with full names containing the parameter value', function() {
|
||||
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
|
||||
filterParams() {
|
||||
return { path: '', spec: 'bar' };
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec('', 'foo bar baz'))).toBeTrue();
|
||||
expect(specFilter.matches(stubSpec('', 'foo baz'))).toBeFalse();
|
||||
expect(specFilter.matches(stubSpec('', 'sandbars'))).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
function stubSpec(path, fullName) {
|
||||
return {
|
||||
getPath() {
|
||||
return path;
|
||||
},
|
||||
getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
107
spec/html/PerformanceViewSpec.js
Normal file
107
spec/html/PerformanceViewSpec.js
Normal file
@@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
describe('PerformanceView', function() {
|
||||
it('shows specs ordered by execution time', function() {
|
||||
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
|
||||
stateBuilder.suiteStarted({});
|
||||
stateBuilder.specDone({
|
||||
fullName: 'spec A',
|
||||
duration: 2
|
||||
});
|
||||
stateBuilder.suiteDone({});
|
||||
stateBuilder.specDone({
|
||||
fullName: 'spec B',
|
||||
duration: 1
|
||||
});
|
||||
stateBuilder.specDone({
|
||||
fullName: 'spec C',
|
||||
duration: 3
|
||||
});
|
||||
const subject = new privateUnderTest.PerformanceView();
|
||||
subject.addResults(stateBuilder.topResults);
|
||||
|
||||
const rows = Array.from(subject.rootEl.querySelectorAll('tbody tr'));
|
||||
const durations = rows.map(r => r.querySelectorAll('td')[0].textContent);
|
||||
const names = rows.map(r => r.querySelectorAll('td')[1].textContent);
|
||||
expect(names).toEqual(['spec C', 'spec A', 'spec B']);
|
||||
expect(durations).toEqual(['3ms', '2ms', '1ms']);
|
||||
});
|
||||
|
||||
it('shows at most 20 specs', function() {
|
||||
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
|
||||
const subject = new privateUnderTest.PerformanceView();
|
||||
|
||||
for (let i = 0; i < 21; i++) {
|
||||
stateBuilder.specDone({
|
||||
fullName: `spec ${i}`,
|
||||
duration: i
|
||||
});
|
||||
}
|
||||
|
||||
subject.addResults(stateBuilder.topResults);
|
||||
|
||||
expect(subject.rootEl.querySelectorAll('tbody tr').length).toEqual(20);
|
||||
expect(subject.textContent).not.toContain('spec 0');
|
||||
});
|
||||
|
||||
it('shows mean and median run times for an odd number of specs', function() {
|
||||
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
|
||||
const subject = new privateUnderTest.PerformanceView();
|
||||
|
||||
stateBuilder.specDone({ duration: 1 });
|
||||
stateBuilder.specDone({ duration: 2 });
|
||||
stateBuilder.specDone({ duration: 5 });
|
||||
subject.addResults(stateBuilder.topResults);
|
||||
|
||||
expect(subject.rootEl.textContent).toContain('Mean spec run time: 3ms');
|
||||
expect(subject.rootEl.textContent).toContain('Median spec run time: 2ms');
|
||||
});
|
||||
|
||||
it('shows mean and median run times for an even number of specs', function() {
|
||||
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
|
||||
const subject = new privateUnderTest.PerformanceView();
|
||||
|
||||
stateBuilder.specDone({ duration: 1 });
|
||||
stateBuilder.specDone({ duration: 3 });
|
||||
stateBuilder.specDone({ duration: 10 });
|
||||
stateBuilder.specDone({ duration: 2 });
|
||||
subject.addResults(stateBuilder.topResults);
|
||||
|
||||
expect(subject.rootEl.textContent).toContain('Mean spec run time: 4ms');
|
||||
expect(subject.rootEl.textContent).toContain('Median spec run time: 2ms');
|
||||
});
|
||||
|
||||
it('copes with 0 specs', function() {
|
||||
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
|
||||
const subject = new privateUnderTest.PerformanceView();
|
||||
|
||||
expect(function() {
|
||||
subject.addResults(stateBuilder.topResults);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('filters out excluded specs', function() {
|
||||
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
|
||||
stateBuilder.specDone({
|
||||
fullName: 'spec A',
|
||||
duration: 2
|
||||
});
|
||||
stateBuilder.specDone({
|
||||
fullName: 'spec B',
|
||||
duration: 1,
|
||||
status: 'excluded'
|
||||
});
|
||||
stateBuilder.specDone({
|
||||
fullName: 'spec C',
|
||||
duration: 3
|
||||
});
|
||||
const subject = new privateUnderTest.PerformanceView();
|
||||
subject.addResults(stateBuilder.topResults);
|
||||
|
||||
const rows = Array.from(subject.rootEl.querySelectorAll('tbody tr'));
|
||||
const names = rows.map(r => r.querySelectorAll('td')[1].textContent);
|
||||
expect(names).toEqual(['spec C', 'spec A']);
|
||||
expect(subject.rootEl.textContent).toContain('Mean spec run time: 3ms');
|
||||
expect(subject.rootEl.textContent).toContain('Median spec run time: 2ms');
|
||||
});
|
||||
});
|
||||
97
spec/html/TabBarSpec.js
Normal file
97
spec/html/TabBarSpec.js
Normal file
@@ -0,0 +1,97 @@
|
||||
describe('TabBar', function() {
|
||||
it('initially renders but hides the tabs', function() {
|
||||
const subject = new privateUnderTest.TabBar([
|
||||
{ id: 'tab1', label: 'tab 1' }
|
||||
]);
|
||||
const tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
|
||||
expect(tabs.length).toEqual(1);
|
||||
expect(tabs[0].id).toEqual('tab1');
|
||||
expect(tabs[0]).toHaveClass('jasmine-hidden');
|
||||
const link = tabs[0].querySelector('a');
|
||||
expect(link).toBeTruthy();
|
||||
expect(link.textContent).toEqual('tab 1');
|
||||
});
|
||||
|
||||
it('does not initially call the onSelect callback', function() {
|
||||
const onSelect = jasmine.createSpy('onSelect');
|
||||
new privateUnderTest.TabBar([{ id: 'tab1', label: '' }], onSelect);
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('#showTab', function() {
|
||||
it('shows the specified tab', function() {
|
||||
const subject = new privateUnderTest.TabBar([
|
||||
{ id: 'tab1' },
|
||||
{ id: 'tab2' }
|
||||
]);
|
||||
|
||||
subject.showTab('tab2');
|
||||
|
||||
const tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
|
||||
expect(tabs[0]).toHaveClass('jasmine-hidden');
|
||||
expect(tabs[1]).not.toHaveClass('jasmine-hidden');
|
||||
});
|
||||
|
||||
it('does not hide previously shown tabs', function() {
|
||||
const subject = new privateUnderTest.TabBar([
|
||||
{ id: 'tab1' },
|
||||
{ id: 'tab2' }
|
||||
]);
|
||||
|
||||
subject.showTab('tab1');
|
||||
subject.showTab('tab2');
|
||||
|
||||
const tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
|
||||
expect(tabs[0]).not.toHaveClass('jasmine-hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe("When a tab's link is clicked", function() {
|
||||
it("calls the onSelect callback with the tab's id", function() {
|
||||
const onSelect = jasmine.createSpy('onSelect');
|
||||
const subject = new privateUnderTest.TabBar(
|
||||
[{ id: 'tab1', label: '' }],
|
||||
onSelect
|
||||
);
|
||||
|
||||
subject.rootEl.querySelector('.jasmine-tab a').click();
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith('tab1');
|
||||
});
|
||||
|
||||
it('shows links on all non-selected tabs only', function() {
|
||||
const subject = new privateUnderTest.TabBar(
|
||||
[
|
||||
{ id: 'tab1', label: 'tab 1' },
|
||||
{ id: 'tab2', label: 'tab 2' },
|
||||
{ id: 'tab3', label: 'tab 3' }
|
||||
],
|
||||
() => {}
|
||||
);
|
||||
|
||||
subject.rootEl.querySelectorAll('.jasmine-tab a')[1].click();
|
||||
let tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
|
||||
expect(tabs[0].querySelector('a'))
|
||||
.withContext('tab 1')
|
||||
.toBeTruthy();
|
||||
expect(tabs[1].querySelector('a'))
|
||||
.withContext('tab 1')
|
||||
.toBeFalsy();
|
||||
expect(tabs[2].querySelector('a'))
|
||||
.withContext('tab 1')
|
||||
.toBeTruthy();
|
||||
|
||||
subject.rootEl.querySelectorAll('.jasmine-tab a')[0].click();
|
||||
tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
|
||||
expect(tabs[0].querySelector('a'))
|
||||
.withContext('tab 1')
|
||||
.toBeFalsy();
|
||||
expect(tabs[1].querySelector('a'))
|
||||
.withContext('tab 1')
|
||||
.toBeTruthy();
|
||||
expect(tabs[2].querySelector('a'))
|
||||
.withContext('tab 1')
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,11 +23,13 @@ module.exports = {
|
||||
'helpers/BrowserFlags.js',
|
||||
'helpers/domHelpers.js',
|
||||
'helpers/integrationMatchers.js',
|
||||
'helpers/callerFilenameShim.js',
|
||||
'helpers/defineJasmineUnderTest.js',
|
||||
'helpers/resetEnv.js'
|
||||
],
|
||||
env: {
|
||||
forbidDuplicateNames: true
|
||||
forbidDuplicateNames: true,
|
||||
safariYieldStrategy: 'time'
|
||||
},
|
||||
random: true,
|
||||
browser: {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"helpers/init.js",
|
||||
"helpers/domHelpers.js",
|
||||
"helpers/integrationMatchers.js",
|
||||
"helpers/callerFilenameShim.js",
|
||||
"helpers/overrideConsoleLogForCircleCi.js",
|
||||
"helpers/nodeDefineJasmineUnderTest.js",
|
||||
"helpers/resetEnv.js"
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
*/
|
||||
env.addReporter(jsApiReporter);
|
||||
env.addReporter(htmlReporter);
|
||||
/**
|
||||
* Configures Jasmine based on the current set of query parameters. This
|
||||
* supports all parameters set by the HTML reporter as well as
|
||||
* spec=partialPath, which filters out specs whose paths don't contain the
|
||||
* parameter.
|
||||
*/
|
||||
env.configure(urls.configFromCurrentUrl());
|
||||
|
||||
/**
|
||||
|
||||
@@ -128,7 +128,45 @@ getJasmineRequireObj().Configuration = function(j$) {
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
detectLateRejectionHandling: false
|
||||
detectLateRejectionHandling: false,
|
||||
|
||||
/**
|
||||
* The number of extra stack frames inserted by a wrapper around {@link it}
|
||||
* or by some other local modification. Jasmine uses this to determine the
|
||||
* filename for {@link SpecStartedEvent} and {@link SpecDoneEvent}.
|
||||
* @name Configuration#extraItStackFrames
|
||||
* @since 5.13.0
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
extraItStackFrames: 0,
|
||||
|
||||
/**
|
||||
* The number of extra stack frames inserted by a wrapper around
|
||||
* {@link describe} or by some other local modification. Jasmine uses this
|
||||
* to determine the filename for {@link SpecStartedEvent} and
|
||||
* {@link SpecDoneEvent}.
|
||||
* @name Configuration#extraDescribeStackFrames
|
||||
* @since 5.13.0
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
extraDescribeStackFrames: 0,
|
||||
|
||||
/**
|
||||
* The strategy to use in Safari and similar browsers to determine how often
|
||||
* to yield control by calling setTimeout. If set to "count", the default,
|
||||
* the frequency of setTimeout calls is based on the number of relevant
|
||||
* function calls. If set to "time", the frequency of setTimeout calls is
|
||||
* based on elapsed time. Using "time" may provide a significant performance
|
||||
* improvement, but as of 6.0 it hasn't been tested with a wide variety of
|
||||
* workloads and should be considered experimental.
|
||||
* @name Configuration#safariYieldStrategy
|
||||
* @since 6.0.0
|
||||
* @type 'count' | 'time'
|
||||
* @default 'count'
|
||||
*/
|
||||
safariYieldStrategy: 'count'
|
||||
};
|
||||
Object.freeze(defaultConfig);
|
||||
|
||||
@@ -179,6 +217,28 @@ getJasmineRequireObj().Configuration = function(j$) {
|
||||
if (typeof changes.seed !== 'undefined') {
|
||||
this.#values.seed = changes.seed;
|
||||
}
|
||||
|
||||
// 0 is a valid value for both of these, so a truthiness check wouldn't work
|
||||
if (typeof changes.extraItStackFrames !== 'undefined') {
|
||||
this.#values.extraItStackFrames = changes.extraItStackFrames;
|
||||
}
|
||||
|
||||
if (typeof changes.extraDescribeStackFrames !== 'undefined') {
|
||||
this.#values.extraDescribeStackFrames =
|
||||
changes.extraDescribeStackFrames;
|
||||
}
|
||||
|
||||
if (typeof changes.safariYieldStrategy !== 'undefined') {
|
||||
const v = changes.safariYieldStrategy;
|
||||
|
||||
if (v === 'count' || v === 'time') {
|
||||
this.#values.safariYieldStrategy = v;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
getJasmineRequireObj().Env = function(j$) {
|
||||
'use strict';
|
||||
|
||||
const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3;
|
||||
|
||||
/**
|
||||
* @class Env
|
||||
* @since 2.0.0
|
||||
@@ -18,7 +20,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
const realSetTimeout = global.setTimeout;
|
||||
const realClearTimeout = global.clearTimeout;
|
||||
const clearStack = j$.private.getClearStack(global);
|
||||
const stackClearer = j$.private.getStackClearer(global);
|
||||
this.clock = new j$.private.Clock(
|
||||
global,
|
||||
function() {
|
||||
@@ -28,7 +30,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
);
|
||||
|
||||
const globalErrors = new GlobalErrors(
|
||||
undefined,
|
||||
global,
|
||||
// Configuration is late-bound because GlobalErrors needs to be constructed
|
||||
// before it's set to detect load-time errors in browsers
|
||||
() => this.configuration()
|
||||
@@ -97,6 +99,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
config.update(changes);
|
||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||
stackClearer.setSafariYieldStrategy(config.safariYieldStrategy);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -299,7 +302,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
|
||||
function runQueue(options) {
|
||||
options.clearStack = options.clearStack || clearStack;
|
||||
options.clearStack = options.clearStack || stackClearer;
|
||||
options.timeout = {
|
||||
setTimeout: realSetTimeout,
|
||||
clearTimeout: realClearTimeout
|
||||
@@ -591,14 +594,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
this.describe = function(description, definitionFn) {
|
||||
ensureIsNotNested('describe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.describe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
|
||||
this.xdescribe = function(description, definitionFn) {
|
||||
ensureIsNotNested('xdescribe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.xdescribe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
@@ -606,30 +609,38 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
this.fdescribe = function(description, definitionFn) {
|
||||
ensureIsNotNested('fdescribe');
|
||||
ensureNonParallel('fdescribe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.fdescribe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
|
||||
this.it = function(description, fn, timeout) {
|
||||
ensureIsNotNested('it');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.it(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
this.xit = function(description, fn, timeout) {
|
||||
ensureIsNotNested('xit');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.xit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
this.fit = function(description, fn, timeout) {
|
||||
ensureIsNotNested('fit');
|
||||
ensureNonParallel('fit');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
function itStackDepth() {
|
||||
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames;
|
||||
}
|
||||
|
||||
function describeStackDepth() {
|
||||
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-defined property as part of the properties field of {@link SpecDoneEvent}
|
||||
* @name Env#getSpecProperty
|
||||
@@ -815,11 +826,12 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
}
|
||||
|
||||
function callerCallerFilename() {
|
||||
function indirectCallerFilename(depth) {
|
||||
const frames = new j$.private.StackTrace(new Error()).frames;
|
||||
// frames[3] should always exist except in Jasmine's own tests, which bypass
|
||||
// the global it/describe layer, but don't crash if it doesn't.
|
||||
return frames[3] && frames[3].file;
|
||||
// The specified frame should always exist except in Jasmine's own tests,
|
||||
// which bypass the global it/describe layer, but could be absent in case
|
||||
// of misconfiguration. Don't crash if it's absent.
|
||||
return frames[depth] && frames[depth].file;
|
||||
}
|
||||
|
||||
return Env;
|
||||
|
||||
@@ -51,11 +51,11 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
}
|
||||
|
||||
this.onComplete = attrs.onComplete || emptyFn;
|
||||
this.clearStack =
|
||||
attrs.clearStack ||
|
||||
function(fn) {
|
||||
this.clearStack = attrs.clearStack || {
|
||||
clearStack(fn) {
|
||||
fn();
|
||||
};
|
||||
}
|
||||
};
|
||||
this.onException = attrs.onException || emptyFn;
|
||||
this.onMultipleDone = attrs.onMultipleDone || fallbackOnMultipleDone;
|
||||
this.userContext = attrs.userContext || new j$.private.UserContext();
|
||||
@@ -235,7 +235,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
}
|
||||
}
|
||||
|
||||
this.clearStack(() => {
|
||||
this.clearStack.clearStack(() => {
|
||||
this.globalErrors.popListener(this.handleFinalError);
|
||||
|
||||
if (this.errored_) {
|
||||
|
||||
@@ -72,8 +72,11 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
// Key and value will eventually be cloned during reporting. The error
|
||||
// thrown at that point if they aren't cloneable isn't very helpful.
|
||||
// Throw a better one now.
|
||||
j$.private.util.assertReporterCloneable(key, 'Key');
|
||||
if (!j$.private.isString(key)) {
|
||||
throw new Error('Key must be a string');
|
||||
}
|
||||
j$.private.util.assertReporterCloneable(value, 'Value');
|
||||
|
||||
this.#executionState.properties = this.#executionState.properties || {};
|
||||
this.#executionState.properties[key] = value;
|
||||
}
|
||||
@@ -153,12 +156,11 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
* @property {String} description - The description passed to the {@link it} that created this spec.
|
||||
* @property {String} fullName - The full description including all ancestors of this spec.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
|
||||
* @property {String} filename - The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. This property may be removed in
|
||||
* a future version unless there is enough user interest in keeping it.
|
||||
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* same call stack height as the originals. You can fix that by setting
|
||||
* {@link Configuration#extraItStackFrames}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
|
||||
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
|
||||
@@ -340,7 +342,20 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
* @returns {Array.<string>}
|
||||
* @since 5.7.0
|
||||
*/
|
||||
getPath: this.getPath.bind(this)
|
||||
getPath: this.getPath.bind(this),
|
||||
|
||||
/**
|
||||
* The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. You can fix that by setting
|
||||
* {@link Configuration#extraItStackFrames}.
|
||||
* @name Spec#filename
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 5.13.0
|
||||
*/
|
||||
filename: this.filename
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
getJasmineRequireObj().clearStack = function(j$) {
|
||||
getJasmineRequireObj().StackClearer = function(j$) {
|
||||
'use strict';
|
||||
|
||||
const maxInlineCallCount = 10;
|
||||
// 25ms gives a good balance of speed and UI responsiveness when running
|
||||
// jasmine-core's own tests in Safari 18. The exact value isn't critical.
|
||||
const safariYieldIntervalMs = 25;
|
||||
|
||||
function browserQueueMicrotaskImpl(global) {
|
||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||
const { queueMicrotask } = global;
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
let yieldStrategy = 'count';
|
||||
let currentCallCount = 0; // for count strategy
|
||||
let nextSetTimeoutTime; // for time strategy
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
queueMicrotask(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
unclampedSetTimeout(fn);
|
||||
return {
|
||||
clearStack(fn) {
|
||||
currentCallCount++;
|
||||
let shouldSetTimeout;
|
||||
|
||||
if (yieldStrategy === 'time') {
|
||||
const now = new Date().getTime();
|
||||
shouldSetTimeout = now >= nextSetTimeoutTime;
|
||||
if (shouldSetTimeout) {
|
||||
nextSetTimeoutTime = now + safariYieldIntervalMs;
|
||||
}
|
||||
} else {
|
||||
shouldSetTimeout = currentCallCount >= maxInlineCallCount;
|
||||
if (shouldSetTimeout) {
|
||||
currentCallCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSetTimeout) {
|
||||
unclampedSetTimeout(fn);
|
||||
} else {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
},
|
||||
setSafariYieldStrategy(strategy) {
|
||||
yieldStrategy = strategy;
|
||||
|
||||
if (yieldStrategy === 'time') {
|
||||
nextSetTimeoutTime = new Date().getTime() + safariYieldIntervalMs;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -22,8 +50,11 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
function nodeQueueMicrotaskImpl(global) {
|
||||
const { queueMicrotask } = global;
|
||||
|
||||
return function(fn) {
|
||||
queueMicrotask(fn);
|
||||
return {
|
||||
clearStack(fn) {
|
||||
queueMicrotask(fn);
|
||||
},
|
||||
setSafariYieldStrategy() {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,15 +63,19 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
const postMessage = getPostMessage(global);
|
||||
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
postMessage(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
return {
|
||||
clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
postMessage(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
},
|
||||
setSafariYieldStrategy() {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,7 +123,7 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
};
|
||||
}
|
||||
|
||||
function getClearStack(global) {
|
||||
function getStackClearer(global) {
|
||||
const NODE_JS =
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
@@ -106,12 +141,10 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
|
||||
// so we avoid the overhead.
|
||||
return nodeQueueMicrotaskImpl(global);
|
||||
} else if (SAFARI_OR_WIN_WEBKIT || !global.MessageChannel /* tests */) {
|
||||
} else if (SAFARI_OR_WIN_WEBKIT) {
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari
|
||||
// and other WebKit-based browsers, such as the one distributed by Playwright
|
||||
// to test Safari-like behavior on Windows.
|
||||
// Some of our own integration tests provide a mock queueMicrotask in all
|
||||
// environments because it's simpler to mock than MessageChannel.
|
||||
return browserQueueMicrotaskImpl(global);
|
||||
} else {
|
||||
// MessageChannel is faster than queueMicrotask in supported browsers
|
||||
@@ -120,5 +153,5 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
}
|
||||
}
|
||||
|
||||
return getClearStack;
|
||||
return getStackClearer;
|
||||
};
|
||||
@@ -37,8 +37,11 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
// Key and value will eventually be cloned during reporting. The error
|
||||
// thrown at that point if they aren't cloneable isn't very helpful.
|
||||
// Throw a better one now.
|
||||
j$.private.util.assertReporterCloneable(key, 'Key');
|
||||
if (!j$.private.isString(key)) {
|
||||
throw new Error('Key must be a string');
|
||||
}
|
||||
j$.private.util.assertReporterCloneable(value, 'Value');
|
||||
|
||||
this.#result.properties = this.#result.properties || {};
|
||||
this.#result.properties[key] = value;
|
||||
}
|
||||
@@ -146,12 +149,11 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
* @property {String} description - The description text passed to the {@link describe} that made this suite.
|
||||
* @property {String} fullName - The full description including all ancestors of this suite.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
|
||||
* @property {String} filename - The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
|
||||
* don't maintain the same call stack height as the originals. This property
|
||||
* may be removed in a future version unless there is enough user interest
|
||||
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* don't maintain the same call stack height as the originals. You can fix
|
||||
* that by setting {@link Configuration#extraDescribeStackFrames}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
|
||||
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
|
||||
@@ -359,6 +361,19 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.description = suite.description;
|
||||
|
||||
/**
|
||||
* The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions
|
||||
* that don't maintain the same call stack height as the originals. You
|
||||
* can fix that by setting {@link Configuration#extraItStackFrames}.
|
||||
* @name Suite#filename
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 5.13.0
|
||||
*/
|
||||
this.filename = suite.filename;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,10 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
_executeSpec(spec, specOverallDone) {
|
||||
const onStart = next => {
|
||||
this.#currentRunableTracker.setCurrentSpec(spec);
|
||||
this.#runableResources.initForRunable(spec.id, spec.parentSuiteId);
|
||||
this.#runableResources.initForRunable(
|
||||
spec.id,
|
||||
spec.parentSuiteId || this.#executionTree.topSuite.id
|
||||
);
|
||||
this.#reportDispatcher.specStarted(spec.startedEvent()).then(next);
|
||||
};
|
||||
const resultCallback = (result, next) => {
|
||||
|
||||
29
src/core/asymmetric_equality/AllOf.js
Normal file
29
src/core/asymmetric_equality/AllOf.js
Normal file
@@ -0,0 +1,29 @@
|
||||
getJasmineRequireObj().AllOf = function(j$) {
|
||||
'use strict';
|
||||
|
||||
function AllOf() {
|
||||
const expectedValues = Array.from(arguments);
|
||||
if (expectedValues.length === 0) {
|
||||
throw new TypeError(
|
||||
'jasmine.allOf() expects at least one argument to be passed.'
|
||||
);
|
||||
}
|
||||
this.expectedValues = expectedValues;
|
||||
}
|
||||
|
||||
AllOf.prototype.asymmetricMatch = function(other, matchersUtil) {
|
||||
for (const expectedValue of this.expectedValues) {
|
||||
if (!matchersUtil.equals(other, expectedValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
AllOf.prototype.jasmineToString = function(pp) {
|
||||
return '<jasmine.allOf(' + pp(this.expectedValues) + ')>';
|
||||
};
|
||||
|
||||
return AllOf;
|
||||
};
|
||||
@@ -229,6 +229,19 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
|
||||
* value being compared matches every provided equality tester.
|
||||
* @name asymmetricEqualityTesters.allOf
|
||||
* @emittedName jasmine.allOf
|
||||
* @since 5.13.0
|
||||
* @function
|
||||
* @param {...*} arguments - The asymmetric equality checkers to compare.
|
||||
*/
|
||||
j$.allOf = function() {
|
||||
return new j$.AllOf(...arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
|
||||
* value being compared is an instance of the specified class/constructor.
|
||||
|
||||
@@ -7,6 +7,7 @@ getJasmineRequireObj().reporterEvents = function(j$) {
|
||||
* {@link ReporterCapabilities} will apply.
|
||||
* @name Reporter#reporterCapabilities
|
||||
* @type ReporterCapabilities | undefined
|
||||
* @optional
|
||||
* @since 5.0
|
||||
*/
|
||||
/**
|
||||
@@ -73,6 +74,7 @@ getJasmineRequireObj().reporterEvents = function(j$) {
|
||||
/**
|
||||
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
|
||||
* @function
|
||||
* @optional
|
||||
* @name Reporter#specStarted
|
||||
* @param {SpecStartedEvent} result Information about the individual {@link it} being run
|
||||
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
|
||||
|
||||
@@ -26,11 +26,12 @@ var getJasmineRequireObj = (function() {
|
||||
j$.private.util = jRequire.util(j$);
|
||||
j$.private.errors = jRequire.errors();
|
||||
j$.private.formatErrorMsg = jRequire.formatErrorMsg(j$);
|
||||
j$.private.AllOf = jRequire.AllOf(j$);
|
||||
j$.private.Any = jRequire.Any(j$);
|
||||
j$.private.Anything = jRequire.Anything(j$);
|
||||
j$.private.CallTracker = jRequire.CallTracker(j$);
|
||||
j$.private.MockDate = jRequire.MockDate(j$);
|
||||
j$.private.getClearStack = jRequire.clearStack(j$);
|
||||
j$.private.getStackClearer = jRequire.StackClearer(j$);
|
||||
j$.private.Clock = jRequire.Clock();
|
||||
j$.private.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
|
||||
j$.private.Deprecator = jRequire.Deprecator(j$);
|
||||
|
||||
@@ -24,6 +24,7 @@ jasmineRequire.AlertsView = function(j$) {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: remove this once HtmlReporterV2 doesn't use it
|
||||
addFailureToggle(onClickFailures, onClickSpecList) {
|
||||
const failuresLink = createDom(
|
||||
'a',
|
||||
@@ -46,14 +47,20 @@ jasmineRequire.AlertsView = function(j$) {
|
||||
return false;
|
||||
};
|
||||
|
||||
this.#createAndAdd('jasmine-menu jasmine-bar jasmine-spec-list', [
|
||||
createDom('span', {}, 'Spec List | '),
|
||||
failuresLink
|
||||
]);
|
||||
this.#createAndAdd('jasmine-menu jasmine-bar jasmine-failure-list', [
|
||||
specListLink,
|
||||
createDom('span', {}, ' | Failures ')
|
||||
]);
|
||||
this.rootEl.appendChild(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
|
||||
[createDom('span', {}, 'Spec List | '), failuresLink]
|
||||
)
|
||||
);
|
||||
this.rootEl.appendChild(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
|
||||
[specListLink, createDom('span', {}, ' | Failures ')]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addGlobalFailure(failure) {
|
||||
|
||||
@@ -142,16 +142,52 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
results.appendChild(summary.rootEl);
|
||||
|
||||
if (this.#stateBuilder.anyNonTopSuiteFailures) {
|
||||
this.#alerts.addFailureToggle(
|
||||
() => this.#setMenuModeTo('jasmine-failure-list'),
|
||||
() => this.#setMenuModeTo('jasmine-spec-list')
|
||||
);
|
||||
|
||||
this.#addFailureToggle();
|
||||
this.#setMenuModeTo('jasmine-failure-list');
|
||||
this.#failures.show();
|
||||
}
|
||||
}
|
||||
|
||||
#addFailureToggle() {
|
||||
const onClickFailures = () => this.#setMenuModeTo('jasmine-failure-list');
|
||||
const onClickSpecList = () => this.#setMenuModeTo('jasmine-spec-list');
|
||||
const failuresLink = createDom(
|
||||
'a',
|
||||
{ className: 'jasmine-failures-menu', href: '#' },
|
||||
'Failures'
|
||||
);
|
||||
let specListLink = createDom(
|
||||
'a',
|
||||
{ className: 'jasmine-spec-list-menu', href: '#' },
|
||||
'Spec List'
|
||||
);
|
||||
|
||||
failuresLink.onclick = function() {
|
||||
onClickFailures();
|
||||
return false;
|
||||
};
|
||||
|
||||
specListLink.onclick = function() {
|
||||
onClickSpecList();
|
||||
return false;
|
||||
};
|
||||
|
||||
this.#alerts.addBar(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
|
||||
[createDom('span', {}, 'Spec List | '), failuresLink]
|
||||
)
|
||||
);
|
||||
this.#alerts.addBar(
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
|
||||
[specListLink, createDom('span', {}, ' | Failures ')]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#find(selector) {
|
||||
return this.#getContainer().querySelector(
|
||||
'.jasmine_html-reporter ' + selector
|
||||
|
||||
@@ -3,6 +3,10 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
|
||||
const { createDom, noExpectations } = j$.private.htmlReporterUtils;
|
||||
|
||||
const specListTabId = 'jasmine-specListTab';
|
||||
const failuresTabId = 'jasmine-failuresTab';
|
||||
const perfTabId = 'jasmine-perfTab';
|
||||
|
||||
/**
|
||||
* @class HtmlReporterV2
|
||||
* @classdesc Displays results and allows re-running individual specs and suites.
|
||||
@@ -15,7 +19,7 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
* const reporter = new jasmine.HtmlReporterV2({
|
||||
* env,
|
||||
* urls,
|
||||
* container: document.body
|
||||
* getContainer: () => document.body
|
||||
* });
|
||||
*/
|
||||
class HtmlReporterV2 {
|
||||
@@ -31,6 +35,7 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
// Sub-views
|
||||
#alerts;
|
||||
#statusBar;
|
||||
#tabBar;
|
||||
#progress;
|
||||
#banner;
|
||||
#failures;
|
||||
@@ -68,6 +73,25 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
this.#statusBar = new j$.private.OverallStatusBar(this.#urlBuilder);
|
||||
this.#statusBar.showRunning();
|
||||
this.#alerts.addBar(this.#statusBar.rootEl);
|
||||
|
||||
this.#tabBar = new j$.private.TabBar(
|
||||
[
|
||||
{ id: specListTabId, label: 'Spec List' },
|
||||
{ id: failuresTabId, label: 'Failures' },
|
||||
{ id: perfTabId, label: 'Performance' }
|
||||
],
|
||||
tabId => {
|
||||
if (tabId === specListTabId) {
|
||||
this.#setMenuModeTo('jasmine-spec-list');
|
||||
} else if (tabId === failuresTabId) {
|
||||
this.#setMenuModeTo('jasmine-failure-list');
|
||||
} else {
|
||||
this.#setMenuModeTo('jasmine-performance');
|
||||
}
|
||||
}
|
||||
);
|
||||
this.#alerts.addBar(this.#tabBar.rootEl);
|
||||
|
||||
this.#progress = new ProgressView();
|
||||
this.#banner = new j$.private.Banner(
|
||||
this.#queryString.navigateWithNewParam.bind(this.#queryString),
|
||||
@@ -158,15 +182,17 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
);
|
||||
summary.addResults(this.#stateBuilder.topResults);
|
||||
results.appendChild(summary.rootEl);
|
||||
const perf = new j$.private.PerformanceView();
|
||||
perf.addResults(this.#stateBuilder.topResults);
|
||||
results.appendChild(perf.rootEl);
|
||||
this.#tabBar.showTab(specListTabId);
|
||||
this.#tabBar.showTab(perfTabId);
|
||||
|
||||
if (this.#stateBuilder.anyNonTopSuiteFailures) {
|
||||
this.#alerts.addFailureToggle(
|
||||
() => this.#setMenuModeTo('jasmine-failure-list'),
|
||||
() => this.#setMenuModeTo('jasmine-spec-list')
|
||||
);
|
||||
|
||||
this.#setMenuModeTo('jasmine-failure-list');
|
||||
this.#failures.show();
|
||||
this.#tabBar.showTab(failuresTabId);
|
||||
this.#tabBar.selectTab(failuresTabId);
|
||||
} else {
|
||||
this.#tabBar.selectTab(specListTabId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +231,6 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
|
||||
this.rootEl.value = this.rootEl.value + 1;
|
||||
|
||||
if (result.status === 'failed') {
|
||||
// TODO: also a non-color indicator
|
||||
this.rootEl.classList.add('failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Configuration} from the current page's URL.
|
||||
* Creates a {@link Configuration} from the current page's URL. Supported
|
||||
* query string parameters include all those set by {@link HtmlReporterV2}
|
||||
* as well as spec=partialPath, which filters out specs whose paths don't
|
||||
* contain partialPath.
|
||||
* @returns {Configuration}
|
||||
* @example
|
||||
* const urls = new jasmine.HtmlReporterV2Urls();
|
||||
@@ -47,9 +50,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) {
|
||||
}
|
||||
|
||||
const specFilter = new j$.private.HtmlSpecFilterV2({
|
||||
filterString: () => {
|
||||
return this.queryString.getParam('path');
|
||||
}
|
||||
filterParams: () => ({
|
||||
path: this.queryString.getParam('path'),
|
||||
spec: this.queryString.getParam('spec')
|
||||
})
|
||||
});
|
||||
|
||||
config.specFilter = function(spec) {
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
jasmineRequire.HtmlSpecFilterV2 = function() {
|
||||
class HtmlSpecFilterV2 {
|
||||
#getFilterString;
|
||||
#getFilterParams;
|
||||
|
||||
constructor(options) {
|
||||
this.#getFilterString = options.filterString;
|
||||
this.#getFilterParams = options.filterParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the spec with the specified name should be executed.
|
||||
* @name HtmlSpecFilterV2#matches
|
||||
* @function
|
||||
* @param {Spec} spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
matches(spec) {
|
||||
const filterString = this.#getFilterString();
|
||||
const params = this.#getFilterParams();
|
||||
|
||||
if (!filterString) {
|
||||
return true;
|
||||
if (params.path) {
|
||||
return this.#matchesPath(spec, JSON.parse(params.path));
|
||||
} else if (params.spec) {
|
||||
// Like legacy HtmlSpecFilter, retained because it's convenient for
|
||||
// hand-constructing filter URLs
|
||||
return spec.getFullName().includes(params.spec);
|
||||
}
|
||||
|
||||
const filterPath = JSON.parse(this.#getFilterString());
|
||||
return true;
|
||||
}
|
||||
|
||||
#matchesPath(spec, path) {
|
||||
const specPath = spec.getPath();
|
||||
|
||||
if (filterPath.length > specPath.length) {
|
||||
if (path.length > specPath.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < filterPath.length; i++) {
|
||||
if (specPath[i] !== filterPath[i]) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
if (specPath[i] !== path[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
98
src/html/PerformanceView.js
Normal file
98
src/html/PerformanceView.js
Normal file
@@ -0,0 +1,98 @@
|
||||
jasmineRequire.PerformanceView = function(j$) {
|
||||
const createDom = j$.private.htmlReporterUtils.createDom;
|
||||
const MAX_SLOW_SPECS = 20;
|
||||
|
||||
class PerformanceView {
|
||||
#summary;
|
||||
#tbody;
|
||||
|
||||
constructor() {
|
||||
this.#tbody = document.createElement('tbody');
|
||||
this.#summary = document.createElement('div');
|
||||
this.rootEl = createDom(
|
||||
'div',
|
||||
{ className: 'jasmine-performance-view' },
|
||||
createDom('h2', {}, 'Performance'),
|
||||
this.#summary,
|
||||
createDom('h3', {}, 'Slowest Specs'),
|
||||
createDom(
|
||||
'table',
|
||||
{},
|
||||
createDom(
|
||||
'thead',
|
||||
{},
|
||||
createDom(
|
||||
'tr',
|
||||
{},
|
||||
createDom('th', {}, 'Duration'),
|
||||
createDom('th', {}, 'Spec Name')
|
||||
)
|
||||
),
|
||||
this.#tbody
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addResults(resultsTree) {
|
||||
const specResults = [];
|
||||
getSpecResults(resultsTree, specResults);
|
||||
|
||||
if (specResults.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
specResults.sort(function(a, b) {
|
||||
if (a.duration < b.duration) {
|
||||
return 1;
|
||||
} else if (a.duration > b.duration) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
this.#populateSumary(specResults);
|
||||
this.#populateTable(specResults);
|
||||
}
|
||||
|
||||
#populateSumary(specResults) {
|
||||
const total = specResults.map(r => r.duration).reduce((a, b) => a + b, 0);
|
||||
const mean = total / specResults.length;
|
||||
const median = specResults[Math.floor(specResults.length / 2)].duration;
|
||||
this.#summary.appendChild(
|
||||
document.createTextNode(`Mean spec run time: ${mean.toFixed(0)}ms`)
|
||||
);
|
||||
this.#summary.appendChild(document.createElement('br'));
|
||||
this.#summary.appendChild(
|
||||
document.createTextNode(`Median spec run time: ${median}ms`)
|
||||
);
|
||||
}
|
||||
|
||||
#populateTable(specResults) {
|
||||
specResults = specResults.slice(0, MAX_SLOW_SPECS);
|
||||
|
||||
for (const r of specResults) {
|
||||
this.#tbody.appendChild(
|
||||
createDom(
|
||||
'tr',
|
||||
{},
|
||||
createDom('td', {}, `${r.duration}ms`),
|
||||
createDom('td', {}, r.fullName)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecResults(resultsTree, dest) {
|
||||
for (const node of resultsTree.children) {
|
||||
if (node.type === 'suite') {
|
||||
getSpecResults(node, dest);
|
||||
} else if (node.result.status !== 'excluded') {
|
||||
dest.push(node.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PerformanceView;
|
||||
};
|
||||
77
src/html/TabBar.js
Normal file
77
src/html/TabBar.js
Normal file
@@ -0,0 +1,77 @@
|
||||
jasmineRequire.TabBar = function(j$) {
|
||||
const createDom = j$.private.htmlReporterUtils.createDom;
|
||||
|
||||
class TabBar {
|
||||
#tabs;
|
||||
#onSelectTab;
|
||||
|
||||
// tabSpecs should be an array of {id, label}.
|
||||
// All tabs are initially not visible and not selected.
|
||||
constructor(tabSpecs, onSelectTab) {
|
||||
this.#onSelectTab = onSelectTab;
|
||||
this.#tabs = [];
|
||||
this.#tabs = tabSpecs.map(ts => new Tab(ts, () => this.selectTab(ts.id)));
|
||||
|
||||
this.rootEl = createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-menu jasmine-bar' },
|
||||
this.#tabs.map(t => t.rootEl)
|
||||
);
|
||||
}
|
||||
|
||||
showTab(id) {
|
||||
for (const tab of this.#tabs) {
|
||||
if (tab.rootEl.id === id) {
|
||||
tab.setVisibility(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectTab(id) {
|
||||
for (const tab of this.#tabs) {
|
||||
tab.setSelected(tab.rootEl.id === id);
|
||||
}
|
||||
|
||||
this.#onSelectTab(id);
|
||||
}
|
||||
}
|
||||
|
||||
class Tab {
|
||||
#spec;
|
||||
#onClick;
|
||||
|
||||
constructor(spec, onClick) {
|
||||
this.#spec = spec;
|
||||
this.#onClick = onClick;
|
||||
this.rootEl = createDom(
|
||||
'span',
|
||||
{ id: spec.id, className: 'jasmine-tab jasmine-hidden' },
|
||||
this.#createLink()
|
||||
);
|
||||
}
|
||||
|
||||
setVisibility(visible) {
|
||||
this.rootEl.classList.toggle('jasmine-hidden', !visible);
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
if (selected) {
|
||||
this.rootEl.textContent = this.#spec.label;
|
||||
} else {
|
||||
this.rootEl.textContent = '';
|
||||
this.rootEl.appendChild(this.#createLink());
|
||||
}
|
||||
}
|
||||
|
||||
#createLink() {
|
||||
const link = createDom('a', { href: '#' }, this.#spec.label);
|
||||
link.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
this.#onClick();
|
||||
});
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
||||
return TabBar;
|
||||
};
|
||||
@@ -23,7 +23,7 @@ $failing-mark: "\d7";
|
||||
$pending-mark: "*";
|
||||
$space: "\0020";
|
||||
|
||||
$font-size: 11px;
|
||||
$font-size: 12px;
|
||||
$large-font-size: 14px;
|
||||
|
||||
body {
|
||||
@@ -280,15 +280,25 @@ body {
|
||||
}
|
||||
|
||||
// simplify toggle control between the two menu bars
|
||||
// TODO: clean this up once HtmlReporter is removed
|
||||
&.jasmine-spec-list {
|
||||
.jasmine-bar.jasmine-menu.jasmine-failure-list,
|
||||
.jasmine-results .jasmine-failures {
|
||||
.jasmine-results .jasmine-failures,
|
||||
.jasmine-performance-view {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.jasmine-failure-list {
|
||||
.jasmine-bar.jasmine-menu.jasmine-spec-list,
|
||||
.jasmine-summary,
|
||||
.jasmine-performance-view {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.jasmine-performance {
|
||||
.jasmine-results .jasmine-failures,
|
||||
.jasmine-summary {
|
||||
display: none;
|
||||
}
|
||||
@@ -464,3 +474,26 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.jasmine-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jasmine-tab + .jasmine-tab:before {
|
||||
content: ' | ';
|
||||
}
|
||||
|
||||
.jasmine-performance-view {
|
||||
h2, h3 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 5px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ jasmineRequire.html = function(j$) {
|
||||
j$.private.SymbolsView = jasmineRequire.SymbolsView(j$);
|
||||
j$.private.SummaryTreeView = jasmineRequire.SummaryTreeView(j$);
|
||||
j$.private.FailuresView = jasmineRequire.FailuresView(j$);
|
||||
j$.private.PerformanceView = jasmineRequire.PerformanceView(j$);
|
||||
j$.private.TabBar = jasmineRequire.TabBar(j$);
|
||||
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
|
||||
j$.HtmlReporterV2Urls = jasmineRequire.HtmlReporterV2Urls(j$);
|
||||
j$.HtmlReporterV2 = jasmineRequire.HtmlReporterV2(j$);
|
||||
|
||||
Reference in New Issue
Block a user