1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/promise/tests/test_promise.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,670 @@ 1.4 +<!-- 1.5 + Any copyright is dedicated to the Public Domain. 1.6 + http://creativecommons.org/publicdomain/zero/1.0/ 1.7 +--> 1.8 +<html> 1.9 +<head> 1.10 + <title>Basic Promise Test</title> 1.11 + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> 1.12 + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 1.13 +</head> 1.14 +<body> 1.15 +<p id="display"></p> 1.16 +<div id="content" style="display: none"> 1.17 + 1.18 +</div> 1.19 +<pre id="test"> 1.20 +<script type="application/javascript"><!-- 1.21 + 1.22 +function promiseResolve() { 1.23 + ok(Promise, "Promise object should exist"); 1.24 + 1.25 + var promise = new Promise(function(resolve, reject) { 1.26 + ok(resolve, "Promise.resolve exists"); 1.27 + ok(reject, "Promise.reject exists"); 1.28 + 1.29 + resolve(42); 1.30 + }).then(function(what) { 1.31 + ok(true, "Then - resolveCb has been called"); 1.32 + is(what, 42, "ResolveCb received 42"); 1.33 + runTest(); 1.34 + }, function() { 1.35 + ok(false, "Then - rejectCb has been called"); 1.36 + runTest(); 1.37 + }); 1.38 +} 1.39 + 1.40 +function promiseResolveNoArg() { 1.41 + var promise = new Promise(function(resolve, reject) { 1.42 + ok(resolve, "Promise.resolve exists"); 1.43 + ok(reject, "Promise.reject exists"); 1.44 + 1.45 + resolve(); 1.46 + }).then(function(what) { 1.47 + ok(true, "Then - resolveCb has been called"); 1.48 + is(what, undefined, "ResolveCb received undefined"); 1.49 + runTest(); 1.50 + }, function() { 1.51 + ok(false, "Then - rejectCb has been called"); 1.52 + runTest(); 1.53 + }); 1.54 +} 1.55 + 1.56 +function promiseReject() { 1.57 + var promise = new Promise(function(resolve, reject) { 1.58 + reject(42); 1.59 + }).then(function(what) { 1.60 + ok(false, "Then - resolveCb has been called"); 1.61 + runTest(); 1.62 + }, function(what) { 1.63 + ok(true, "Then - rejectCb has been called"); 1.64 + is(what, 42, "RejectCb received 42"); 1.65 + runTest(); 1.66 + }); 1.67 +} 1.68 + 1.69 +function promiseRejectNoHandler() { 1.70 + // This test only checks that the code that reports unhandled errors in the 1.71 + // Promises implementation does not crash or leak. 1.72 + var promise = new Promise(function(res, rej) { 1.73 + noSuchMethod(); 1.74 + }); 1.75 + runTest(); 1.76 +} 1.77 + 1.78 +function promiseRejectNoArg() { 1.79 + var promise = new Promise(function(resolve, reject) { 1.80 + reject(); 1.81 + }).then(function(what) { 1.82 + ok(false, "Then - resolveCb has been called"); 1.83 + runTest(); 1.84 + }, function(what) { 1.85 + ok(true, "Then - rejectCb has been called"); 1.86 + is(what, undefined, "RejectCb received undefined"); 1.87 + runTest(); 1.88 + }); 1.89 +} 1.90 + 1.91 +function promiseException() { 1.92 + var promise = new Promise(function(resolve, reject) { 1.93 + throw 42; 1.94 + }).then(function(what) { 1.95 + ok(false, "Then - resolveCb has been called"); 1.96 + runTest(); 1.97 + }, function(what) { 1.98 + ok(true, "Then - rejectCb has been called"); 1.99 + is(what, 42, "RejectCb received 42"); 1.100 + runTest(); 1.101 + }); 1.102 +} 1.103 + 1.104 +function promiseGC() { 1.105 + var resolve; 1.106 + var promise = new Promise(function(r1, r2) { 1.107 + resolve = r1; 1.108 + }).then(function(what) { 1.109 + ok(true, "Then - promise is still alive"); 1.110 + runTest(); 1.111 + }); 1.112 + 1.113 + promise = null; 1.114 + 1.115 + SpecialPowers.gc(); 1.116 + SpecialPowers.forceGC(); 1.117 + SpecialPowers.forceCC(); 1.118 + 1.119 + resolve(42); 1.120 +} 1.121 + 1.122 +function promiseAsync() { 1.123 + var global = "foo"; 1.124 + var f = new Promise(function(r1, r2) { 1.125 + is(global, "foo", "Global should be foo"); 1.126 + r1(42); 1.127 + is(global, "foo", "Global should still be foo"); 1.128 + setTimeout(function() { 1.129 + is(global, "bar", "Global should still be bar!"); 1.130 + runTest(); 1.131 + }, 0); 1.132 + }).then(function() { 1.133 + global = "bar"; 1.134 + }); 1.135 + is(global, "foo", "Global should still be foo (2)"); 1.136 +} 1.137 + 1.138 +function promiseDoubleThen() { 1.139 + var steps = 0; 1.140 + var promise = new Promise(function(r1, r2) { 1.141 + r1(42); 1.142 + }); 1.143 + 1.144 + promise.then(function(what) { 1.145 + ok(true, "Then.resolve has been called"); 1.146 + is(what, 42, "Value == 42"); 1.147 + steps++; 1.148 + }, function(what) { 1.149 + ok(false, "Then.reject has been called"); 1.150 + }); 1.151 + 1.152 + promise.then(function(what) { 1.153 + ok(true, "Then.resolve has been called"); 1.154 + is(steps, 1, "Then.resolve - step == 1"); 1.155 + is(what, 42, "Value == 42"); 1.156 + runTest(); 1.157 + }, function(what) { 1.158 + ok(false, "Then.reject has been called"); 1.159 + }); 1.160 +} 1.161 + 1.162 +function promiseThenException() { 1.163 + var promise = new Promise(function(resolve, reject) { 1.164 + resolve(42); 1.165 + }); 1.166 + 1.167 + promise.then(function(what) { 1.168 + ok(true, "Then.resolve has been called"); 1.169 + throw "booh"; 1.170 + }).catch(function(e) { 1.171 + ok(true, "window.onerror has been called!"); 1.172 + runTest(); 1.173 + }); 1.174 +} 1.175 + 1.176 +function promiseThenCatchThen() { 1.177 + var promise = new Promise(function(resolve, reject) { 1.178 + resolve(42); 1.179 + }); 1.180 + 1.181 + var promise2 = promise.then(function(what) { 1.182 + ok(true, "Then.resolve has been called"); 1.183 + is(what, 42, "Value == 42"); 1.184 + return what + 1; 1.185 + }, function(what) { 1.186 + ok(false, "Then.reject has been called"); 1.187 + }); 1.188 + 1.189 + isnot(promise, promise2, "These 2 promise objs are different"); 1.190 + 1.191 + promise2.then(function(what) { 1.192 + ok(true, "Then.resolve has been called"); 1.193 + is(what, 43, "Value == 43"); 1.194 + return what + 1; 1.195 + }, function(what) { 1.196 + ok(false, "Then.reject has been called"); 1.197 + }).catch(function() { 1.198 + ok(false, "Catch has been called"); 1.199 + }).then(function(what) { 1.200 + ok(true, "Then.resolve has been called"); 1.201 + is(what, 44, "Value == 44"); 1.202 + runTest(); 1.203 + }, function(what) { 1.204 + ok(false, "Then.reject has been called"); 1.205 + }); 1.206 +} 1.207 + 1.208 +function promiseThenNoArg() { 1.209 + var promise = new Promise(function(resolve, reject) { 1.210 + resolve(42); 1.211 + }); 1.212 + 1.213 + var clone = promise.then(); 1.214 + isnot(promise, clone, "These 2 promise objs are different"); 1.215 + promise.then(function(v) { 1.216 + clone.then(function(cv) { 1.217 + is(v, cv, "Both resolve to the same value"); 1.218 + runTest(); 1.219 + }); 1.220 + }); 1.221 +} 1.222 + 1.223 +function promiseThenUndefinedResolveFunction() { 1.224 + var promise = new Promise(function(resolve, reject) { 1.225 + reject(42); 1.226 + }); 1.227 + 1.228 + try { 1.229 + promise.then(undefined, function(v) { 1.230 + is(v, 42, "Promise rejected with 42"); 1.231 + runTest(); 1.232 + }); 1.233 + } catch (e) { 1.234 + ok(false, "then should not throw on undefined resolve function"); 1.235 + } 1.236 +} 1.237 + 1.238 +function promiseThenNullResolveFunction() { 1.239 + var promise = new Promise(function(resolve, reject) { 1.240 + reject(42); 1.241 + }); 1.242 + 1.243 + try { 1.244 + promise.then(null, function(v) { 1.245 + is(v, 42, "Promise rejected with 42"); 1.246 + runTest(); 1.247 + }); 1.248 + } catch (e) { 1.249 + ok(false, "then should not throw on null resolve function"); 1.250 + } 1.251 +} 1.252 + 1.253 +function promiseRejectThenCatchThen() { 1.254 + var promise = new Promise(function(resolve, reject) { 1.255 + reject(42); 1.256 + }); 1.257 + 1.258 + var promise2 = promise.then(function(what) { 1.259 + ok(false, "Then.resolve has been called"); 1.260 + }, function(what) { 1.261 + ok(true, "Then.reject has been called"); 1.262 + is(what, 42, "Value == 42"); 1.263 + return what + 1; 1.264 + }); 1.265 + 1.266 + isnot(promise, promise2, "These 2 promise objs are different"); 1.267 + 1.268 + promise2.then(function(what) { 1.269 + ok(true, "Then.resolve has been called"); 1.270 + is(what, 43, "Value == 43"); 1.271 + return what+1; 1.272 + }).catch(function(what) { 1.273 + ok(false, "Catch has been called"); 1.274 + }).then(function(what) { 1.275 + ok(true, "Then.resolve has been called"); 1.276 + is(what, 44, "Value == 44"); 1.277 + runTest(); 1.278 + }); 1.279 +} 1.280 + 1.281 +function promiseRejectThenCatchThen2() { 1.282 + var promise = new Promise(function(resolve, reject) { 1.283 + reject(42); 1.284 + }); 1.285 + 1.286 + promise.then(function(what) { 1.287 + ok(true, "Then.resolve has been called"); 1.288 + is(what, 42, "Value == 42"); 1.289 + return what+1; 1.290 + }).catch(function(what) { 1.291 + is(what, 42, "Value == 42"); 1.292 + ok(true, "Catch has been called"); 1.293 + return what+1; 1.294 + }).then(function(what) { 1.295 + ok(true, "Then.resolve has been called"); 1.296 + is(what, 43, "Value == 43"); 1.297 + runTest(); 1.298 + }); 1.299 +} 1.300 + 1.301 +function promiseRejectThenCatchExceptionThen() { 1.302 + var promise = new Promise(function(resolve, reject) { 1.303 + reject(42); 1.304 + }); 1.305 + 1.306 + promise.then(function(what) { 1.307 + ok(false, "Then.resolve has been called"); 1.308 + }, function(what) { 1.309 + ok(true, "Then.reject has been called"); 1.310 + is(what, 42, "Value == 42"); 1.311 + throw(what + 1); 1.312 + }).catch(function(what) { 1.313 + ok(true, "Catch has been called"); 1.314 + is(what, 43, "Value == 43"); 1.315 + return what + 1; 1.316 + }).then(function(what) { 1.317 + ok(true, "Then.resolve has been called"); 1.318 + is(what, 44, "Value == 44"); 1.319 + runTest(); 1.320 + }); 1.321 +} 1.322 + 1.323 +function promiseThenCatchOrderingResolve() { 1.324 + var global = 0; 1.325 + var f = new Promise(function(r1, r2) { 1.326 + r1(42); 1.327 + }); 1.328 + 1.329 + f.then(function() { 1.330 + f.then(function() { 1.331 + global++; 1.332 + }); 1.333 + f.catch(function() { 1.334 + global++; 1.335 + }); 1.336 + f.then(function() { 1.337 + global++; 1.338 + }); 1.339 + setTimeout(function() { 1.340 + is(global, 2, "Many steps... should return 2"); 1.341 + runTest(); 1.342 + }, 0); 1.343 + }); 1.344 +} 1.345 + 1.346 +function promiseThenCatchOrderingReject() { 1.347 + var global = 0; 1.348 + var f = new Promise(function(r1, r2) { 1.349 + r2(42); 1.350 + }) 1.351 + 1.352 + f.then(function() {}, function() { 1.353 + f.then(function() { 1.354 + global++; 1.355 + }); 1.356 + f.catch(function() { 1.357 + global++; 1.358 + }); 1.359 + f.then(function() {}, function() { 1.360 + global++; 1.361 + }); 1.362 + setTimeout(function() { 1.363 + is(global, 2, "Many steps... should return 2"); 1.364 + runTest(); 1.365 + }, 0); 1.366 + }); 1.367 +} 1.368 + 1.369 +function promiseCatchNoArg() { 1.370 + var promise = new Promise(function(resolve, reject) { 1.371 + reject(42); 1.372 + }); 1.373 + 1.374 + var clone = promise.catch(); 1.375 + isnot(promise, clone, "These 2 promise objs are different"); 1.376 + promise.catch(function(v) { 1.377 + clone.catch(function(cv) { 1.378 + is(v, cv, "Both reject to the same value"); 1.379 + runTest(); 1.380 + }); 1.381 + }); 1.382 +} 1.383 + 1.384 +function promiseNestedPromise() { 1.385 + new Promise(function(resolve, reject) { 1.386 + resolve(new Promise(function(resolve, reject) { 1.387 + ok(true, "Nested promise is executed"); 1.388 + resolve(42); 1.389 + })); 1.390 + }).then(function(value) { 1.391 + is(value, 42, "Nested promise is executed and then == 42"); 1.392 + runTest(); 1.393 + }); 1.394 +} 1.395 + 1.396 +function promiseNestedNestedPromise() { 1.397 + new Promise(function(resolve, reject) { 1.398 + resolve(new Promise(function(resolve, reject) { 1.399 + ok(true, "Nested promise is executed"); 1.400 + resolve(42); 1.401 + }).then(function(what) { return what+1; })); 1.402 + }).then(function(value) { 1.403 + is(value, 43, "Nested promise is executed and then == 43"); 1.404 + runTest(); 1.405 + }); 1.406 +} 1.407 + 1.408 +function promiseWrongNestedPromise() { 1.409 + new Promise(function(resolve, reject) { 1.410 + resolve(new Promise(function(r, r2) { 1.411 + ok(true, "Nested promise is executed"); 1.412 + r(42); 1.413 + })); 1.414 + reject(42); 1.415 + }).then(function(value) { 1.416 + is(value, 42, "Nested promise is executed and then == 42"); 1.417 + runTest(); 1.418 + }, function(value) { 1.419 + ok(false, "This is wrong"); 1.420 + }); 1.421 +} 1.422 + 1.423 +function promiseLoop() { 1.424 + new Promise(function(resolve, reject) { 1.425 + resolve(new Promise(function(r1, r2) { 1.426 + ok(true, "Nested promise is executed"); 1.427 + r1(new Promise(function(r1, r2) { 1.428 + ok(true, "Nested nested promise is executed"); 1.429 + r1(42); 1.430 + })); 1.431 + })); 1.432 + }).then(function(value) { 1.433 + is(value, 42, "Nested nested promise is executed and then == 42"); 1.434 + runTest(); 1.435 + }, function(value) { 1.436 + ok(false, "This is wrong"); 1.437 + }); 1.438 +} 1.439 + 1.440 +function promiseStaticReject() { 1.441 + var promise = Promise.reject(42).then(function(what) { 1.442 + ok(false, "This should not be called"); 1.443 + }, function(what) { 1.444 + is(what, 42, "Value == 42"); 1.445 + runTest(); 1.446 + }); 1.447 +} 1.448 + 1.449 +function promiseStaticResolve() { 1.450 + var promise = Promise.resolve(42).then(function(what) { 1.451 + is(what, 42, "Value == 42"); 1.452 + runTest(); 1.453 + }, function() { 1.454 + ok(false, "This should not be called"); 1.455 + }); 1.456 +} 1.457 + 1.458 +function promiseResolveNestedPromise() { 1.459 + var promise = Promise.resolve(new Promise(function(r, r2) { 1.460 + ok(true, "Nested promise is executed"); 1.461 + r(42); 1.462 + }, function() { 1.463 + ok(false, "This should not be called"); 1.464 + })).then(function(what) { 1.465 + is(what, 42, "Value == 42"); 1.466 + runTest(); 1.467 + }, function() { 1.468 + ok(false, "This should not be called"); 1.469 + }); 1.470 +} 1.471 + 1.472 +function promiseSimpleThenableResolve() { 1.473 + var thenable = { then: function(resolve) { resolve(5); } }; 1.474 + var promise = new Promise(function(resolve, reject) { 1.475 + resolve(thenable); 1.476 + }); 1.477 + 1.478 + promise.then(function(v) { 1.479 + ok(v === 5, "promiseSimpleThenableResolve"); 1.480 + runTest(); 1.481 + }, function(e) { 1.482 + ok(false, "promiseSimpleThenableResolve: Should not reject"); 1.483 + }); 1.484 +} 1.485 + 1.486 +function promiseSimpleThenableReject() { 1.487 + var thenable = { then: function(resolve, reject) { reject(5); } }; 1.488 + var promise = new Promise(function(resolve, reject) { 1.489 + resolve(thenable); 1.490 + }); 1.491 + 1.492 + promise.then(function() { 1.493 + ok(false, "promiseSimpleThenableReject: Should not resolve"); 1.494 + runTest(); 1.495 + }, function(e) { 1.496 + ok(e === 5, "promiseSimpleThenableReject"); 1.497 + runTest(); 1.498 + }); 1.499 +} 1.500 + 1.501 +function promiseThenableThrowsBeforeCallback() { 1.502 + var thenable = { then: function(resolve) { 1.503 + throw new TypeError("Hi there"); 1.504 + resolve(5); 1.505 + }}; 1.506 + 1.507 + var promise = Promise.resolve(thenable); 1.508 + promise.then(function(v) { 1.509 + ok(false, "promiseThenableThrowsBeforeCallback: Should've rejected"); 1.510 + runTest(); 1.511 + }, function(e) { 1.512 + ok(e instanceof TypeError, "promiseThenableThrowsBeforeCallback"); 1.513 + runTest(); 1.514 + }); 1.515 +} 1.516 + 1.517 +function promiseThenableThrowsAfterCallback() { 1.518 + var thenable = { then: function(resolve) { 1.519 + resolve(5); 1.520 + throw new TypeError("Hi there"); 1.521 + }}; 1.522 + 1.523 + var promise = Promise.resolve(thenable); 1.524 + promise.then(function(v) { 1.525 + ok(v === 5, "promiseThenableThrowsAfterCallback"); 1.526 + runTest(); 1.527 + }, function(e) { 1.528 + ok(false, "promiseThenableThrowsAfterCallback: Should've resolved"); 1.529 + runTest(); 1.530 + }); 1.531 +} 1.532 + 1.533 +function promiseThenableRejectThenResolve() { 1.534 + var thenable = { then: function(resolve, reject) { 1.535 + reject(new TypeError("Hi there")); 1.536 + resolve(5); 1.537 + }}; 1.538 + 1.539 + var promise = Promise.resolve(thenable); 1.540 + promise.then(function(v) { 1.541 + ok(false, "promiseThenableRejectThenResolve should have rejected"); 1.542 + runTest(); 1.543 + }, function(e) { 1.544 + ok(e instanceof TypeError, "promiseThenableRejectThenResolve"); 1.545 + runTest(); 1.546 + }); 1.547 +} 1.548 + 1.549 +function promiseWithThenReplaced() { 1.550 + // Ensure that we call the 'then' on the promise and not the internal then. 1.551 + var promise = new Promise(function(resolve, reject) { 1.552 + resolve(5); 1.553 + }); 1.554 + 1.555 + // Rogue `then` always rejects. 1.556 + promise.then = function(onFulfill, onReject) { 1.557 + onReject(new TypeError("Foo")); 1.558 + } 1.559 + 1.560 + var promise2 = Promise.resolve(promise); 1.561 + promise2.then(function(v) { 1.562 + ok(false, "promiseWithThenReplaced: Should've rejected"); 1.563 + runTest(); 1.564 + }, function(e) { 1.565 + ok(e instanceof TypeError, "promiseWithThenReplaced"); 1.566 + runTest(); 1.567 + }); 1.568 +} 1.569 + 1.570 +function promiseStrictHandlers() { 1.571 + var promise = Promise.resolve(5); 1.572 + promise.then(function() { 1.573 + "use strict"; 1.574 + ok(this === undefined, "Strict mode callback should have this === undefined."); 1.575 + runTest(); 1.576 + }); 1.577 +} 1.578 + 1.579 +function promiseStrictExecutorThisArg() { 1.580 + var promise = new Promise(function(resolve, reject) { 1.581 + "use strict"; 1.582 + ok(this === undefined, "thisArg should be undefined."); 1.583 + runTest(); 1.584 + }); 1.585 +} 1.586 + 1.587 +function promiseResolveArray() { 1.588 + var p = Promise.resolve([1,2,3]); 1.589 + ok(p instanceof Promise, "Should return a Promise."); 1.590 + p.then(function(v) { 1.591 + ok(Array.isArray(v), "Resolved value should be an Array"); 1.592 + is(v.length, 3, "Length should match"); 1.593 + is(v[0], 1, "Resolved value should match original"); 1.594 + is(v[1], 2, "Resolved value should match original"); 1.595 + is(v[2], 3, "Resolved value should match original"); 1.596 + runTest(); 1.597 + }); 1.598 +} 1.599 + 1.600 +function promiseResolveThenable() { 1.601 + var p = Promise.resolve({ then: function(onFulfill, onReject) { onFulfill(2); } }); 1.602 + ok(p instanceof Promise, "Should cast to a Promise."); 1.603 + p.then(function(v) { 1.604 + is(v, 2, "Should resolve to 2."); 1.605 + runTest(); 1.606 + }, function(e) { 1.607 + ok(false, "promiseResolveThenable should've resolved"); 1.608 + runTest(); 1.609 + }); 1.610 +} 1.611 + 1.612 +function promiseResolvePromise() { 1.613 + var original = Promise.resolve(true); 1.614 + var cast = Promise.resolve(original); 1.615 + 1.616 + ok(cast instanceof Promise, "Should cast to a Promise."); 1.617 + is(cast, original, "Should return original Promise."); 1.618 + cast.then(function(v) { 1.619 + is(v, true, "Should resolve to true."); 1.620 + runTest(); 1.621 + }); 1.622 +} 1.623 + 1.624 +var tests = [ promiseResolve, promiseReject, 1.625 + promiseException, promiseGC, promiseAsync, 1.626 + promiseDoubleThen, promiseThenException, 1.627 + promiseThenCatchThen, promiseRejectThenCatchThen, 1.628 + promiseRejectThenCatchThen2, 1.629 + promiseRejectThenCatchExceptionThen, 1.630 + promiseThenCatchOrderingResolve, 1.631 + promiseThenCatchOrderingReject, 1.632 + promiseNestedPromise, promiseNestedNestedPromise, 1.633 + promiseWrongNestedPromise, promiseLoop, 1.634 + promiseStaticReject, promiseStaticResolve, 1.635 + promiseResolveNestedPromise, 1.636 + promiseResolveNoArg, 1.637 + promiseRejectNoArg, 1.638 + promiseThenNoArg, 1.639 + promiseThenUndefinedResolveFunction, 1.640 + promiseThenNullResolveFunction, 1.641 + promiseCatchNoArg, 1.642 + promiseRejectNoHandler, 1.643 + promiseSimpleThenableResolve, 1.644 + promiseSimpleThenableReject, 1.645 + promiseThenableThrowsBeforeCallback, 1.646 + promiseThenableThrowsAfterCallback, 1.647 + promiseThenableRejectThenResolve, 1.648 + promiseWithThenReplaced, 1.649 + promiseStrictHandlers, 1.650 + promiseStrictExecutorThisArg, 1.651 + promiseResolveArray, 1.652 + promiseResolveThenable, 1.653 + promiseResolvePromise, 1.654 + ]; 1.655 + 1.656 +function runTest() { 1.657 + if (!tests.length) { 1.658 + SimpleTest.finish(); 1.659 + return; 1.660 + } 1.661 + 1.662 + var test = tests.shift(); 1.663 + test(); 1.664 +} 1.665 + 1.666 +SimpleTest.waitForExplicitFinish(); 1.667 +runTest(); 1.668 +// --> 1.669 +</script> 1.670 +</pre> 1.671 +</body> 1.672 +</html> 1.673 +