/* *TestEnvironmentisanabstractionfortheenvironmentinwhichthetest *harnessisused.Eachimplementationofatestenvironmenthastoprovide *thefollowinginterface: * *interfaceTestEnvironment{ *// Invoked after the global 'tests' object has been created and it's *// safe to call add_*_callback() to register event handlers. *voidon_tests_ready(); * *// Invoked after setup() has been called to notify the test environment *// of changes to the test harness properties. *voidon_new_harness_properties(objectproperties); * *// Should return a new unique default test name. *DOMStringnext_default_test_name(); * *// Should return the test harness timeout duration in milliseconds. *floattest_timeout(); *};
*/
on_event(window, 'message', function(event) { if (event.data && event.data.type === "getmessages" && event.source) { // A window can post "getmessages" to receive a duplicate of every // message posted by this environment so far. This allows subscribers // from fetch_tests_from_window to 'catch up' to the current state of // this environment. for (var i = 0; i < this_obj.dispatched_messages.length; ++i)
{
event.source.postMessage(this_obj.dispatched_messages[i], "*");
}
}
});
}
WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) { this.dispatched_messages.push(message_arg); this._forEach_windows( function(w, same_origin) { if (same_origin) { try { var has_selector = selector in w;
} catch(e) { // If document.domain was set at some point same_origin can be // wrong and the above will fail.
has_selector = false;
} if (has_selector) { try {
w[selector].apply(undefined, callback_args);
} catch (e) {}
}
} if (w !== self) {
w.postMessage(message_arg, "*");
}
});
};
WindowTestEnvironment.prototype._forEach_windows = function(callback) { // Iterate over the windows [self ... top, opener]. The callback is passed // two objects, the first one is the window object itself, the second one // is a boolean indicating whether or not it's on the same origin as the // current window. var cache = this.window_cache; if (!cache) {
cache = [[self, true]]; var w = self; var i = 0; var so; while (w != w.parent) {
w = w.parent;
so = is_same_origin(w);
cache.push([w, so]);
i++;
}
w = window.opener; if (w) {
cache.push([w, is_same_origin(w)]);
} this.window_cache = cache;
}
WorkerTestEnvironment.prototype._dispatch = function(message) { this.message_list.push(message); for (var i = 0; i < this.message_ports.length; ++i)
{ this.message_ports[i].postMessage(message);
}
};
// The only requirement is that port has a postMessage() method. It doesn't // have to be an instance of a MessagePort, and often isn't.
WorkerTestEnvironment.prototype._add_message_port = function(port) { this.message_ports.push(port); for (var i = 0; i < this.message_list.length; ++i)
{
port.postMessage(this.message_list[i]);
}
};
WorkerTestEnvironment.prototype.test_timeout = function() { // Tests running in a worker don't have a default timeout. I.e. all // worker tests behave as if settings.explicit_timeout is true. returnnull;
};
/* *Dedicatedwebworkers. *https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope * *Thisclassisusedasthetest_environmentwhentestharnessisrunning *insideadedicatedworker.
*/ function DedicatedWorkerTestEnvironment() {
WorkerTestEnvironment.call(this); // self is an instance of DedicatedWorkerGlobalScope which exposes // a postMessage() method for communicating via the message channel // established when the worker is created. this._add_message_port(self);
}
DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
WorkerTestEnvironment.prototype.on_tests_ready.call(this); // In the absence of an onload notification, we a require dedicated // workers to explicitly signal when the tests are done.
tests.wait_for_finish = true;
};
/* *Sharedwebworkers. *https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope * *Thisclassisusedasthetest_environmentwhentestharnessisrunning *insideasharedwebworker.
*/ function SharedWorkerTestEnvironment() {
WorkerTestEnvironment.call(this); var this_obj = this; // Shared workers receive message ports via the 'onconnect' event for // each connection.
self.addEventListener("connect", function(message_event) {
this_obj._add_message_port(message_event.source);
}, false);
}
SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
WorkerTestEnvironment.prototype.on_tests_ready.call(this); // In the absence of an onload notification, we a require shared // workers to explicitly signal when the tests are done.
tests.wait_for_finish = true;
};
// The oninstall event is received after the service worker script and // all imported scripts have been fetched and executed. It's the // equivalent of an onload event for a document. All tests should have // been added by the time this event is received, thus it's not // necessary to wait until the onactivate event. However, tests for // installed service workers need another event which is equivalent to // the onload event because oninstall is fired only on installation. The // onmessage event is used for that purpose since tests using // testharness.js should ask the result to its service worker by // PostMessage. If the onmessage event is triggered on the service // worker's context, that means the worker's script has been evaluated.
on_event(self, "install", on_all_loaded);
on_event(self, "message", on_all_loaded); function on_all_loaded() { if (this_obj.all_loaded) return;
this_obj.all_loaded = true; if (this_obj.on_loaded_callback) {
this_obj.on_loaded_callback();
}
}
}
ShellTestEnvironment.prototype.test_timeout = function() { // Tests running in a shell don't have a default timeout, so behave as // if settings.explicit_timeout is true. returnnull;
};
function create_test_environment() { if ('document' in global_scope) { returnnew WindowTestEnvironment();
} if ('DedicatedWorkerGlobalScope' in global_scope &&
global_scope instanceof DedicatedWorkerGlobalScope) { returnnew DedicatedWorkerTestEnvironment();
} if ('SharedWorkerGlobalScope' in global_scope &&
global_scope instanceof SharedWorkerGlobalScope) { returnnew SharedWorkerTestEnvironment();
} if ('ServiceWorkerGlobalScope' in global_scope &&
global_scope instanceof ServiceWorkerGlobalScope) { returnnew ServiceWorkerTestEnvironment();
} if ('WorkerGlobalScope' in global_scope &&
global_scope instanceof WorkerGlobalScope) { returnnew DedicatedWorkerTestEnvironment();
} /* Shadow realm global objects are _ordinary_ objects (i.e. their prototype is *Object)sowedon'thaveanice`instanceof`testtouse;instead,we *checkifthethereisaGLOBAL.isShadowRealm()property *ontheglobalobject.thatwassetbythetestharnesswhenit *createdtheShadowRealm.
*/ if (global_scope.GLOBAL && global_scope.GLOBAL.isShadowRealm()) { returnnew ShadowRealmTestEnvironment();
}
returnnew ShellTestEnvironment();
}
var test_environment = create_test_environment();
function is_shared_worker(worker) { return'SharedWorker' in global_scope && worker instanceof SharedWorker;
}
function is_service_worker(worker) { // The worker object may be from another execution context, // so do not use instanceof here. return'ServiceWorker' in global_scope &&
Object.prototype.toString.call(worker) === '[object ServiceWorker]';
}
var seen_func_name = Object.create(null);
function get_test_name(func, name)
{ if (name) { return name;
}
if (func) { var func_code = func.toString();
// Try and match with brackets, but fallback to matching without var arrow = func_code.match(/^\(\)\s*=>\s*(?:{(.*)}\s*|(.*))$/);
// Check for JS line separators if (arrow !== null && !/[\u000A\u000D\u2028\u2029]/.test(func_code)) { var trimmed = (arrow[1] !== undefined ? arrow[1] : arrow[2]).trim(); // drop trailing ; if there's no earlier ones
trimmed = trimmed.replace(/^([^;]*)(;\s*)+$/, "$1");
if (trimmed) {
let name = trimmed; if (seen_func_name[trimmed]) { // This subtest name already exists, so add a suffix.
name += " " + seen_func_name[trimmed];
} else {
seen_func_name[trimmed] = 0;
}
seen_func_name[trimmed] += 1; return name;
}
}
}
if (test_obj.phase === test_obj.phases.STARTED) {
test_obj.done();
}
}
/** *Createanasynchronoustest * *@param{TestFunction|string}funcOrName-Initialstepfunction *tocallimmediatelywiththetestnameasanargument(ifany), *ornameofthetest. *@param{String}name-Testname(ifatestfunctionwas *provided).Thismustbeuniqueinagivenfileandmustbe *invariantbetweenruns. *@returns{Test}Anobjectrepresentingtheongoingtest.
*/ function async_test(func, name, properties)
{ if (tests.promise_setup_called) {
tests.status.status = tests.status.ERROR;
tests.status.message = '`async_test` invoked after `promise_setup`';
tests.complete();
} if (typeof func !== "function") {
properties = name;
name = func;
func = null;
} var test_name = get_test_name(func, name); var test_obj = new Test(test_name, properties); if (func) { var value = test_obj.step(func, test_obj, test_obj);
// Test authors sometimes return values to async_test, expecting us // to handle the value somehow. Make doing so a harness error to be // clear this is invalid, and point authors to promise_test if it // may be appropriate. // // Note that we only perform this check on the initial function // passed to async_test, not on any later steps - we haven't seen a // consistent problem with those (and it's harder to check). if (value !== undefined) { var msg = 'Test named "' + test_name + '" passed a function to `async_test` that returned a value.';
try { if (value && typeof value.then === 'function') {
msg += ' Consider using `promise_test` instead when ' + 'using Promises or async/await.';
}
} catch (err) {}
/** *Createapromisetest. * *Promisetestsaretestswhicharerepresentedbyapromise *object.Ifthepromiseisfulfilledthetestpasses,ifit's *rejectedthetestfails,otherwisethetestpasses. * *@param{TestFunction}func-Testfunction.Thismustreturna *promise.Thetestisautomaticallymarkedascompleteoncethe *promisesettles. *@param{String}name-Testname.Thismustbeuniqueina *givenfileandmustbeinvariantbetweenruns.
*/ function promise_test(func, name, properties) { if (typeof func !== "function") {
properties = name;
name = func;
func = null;
} var test_name = get_test_name(func, name); var test = new Test(test_name, properties);
test._is_promise_test = true;
// If there is no promise tests queue make one. if (!tests.promise_tests) {
tests.promise_tests = Promise.resolve();
}
tests.promise_tests = tests.promise_tests.then(function() { returnnew Promise(function(resolve) { var promise = test.step(func, test, test);
test.step(function() { assert(!!promise, "promise_test", null, "test body must return a 'thenable' object (received ${value})",
{value:promise}); assert(typeof promise.then === "function", "promise_test", null, "test body must return a 'thenable' object (received an object with no `then` method)", null);
});
// Test authors may use the `step` method within a // `promise_test` even though this reflects a mixture of // asynchronous control flow paradigms. The "done" callback // should be registered prior to the resolution of the // user-provided Promise to avoid timeouts in cases where the // Promise does not settle but a `step` function has thrown an // error.
add_test_done_callback(test, resolve);
if (Array.isArray(recordedEvents)) {
recordedEvents.push(evt);
}
if (waitingFor.types.length > 1) { // Pop first event from array
waitingFor.types.shift(); return;
} // We need to null out waitingFor before calling the resolve function // since the Promise's resolve handlers may call wait_for() which will // need to set waitingFor. var resolveFunc = waitingFor.resolve;
waitingFor = null; // Likewise, we should reset the state of recordedEvents. var result = recordedEvents || evt;
recordedEvents = null;
resolveFunc(result);
});
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.addEventListener(eventTypes[i], eventHandler, false);
}
/** *ReturnsaPromisethatwillresolveafterthespecifiedeventor *seriesofeventshasoccurred. * *@param{Object}optionsAnoptionaloptionsobject.Ifthe'record'property *onthisobjecthasthevalue'all',whenthePromise *returnedbythisfunctionisresolved,*all*Event *objectsthatwerewaitedforwillbereturnedasan *array. * *@example *constwatcher=newEventWatcher(t,div,['animationstart', *'animationiteration', *'animationend']); *returnwatcher.wait_for(['animationstart','animationend'], *{record:'all'}).then(evts=>{ *assert_equals(evts[0].elapsedTime,0.0); *assert_equals(evts[1].elapsedTime,2.0); *});
*/ this.wait_for = function(types, options) { if (waitingFor) { return Promise.reject('Already waiting for an event or events');
} if (typeof types === 'string') {
types = [types];
} if (options && options.record && options.record === 'all') {
recordedEvents = [];
} returnnew Promise(function(resolve, reject) { var timeout = test.step_func(function() { // If the timeout fires after the events have been received // or during a subsequent call to wait_for, ignore it. if (!waitingFor || waitingFor.resolve !== resolve) return;
// This should always fail, otherwise we should have // resolved the promise.
assert_true(waitingFor.types.length === 0, 'Timed out waiting for ' + waitingFor.types.join(', ')); var result = recordedEvents;
recordedEvents = null; var resolveFunc = waitingFor.resolve;
waitingFor = null;
resolveFunc(result);
});
if (timeoutPromise) {
timeoutPromise().then(timeout);
}
/** *Configuretheharness,waitingforapromisetoresolve *beforerunningany`promise_test`tests. * *@param{Function}func-Functionreturningapromisethat's *runsynchronously.Promisetestsarenotrununtilafterthis *functionhasresolved. *@param{SettingsObject}[properties]-Anobjectcontaining *theharnesssettingstouse. *
*/ function promise_setup(func, properties={})
{ if (typeof func !== "function") {
tests.set_status(tests.status.ERROR, "`promise_setup` invoked without a function");
tests.complete(); return;
}
tests.promise_setup_called = true;
if (!tests.promise_tests) {
tests.promise_tests = Promise.resolve();
}
tests.promise_tests = tests.promise_tests
.then(function()
{ var result;
tests.setup(null, properties);
result = func();
test_environment.on_new_harness_properties(properties);
if (!result || typeof result.then !== "function") { throw"Non-thenable returned by function passed to `promise_setup`";
} return result;
})
.catch(function(e)
{
tests.set_status(tests.status.ERROR,
String(e),
e && e.stack);
tests.complete();
});
}
/** *Marktestloadingascomplete. * *Typicallythisfunctioniscalledimplicitlyonpageload;it's *onlynecessaryforuserstocallthiswheneitherthe *``explicit_done``or``single_test``propertieshavebeenset *viathe:js:func:`setup`function. * *Forsinglepageteststhismarksthetestascompleteandsetsitsstatus. *Forothertests,thismarkstestloadingascomplete,butdoesn'taffectongoingtests.
*/ function done() { if (tests.tests.length === 0) { // `done` is invoked after handling uncaught exceptions, so if the // harness status is already set, the corresponding message is more // descriptive than the generic message defined here. if (tests.status.status === null) {
tests.status.status = tests.status.ERROR;
tests.status.message = "done() was called without first defining any tests";
}
tests.complete(); return;
} if (tests.file_is_test) { // file is test files never have asynchronous cleanup logic, // meaning the fully-synchronous `done` function can be used here.
tests.tests[0].done();
}
tests.end_wait();
}
// Internal helper function to provide timeout-like functionality in // environments where there is no setTimeout(). (No timeout ID or // clearTimeout().) function fake_set_timeout(callback, delay) { var p = Promise.resolve(); var start = Date.now(); var end = start + delay; function check() { if ((end - Date.now()) > 0) {
p.then(check);
} else {
callback();
}
}
p.then(check);
}
/* *Returnastringtruncatedtothegivenlength,with...addedattheend *ifitwaslonger.
*/ function truncate(s, len)
{ if (s.length > len) { return s.substring(0, len - 3) + "...";
} return s;
}
/* *ReturntrueifobjectisprobablyaNodeobject.
*/ function is_node(object)
{ // I use duck-typing instead of instanceof, because // instanceof doesn't work if the node is from another window (like an // iframe's contentWindow): // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 try { var has_node_properties = ("nodeType" in object && "nodeName" in object && "nodeValue" in object && "childNodes" in object);
} catch (e) { // We're probably cross-origin, which means we aren't a node returnfalse;
}
if (has_node_properties) { try {
object.nodeType;
} catch (e) { // The object is probably Node.prototype or another prototype // object that inherits from it, and not a Node instance. returnfalse;
} returntrue;
} returnfalse;
}
function same_value(x, y) { if (y !== y) { //NaN case return x !== x;
} if (x === 0 && y === 0) { //Distinguish +0 and -0 return1/x === 1/y;
} return x === y;
}
/** *Assertthat``expected``isanarrayand``actual``isoneofthemembers. *Thisisimplementedusing``indexOf``,sodoesn'thandleNaNor±0correctly. * *@param{Any}actual-Testvalue. *@param{Array}expected-Anarraythat``actual``isexpectedto *beamemberof. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_in_array(actual, expected, description)
{ assert(expected.indexOf(actual) != -1, "assert_in_array", description, "value ${actual} not in array ${expected}",
{actual:actual, expected:expected});
}
expose_assert(assert_in_array, "assert_in_array");
// This function was deprecated in July of 2015. // See https://github.com/web-platform-tests/wpt/issues/2033 /** *@deprecated *Recursivelycomparetwoobjectsforequality. * *See`Issue2033 *<https://github.com/web-platform-tests/wpt/issues/2033>`_ for *moreinformation. * *@param{Object}actual-Testvalue. *@param{Object}expected-Expectedvalue. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_object_equals(actual, expected, description)
{ assert(typeof actual === "object" && actual !== null, "assert_object_equals", description, "value is ${actual}, expected object",
{actual: actual}); //This needs to be improved a great deal function check_equal(actual, expected, stack)
{
stack.push(actual);
var p; for (p in actual) { assert(expected.hasOwnProperty(p), "assert_object_equals", description, "unexpected property ${p}", {p:p});
/** *Assertthat``actual``and``expected``arebotharrays,andthatthearraypropertiesof *``actual``and``expected``areallthesamevalue(asfor:js:func:`assert_equals`). * *@param{Array}actual-Testarray. *@param{Array}expected-Arraythatisexpectedtocontainthesamevaluesas``actual``. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_array_equals(actual, expected, description)
{ const max_array_length = 20; function shorten_array(arr, offset = 0) { // Make ", …" only show up when it would likely reduce the length, not accounting for // fonts. if (arr.length < max_array_length + 2) { return arr;
} // By default we want half the elements after the offset and half before // But if that takes us past the end of the array, we have more before, and // if it takes us before the start we have more after. const length_after_offset = Math.floor(max_array_length / 2);
let upper_bound = Math.min(length_after_offset + offset, arr.length); const lower_bound = Math.max(upper_bound - max_array_length, 0);
if (lower_bound === 0) {
upper_bound = max_array_length;
}
for (var i = 0; i < actual.length; i++) { assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), "assert_array_approx_equals", description, "property ${i}, property expected to be ${expected} but was ${actual}",
{i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
actual:actual.hasOwnProperty(i) ? "present" : "missing"}); assert(typeof actual[i] === "number", "assert_array_approx_equals", description, "property ${i}, expected a number but got a ${type_actual}",
{i:i, type_actual:typeof actual[i]}); assert(Math.abs(actual[i] - expected[i]) <= epsilon, "assert_array_approx_equals", description, "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}",
{i:i, expected:expected[i], actual:actual[i], epsilon:epsilon});
}
}
expose_assert(assert_array_approx_equals, "assert_array_approx_equals");
/** *Assertthat``actual``iswithin±``epsilon``of``expected``. * *@param{number}actual-Testvalue. *@param{number}expected-Valuenumberisexpectedtobecloseto. *@param{number}epsilon-Magnitudeofalloweddifferencebetween``actual``and``expected``. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_approx_equals(actual, expected, epsilon, description)
{ /* *Testiftwoprimitivenumbersareequalwithin+/-epsilon
*/ assert(typeof actual === "number", "assert_approx_equals", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
// The epsilon math below does not place nice with NaN and Infinity // But in this case Infinity = Infinity and NaN = NaN if (isFinite(actual) || isFinite(expected)) { assert(Math.abs(actual - expected) <= epsilon, "assert_approx_equals", description, "expected ${expected} +/- ${epsilon} but got ${actual}",
{expected:expected, actual:actual, epsilon:epsilon});
} else {
assert_equals(actual, expected);
}
}
expose_assert(assert_approx_equals, "assert_approx_equals");
/** *Assertthat``actual``isanumberlessthan``expected``. * *@param{number|bigint}actual-Testvalue. *@param{number|bigint}expected-Valuethat``actual``mustbelessthan. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_less_than(actual, expected, description)
{ /* *Testifaprimitivenumber(orbigint)islessthananother
*/ assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(typeof actual === typeof expected, "assert_less_than", description, "expected a ${type_expected} but got a ${type_actual}",
{type_expected:typeof expected, type_actual:typeof actual});
assert(actual < expected, "assert_less_than", description, "expected a number less than ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose_assert(assert_less_than, "assert_less_than");
/** *Assertthat``actual``isanumbergreaterthan``expected``. * *@param{number|bigint}actual-Testvalue. *@param{number|bigint}expected-Valuethat``actual``mustbegreaterthan. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_greater_than(actual, expected, description)
{ /* *Testifaprimitivenumber(orbigint)isgreaterthananother
*/ assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(typeof actual === typeof expected, "assert_greater_than", description, "expected a ${type_expected} but got a ${type_actual}",
{type_expected:typeof expected, type_actual:typeof actual});
assert(actual > expected, "assert_greater_than", description, "expected a number greater than ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose_assert(assert_greater_than, "assert_greater_than");
/** *Assertthat``actual``isanumbergreaterthan``lower``andless *than``upper``butnotequaltoeither. * *@param{number|bigint}actual-Testvalue. *@param{number|bigint}lower-Valuethat``actual``mustbegreaterthan. *@param{number|bigint}upper-Valuethat``actual``mustbelessthan. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_between_exclusive(actual, lower, upper, description)
{ /* *Testifaprimitivenumber(orbigint)isbetweentwoothers
*/ assert(typeof lower === typeof upper, "assert_between_exclusive", description, "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)",
{type_lower:typeof lower, type_upper:typeof upper});
assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_exclusive", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(typeof actual === typeof lower, "assert_between_exclusive", description, "expected a ${type_lower} but got a ${type_actual}",
{type_lower:typeof lower, type_actual:typeof actual});
assert(actual > lower && actual < upper, "assert_between_exclusive", description, "expected a number greater than ${lower} " + "and less than ${upper} but got ${actual}",
{lower:lower, upper:upper, actual:actual});
}
expose_assert(assert_between_exclusive, "assert_between_exclusive");
/** *Assertthat``actual``isanumberlessthanorequalto``expected``. * *@param{number|bigint}actual-Testvalue. *@param{number|bigint}expected-Valuethat``actual``mustbeless *thanorequalto. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_less_than_equal(actual, expected, description)
{ /* *Testifaprimitivenumber(orbigint)islessthanorequaltoanother
*/ assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than_equal", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(typeof actual === typeof expected, "assert_less_than_equal", description, "expected a ${type_expected} but got a ${type_actual}",
{type_expected:typeof expected, type_actual:typeof actual});
assert(actual <= expected, "assert_less_than_equal", description, "expected a number less than or equal to ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose_assert(assert_less_than_equal, "assert_less_than_equal");
/** *Assertthat``actual``isanumbergreaterthanorequalto``expected``. * *@param{number|bigint}actual-Testvalue. *@param{number|bigint}expected-Valuethat``actual``mustbegreater *thanorequalto. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/ function assert_greater_than_equal(actual, expected, description)
{ /* *Testifaprimitivenumber(orbigint)isgreaterthanorequaltoanother
*/ assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than_equal", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(typeof actual === typeof expected, "assert_greater_than_equal", description, "expected a ${type_expected} but got a ${type_actual}",
{type_expected:typeof expected, type_actual:typeof actual});
assert(actual >= expected, "assert_greater_than_equal", description, "expected a number greater than or equal to ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose_assert(assert_greater_than_equal, "assert_greater_than_equal");
/** *Assertthat``actual``isanumbergreaterthanorequalto``lower``andless *thanorequalto``upper``. * *@param{number|bigint}actual-Testvalue. *@param{number|bigint}lower-Valuethat``actual``mustbegreaterthanorequalto. *@param{number|bigint}upper-Valuethat``actual``mustbelessthanorequalto. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/
function assert_between_inclusive(actual, lower, upper, description)
{ /* *Testifaprimitivenumber(orbigint)isbetweentotwoothersorequaltoeitherofthem
*/
assert(typeof lower === typeof upper, "assert_between_inclusive", description, "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)",
{type_lower:typeof lower, type_upper:typeof upper});
assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_inclusive", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(typeof actual === typeof lower, "assert_between_inclusive", description, "expected a ${type_lower} but got a ${type_actual}",
{type_lower:typeof lower, type_actual:typeof actual});
assert(actual >= lower && actual <= upper, "assert_between_inclusive", description, "expected a number greater than or equal to ${lower} " + "and less than or equal to ${upper} but got ${actual}",
{lower:lower, upper:upper, actual:actual});
}
expose_assert(assert_between_inclusive, "assert_between_inclusive");
/** *Assertthat``object``doesnothaveanownpropertywithname``property_name``. * *@param{Object}object-Objectthatshouldnothavethegivenproperty. *@param{string}property_name-Propertynametotest. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/
function assert_not_own_property(object, property_name, description) {
assert(!object.hasOwnProperty(property_name), "assert_not_own_property", description, "unexpected property ${p} is found on object", {p:property_name});
}
expose_assert(assert_not_own_property, "assert_not_own_property");
function _assert_inherits(name) { return function (object, property_name, description)
{
assert((typeof object === "object" && object !== null) ||
typeof object === "function" || // Or has [[IsHTMLDDA]] slot
String(object) === "[object HTMLAllCollection]",
name, description, "provided value is not an object");
assert("hasOwnProperty" in object,
name, description, "provided value is an object but has no hasOwnProperty method");
assert(!object.hasOwnProperty(property_name),
name, description, "property ${p} found on object expected in prototype chain",
{p:property_name});
assert(property_name in object,
name, description, "property ${p} not found in prototype chain",
{p:property_name});
};
}
/** *Assertthat``object``hasapropertynamed``property_name``andthatthepropertyisnotwritableorhasnosetter. * *@param{Object}object-Objectthatshouldhavethegiven(notnecessarilyown)property. *@param{string}property_name-Expectedpropertyname. *@param{string}[description]-Descriptionoftheconditionbeingtested.
*/
function assert_readonly(object, property_name, description)
{
assert(property_name in object, "assert_readonly", description, "property ${p} not found",
{p:property_name});
let desc; while (object && (desc = Object.getOwnPropertyDescriptor(object, property_name)) === undefined) {
object = Object.getPrototypeOf(object);
}
assert(desc !== undefined, "assert_readonly", description, "could not find a descriptor for property ${p}",
{p:property_name});
if (desc.hasOwnProperty("value")) { // We're a data property descriptor
assert(desc.writable === false, "assert_readonly", description, "descriptor [[Writable]] expected false got ${actual}", {actual:desc.writable});
} elseif (desc.hasOwnProperty("get") || desc.hasOwnProperty("set")) { // We're an accessor property descriptor
assert(desc.set === undefined, "assert_readonly", description, "property ${p} is an accessor property with a [[Set]] attribute, cannot test readonly-ness",
{p:property_name});
} else { // We're a generic property descriptor // This shouldn't happen, because Object.getOwnPropertyDescriptor // forwards the return value of [[GetOwnProperty]] (P), which must // be a fully populated Property Descriptor or Undefined.
assert(false, "assert_readonly", description, "Object.getOwnPropertyDescriptor must return a fully populated property descriptor");
}
}
expose_assert(assert_readonly, "assert_readonly");
/** *Likeassert_throws_jsbutallowsspecifyingtheassertiontype *(assert_throws_jsorpromise_rejects_js,inpractice).
*/
function assert_throws_js_impl(constructor, func, description,
assertion_type)
{
try {
func.call(this);
assert(false, assertion_type, description, "${func} did not throw", {func:func});
} catch (e) { if (e instanceof AssertionError) { throw e;
}
// Basic sanity-checks on the thrown exception.
assert(typeof e === "object",
assertion_type, description, "${func} threw ${e} with type ${type}, not an object",
{func:func, e:e, type:typeof e});
assert(e !== null,
assertion_type, description, "${func} threw null, not an object",
{func:func});
// Basic sanity-check on the passed-in constructor
assert(typeof constructor === "function",
assertion_type, description, "${constructor} is not a constructor",
{constructor:constructor});
var obj = constructor; while (obj) { if (typeof obj === "function" &&
obj.name === "Error") { break;
}
obj = Object.getPrototypeOf(obj);
}
assert(obj != null,
assertion_type, description, "${constructor} is not an Error subtype",
{constructor:constructor});
// And checking that our exception is reasonable
assert(e.constructor === constructor &&
e.name === constructor.name,
assertion_type, description, "${func} threw ${actual} (${actual_name}) expected instance of ${expected} (${expected_name})",
{func:func, actual:e, actual_name:e.name,
expected:constructor,
expected_name:constructor.name});
}
}
// TODO: Figure out how to document the overloads better. // sphinx-js doesn't seem to handle @variation correctly, // and only expects a single JSDoc entry per function. /** *AssertaDOMExceptionwiththeexpectedtypeisthrown. * *Therearetwowaysofcallingassert_throws_dom: * *1)IftheDOMExceptionisexpectedtocomefromthecurrentglobal,the *secondargumentshouldbethefunctionexpectedtothrowandathird, *optional,argumentistheassertiondescription. * *2)IftheDOMExceptionisexpectedtocomefromsomeotherglobal,the *secondargumentshouldbetheDOMExceptionconstructorfromthatglobal, *thethirdargumentthefunctionexpectedtothrow,andthefourth,optional, *argumenttheassertiondescription. * *@param{number|string}type-Theexpectedexceptionnameor *code.Seethe`tableofnamesandcodes *<https://webidl.spec.whatwg.org/#dfn-error-names-table>`_. If a *numberispasseditshouldbeoneofthenumericcodevaluesin *thattable(e.g.3,4,etc).Ifastringispasseditcan *eitherbeanexceptionname(e.g."HierarchyRequestError", *"WrongDocumentError")orthenameofthecorrespondingerror *code(e.g."``HIERARCHY_REQUEST_ERR``","``WRONG_DOCUMENT_ERR``"). *@param{Function}descriptionOrFunc-Thefunctionexpectedto *throw(iftheexceptioncomesfromanotherglobal),orthe *optionaldescriptionoftheconditionbeingtested(ifthe *exceptioncomesfromthecurrentglobal). *@param{string}[description]-Descriptionofthecondition *beingtested(iftheexceptioncomesfromanotherglobal). *
*/
function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription)
{
let constructor, func, description; if (funcOrConstructor.name === "DOMException") {
constructor = funcOrConstructor;
func = descriptionOrFunc;
description = maybeDescription;
} else {
constructor = self.DOMException;
func = funcOrConstructor;
description = descriptionOrFunc;
assert(maybeDescription === undefined, "Too many args passed to no-constructor version of assert_throws_dom, or accidentally explicitly passed undefined");
}
assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor);
}
expose_assert(assert_throws_dom, "assert_throws_dom");
/** *Similartoassert_throws_dombutallowsspecifyingtheassertiontype *(assert_throws_domorpromise_rejects_dom,inpractice).The *"constructor"argumentmustbetheDOMExceptionconstructorfromthe *globalweexpecttheexceptiontocomefrom.
*/
function assert_throws_dom_impl(type, func, description, assertion_type, constructor)
{
try {
func.call(this);
assert(false, assertion_type, description, "${func} did not throw", {func:func});
} catch (e) { if (e instanceof AssertionError) { throw e;
}
// Basic sanity-checks on the thrown exception.
assert(typeof e === "object",
assertion_type, description, "${func} threw ${e} with type ${type}, not an object",
{func:func, e:e, type:typeof e});
assert(e !== null,
assertion_type, description, "${func} threw null, not an object",
{func:func});
// Sanity-check our type
assert(typeof type === "number" ||
typeof type === "string",
assertion_type, description, "${type} is not a number or string",
{type:type});
var code_name_map = {}; for (var key in name_code_map) { if (name_code_map[key] > 0) {
code_name_map[name_code_map[key]] = key;
}
}
var required_props = {};
var name;
if (typeof type === "number") { if (type === 0) { thrownew AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()');
} if (type === 22) { thrownew AssertionError('Test bug: QuotaExceededError needs to be tested for using assert_throws_quotaexceedederror()');
} if (!(type in code_name_map)) { thrownew AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()');
}
name = code_name_map[type];
required_props.code = type;
} elseif (typeof type === "string") { if (name === "QuotaExceededError") { thrownew AssertionError('Test bug: QuotaExceededError needs to be tested for using assert_throws_quotaexceedederror()');
}
name = type in codename_name_map ? codename_name_map[type] : type; if (!(name in name_code_map)) { thrownew AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()');
}
required_props.code = name_code_map[name];
}
if (required_props.code === 0 ||
("name" in e &&
e.name !== e.name.toUpperCase() &&
e.name !== "DOMException")) { // New style exception: also test the name property.
required_props.name = name;
}
for (var prop in required_props) {
assert(prop in e && e[prop] == required_props[prop],
assertion_type, description, "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}",
{func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
}
// Check that the exception is from the right global. This check is last // so more specific, and more informative, checks on the properties can // happen in case a totally incorrect exception is thrown.
assert(e.constructor === constructor,
assertion_type, description, "${func} threw an exception from the wrong global",
{func});
/** *Similarto`assert_throws_quotaexceedederror`butallows *specifyingtheassertiontype *(`"assert_throws_quotaexceedederror"`or *`"promise_rejects_quotaexceedederror"`,inpractice).The *`constructor`argumentmustbethe`QuotaExceededError` *constructorfromtheglobalweexpecttheexceptiontocomefrom.
*/
function assert_throws_quotaexceedederror_impl(func, requested, quota, description, assertion_type, constructor)
{
try {
func.call(this);
assert(false, assertion_type, description, "${func} did not throw",
{func});
} catch (e) { if (e instanceof AssertionError) { throw e;
}
// Basic sanity-checks on the thrown exception.
assert(typeof e === "object",
assertion_type, description, "${func} threw ${e} with type ${type}, not an object",
{func, e, type:typeof e});
assert(e !== null,
assertion_type, description, "${func} threw null, not an object",
{func});
// Sanity-check our requested and quota.
assert(requested === null ||
typeof requested === "number" ||
typeof requested === "function",
assertion_type, description, "${requested} is not null, a number, or a function",
{requested});
assert(quota === null ||
typeof quota === "number" ||
typeof quota === "function",
assertion_type, description, "${quota} is not null or a number",
{quota});
for (const [prop, expected] of Object.entries(required_props)) {
assert(prop in e && e[prop] == expected,
assertion_type, description, "${func} threw ${e} that is not a correct QuotaExceededError: property ${prop} is equal to ${actual}, expected ${expected}",
{func, e, prop, actual:e[prop], expected});
}
if (typeof requested === "function") {
assert(requested(e.requested),
assertion_type, description, "${func} threw ${e} that is not a correct QuotaExceededError: requested value ${requested} did not pass the requested predicate",
{func, e, requested});
} if (typeof quota === "function") {
assert(quota(e.quota),
assertion_type, description, "${func} threw ${e} that is not a correct QuotaExceededError: quota value ${quota} did not pass the quota predicate",
{func, e, quota});
}
// Check that the exception is from the right global. This check is last // so more specific, and more informative, checks on the properties can // happen in case a totally incorrect exception is thrown.
assert(e.constructor === constructor,
assertion_type, description, "${func} threw an exception from the wrong global",
{func});
}
}
/** *Likeassert_throws_exactlybutallowsspecifyingtheassertiontype *(assert_throws_exactlyorpromise_rejects_exactly,inpractice).
*/
function assert_throws_exactly_impl(exception, func, description,
assertion_type)
{
try {
func.call(this);
assert(false, assertion_type, description, "${func} did not throw", {func:func});
} catch (e) { if (e instanceof AssertionError) { throw e;
}
assert(same_value(e, exception), assertion_type, description, "${func} threw ${e} but we expected it to throw ${exception}",
{func:func, e:e, exception:exception});
}
}
/** *@class * *Asinglesubtest.ATestisnotconstructeddirectlybutviathe *:js:func:`test`,:js:func:`async_test`or:js:func:`promise_test`functions. * *@param{string}name-Thismustbeuniqueinagivenfileandmustbe *invariantbetweenruns. *
*/
function Test(name, properties)
{ if (tests.file_is_test && tests.tests.length) { thrownew Error("Tried to create a test with file_is_test");
} /** The test name. */
this.name = name;
if (typeof AbortController === "function") {
this._abortController = new AbortController();
}
// Tests declared following harness completion are likely an indication // of a programming error, but they cannot be reported // deterministically. if (tests.phase === tests.phases.COMPLETE) { return;
}
if (settings.debug && this.phase !== this.phases.STARTED) {
console.log("TEST START", this.name);
}
this.phase = this.phases.STARTED; //If we don't get a result before the harness times out that will be a test timeout
this.set_status(this.TIMEOUT, "Test timed out");
if (this.phase <= this.phases.STARTED) {
this.set_status(this.PASS, null);
}
if (global_scope.clearTimeout) {
clearTimeout(this.timeout_id);
}
if (settings.debug) {
console.log("TEST DONE",
this.status,
this.name);
}
this.cleanup();
};
function add_test_done_callback(test, callback)
{ if (test.phase === test.phases.COMPLETE) {
callback(); return;
}
test._done_callbacks.push(callback);
}
/* *Invokeallspecifiedcleanupfunctions.Ifoneormoreproduceanerror, *thecontextisinanunpredictablestate,soallfurthertestingshould *becancelled.
*/
Test.prototype.cleanup = function() {
var errors = [];
var bad_value_count = 0;
function on_error(e) {
errors.push(e); // Abort tests immediately so that tests declared within subsequent // cleanup functions are not run.
tests.abort();
}
var this_obj = this;
var results = [];
this.phase = this.phases.CLEANING;
if (this._abortController) {
this._abortController.abort("Test cleanup");
}
forEach(this.cleanup_callbacks,
function(cleanup_callback) {
var result;
if (!is_valid_cleanup_result(this_obj, result)) {
bad_value_count += 1; // Abort tests immediately so that tests declared // within subsequent cleanup functions are not run.
tests.abort();
}
if (bad_value_count) {
var type = test._is_promise_test ? "non-thenable" : "non-undefined";
tests.status.message += ", and " + bad_value_count + " returned a " + type + " value";
}
/** *GivesanAbortSignalthatwillbeabortedwhenthetestfinishes.
*/
Test.prototype.get_signal = function() { if (!this._abortController) { thrownew Error("AbortController is not supported in this browser");
} return this._abortController.signal;
};
RemoteTest.prototype.structured_clone = function() {
var clone = {};
Object.keys(this).forEach(
(function(key) {
var value = this[key]; // `RemoteTest` instances are responsible for managing // their own "done" callback functions, so those functions // are not relevant in other execution contexts. Because of // this (and because Function values cannot be serialized // for cross-realm transmittance), the property should not // be considered when cloning instances. if (key === '_done_callbacks' ) { return;
}
if (typeof value === "object" && value !== null) {
clone[key] = merge({}, value);
} else {
clone[key] = value;
}
}).bind(this));
clone.phases = merge({}, this.phases); return clone;
};
var this_obj = this; // If remote context is cross origin assigning to onerror is not // possible, so silently catch those errors.
try {
remote.onerror = function(error) { this_obj.remote_error(error); };
} catch (e) { // Ignore.
}
// Keeping a reference to the remote object and the message handler until // remote_done() is seen prevents the remote object and its message channel // from going away before all the messages are dispatched.
this.remote = remote;
this.message_target = message_target;
this.message_handler = function(message) {
var passesFilter = !message_filter || message_filter(message); // The reference to the `running` property in the following // condition is unnecessary because that value is only set to // `false` after the `message_handler` function has been // unsubscribed. // TODO: Simplify the condition by removing the reference. if (this_obj.running && message.data && passesFilter &&
(message.data.type in this_obj.message_handlers)) {
this_obj.message_handlers[message.data.type].call(this_obj, message.data);
}
};
if (self.Promise) {
this.done = new Promise(function(resolve) {
this_obj.doneResolve = resolve;
});
}
RemoteContext.prototype.remote_error = function(error) { if (error.preventDefault) {
error.preventDefault();
}
// Defer interpretation of errors until the testing protocol has // started and the remote test's `allow_uncaught_exception` property // is available. if (!this.started) {
this.early_exception = error;
} elseif (!this.allow_uncaught_exception) {
this.report_uncaught(error);
}
};
RemoteContext.prototype.report_uncaught = function(error) {
var message = error.message || String(error);
var filename = (error.filename ? " " + error.filename: ""); // FIXME: Display remote error states separately from main document // error state.
tests.set_status(tests.status.ERROR, "Error in remote" + filename + ": " + message,
error.stack);
};
// If remote context is cross origin assigning to onerror is not // possible, so silently catch those errors.
try {
this.remote.onerror = null;
} catch (e) { // Ignore.
}
/** *@class *Statusoftheoverallharness
*/
function TestsStatus()
{ /** The status code */
this.status = null; /** Message in case of failure */
this.message = null; /** Stack trace in case of an exception. */
this.stack = null;
}
/** *@class *Recordofanassertthatran. * *@param{Test}test-Thetestwhichrantheassert. *@param{string}assert_name-Thefunctionnameoftheassert. *@param{Any}args-Theargumentspassedtotheassertfunction.
*/
function AssertRecord(test, assert_name, args = []) { /** Name of the assert that ran */
this.assert_name = assert_name; /** Test that ran the assert */
this.test = test; // Avoid keeping complex objects alive /** Stringification of the arguments that were passed to the assert function */
this.args = args.map(x => format_value(x).replace(/\n/g, " ")); /** Status of the assert */
this.status = null;
}
this.file_is_test = false; // This value is lazily initialized in order to avoid introducing a // dependency on ECMAScript 2015 Promises to all tests.
this.promise_tests = null;
this.promise_setup_called = false;
// Track whether output is enabled, and thus whether or not we should // track asserts. // // On workers we don't get properties set from testharnessreport.js, so // we don't know whether or not to track asserts. To avoid the // resulting performance hit, we assume we are not meant to. This means // that assert tracking does not function on workers.
this.output = settings.output && 'document' in global_scope;
this.status = new TestsStatus();
var this_obj = this;
test_environment.add_on_loaded_callback(function() { if (this_obj.all_done()) {
this_obj.complete();
}
});
Tests.prototype.set_file_is_test = function() { if (this.tests.length > 0) { thrownew Error("Tried to set file as test after creating a test");
}
this.wait_for_finish = true;
this.file_is_test = true; // Create the test, which will add it to the list of tests
tests.current_test = async_test();
};
Tests.prototype.set_timeout = function() { if (global_scope.clearTimeout) {
var this_obj = this;
clearTimeout(this.timeout_id); if (this.timeout_length !== null) {
this.timeout_id = setTimeout(function() {
this_obj.timeout();
}, this.timeout_length);
}
}
};
Tests.prototype.timeout = function() {
var test_in_cleanup = null;
if (this.status.status === null) {
forEach(this.tests,
function(test) { // No more than one test is expected to be in the // "CLEANUP" phase at any time if (test.phase === test.phases.CLEANING) {
test_in_cleanup = test;
}
test.phase = test.phases.COMPLETE;
});
// Timeouts that occur while a test is in the "cleanup" phase // indicate that some global state was not properly reverted. This // invalidates the overall test execution, so the timeout should be // reported as an error and cancel the execution of any remaining // tests. if (test_in_cleanup) {
this.status.status = this.status.ERROR;
this.status.message = "Timeout while running cleanup for " + "test named \"" + test_in_cleanup.name + "\".";
tests.status.stack = null;
} else {
this.status.status = this.status.TIMEOUT;
}
}
Tests.prototype.result = function(test)
{ // If the harness has already transitioned beyond the `HAVE_RESULTS` // phase, subsequent tests should not cause it to revert. if (this.phase <= this.phases.HAVE_RESULTS) {
this.phase = this.phases.HAVE_RESULTS;
}
this.num_pending--;
this.notify_result(test);
};
/* *Determineifanytestssharethesame`name`property.Returnanarray *containingthenamesofanysuchduplicates.
*/
Tests.prototype.find_duplicates = function() {
var names = Object.create(null);
var duplicates = [];
forEach (this.tests,
function(test)
{ if (test.name in names && duplicates.indexOf(test.name) === -1) {
duplicates.push(test.name);
}
names[test.name] = true;
});
return duplicates;
};
function code_unit_str(char) { return'U+' + char.charCodeAt(0).toString(16);
}
function sanitize_unpaired_surrogates(str) { return str.replace(
/([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
function(_, low, prefix, high) {
var output = prefix || ""; // prefix may be undefined
var string = low || high; // only one of these alternates can match for (var i = 0; i < string.length; i++) {
output += code_unit_str(string[i]);
} return output;
});
}
function sanitize_all_unpaired_surrogates(tests) {
forEach (tests,
function (test)
{
var sanitized = sanitize_unpaired_surrogates(test.name);
Tests.prototype.notify_complete = function() {
var this_obj = this;
var duplicates;
if (this.status.status === null) {
duplicates = this.find_duplicates();
// Some transports adhere to UTF-8's restriction on unpaired // surrogates. Sanitize the titles so that the results can be // consistently sent via all transports.
sanitize_all_unpaired_surrogates(this.tests);
// Test names are presumed to be unique within test files--this // allows consumers to use them for identification purposes. // Duplicated names violate this expectation and should therefore // be reported as an error. if (duplicates.length) {
this.status.status = this.status.ERROR;
this.status.message =
duplicates.length + ' duplicate test name' +
(duplicates.length > 1 ? 's' : '') + ': "' +
duplicates.join('", "') + '"';
} else {
this.status.status = this.status.OK;
}
}
//If output is disabled in testharnessreport.js the test shouldn't be //able to override that
this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
properties.output : settings.output);
};
output_document.getElementById("rerun").addEventListener("click",
function() {
let evt = new Event('__test_restart');
let canceled = !window.dispatchEvent(evt); if (!canceled) { location.reload(); }
});
function has_assertions()
{ for (var i = 0; i < tests.length; i++) { if (tests[i].properties.hasOwnProperty("assert")) { return true;
}
} returnfalse;
}
function get_assertion(test)
{ if (test.properties.hasOwnProperty("assert")) { if (Array.isArray(test.properties.assert)) { return test.properties.assert.join(' ');
} return test.properties.assert;
} return'';
}
var asserts_run_by_test = new Map();
asserts_run.forEach(assert => { if (!asserts_run_by_test.has(assert.test)) {
asserts_run_by_test.set(assert.test, []);
}
asserts_run_by_test.get(assert.test).push(assert);
});
var asserts = asserts_run_by_test.get(test); if (!asserts) {
asserts_output.querySelector("summary").insertAdjacentText("afterend", "No asserts ran"); return asserts_output;
}
function is_single_node(template)
{ return typeof template[0] === "string";
}
function substitute(template, substitutions)
{ if (typeof template === "function") {
var replacement = template(substitutions); if (!replacement) { return null;
}
return substitute(replacement, substitutions);
}
if (is_single_node(template)) { return substitute_single(template, substitutions);
}
function substitute_single(template, substitutions)
{
var substitution_re = /\$\{([^ }]*)\}/g;
function do_substitution(input)
{
var components = input.split(substitution_re);
var rv = []; if (components.length === 1) {
rv = components;
} elseif (substitutions) { for (var i = 0; i < components.length; i += 2) { if (components[i]) {
rv.push(components[i]);
} if (substitutions[components[i + 1]]) {
rv.push(String(substitutions[components[i + 1]]));
}
}
} return rv;
}
function substitute_attrs(attrs, rv)
{
rv[1] = {}; for (var name in template[1]) { if (attrs.hasOwnProperty(name)) {
var new_name = do_substitution(name).join("");
var new_value = do_substitution(attrs[name]).join("");
rv[1][new_name] = new_value;
}
}
}
function substitute_children(children, rv)
{ for (var i = 0; i < children.length; i++) { if (children[i] instanceof Object) {
var replacement = substitute(children[i], substitutions); if (replacement !== null) { if (is_single_node(replacement)) {
rv.push(replacement);
} else {
extend(rv, replacement);
}
}
} else {
extend(rv, do_substitution(String(children[i])));
}
} return rv;
}
var rv = [];
rv.push(do_substitution(String(template[0])).join(""));
function make_dom_single(template, doc)
{
var output_document = doc || document;
var element; if (template[0] === "{text}") {
element = output_document.createTextNode(""); for (var i = 1; i < template.length; i++) {
element.data += template[i];
}
} else {
element = output_document.createElementNS(xhtml_ns, template[0]); for (var name in template[1]) { if (template[1].hasOwnProperty(name)) {
element.setAttribute(name, template[1][name]);
}
} for (var i = 2; i < template.length; i++) { if (template[i] instanceof Object) {
var sub_element = make_dom(template[i]);
element.appendChild(sub_element);
} else {
var text_node = output_document.createTextNode(template[i]);
element.appendChild(text_node);
}
}
}
return element;
}
function make_dom(template, substitutions, output_document)
{ if (is_single_node(template)) { return make_dom_single(template, output_document);
}
const get_stack = function() {
var stack = new Error().stack;
// 'Error.stack' is not supported in all browsers/versions if (!stack) { return"(Stack trace unavailable)";
}
var lines = stack.split("\n");
// Create a pattern to match stack frames originating within testharness.js. These include the // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21'). // Escape the URL per http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript // in case it contains RegExp characters.
var script_url = get_script_url();
var re_text = script_url ? script_url.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : "\\btestharness.js";
var re = new RegExp(re_text + ":\\d+:\\d+");
// Some browsers include a preamble that specifies the type of the error object. Skip this by // advancing until we find the first stack frame originating from testharness.js.
var i = 0; while (!re.test(lines[i]) && i < lines.length) {
i++;
}
// Then skip the top frames originating from testharness.js to begin the stack at the test code. while (re.test(lines[i]) && i < lines.length) {
i++;
}
// Paranoid check that we didn't skip all frames. If so, return the original stack unmodified. if (i >= lines.length) { return stack;
}
function make_message(function_name, description, error, substitutions)
{ for (var p in substitutions) { if (substitutions.hasOwnProperty(p)) {
substitutions[p] = format_value(substitutions[p]);
}
}
var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
merge({function_name:function_name,
description:(description?description + " ":"")},
substitutions)); return node_form.slice(1).join("");
}
function filter(array, callable, thisObj) {
var rv = []; for (var i = 0; i < array.length; i++) { if (array.hasOwnProperty(i)) {
var pass = callable.call(thisObj, array[i], i, array); if (pass) {
rv.push(array[i]);
}
}
} return rv;
}
function map(array, callable, thisObj)
{
var rv = [];
rv.length = array.length; for (var i = 0; i < array.length; i++) { if (array.hasOwnProperty(i)) {
rv[i] = callable.call(thisObj, array[i], i, array);
}
} return rv;
}
function extend(array, items)
{
Array.prototype.push.apply(array, items);
}
function forEach(array, callback, thisObj)
{ for (var i = 0; i < array.length; i++) { if (array.hasOwnProperty(i)) {
callback.call(thisObj, array[i], i, array);
}
}
}
forEach(values,
function(element) {
var invoked = false;
var elDone = function() { if (invoked) { return;
}
invoked = true;
remaining -= 1;
if (remaining === 0) {
done_callback();
}
};
iter_callback(element, elDone);
});
}
function merge(a,b)
{
var rv = {};
var p; for (p in a) {
rv[p] = a[p];
} for (p in b) {
rv[p] = b[p];
} return rv;
}
function expose(object, name)
{
var components = name.split(".");
var target = global_scope; for (var i = 0; i < components.length - 1; i++) { if (!(components[i] in target)) {
target[components[i]] = {};
}
target = target[components[i]];
}
target[components[components.length - 1]] = object;
}
function is_same_origin(w) {
try { 'random_prop' in w; return true;
} catch (e) { returnfalse;
}
}
/** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */
function get_script_url()
{ if (!('document' in global_scope)) { return undefined;
}
var scripts = document.getElementsByTagName("script"); for (var i = 0; i < scripts.length; i++) {
var src; if (scripts[i].src) {
src = scripts[i].src;
} elseif (scripts[i].href) { //SVG case
src = scripts[i].href.baseVal;
}
var matches = src && src.match(/^(.*\/|)testharness\.js$/); if (matches) { return src;
}
} return undefined;
}
/** Returns the <title> or filename or "Untitled" */
function get_title()
{ if ('document' in global_scope) { //Don't use document.title to work around an Opera/Presto bug in XHTML documents
var title = document.getElementsByTagName("title")[0]; if (title && title.firstChild && title.firstChild.data) { return title.firstChild.data;
}
} if ('META_TITLE' in global_scope && META_TITLE) { return META_TITLE;
} if ('location' in global_scope && 'pathname' in location) {
var filename = location.pathname.substring(location.pathname.lastIndexOf('/') + 1); return filename.substring(0, filename.indexOf('.'));
} return"Untitled";
}
/** Fetches a JSON resource and parses it */
async function fetch_json(resource) { const response = await fetch(resource); return await response.json();
} if (!global_scope.GLOBAL || !global_scope.GLOBAL.isShadowRealm()) {
expose(fetch_json, 'fetch_json');
}
/** *Setupglobals
*/
var tests = new Tests();
if (global_scope.addEventListener) {
var error_handler = function(error, message, stack) {
var optional_unsupported = error instanceof OptionalFeatureUnsupportedError; if (tests.file_is_test) {
var test = tests.tests[0]; if (test.phase >= test.phases.HAS_RESULT) { return;
}
var status = optional_unsupported ? test.PRECONDITION_FAILED : test.FAIL;
test.set_status(status, message, stack);
test.phase = test.phases.HAS_RESULT;
} elseif (!tests.allow_uncaught_exception) {
var status = optional_unsupported ? tests.status.PRECONDITION_FAILED : tests.status.ERROR;
tests.status.status = status;
tests.status.message = message;
tests.status.stack = stack;
}
// Do not transition to the "complete" phase if the test has been // configured to allow uncaught exceptions. This gives the test an // opportunity to define subtests based on the exception reporting // behavior. if (!tests.allow_uncaught_exception) {
done();
}
};
})(self); // vim: set expandtab shiftwidth=4 tabstop=4:
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.373Bemerkung:
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.