diff options
Diffstat (limited to 'testing/mochitest/tests')
130 files changed, 24886 insertions, 0 deletions
diff --git a/testing/mochitest/tests/Harness_sanity/ImportTesting.jsm b/testing/mochitest/tests/Harness_sanity/ImportTesting.jsm new file mode 100644 index 000000000..b4d5089ff --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/ImportTesting.jsm @@ -0,0 +1,5 @@ +this.EXPORTED_SYMBOLS = ["ImportTesting"]; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +// Empty module for testing via SpecialPowers.importInMainProcess. diff --git a/testing/mochitest/tests/Harness_sanity/SpecialPowersLoadChromeScript.js b/testing/mochitest/tests/Harness_sanity/SpecialPowersLoadChromeScript.js new file mode 100644 index 000000000..731081832 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/SpecialPowersLoadChromeScript.js @@ -0,0 +1,15 @@ +// Just receive 'foo' message and forward it back +// as 'bar' message +addMessageListener("foo", function (message) { + sendAsyncMessage("bar", message); +}); + +addMessageListener("valid-assert", function (message) { + assert.ok(true, "valid assertion"); + assert.equal(1, 1, "another valid assertion"); + sendAsyncMessage("valid-assert-done"); +}); + +addMessageListener("sync-message", () => { + return "Received a synchronous message."; +}); diff --git a/testing/mochitest/tests/Harness_sanity/empty.js b/testing/mochitest/tests/Harness_sanity/empty.js new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/empty.js diff --git a/testing/mochitest/tests/Harness_sanity/file_SpecialPowersFrame1.html b/testing/mochitest/tests/Harness_sanity/file_SpecialPowersFrame1.html new file mode 100644 index 000000000..623460c52 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/file_SpecialPowersFrame1.html @@ -0,0 +1,14 @@ +<html> + <head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <div id="content" style="display: none"> + <script type="text/javascript"> + is(SpecialPowers.sanityCheck(), "foo", "Check Special Powers in iframe"); + </script> + </div> + </body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/importtesting_chromescript.js b/testing/mochitest/tests/Harness_sanity/importtesting_chromescript.js new file mode 100644 index 000000000..2c2f9bd55 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/importtesting_chromescript.js @@ -0,0 +1,3 @@ +addMessageListener("ImportTesting:IsModuleLoaded", function (msg) { + sendAsyncMessage("ImportTesting:IsModuleLoadedReply", Components.utils.isModuleLoaded(msg)); +}); diff --git a/testing/mochitest/tests/Harness_sanity/mochitest.ini b/testing/mochitest/tests/Harness_sanity/mochitest.ini new file mode 100644 index 000000000..e9f744102 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/mochitest.ini @@ -0,0 +1,44 @@ +[DEFAULT] +[test_TestsRunningAfterSimpleTestFinish.html] +skip-if = true #depends on fix for bug 1048446 +[test_add_task.html] +[test_createFiles.html] +[test_importInMainProcess.html] +support-files = importtesting_chromescript.js +[test_sanity.html] +[test_sanityException.html] +[test_sanityException2.html] +[test_sanityParams.html] +[test_sanityRegisteredServiceWorker.html] +support-files = empty.js +[test_sanityRegisteredServiceWorker2.html] +support-files = empty.js +[test_sanityWindowSnapshot.html] +[test_SpecialPowersExtension.html] +[test_SpecialPowersExtension2.html] +support-files = file_SpecialPowersFrame1.html +[test_SpecialPowersPushPermissions.html] +support-files = + specialPowers_framescript.js +[test_SpecialPowersPushPrefEnv.html] +[test_SimpletestGetTestFileURL.html] +[test_SpecialPowersLoadChromeScript.html] +support-files = SpecialPowersLoadChromeScript.js +[test_SpecialPowersLoadChromeScript_function.html] +[test_SpecialPowersLoadPrivilegedScript.html] +[test_bug649012.html] +[test_sanity_cleanup.html] +[test_sanity_cleanup2.html] +[test_sanityEventUtils.html] +skip-if = toolkit == 'android' # bug 688052 +[test_sanitySimpletest.html] +subsuite = clipboard +skip-if = toolkit == 'android' # bug 688052 +[test_sanity_manifest.html] +skip-if = toolkit == 'android' # we use the old manifest style on android +fail-if = true +[test_sanity_manifest_pf.html] +skip-if = toolkit == 'android' # we use the old manifest style on android +fail-if = true +[test_spawn_task.html] +[test_sanity_waitForCondition.html] diff --git a/testing/mochitest/tests/Harness_sanity/specialPowers_framescript.js b/testing/mochitest/tests/Harness_sanity/specialPowers_framescript.js new file mode 100644 index 000000000..da1b87782 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/specialPowers_framescript.js @@ -0,0 +1,13 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +var permChangedObs = { + observe: function(subject, topic, data) { + if (topic == 'perm-changed') { + var permission = subject.QueryInterface(Components.interfaces.nsIPermission); + var msg = { op: data, type: permission.type }; + sendAsyncMessage('perm-changed', msg); + } + } +}; + +Services.obs.addObserver(permChangedObs, 'perm-changed', false); diff --git a/testing/mochitest/tests/Harness_sanity/test_SimpletestGetTestFileURL.html b/testing/mochitest/tests/Harness_sanity/test_SimpletestGetTestFileURL.html new file mode 100644 index 000000000..c07d3f7e2 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SimpletestGetTestFileURL.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var filename = "MyTestDataFile.txt"; +var url = SimpleTest.getTestFileURL(filename); +is(url, document.location.href.replace(/test_SimpletestGetTestFileURL\.html.*/, filename)); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html new file mode 100644 index 000000000..299497e15 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html @@ -0,0 +1,192 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest();"> + +<div id="content" style="display: none"> + <canvas id="testcanvas" width="200" height="200"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var eventCount = 0; +function testEventListener(e) { + ++eventCount; +} + +function testEventListener2(e) { + ++eventCount; +} + +function dispatchTestEvent() { + var e = document.createEvent("Event"); + e.initEvent("TestEvent", true, true); + window.dispatchEvent(e); +} + +dump("\nSPECIALPTEST:::Test script loaded " + (new Date).getTime() + "\n"); +SimpleTest.waitForExplicitFinish(); +var startTime = new Date(); +function starttest(){ + dump("\nSPECIALPTEST:::Test script running after load " + (new Date).getTime() + "\n"); + + /** Test for SpecialPowers extension **/ + is(SpecialPowers.sanityCheck(), "foo", "check to see whether the Special Powers extension is installed."); + + // Test a sync call into chrome + SpecialPowers.setBoolPref('extensions.checkCompatibility', true); + is(SpecialPowers.getBoolPref('extensions.checkCompatibility'), true, "Check to see if we can set a preference properly"); + SpecialPowers.clearUserPref('extensions.checkCompatibility'); + + // Test a int pref + SpecialPowers.setIntPref('extensions.foobar', 42); + is(SpecialPowers.getIntPref('extensions.foobar'), 42, "Check int pref"); + SpecialPowers.clearUserPref('extensions.foobar'); + + // Test a string pref + SpecialPowers.setCharPref("extensions.foobaz", "hi there"); + is(SpecialPowers.getCharPref("extensions.foobaz"), "hi there", "Check string pref"); + SpecialPowers.clearUserPref("extensions.foobaz"); + + // Test an invalid pref + var retVal = null; + try { + retVal = SpecialPowers.getBoolPref('extensions.checkCompat0123456789'); + } catch (ex) { + retVal = ex; + } + is(retVal, "Error getting pref 'extensions.checkCompat0123456789'", "received an exception trying to get an unset preference value"); + + SpecialPowers.addChromeEventListener("TestEvent", testEventListener, true, true); + SpecialPowers.addChromeEventListener("TestEvent", testEventListener2, true, false); + dispatchTestEvent(); + is(eventCount, 1, "Should have got an event!"); + + SpecialPowers.removeChromeEventListener("TestEvent", testEventListener, true); + SpecialPowers.removeChromeEventListener("TestEvent", testEventListener2, true); + dispatchTestEvent(); + is(eventCount, 1, "Shouldn't have got an event!"); + + // Test Complex Pref - TODO: Without chrome access, I don't know how you'd actually + // set this preference since you have to create an XPCOM object. + // Leaving untested for now. + + // Test a DOMWindowUtils method and property + is(SpecialPowers.DOMWindowUtils.getClassName(window), "Proxy"); + is(SpecialPowers.DOMWindowUtils.docCharsetIsForced, false); + + // QueryInterface and getPrivilegedProps tests + is(SpecialPowers.can_QI(SpecialPowers), false); + ok(SpecialPowers.can_QI(window)); + ok(SpecialPowers.do_QueryInterface(window, "nsIDOMWindow")); + is(SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(window, "nsIDOMWindow"), "document.nodeName"), "#document"); + + //try to run garbage collection + SpecialPowers.gc(); + + // + // Test the SpecialPowers wrapper. + // + + // Try some basic stuff with XHR. + var xhr2 = SpecialPowers.Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(SpecialPowers.Ci.nsIXMLHttpRequest); + is(xhr2.readyState, XMLHttpRequest.UNSENT, "Should be able to get props off privileged objects"); + var testURI = SpecialPowers.Cc['@mozilla.org/network/standard-url;1'] + .createInstance(SpecialPowers.Ci.nsIURI); + testURI.spec = "http://www.foobar.org/"; + is(testURI.spec, "http://www.foobar.org/", "Getters/Setters should work correctly"); + is(SpecialPowers.wrap(document).getElementsByTagName('details').length, 0, "Should work with proxy-based DOM bindings."); + + // Play with the window object. + var webnav = SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation); + webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell); + ok(webnav.allowJavascript, "Able to pull properties off of docshell!"); + + // Make sure Xray-wrapped functions work. + try { + SpecialPowers.wrap(SpecialPowers.Components).ID('{00000000-0000-0000-0000-000000000000}'); + ok(true, "Didn't throw"); + } + catch (e) { + ok(false, "Threw while trying to call Xray-wrapped function."); + } + + // Check constructors. + var BinaryInputStream = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/binaryinputstream;1"); + var bis = new BinaryInputStream(); + ok(/nsISupports/.exec(bis.toString()), "Should get the proper object out of the constructor"); + function TestConstructor() { + SpecialPowers.wrap(this).foo = 2; + } + var WrappedConstructor = SpecialPowers.wrap(TestConstructor); + is((new WrappedConstructor()).foo, 2, "JS constructors work properly when wrapped"); + + // Try messing around with QuickStubbed getters/setters and make sure the wrapper deals. + var ctx = SpecialPowers.wrap(document).getElementById('testcanvas').getContext('2d'); + var pixels = ctx.getImageData(0,0,1,1); + try { + pixels.data; + ok(true, "Didn't throw getting quickstubbed accessor prop from proto"); + } + catch (e) { + ok(false, "Threw while getting quickstubbed accessor prop from proto"); + } + + // Check functions that return null. + var returnsNull = function() { return null; } + is(SpecialPowers.wrap(returnsNull)(), null, "Should be able to handle functions that return null."); + + // Check a function that throws. + var thrower = function() { throw new Error('hah'); } + try { + SpecialPowers.wrap(thrower)(); + ok(false, "Should have thrown"); + } catch (e) { + ok(SpecialPowers.isWrapper(e), "Exceptions should be wrapped for call"); + is(e.message, 'hah', "Correct message"); + } + try { + var ctor = SpecialPowers.wrap(thrower); + new ctor(); + ok(false, "Should have thrown"); + } catch (e) { + ok(SpecialPowers.isWrapper(e), "Exceptions should be wrapped for construct"); + is(e.message, 'hah', "Correct message"); + } + + // Play around with a JS object to check the non-xray path. + var noxray_proto = {a: 3, b: 12}; + var noxray = {a: 5, c: 32}; + noxray.__proto__ = noxray_proto; + var noxray_wrapper = SpecialPowers.wrap(noxray); + is(noxray_wrapper.c, 32, "Regular properties should work."); + is(noxray_wrapper.a, 5, "Shadow properties should work."); + is(noxray_wrapper.b, 12, "Proto properties should work."); + noxray.b = 122; + is(noxray_wrapper.b, 122, "Should be able to shadow."); + + // Try setting file input values via an Xray wrapper. + SpecialPowers.wrap(document).title = "foo"; + is(document.title, "foo", "Set property correctly on Xray-wrapped DOM object"); + is(SpecialPowers.wrap(document).URI, document.URI, "Got property correctly on Xray-wrapped DOM object"); + + info("\nProfile::SpecialPowersRunTime: " + (new Date() - startTime) + "\n"); + + // bug 855192 + ok(SpecialPowers.MockPermissionPrompt, "check mock permission prompt"); + + // Set a pref using pushPrefEnv to make sure that flushPrefEnv is + // automatically called before we invoke + // test_SpecialPowersExtension2.html. + SpecialPowers.pushPrefEnv({set: [['testing.some_arbitrary_pref', true]]}, + function() { SimpleTest.finish(); }); +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension2.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension2.html new file mode 100644 index 000000000..fc77aa82a --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension2.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + dump("\nSPECIALPTEST2:::Loading test2 file now " + (new Date).getTime() + "\n"); + is(SpecialPowers.sanityCheck(), "foo", "Special Powers top level"); + ok(!SpecialPowers.Services.prefs.prefHasUserValue('testing.some_arbitrary_pref'), + "should not retain pref from previous test"); + </script> + <iframe id="frame1" src="file_SpecialPowersFrame1.html"> + </iframe> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html new file mode 100644 index 000000000..4b78928bf --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.loadChromeScript</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var url = SimpleTest.getTestFileURL("SpecialPowersLoadChromeScript.js"); +var script = SpecialPowers.loadChromeScript(url); + +var MESSAGE = { bar: true }; +script.addMessageListener("bar", function (message) { + is(JSON.stringify(message), JSON.stringify(MESSAGE), + "received back message from the chrome script"); + + checkAssert(); +}); + +function checkAssert() { + script.sendAsyncMessage("valid-assert"); + script.addMessageListener("valid-assert-done", endOfTest); +} + +function endOfTest() { + script.destroy(); + SimpleTest.finish(); +} + +script.sendAsyncMessage("foo", MESSAGE); + +/* + * [0][0] is because we're using one real message listener in SpecialPowersObserverAPI.js + * and dispatching that to multiple _chromeScriptListeners. The outer array comes + * from the message manager since there can be multiple real listeners. The inner + * array is for the return values of _chromeScriptListeners. + */ +is(script.sendSyncMessage("sync-message")[0][0], "Received a synchronous message.", + "Check sync return value"); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html new file mode 100644 index 000000000..da29eadb8 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.loadChromeScript</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + + +var script = SpecialPowers.loadChromeScript(function loadChromeScriptTest() { + // Copied from SpecialPowersLoadChromeScript.js + + // Just receive 'foo' message and forward it back + // as 'bar' message + addMessageListener("foo", function (message) { + sendAsyncMessage("bar", message); + }); + + addMessageListener("valid-assert", function (message) { + assert.ok(true, "valid assertion"); + assert.equal(1, 1, "another valid assertion"); + sendAsyncMessage("valid-assert-done"); + }); + + addMessageListener("sync-message", () => { + return "Received a synchronous message."; + }); +}); + +var MESSAGE = { bar: true }; +script.addMessageListener("bar", function (message) { + is(JSON.stringify(message), JSON.stringify(MESSAGE), + "received back message from the chrome script"); + + checkAssert(); +}); + +function checkAssert() { + script.sendAsyncMessage("valid-assert"); + script.addMessageListener("valid-assert-done", endOfTest); +} + +function endOfTest() { + script.destroy(); + SimpleTest.finish(); +} + +script.sendAsyncMessage("foo", MESSAGE); + +/* + * [0][0] is because we're using one real message listener in SpecialPowersObserverAPI.js + * and dispatching that to multiple _chromeScriptListeners. The outer array comes + * from the message manager since there can be multiple real listeners. The inner + * array is for the return values of _chromeScriptListeners. + */ +is(script.sendSyncMessage("sync-message")[0][0], "Received a synchronous message.", + "Check sync return value"); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadPrivilegedScript.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadPrivilegedScript.html new file mode 100644 index 000000000..e9b545376 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadPrivilegedScript.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.loadChromeScript</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function loadPrivilegedScriptTest() { + var Cc = Components.classes; + var Ci = Components.interfaces; + function isMainProcess() { + return Cc["@mozilla.org/xre/app-info;1"]. + getService(Ci.nsIXULRuntime). + processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + } + port.postMessage({'isMainProcess': isMainProcess()}); +} + +var contentProcessType = SpecialPowers.isMainProcess(); +var port; +try { + port = SpecialPowers.loadPrivilegedScript(loadPrivilegedScriptTest.toSource()); +} catch (e) { + ok(false, "loadPrivilegedScript shoulde not throw"); +} +port.onmessage = (e) => { + is(contentProcessType, e.data['isMainProcess'], "content and the script should be in the same process"); + SimpleTest.finish(); +}; +</script> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html new file mode 100644 index 000000000..709d2cc7d --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html @@ -0,0 +1,237 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest();"> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +const ALLOW_ACTION = SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION; +const DENY_ACTION = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION; +const UNKNOWN_ACTION = SpecialPowers.Ci.nsIPermissionManager.UNKNOWN_ACTION; +const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION; +const ACCESS_SESSION = SpecialPowers.Ci.nsICookiePermission.ACCESS_SESSION; +const ACCESS_ALLOW_FIRST_PARTY_ONLY = SpecialPowers.Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY; +const ACCESS_LIMIT_THIRD_PARTY = SpecialPowers.Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY; + +const EXPIRE_TIME = SpecialPowers.Ci.nsIPermissionManager.EXPIRE_TIME; +// expire Setting: +// start expire time point +// ----|------------------------|----------- +// <------------------------> +// PERIOD +var start; +// PR_Now() that called in nsPermissionManager to get the system time +// is sometimes 100ms~600s more than Date.now() on Android 4.3 API11. +// Thus, the PERIOD should be larger than 600ms in this test. +const PERIOD = 900; +var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('specialPowers_framescript.js')); +SimpleTest.requestFlakyTimeout("untriaged"); + +function starttest(){ + SpecialPowers.addPermission("pPROMPT", PROMPT_ACTION, document); + SpecialPowers.addPermission("pALLOW", ALLOW_ACTION, document); + SpecialPowers.addPermission("pDENY", DENY_ACTION, document); + SpecialPowers.addPermission("pREMOVE", ALLOW_ACTION, document); + SpecialPowers.addPermission("pSESSION", ACCESS_SESSION, document); + SpecialPowers.addPermission("pFIRSTPARTY", ACCESS_ALLOW_FIRST_PARTY_ONLY, document); + SpecialPowers.addPermission("pTHIRDPARTY", ACCESS_LIMIT_THIRD_PARTY, document); + + setTimeout(test1, 0); +} + +SimpleTest.waitForExplicitFinish(); + +function test1() { + if (!SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document)) { + dump('/**** allow not set ****/\n'); + setTimeout(test1, 0); + } else if (!SpecialPowers.testPermission('pDENY', DENY_ACTION, document)) { + dump('/**** deny not set ****/\n'); + setTimeout(test1, 0); + } else if (!SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document)) { + dump('/**** prompt not set ****/\n'); + setTimeout(test1, 0); + } else if (!SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document)) { + dump('/**** remove not set ****/\n'); + setTimeout(test1, 0); + } else if (!SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document)) { + dump('/**** ACCESS_SESSION not set ****/\n'); + setTimeout(test1, 0); + } else if (!SpecialPowers.testPermission('pFIRSTPARTY', ACCESS_ALLOW_FIRST_PARTY_ONLY, document)) { + dump('/**** ACCESS_ALLOW_FIRST_PARTY_ONLY not set ****/\n'); + setTimeout(test1, 0); + } else if (!SpecialPowers.testPermission('pTHIRDPARTY', ACCESS_LIMIT_THIRD_PARTY, document)) { + dump('/**** ACCESS_LIMIT_THIRD_PARTY not set ****/\n'); + setTimeout(test1, 0); + } else { + test2(); + } +} + +function test2() { + ok(SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN value should have UNKOWN permission'); + SpecialPowers.pushPermissions([{'type': 'pUNKNOWN', 'allow': true, 'context': document}, {'type': 'pALLOW', 'allow': false, 'context': document}, {'type': 'pDENY', 'allow': true, 'context': document}, {'type': 'pPROMPT', 'allow': true, 'context': document}, {'type': 'pSESSION', 'allow': true, 'context': document}, {'type': 'pFIRSTPARTY', 'allow': true, 'context': document}, {'type': 'pTHIRDPARTY', 'allow': true, 'context': document}, {'type': 'pREMOVE', 'remove': true, 'context': document}], test3); +} + +function test3() { + ok(SpecialPowers.testPermission('pUNKNOWN', ALLOW_ACTION, document), 'pUNKNOWN value should have ALLOW permission'); + ok(SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission'); + ok(SpecialPowers.testPermission('pALLOW', DENY_ACTION, document), 'pALLOW should have DENY permission'); + ok(SpecialPowers.testPermission('pDENY', ALLOW_ACTION, document), 'pDENY should have ALLOW permission'); + ok(SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document), 'pREMOVE should have REMOVE permission'); + ok(SpecialPowers.testPermission('pSESSION', ALLOW_ACTION, document), 'pSESSION should have ALLOW permission'); + ok(SpecialPowers.testPermission('pFIRSTPARTY', ALLOW_ACTION, document), 'pFIRSTPARTY should have ALLOW permission'); + ok(SpecialPowers.testPermission('pTHIRDPARTY', ALLOW_ACTION, document), 'pTHIRDPARTY should have ALLOW permission'); + + // only pPROMPT (last one) is different, the other stuff is just to see if it doesn't cause test failures + SpecialPowers.pushPermissions([{'type': 'pUNKNOWN', 'allow': true, 'context': document}, {'type': 'pALLOW', 'allow': false, 'context': document}, {'type': 'pDENY', 'allow': true, 'context': document}, {'type': 'pPROMPT', 'allow': false, 'context': document}, {'type': 'pREMOVE', 'remove': true, 'context': document}], test3b); +} + +function test3b() { + ok(SpecialPowers.testPermission('pPROMPT', DENY_ACTION, document), 'pPROMPT value should have DENY permission'); + SpecialPowers.pushPermissions([{'type': 'pUNKNOWN', 'allow': DENY_ACTION, 'context': document}, {'type': 'pALLOW', 'allow': PROMPT_ACTION, 'context': document}, {'type': 'pDENY', 'allow': PROMPT_ACTION, 'context': document}, {'type': 'pPROMPT', 'allow': ALLOW_ACTION, 'context': document}], test4); +} + +function test4() { + ok(SpecialPowers.testPermission('pUNKNOWN', DENY_ACTION, document), 'pUNKNOWN value should have DENY permission'); + ok(SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission'); + ok(SpecialPowers.testPermission('pALLOW', PROMPT_ACTION, document), 'pALLOW should have PROMPT permission'); + ok(SpecialPowers.testPermission('pDENY', PROMPT_ACTION, document), 'pDENY should have PROMPT permission'); + //this should reset all the permissions to before all the pushPermissions calls + SpecialPowers.flushPermissions(test5); +} + + +function test5() { + ok(SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN should have UNKNOWN permission'); + ok(SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document), 'pALLOW should have ALLOW permission'); + ok(SpecialPowers.testPermission('pDENY', DENY_ACTION, document), 'pDENY should have DENY permission'); + ok(SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document), 'pPROMPT should have PROMPT permission'); + ok(SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document), 'pREMOVE should have ALLOW permission'); + ok(SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document), 'pSESSION should have ACCESS_SESSION permission'); + ok(SpecialPowers.testPermission('pFIRSTPARTY', ACCESS_ALLOW_FIRST_PARTY_ONLY, document), 'pFIRSTPARTY should have ACCESS_ALLOW_FIRST_PARTY_ONLY permission'); + ok(SpecialPowers.testPermission('pTHIRDPARTY', ACCESS_LIMIT_THIRD_PARTY, document), 'pTHIRDPARTY should have ACCESS_LIMIT_THIRD_PARTY permission'); + + SpecialPowers.removePermission("pPROMPT", document); + SpecialPowers.removePermission("pALLOW", document); + SpecialPowers.removePermission("pDENY", document); + SpecialPowers.removePermission("pREMOVE", document); + SpecialPowers.removePermission("pSESSION", document); + SpecialPowers.removePermission("pFIRSTPARTY", document); + SpecialPowers.removePermission("pTHIRDPARTY", document); + + setTimeout(test6, 0); +} + +function test6() { + if (!SpecialPowers.testPermission('pALLOW', UNKNOWN_ACTION, document)) { + dump('/**** allow still set ****/\n'); + setTimeout(test6, 0); + } else if (!SpecialPowers.testPermission('pDENY', UNKNOWN_ACTION, document)) { + dump('/**** deny still set ****/\n'); + setTimeout(test6, 0); + } else if (!SpecialPowers.testPermission('pPROMPT', UNKNOWN_ACTION, document)) { + dump('/**** prompt still set ****/\n'); + setTimeout(test6, 0); + } else if (!SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document)) { + dump('/**** remove still set ****/\n'); + setTimeout(test6, 0); + } else if (!SpecialPowers.testPermission('pSESSION', UNKNOWN_ACTION, document)) { + dump('/**** pSESSION still set ****/\n'); + setTimeout(test6, 0); + } else if (!SpecialPowers.testPermission('pFIRSTPARTY', UNKNOWN_ACTION, document)) { + dump('/**** pFIRSTPARTY still set ****/\n'); + setTimeout(test6, 0); + } else if (!SpecialPowers.testPermission('pTHIRDPARTY', UNKNOWN_ACTION, document)) { + dump('/**** pTHIRDPARTY still set ****/\n'); + setTimeout(test6, 0); + } else { + test7(); + } +} + +function test7() { + afterPermissionChanged('pEXPIRE', 'deleted', test8); + afterPermissionChanged('pEXPIRE', 'added', permissionPollingCheck); + start = Number(Date.now()); + SpecialPowers.addPermission('pEXPIRE', + true, + document, + EXPIRE_TIME, + (start + PERIOD + getPlatformInfo().timeCompensation)); +} + +function test8() { + afterPermissionChanged('pEXPIRE', 'deleted', SimpleTest.finish); + afterPermissionChanged('pEXPIRE', 'added', permissionPollingCheck); + start = Number(Date.now()); + SpecialPowers.pushPermissions([ + { 'type': 'pEXPIRE', + 'allow': true, + 'expireType': EXPIRE_TIME, + 'expireTime': (start + PERIOD + getPlatformInfo().timeCompensation), + 'context': document + }], function() { + info("Wait for permission-changed signal!"); + } + ); +} + +function afterPermissionChanged(type, op, callback) { + // handle the message from specialPowers_framescript.js + gScript.addMessageListener('perm-changed', function onChange(msg) { + if (msg.type == type && msg.op == op) { + gScript.removeMessageListener('perm-changed', onChange); + callback(); + } + }); +} + +function permissionPollingCheck() { + var now = Number(Date.now()); + if (now < (start + PERIOD)) { + if (SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) { + // To make sure that permission will be expired in next round, + // the next permissionPollingCheck calling will be fired 100ms later after + // permission is out-of-period. + setTimeout(permissionPollingCheck, PERIOD + 100); + return; + } + + errorHandler('unexpired permission should be allowed!'); + } + + // The permission is already expired! + if (SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) { + errorHandler('expired permission should be removed!'); + } +} + +function getPlatformInfo() { + var version = SpecialPowers.Services.sysinfo.getProperty('version'); + version = parseFloat(version); + + // PR_Now() that called in nsPermissionManager to get the system time and + // Date.now() are out of sync on win32 platform(XP/win7). The PR_Now() is + // 15~20ms less than Date.now(). Unfortunately, this time skew can't be + // avoided, so it needs to add a time buffer to compensate. + // Version 5.1 is win XP, 6.1 is win7 + if (navigator.platform.startsWith('Win32') && (version <= 6.1)) { + return { platform: "Win32", timeCompensation: -100 }; + } + + return { platform: "NoMatter", timeCompensation: 0 }; +} + +function errorHandler(msg) { + ok(false, msg); + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html new file mode 100644 index 000000000..cc352aeaa --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html @@ -0,0 +1,211 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest();"> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +function starttest() { + try { + SpecialPowers.setBoolPref("test.bool", 1); + } catch(e) { + SpecialPowers.setBoolPref("test.bool", true); + } + try { + SpecialPowers.setIntPref("test.int", true); + } catch(e) { + SpecialPowers.setIntPref("test.int", 1); + } + SpecialPowers.setCharPref("test.char", 'test'); + + setTimeout(test1, 0, 0); +} + +SimpleTest.waitForExplicitFinish(); + +function test1(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + } catch(e) { + setTimeout(test1, 0, ++aCount); + return; + } + + try { + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + } catch(e) { + setTimeout(test1, 0, ++aCount); + return; + } + + try { + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + } catch(e) { + setTimeout(test1, 0, ++aCount); + return; + } + + test2(); +} + +function test2() { + // test non-changing values + SpecialPowers.pushPrefEnv({"set": [["test.bool", true], ["test.int", 1], ["test.char", "test"]]}, test3); +} + +function test3() { + // test changing char pref using the Promise + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + SpecialPowers.pushPrefEnv({"set": [["test.bool", true], ["test.int", 1], ["test.char", "test2"]]}).then(test4); +} + +function test4() { + // test changing all values and adding test.char2 pref + is(SpecialPowers.getCharPref('test.char'), 'test2', 'test.char should be test2'); + SpecialPowers.pushPrefEnv({"set": [["test.bool", false], ["test.int", 10], ["test.char", "test2"], ["test.char2", "test"]]}, test5); +} + +function test5() { + // test flushPrefEnv + is(SpecialPowers.getBoolPref('test.bool'), false, 'test.bool should be false'); + is(SpecialPowers.getIntPref('test.int'), 10, 'test.int should be 10'); + is(SpecialPowers.getCharPref('test.char'), 'test2', 'test.char should be test2'); + is(SpecialPowers.getCharPref('test.char2'), 'test', 'test.char2 should be test'); + SpecialPowers.flushPrefEnv(test6); +} + +function test6() { + // test clearing prefs + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + is(typeof SpecialPowers.getBoolPref('test.bool'), typeof true, 'test.bool should be boolean'); + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + is(typeof SpecialPowers.getIntPref('test.int'), typeof 1, 'test.int should be integer'); + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + is(typeof SpecialPowers.getCharPref('test.char'), typeof 'test', 'test.char should be String'); + try { + SpecialPowers.getCharPref('test.char2'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char2") should throw'); + } + SpecialPowers.pushPrefEnv({"clear": [["test.bool"], ["test.int"], ["test.char"], ["test.char2"]]}, test6b); +} + +function test6b() { + // test if clearing another time doesn't cause issues + SpecialPowers.pushPrefEnv({"clear": [["test.bool"], ["test.int"], ["test.char"], ["test.char2"]]}, test7); +} + +function test7() { + try { + SpecialPowers.getBoolPref('test.bool'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getBoolPref("test.bool") should throw'); + } + + try { + SpecialPowers.getIntPref('test.int'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getIntPref("test.int") should throw'); + } + + try { + SpecialPowers.getCharPref('test.char'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char") should throw'); + } + + try { + SpecialPowers.getCharPref('test.char2'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char2") should throw'); + } + + SpecialPowers.flushPrefEnv().then(test8); +} + +function test8() { + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + is(typeof SpecialPowers.getBoolPref('test.bool'), typeof true, 'test.bool should be boolean'); + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + is(typeof SpecialPowers.getIntPref('test.int'), typeof 1, 'test.int should be integer'); + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + is(typeof SpecialPowers.getCharPref('test.char'), typeof 'test', 'test.char should be String'); + try { + SpecialPowers.getCharPref('test.char2'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char2") should throw'); + } + SpecialPowers.clearUserPref("test.bool"); + SpecialPowers.clearUserPref("test.int"); + SpecialPowers.clearUserPref("test.char"); + setTimeout(test9, 0, 0); +} + +function test9(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + SpecialPowers.getBoolPref('test.bool'); + setTimeout(test9, 0, ++aCount); + } catch(e) { + test10(0); + } +} + +function test10(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + SpecialPowers.getIntPref('test.int'); + setTimeout(test10, 0, ++aCount); + } catch(e) { + test11(0); + } +} + +function test11(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + SpecialPowers.getCharPref('test.char'); + setTimeout(test11, 0, ++aCount); + } catch(e) { + SimpleTest.finish(); + } +} +// todo - test non-changing values, test complex values, test mixing of pushprefEnv 'set' and 'clear' +// When bug 776424 gets fixed, getPref doesn't throw anymore, so this test would have to be changed accordingly +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_TestsRunningAfterSimpleTestFinish.html b/testing/mochitest/tests/Harness_sanity/test_TestsRunningAfterSimpleTestFinish.html new file mode 100644 index 000000000..7d2a97eb5 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_TestsRunningAfterSimpleTestFinish.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for whether SimpLeTest.ok after SimpleTest.finish is causing an error to be logged</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + ok(true, "This should pass"); + SimpleTest.finish(); + }); + window.onbeforeunload = function() { + ok(true, "This should cause failures in the harness, because it's run after SimpleTest.finish()"); + } + </script> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_add_task.html b/testing/mochitest/tests/Harness_sanity/test_add_task.html new file mode 100644 index 000000000..0b551e453 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_add_task.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for mochitest add_task, found in SpawnTask.js</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug 1187701</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// Check that we can 'add_task' a few times and all tasks run asynchronously before test finishes. + +add_task(function* () { + var x = yield Promise.resolve(1); + is(x, 1, "task yields Promise value as expected"); +}); + +add_task(function* () { + var x = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + is(x.join(""), "123", "task yields Promise value as expected"); +}); + +add_task(function* () { + var x = yield (function* () { + return 3; + }()); + is(x, 3, "task yields generator function return value as expected"); +}); +</script> +</pre> +</body> +</html> + diff --git a/testing/mochitest/tests/Harness_sanity/test_bug649012.html b/testing/mochitest/tests/Harness_sanity/test_bug649012.html new file mode 100644 index 000000000..d6205da34 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_bug649012.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=649012 +--> +<head> + <title>Test for Bug 649012</title> + <script type="application/javascript" src="/MochiKit/packed.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=649012">Mozilla Bug 649012</a> +<p id="display"></p> +<div id="content"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 649012 **/ +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + // Test that setTimeout(f, 0) doesn't raise an error + setTimeout(function() { + // Test that setTimeout(f, t) where t > 0 doesn't raise an error if we've used + // SimpleTest.requestFlakyTimeout + SimpleTest.requestFlakyTimeout("Just testing to make sure things work. I would never do this in real life of course!"); + setTimeout(function() { + SimpleTest.finish(); + }, 1); + }, 0); +}); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_createFiles.html b/testing/mochitest/tests/Harness_sanity/test_createFiles.html new file mode 100644 index 000000000..502592acc --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_createFiles.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.createFiles</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + // Creating one file, followed by failing to create a file. + function test1() { + const fileType = "some file type"; + let fdata = "this is same data for a file"; + SpecialPowers.createFiles([{name: "test1.txt", data:fdata, options:{type:fileType}}], + function (files) { + is(files.length, 1, "Created 1 file"); + let f = files[0]; + is("[object File]", f.toString(), "first thing in array is a file"); + is(f.size, fdata.length, "test1 size of first file should be length of its data"); + is("test1.txt", f.name, "test1 test file should have the right name"); + is(f.type, fileType, "File should have the specified type"); + test2(); + }, + function (msg) { ok(false, "Should be able to create a file without an error"); test2(); } + ); + } + + // Failing to create a file, followed by creating a file. + function test2() { + function test3Check(passed) { + ok(passed, "Should trigger the error handler for a bad file name."); + test3(); + }; + + SpecialPowers.createFiles([{name: "/\/\/\/\/\/\/\/\/\/\/\invalidname",}], + function () { test3Check(false); }, + function (msg) { test3Check(true); } + ); + } + + // Creating two files at the same time. + function test3() { + let f1data = "hello"; + SpecialPowers.createFiles([{name: "test3_file.txt", data:f1data}, {name: "emptyfile.txt"}], + function (files) { + is(files.length, 2, "Expected two files to be created"); + let f1 = files[0]; + let f2 = files[1]; + is("[object File]", f1.toString(), "first thing in array is a file"); + is("[object File]", f2.toString(), "second thing in array is a file"); + is("test3_file.txt", f1.name, "first test3 test file should have the right name"); + is("emptyfile.txt", f2.name, "second test3 test file should have the right name"); + is(f1.size, f1data.length, "size of first file should be length of its data"); + is(f2.size, 0, "size of second file should be 0"); + test4(); + }, + function (msg) { + ok(false, "Failed to create files: " + msg); + test4(); + } + ); + }; + + // Creating a file without specifying a name should work. + function test4() { + let fdata = "this is same data for a file"; + SpecialPowers.createFiles([{data:fdata}], + function (files) { + is(files.length, 1, "Created 1 file"); + let f = files[0]; + is("[object File]", f.toString(), "first thing in array is a file"); + is(f.size, fdata.length, "test4 size of first file should be length of its data"); + ok(f.name, "test4 test file should have a name"); + SimpleTest.finish(); + }, + function (msg) { + ok(false, "Should be able to create a file without a name without an error"); + SimpleTest.finish(); + } + ); + } + + SimpleTest.waitForExplicitFinish(); + test1(); + + </script> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_importInMainProcess.html b/testing/mochitest/tests/Harness_sanity/test_importInMainProcess.html new file mode 100644 index 000000000..569966074 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_importInMainProcess.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.importInMainProcess</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + var failed = false; + try { + SpecialPowers.importInMainProcess("invalid file for import"); + } catch (e) { + ok(e.toString().indexOf("NS_ERROR_MALFORMED_URI") > -1, "Exception should be for a malformed URI"); + failed = true; + } + ok(failed, "An invalid import should throw"); + + const testingResource = "resource://testing-common/ImportTesting.jsm"; + var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('importtesting_chromescript.js')); + + script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply); + script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource); + + function handleFirstReply(aMsg) { + ok(!aMsg, "ImportTesting.jsm shouldn't be loaded before we import it"); + + try { + SpecialPowers.importInMainProcess(testingResource); + } catch (e) { + ok(false, "Unexpected exception when importing a valid resource: " + e.toString()); + } + + script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply); + script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply); + script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource); + } + + function handleSecondReply(aMsg) { + script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply); + + ok(aMsg, "ImportTesting.jsm should be loaded after we import it"); + + SimpleTest.finish(); + } + + </script> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity.html b/testing/mochitest/tests/Harness_sanity/test_sanity.html new file mode 100644 index 000000000..3c9266685 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for mochitest harness sanity</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"> + <input id="testKeyEvent1" onkeypress="press1 = true"> + <input id="testKeyEvent2" onkeydown="return false;" onkeypress="press2 = true"> + <input id="testKeyEvent3" onkeypress="press3 = true"> + <input id="testKeyEvent4" onkeydown="return false;" onkeypress="press4 = true"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for sanity **/ +ok(true, "true must be ok"); +isnot(1, true, "1 must not be true"); +isnot(1, false, "1 must not be false"); +isnot(0, false, "0 must not be false"); +isnot(0, true, "0 must not be true"); +isnot("", 0, "Empty string must not be 0"); +isnot("1", 1, "Numeric string must not equal the number"); +isnot("", null, "Empty string must not be null"); +isnot(undefined, null, "Undefined must not be null"); + +var press1 = false; +$("testKeyEvent1").focus(); +synthesizeKey("x", {}); +is($("testKeyEvent1").value, "x", "synthesizeKey should work"); +is(press1, true, "synthesizeKey should dispatch keyPress"); + +var press2 = false; +$("testKeyEvent2").focus(); +synthesizeKey("x", {}); +is($("testKeyEvent2").value, "", "synthesizeKey should respect keydown preventDefault"); +is(press2, false, "synthesizeKey should not dispatch keyPress with default prevented"); + +var press3 = false; +$("testKeyEvent3").focus(); +sendChar("x") +is($("testKeyEvent3").value, "x", "sendChar should work"); +is(press3, true, "sendChar should dispatch keyPress"); + +var press4 = false; +$("testKeyEvent4").focus(); +sendChar("x") +is($("testKeyEvent4").value, "", "sendChar should respect keydown preventDefault"); +is(press4, false, "sendChar should not dispatch keyPress with default prevented"); + + +</script> +</pre> +</body> +</html> + diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html b/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html new file mode 100644 index 000000000..07efaaf4d --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html @@ -0,0 +1,188 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Profiling test suite for EventUtils</title> + <script type="text/javascript"> + var start = new Date(); + </script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript"> + var loadTime = new Date(); + </script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest()"> +<input type="radio" id="radioTarget1" name="group">Radio Target 1</input> +<input id="textBoxA"> +<input id="textBoxB"> +<input id="testMouseEvent" type="button" value="click"> +<input id="testKeyEvent" > +<input id="testStrEvent" > +<div id="scrollB" style="width: 190px;height: 250px;overflow:auto"> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +</div> +<script class="testbody" type="text/javascript"> +info("\nProfile::EventUtilsLoadTime: " + (loadTime - start) + "\n"); +function starttest() { + SimpleTest.waitForFocus( + function () { + SimpleTest.waitForExplicitFinish(); + var startTime = new Date(); + var check = false; + + /* test send* functions */ + $("testMouseEvent").addEventListener("click", function() { check=true; }, false); + sendMouseEvent({type:'click'}, "testMouseEvent"); + is(check, true, 'sendMouseEvent should dispatch click event'); + + check = false; + $("testKeyEvent").addEventListener("keypress", function() { check = true; }, false); + $("testKeyEvent").focus(); + sendChar("x"); + is($("testKeyEvent").value, "x", "sendChar should work"); + is(check, true, "sendChar should dispatch keyPress"); + $("testKeyEvent").value = ""; + + $("testStrEvent").focus(); + sendString("string"); + is($("testStrEvent").value, "string", "sendString should work"); + $("testStrEvent").value = ""; + + check = false; + $("testKeyEvent").focus(); + sendKey("DOWN"); + is(check, true, "sendKey should dispatch keyPress"); + + /* test synthesizeMouse* */ + //focus trick enables us to run this in iframes + $("radioTarget1").addEventListener('focus', function (aEvent) { + $("radioTarget1").removeEventListener('focus', arguments.callee, false); + synthesizeMouse($("radioTarget1"), 1, 1, {}); + is($("radioTarget1").checked, true, "synthesizeMouse should work") + $("radioTarget1").checked = false; + disableNonTestMouseEvents(true); + synthesizeMouse($("radioTarget1"), 1, 1, {}); + is($("radioTarget1").checked, true, "synthesizeMouse should still work with non-test mouse events disabled"); + $("radioTarget1").checked = false; + disableNonTestMouseEvents(false); + }); + $("radioTarget1").focus(); + + //focus trick enables us to run this in iframes + $("textBoxA").addEventListener("focus", function (aEvent) { + $("textBoxA").removeEventListener("focus", arguments.callee, false); + check = false; + $("textBoxA").addEventListener("click", function() { check = true; }, false); + synthesizeMouseAtCenter($("textBoxA"), {}); + is(check, true, 'synthesizeMouse should dispatch mouse event'); + + check = false; + synthesizeMouseExpectEvent($("textBoxA"), 1, 1, {}, $("textBoxA"), "click", "synthesizeMouseExpectEvent should fire click event"); + is(check, true, 'synthesizeMouse should dispatch mouse event'); + }); + $("textBoxA").focus(); + + /** + * TODO: testing synthesizeWheel requires a setTimeout + * since there is delay between the scroll event and a check, so for now just test + * that we can successfully call it to avoid having setTimeout vary the runtime metric. + * Testing of this method is currently done here: + * toolkit/content/tests/chrome/test_mousescroll.xul + */ + synthesizeWheel($("scrollB"), 5, 5, {'deltaY': 10.0, deltaMode: WheelEvent.DOM_DELTA_LINE}); + + /* test synthesizeKey* */ + check = false; + $("testKeyEvent").addEventListener("keypress", function() { check = true; }, false); + $("testKeyEvent").focus(); + synthesizeKey("a", {}); + is($("testKeyEvent").value, "a", "synthesizeKey should work"); + is(check, true, "synthesizeKey should dispatch keyPress"); + $("testKeyEvent").value = ""; + + check = false; + synthesizeKeyExpectEvent("a", {}, $("testKeyEvent"), "keypress"); + is($("testKeyEvent").value, "a", "synthesizeKey should work"); + is(check, true, "synthesizeKey should dispatch keyPress"); + $("testKeyEvent").value = ""; + + /* test synthesizeComposition */ + $("textBoxB").focus(); + check = false; + window.addEventListener("compositionstart", function() { check = true; }, false); + synthesizeComposition({ type: "compositionstart" }); + is(check, true, 'synthesizeComposition() should dispatch compositionstart'); + + check = false; + window.addEventListener("compositionupdate", function() { check = true; }, false); + synthesizeComposition({ type: "compositionupdate", data: "a" }); + is(check, false, 'synthesizeComposition() should not dispatch compositionupdate without error'); + + check = false; + window.addEventListener("text", function() { check = true; }, false); + synthesizeCompositionChange( + { "composition": + { "string": "a", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + } + ); + is(check, true, "synthesizeCompositionChange should cause dispatching a DOM text event"); + + synthesizeCompositionChange( + { "composition": + { "string": "a", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 1, "length": 0 } + } + ); + + check = false; + window.addEventListener("compositionend", function() { check = true; }, false); + synthesizeComposition({ type: "compositionend", data: "a" }); + is(check, false, 'synthesizeComposition() should not dispatch compositionend'); + + synthesizeComposition({ type: "compositioncommit", data: "a" }); + is(check, true, 'synthesizeComposition() should dispatch compositionend'); + + var querySelectedText = synthesizeQuerySelectedText(); + ok(querySelectedText, "query selected text event result is null"); + ok(querySelectedText.succeeded, "query selected text event failed"); + is(querySelectedText.offset, 1, + "query selected text event returns wrong offset"); + is(querySelectedText.text, "", + "query selected text event returns wrong selected text"); + $("textBoxB").value = ""; + + querySelectedText = synthesizeQuerySelectedText(); + ok(querySelectedText, "query selected text event result is null"); + ok(querySelectedText.succeeded, "query selected text event failed"); + is(querySelectedText.offset, 0, + "query selected text event returns wrong offset"); + is(querySelectedText.text, "", + "query selected text event returns wrong selected text"); + var endTime = new Date(); + info("\nProfile::EventUtilsRunTime: " + (endTime-startTime) + "\n"); + SimpleTest.finish(); + } + ); +}; +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityException.html b/testing/mochitest/tests/Harness_sanity/test_sanityException.html new file mode 100644 index 000000000..e1ed14836 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityException.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that uncaught exceptions in mochitests cause failures</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=670817">Mozilla Bug 670817</a> +<script> +ok(true, "a call to ok"); +SimpleTest.expectUncaughtException(); +throw "an uncaught exception"; +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityException2.html b/testing/mochitest/tests/Harness_sanity/test_sanityException2.html new file mode 100644 index 000000000..57d04764f --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityException2.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that uncaught exceptions in mochitests cause failures</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=670817">Mozilla Bug 670817</a> +<script> +SimpleTest.waitForExplicitFinish(); +ok(true, "a call to ok"); +SimpleTest.executeSoon(function() { + SimpleTest.expectUncaughtException(); + throw "an uncaught exception"; +}); +SimpleTest.executeSoon(function() { + SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityParams.html b/testing/mochitest/tests/Harness_sanity/test_sanityParams.html new file mode 100644 index 000000000..0d870f77f --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityParams.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for exposing test suite information</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +ok(SimpleTest.harnessParameters, "Should have parameters."); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker.html b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker.html new file mode 100644 index 000000000..6c8fc3524 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that service worker registrations not cleaned up in mochitests cause failures</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); +SimpleTest.expectRegisteredServiceWorker(); +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] +]}, function() { + navigator.serviceWorker.register("empty.js", {scope: "scope"}) + .then(function(registration) { + ok(registration, "Registration succeeded"); + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker2.html b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker2.html new file mode 100644 index 000000000..105fa6614 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker2.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that service worker registrations not cleaned up in mochitests cause failures</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] +]}, function() { + navigator.serviceWorker.getRegistration("scope") + .then(function(registration) { + ok(registration, "Registration successfully obtained"); + return registration.unregister(); + }).then(function() { + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanitySimpletest.html b/testing/mochitest/tests/Harness_sanity/test_sanitySimpletest.html new file mode 100644 index 000000000..1dc49a5d7 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanitySimpletest.html @@ -0,0 +1,95 @@ +<!--This test should be updated each time new functionality is added to SimpleTest--> +<!DOCTYPE HTML> +<html> +<head> + <title>Profiling test suite for SimpleTest</title> + <script type="text/javascript"> + var start = new Date(); + </script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript"> + var loadTime = new Date(); + </script> +</head> +<body> +<input id="textB"/> +<script class="testbody" type="text/javascript"> +info("Profile::SimpleTestLoadTime: " + (loadTime - start)); +var startTime = new Date(); +SimpleTest.waitForExplicitFinish(); +function starttest() { + SimpleTest.waitForFocus( + function() { + //test log + info("Logging some info") + + //basic usage + ok(true, "test ok", "This should be true"); + is(0, 0, "is() test failed"); + isnot(0, 1, "isnot() test failed"); + + //todo tests + todo(false, "test todo", "todo() test should not pass"); + todo_is(false, true, "test todo_is"); + todo_isnot(true, true, "test todo_isnot"); + + //misc + SimpleTest.requestLongerTimeout(1); + + //note: this test may alter runtimes as it waits + var check = false; + $('textB').focus(); + SimpleTest.waitForClipboard("a", + function () { + SpecialPowers.clipboardCopyString("a"); + }, + function () { + check = true; + }, + function () { + check = false; + } + ); + is(check, true, "waitForClipboard should work"); + + //use helper functions + var div1 = createEl('div', {'id': 'somediv', 'display': 'block'}, "I am a div"); + document.body.appendChild(div1); + var divObj = this.getElement('somediv'); + is(divObj, div1, 'createEl did not create element as expected'); + is($('somediv'), divObj, '$ helper did not get element as expected'); + is(computedStyle(divObj, 'display'), 'block', 'computedStyle did not get right display value'); + document.body.removeChild(div1); + + /* note: expectChildProcessCrash is not being tested here, as it causes wildly variable + * run times. It is currently being tested in: + * dom/plugins/test/test_hanging.html and dom/plugins/test/test_crashing.html + */ + + //note: this also adds a short wait period + SimpleTest.executeSoon( + function () { + //finish() calls a slew of SimpleTest functions + SimpleTest.finish(); + //call this after finish so we can make sure it works and doesn't hang our process + var endTime = new Date(); + info("Profile::SimpleTestRunTime: " + (endTime-startTime)); + //expect and throw exception here. Otherwise, any code that follows the throw call will never be executed + SimpleTest.expectUncaughtException(); + //make sure we catch this error + throw "i am an uncaught exception" + } + ); + } + ); +}; +//use addLoadEvent +addLoadEvent( + function() { + starttest(); + } +); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityWindowSnapshot.html b/testing/mochitest/tests/Harness_sanity/test_sanityWindowSnapshot.html new file mode 100644 index 000000000..4b8e9f3a4 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityWindowSnapshot.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Profiling test suite for WindowSnapshot</title> + <script type="text/javascript"> + var start = new Date(); + </script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <script type="text/javascript"> + var loadTime = new Date(); + </script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest()"> +<script class="testbody" type="text/javascript"> +info("\nProfile::WindowSnapshotLoadTime: " + (loadTime - start) + "\n"); +function starttest() { + SimpleTest.waitForExplicitFinish(); + var startTime = new Date(); + var snap = snapshotWindow(window, false); + var snap2 = snapshotWindow(window, false); + is(compareSnapshots(snap, snap2, true)[0], true, "this should be true"); + var div1 = createEl('div', {'id': 'somediv', 'display': 'block'}, "I am a div"); + document.body.appendChild(div1); + snap2 = snapshotWindow(window, false); + is(compareSnapshots(snap, snap2, true)[0], false, "this should be false"); + document.body.removeChild(div1); + var endTime = new Date(); + info("\nProfile::WindowSnapshotRunTime: " + (endTime-startTime) + "\n"); + SimpleTest.finish(); +}; +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup.html b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup.html new file mode 100644 index 000000000..af8d4fd31 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup.html @@ -0,0 +1,30 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.registerCleanupFunction test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +// Not a great example, since we have the pushPrefEnv API to cover +// this use case, but I want to be able to test that the cleanup +// function gets run, so setting and clearing a pref seems straightforward. +function do_cleanup1() { + SpecialPowers.clearUserPref("simpletest.cleanup.1"); + info("do_cleanup1 run!"); +} +function do_cleanup2() { + SpecialPowers.clearUserPref("simpletest.cleanup.2"); + info("do_cleanup2 run!"); +} +SpecialPowers.setBoolPref("simpletest.cleanup.1", true); +SpecialPowers.setBoolPref("simpletest.cleanup.2", true); +SimpleTest.registerCleanupFunction(do_cleanup1); +SimpleTest.registerCleanupFunction(do_cleanup2); +ok(true, "dummy check"); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup2.html b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup2.html new file mode 100644 index 000000000..169d43a61 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup2.html @@ -0,0 +1,24 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.registerCleanupFunction test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +for (pref of [1, 2]) { + try { + SpecialPowers.getBoolPref("simpletest.cleanup." + pref); + ok(false, "Cleanup function should have unset pref"); + } + catch(ex) { + ok(true, "Pref was not set"); + } +} + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_manifest.html b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest.html new file mode 100644 index 000000000..d8c791b4e --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest.html @@ -0,0 +1,16 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.expected = 'fail' test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +ok(false, "We expect this to fail"); + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_manifest_pf.html b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest_pf.html new file mode 100644 index 000000000..1f71fdd5a --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest_pf.html @@ -0,0 +1,17 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.expected = 'fail' test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +ok(true, "We expect this to pass"); +ok(false, "We expect this to fail"); + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_waitForCondition.html b/testing/mochitest/tests/Harness_sanity/test_sanity_waitForCondition.html new file mode 100644 index 000000000..5ac1daf2e --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_waitForCondition.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>SimpleTest.waitForCondition test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<script> + +var captureFailure = false; +var capturedFailures = []; +window.ok = function (cond, name, diag) { + if (!captureFailure) { + SimpleTest.ok(cond, name, diag); + } else { + if (cond) { + SimpleTest.ok(false, `Expect a failure with "${name}"`); + } else { + capturedFailures.push(name); + } + } +}; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("test behavior SimpleTest.waitForCondition"); + +addLoadEvent(testNormal); + +function testNormal() { + var condition = false; + SimpleTest.waitForCondition(() => condition, () => { + ok(condition, "Should only be called when condition is true"); + SimpleTest.executeSoon(testTimeout); + }, "Shouldn't timeout"); + setTimeout(() => { condition = true; }, 1000); +} + +function testTimeout() { + captureFailure = true; + SimpleTest.waitForCondition(() => false, () => { + captureFailure = false; + is(capturedFailures.length, 1, "Should captured one failure"); + is(capturedFailures[0], "Should timeout", + "Should capture the failure passed in"); + SimpleTest.executeSoon(() => SimpleTest.finish()); + }, "Should timeout"); +} + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_spawn_task.html b/testing/mochitest/tests/Harness_sanity/test_spawn_task.html new file mode 100644 index 000000000..326318746 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_spawn_task.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for mochitest SpawnTask.js sanity</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug 1078657</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for sanity **/ +SimpleTest.waitForExplicitFinish(); + +var externalGeneratorFunction = function* () { + return 8; +}; + +var nestedFunction = function* () { + return yield function* () { + return yield function* () { + return yield function* () { + return yield Promise.resolve(9); + }(); + }(); + }(); +} + +var variousTests = function* () { + var val1 = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + is(val1.join(""), "123", "Array of promises -> Promise.all"); + var val2 = yield Promise.resolve(2); + is(val2, 2, "Resolved promise yields value."); + var val3 = yield function* () { return 3; }; + is(val3, 3, "Generator functions are spawned."); + //var val4 = yield function () { return 4; }; + //is(val4, 4, "Plain functions run and return."); + var val5 = yield (function* () { return 5; }()); + is(val5, 5, "Generators are spawned."); + try { + var val6 = yield Promise.reject(Error("error6")); + ok(false, "Shouldn't reach this line."); + } catch (error) { + is(error.message, "error6", "Rejected promise throws error."); + } + try { + var val7 = yield function* () { throw Error("error7"); }; + ok(false, "Shouldn't reach this line."); + } catch (error) { + is(error.message, "error7", "Thrown error propagates."); + } + var val8 = yield externalGeneratorFunction(); + is(val8, 8, "External generator also spawned."); + var val9 = yield nestedFunction(); + is(val9, 9, "Nested generator functions work."); + return 10; +}; + +spawn_task(variousTests).then(function(result) { + is(result, 10, "spawn_task(...) returns promise"); + SimpleTest.finish(); +}); + + +</script> +</pre> +</body> +</html> + diff --git a/testing/mochitest/tests/MochiKit-1.4.2/LICENSE.txt b/testing/mochitest/tests/MochiKit-1.4.2/LICENSE.txt new file mode 100644 index 000000000..4d0065bef --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/LICENSE.txt @@ -0,0 +1,69 @@ +MochiKit is dual-licensed software. It is available under the terms of the +MIT License, or the Academic Free License version 2.1. The full text of +each license is included below. + +MIT License +=========== + +Copyright (c) 2005 Bob Ippolito. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Academic Free License v. 2.1 +============================ + +Copyright (c) 2005 Bob Ippolito. All rights reserved. + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work: + +Licensed under the Academic Free License version 2.1 + +1) Grant of Copyright License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license to do the following: + +a) to reproduce the Original Work in copies; + +b) to prepare derivative works ("Derivative Works") based upon the Original Work; + +c) to distribute copies of the Original Work and Derivative Works to the public; + +d) to perform the Original Work publicly; and + +e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, to make, use, sell and offer for sale the Original Work and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor hereby agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work, and by publishing the address of that information repository in a notice immediately following the copyright notice that applies to the Original Work. + +4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior written permission of the Licensor. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor except as expressly stated herein. No patent license is granted to make, use, sell or offer to sell embodiments of any patent claims other than the licensed claims defined in Section 2. No right is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any Original Work that Licensor otherwise would have a right to license. + +5) This section intentionally omitted. + +6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately proceeding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to Original Work is granted hereunder except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to any person for any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to liability for death or personal injury resulting from Licensor's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. + +9) Acceptance and Termination. If You distribute copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. Nothing else but this License (or another written agreement between Licensor and You) grants You permission to create Derivative Works based upon the Original Work or to exercise any of the rights granted in Section 1 herein, and any attempt to do so except under the terms of this License (or another written agreement between Licensor and You) is expressly prohibited by U.S. copyright law, the equivalent laws of other countries, and by international treaty. Therefore, by exercising any of the rights granted to You in Section 1 herein, You indicate Your acceptance of this License and all of its terms and conditions. + +10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of other countries, and international treaty. This section shall survive the termination of this License. + +12) Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + +13) Miscellaneous. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + +This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. Permission is hereby granted to copy and distribute this license without modification. This license may not be modified without the express written permission of its copyright owner. + + + diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Async.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Async.js new file mode 100644 index 000000000..6c5da1592 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Async.js @@ -0,0 +1,682 @@ +/*** + +MochiKit.Async 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('Async', ['Base']); + +MochiKit.Async.NAME = "MochiKit.Async"; +MochiKit.Async.VERSION = "1.4.2"; +MochiKit.Async.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; +MochiKit.Async.toString = function () { + return this.__repr__(); +}; + +/** @id MochiKit.Async.Deferred */ +MochiKit.Async.Deferred = function (/* optional */ canceller) { + this.chain = []; + this.id = this._nextId(); + this.fired = -1; + this.paused = 0; + this.results = [null, null]; + this.canceller = canceller; + this.silentlyCancelled = false; + this.chained = false; +}; + +MochiKit.Async.Deferred.prototype = { + /** @id MochiKit.Async.Deferred.prototype.repr */ + repr: function () { + var state; + if (this.fired == -1) { + state = 'unfired'; + } else if (this.fired === 0) { + state = 'success'; + } else { + state = 'error'; + } + return 'Deferred(' + this.id + ', ' + state + ')'; + }, + + toString: MochiKit.Base.forwardCall("repr"), + + _nextId: MochiKit.Base.counter(), + + /** @id MochiKit.Async.Deferred.prototype.cancel */ + cancel: function () { + var self = MochiKit.Async; + if (this.fired == -1) { + if (this.canceller) { + this.canceller(this); + } else { + this.silentlyCancelled = true; + } + if (this.fired == -1) { + this.errback(new self.CancelledError(this)); + } + } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) { + this.results[0].cancel(); + } + }, + + _resback: function (res) { + /*** + + The primitive that means either callback or errback + + ***/ + this.fired = ((res instanceof Error) ? 1 : 0); + this.results[this.fired] = res; + this._fire(); + }, + + _check: function () { + if (this.fired != -1) { + if (!this.silentlyCancelled) { + throw new MochiKit.Async.AlreadyCalledError(this); + } + this.silentlyCancelled = false; + return; + } + }, + + /** @id MochiKit.Async.Deferred.prototype.callback */ + callback: function (res) { + this._check(); + if (res instanceof MochiKit.Async.Deferred) { + throw new Error("Deferred instances can only be chained if they are the result of a callback"); + } + this._resback(res); + }, + + /** @id MochiKit.Async.Deferred.prototype.errback */ + errback: function (res) { + this._check(); + var self = MochiKit.Async; + if (res instanceof self.Deferred) { + throw new Error("Deferred instances can only be chained if they are the result of a callback"); + } + if (!(res instanceof Error)) { + res = new self.GenericError(res); + } + this._resback(res); + }, + + /** @id MochiKit.Async.Deferred.prototype.addBoth */ + addBoth: function (fn) { + if (arguments.length > 1) { + fn = MochiKit.Base.partial.apply(null, arguments); + } + return this.addCallbacks(fn, fn); + }, + + /** @id MochiKit.Async.Deferred.prototype.addCallback */ + addCallback: function (fn) { + if (arguments.length > 1) { + fn = MochiKit.Base.partial.apply(null, arguments); + } + return this.addCallbacks(fn, null); + }, + + /** @id MochiKit.Async.Deferred.prototype.addErrback */ + addErrback: function (fn) { + if (arguments.length > 1) { + fn = MochiKit.Base.partial.apply(null, arguments); + } + return this.addCallbacks(null, fn); + }, + + /** @id MochiKit.Async.Deferred.prototype.addCallbacks */ + addCallbacks: function (cb, eb) { + if (this.chained) { + throw new Error("Chained Deferreds can not be re-used"); + } + this.chain.push([cb, eb]); + if (this.fired >= 0) { + this._fire(); + } + return this; + }, + + _fire: function () { + /*** + + Used internally to exhaust the callback sequence when a result + is available. + + ***/ + var chain = this.chain; + var fired = this.fired; + var res = this.results[fired]; + var self = this; + var cb = null; + while (chain.length > 0 && this.paused === 0) { + // Array + var pair = chain.shift(); + var f = pair[fired]; + if (f === null) { + continue; + } + try { + res = f(res); + fired = ((res instanceof Error) ? 1 : 0); + if (res instanceof MochiKit.Async.Deferred) { + cb = function (res) { + self._resback(res); + self.paused--; + if ((self.paused === 0) && (self.fired >= 0)) { + self._fire(); + } + }; + this.paused++; + } + } catch (err) { + fired = 1; + if (!(err instanceof Error)) { + err = new MochiKit.Async.GenericError(err); + } + res = err; + } + } + this.fired = fired; + this.results[fired] = res; + if (cb && this.paused) { + // this is for "tail recursion" in case the dependent deferred + // is already fired + res.addBoth(cb); + res.chained = true; + } + } +}; + +MochiKit.Base.update(MochiKit.Async, { + /** @id MochiKit.Async.evalJSONRequest */ + evalJSONRequest: function (req) { + return MochiKit.Base.evalJSON(req.responseText); + }, + + /** @id MochiKit.Async.succeed */ + succeed: function (/* optional */result) { + var d = new MochiKit.Async.Deferred(); + d.callback.apply(d, arguments); + return d; + }, + + /** @id MochiKit.Async.fail */ + fail: function (/* optional */result) { + var d = new MochiKit.Async.Deferred(); + d.errback.apply(d, arguments); + return d; + }, + + /** @id MochiKit.Async.getXMLHttpRequest */ + getXMLHttpRequest: function () { + var self = arguments.callee; + if (!self.XMLHttpRequest) { + var tryThese = [ + function () { return new XMLHttpRequest(); }, + function () { return new ActiveXObject('Msxml2.XMLHTTP'); }, + function () { return new ActiveXObject('Microsoft.XMLHTTP'); }, + function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); }, + function () { + throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest"); + } + ]; + for (var i = 0; i < tryThese.length; i++) { + var func = tryThese[i]; + try { + self.XMLHttpRequest = func; + return func(); + } catch (e) { + // pass + } + } + } + return self.XMLHttpRequest(); + }, + + _xhr_onreadystatechange: function (d) { + // MochiKit.Logging.logDebug('this.readyState', this.readyState); + var m = MochiKit.Base; + if (this.readyState == 4) { + // IE SUCKS + try { + this.onreadystatechange = null; + } catch (e) { + try { + this.onreadystatechange = m.noop; + } catch (e) { + } + } + var status = null; + try { + status = this.status; + if (!status && m.isNotEmpty(this.responseText)) { + // 0 or undefined seems to mean cached or local + status = 304; + } + } catch (e) { + // pass + // MochiKit.Logging.logDebug('error getting status?', repr(items(e))); + } + // 200 is OK, 201 is CREATED, 204 is NO CONTENT + // 304 is NOT MODIFIED, 1223 is apparently a bug in IE + if (status == 200 || status == 201 || status == 204 || + status == 304 || status == 1223) { + d.callback(this); + } else { + var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed"); + if (err.number) { + // XXX: This seems to happen on page change + d.errback(err); + } else { + // XXX: this seems to happen when the server is unreachable + d.errback(err); + } + } + } + }, + + _xhr_canceller: function (req) { + // IE SUCKS + try { + req.onreadystatechange = null; + } catch (e) { + try { + req.onreadystatechange = MochiKit.Base.noop; + } catch (e) { + } + } + req.abort(); + }, + + + /** @id MochiKit.Async.sendXMLHttpRequest */ + sendXMLHttpRequest: function (req, /* optional */ sendContent) { + if (typeof(sendContent) == "undefined" || sendContent === null) { + sendContent = ""; + } + + var m = MochiKit.Base; + var self = MochiKit.Async; + var d = new self.Deferred(m.partial(self._xhr_canceller, req)); + + try { + req.onreadystatechange = m.bind(self._xhr_onreadystatechange, + req, d); + req.send(sendContent); + } catch (e) { + try { + req.onreadystatechange = null; + } catch (ignore) { + // pass + } + d.errback(e); + } + + return d; + + }, + + /** @id MochiKit.Async.doXHR */ + doXHR: function (url, opts) { + /* + Work around a Firefox bug by dealing with XHR during + the next event loop iteration. Maybe it's this one: + https://bugzilla.mozilla.org/show_bug.cgi?id=249843 + */ + var self = MochiKit.Async; + return self.callLater(0, self._doXHR, url, opts); + }, + + _doXHR: function (url, opts) { + var m = MochiKit.Base; + opts = m.update({ + method: 'GET', + sendContent: '' + /* + queryString: undefined, + username: undefined, + password: undefined, + headers: undefined, + mimeType: undefined + */ + }, opts); + var self = MochiKit.Async; + var req = self.getXMLHttpRequest(); + if (opts.queryString) { + var qs = m.queryString(opts.queryString); + if (qs) { + url += "?" + qs; + } + } + // Safari will send undefined:undefined, so we have to check. + // We can't use apply, since the function is native. + if ('username' in opts) { + req.open(opts.method, url, true, opts.username, opts.password); + } else { + req.open(opts.method, url, true); + } + if (req.overrideMimeType && opts.mimeType) { + req.overrideMimeType(opts.mimeType); + } + req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + if (opts.headers) { + var headers = opts.headers; + if (!m.isArrayLike(headers)) { + headers = m.items(headers); + } + for (var i = 0; i < headers.length; i++) { + var header = headers[i]; + var name = header[0]; + var value = header[1]; + req.setRequestHeader(name, value); + } + } + return self.sendXMLHttpRequest(req, opts.sendContent); + }, + + _buildURL: function (url/*, ...*/) { + if (arguments.length > 1) { + var m = MochiKit.Base; + var qs = m.queryString.apply(null, m.extend(null, arguments, 1)); + if (qs) { + return url + "?" + qs; + } + } + return url; + }, + + /** @id MochiKit.Async.doSimpleXMLHttpRequest */ + doSimpleXMLHttpRequest: function (url/*, ...*/) { + var self = MochiKit.Async; + url = self._buildURL.apply(self, arguments); + return self.doXHR(url); + }, + + /** @id MochiKit.Async.loadJSONDoc */ + loadJSONDoc: function (url/*, ...*/) { + var self = MochiKit.Async; + url = self._buildURL.apply(self, arguments); + var d = self.doXHR(url, { + 'mimeType': 'text/plain', + 'headers': [['Accept', 'application/json']] + }); + d = d.addCallback(self.evalJSONRequest); + return d; + }, + + /** @id MochiKit.Async.wait */ + wait: function (seconds, /* optional */value) { + var d = new MochiKit.Async.Deferred(); + var m = MochiKit.Base; + if (typeof(value) != 'undefined') { + d.addCallback(function () { return value; }); + } + var timeout = setTimeout( + m.bind("callback", d), + Math.floor(seconds * 1000)); + d.canceller = function () { + try { + clearTimeout(timeout); + } catch (e) { + // pass + } + }; + return d; + }, + + /** @id MochiKit.Async.callLater */ + callLater: function (seconds, func) { + var m = MochiKit.Base; + var pfunc = m.partial.apply(m, m.extend(null, arguments, 1)); + return MochiKit.Async.wait(seconds).addCallback( + function (res) { return pfunc(); } + ); + } +}); + + +/** @id MochiKit.Async.DeferredLock */ +MochiKit.Async.DeferredLock = function () { + this.waiting = []; + this.locked = false; + this.id = this._nextId(); +}; + +MochiKit.Async.DeferredLock.prototype = { + __class__: MochiKit.Async.DeferredLock, + /** @id MochiKit.Async.DeferredLock.prototype.acquire */ + acquire: function () { + var d = new MochiKit.Async.Deferred(); + if (this.locked) { + this.waiting.push(d); + } else { + this.locked = true; + d.callback(this); + } + return d; + }, + /** @id MochiKit.Async.DeferredLock.prototype.release */ + release: function () { + if (!this.locked) { + throw TypeError("Tried to release an unlocked DeferredLock"); + } + this.locked = false; + if (this.waiting.length > 0) { + this.locked = true; + this.waiting.shift().callback(this); + } + }, + _nextId: MochiKit.Base.counter(), + repr: function () { + var state; + if (this.locked) { + state = 'locked, ' + this.waiting.length + ' waiting'; + } else { + state = 'unlocked'; + } + return 'DeferredLock(' + this.id + ', ' + state + ')'; + }, + toString: MochiKit.Base.forwardCall("repr") + +}; + +/** @id MochiKit.Async.DeferredList */ +MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) { + + // call parent constructor + MochiKit.Async.Deferred.apply(this, [canceller]); + + this.list = list; + var resultList = []; + this.resultList = resultList; + + this.finishedCount = 0; + this.fireOnOneCallback = fireOnOneCallback; + this.fireOnOneErrback = fireOnOneErrback; + this.consumeErrors = consumeErrors; + + var cb = MochiKit.Base.bind(this._cbDeferred, this); + for (var i = 0; i < list.length; i++) { + var d = list[i]; + resultList.push(undefined); + d.addCallback(cb, i, true); + d.addErrback(cb, i, false); + } + + if (list.length === 0 && !fireOnOneCallback) { + this.callback(this.resultList); + } + +}; + +MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred(); + +MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) { + this.resultList[index] = [succeeded, result]; + this.finishedCount += 1; + if (this.fired == -1) { + if (succeeded && this.fireOnOneCallback) { + this.callback([index, result]); + } else if (!succeeded && this.fireOnOneErrback) { + this.errback(result); + } else if (this.finishedCount == this.list.length) { + this.callback(this.resultList); + } + } + if (!succeeded && this.consumeErrors) { + result = null; + } + return result; +}; + +/** @id MochiKit.Async.gatherResults */ +MochiKit.Async.gatherResults = function (deferredList) { + var d = new MochiKit.Async.DeferredList(deferredList, false, true, false); + d.addCallback(function (results) { + var ret = []; + for (var i = 0; i < results.length; i++) { + ret.push(results[i][1]); + } + return ret; + }); + return d; +}; + +/** @id MochiKit.Async.maybeDeferred */ +MochiKit.Async.maybeDeferred = function (func) { + var self = MochiKit.Async; + var result; + try { + var r = func.apply(null, MochiKit.Base.extend([], arguments, 1)); + if (r instanceof self.Deferred) { + result = r; + } else if (r instanceof Error) { + result = self.fail(r); + } else { + result = self.succeed(r); + } + } catch (e) { + result = self.fail(e); + } + return result; +}; + + +MochiKit.Async.EXPORT = [ + "AlreadyCalledError", + "CancelledError", + "BrowserComplianceError", + "GenericError", + "XMLHttpRequestError", + "Deferred", + "succeed", + "fail", + "getXMLHttpRequest", + "doSimpleXMLHttpRequest", + "loadJSONDoc", + "wait", + "callLater", + "sendXMLHttpRequest", + "DeferredLock", + "DeferredList", + "gatherResults", + "maybeDeferred", + "doXHR" +]; + +MochiKit.Async.EXPORT_OK = [ + "evalJSONRequest" +]; + +MochiKit.Async.__new__ = function () { + var m = MochiKit.Base; + var ne = m.partial(m._newNamedError, this); + + ne("AlreadyCalledError", + /** @id MochiKit.Async.AlreadyCalledError */ + function (deferred) { + /*** + + Raised by the Deferred if callback or errback happens + after it was already fired. + + ***/ + this.deferred = deferred; + } + ); + + ne("CancelledError", + /** @id MochiKit.Async.CancelledError */ + function (deferred) { + /*** + + Raised by the Deferred cancellation mechanism. + + ***/ + this.deferred = deferred; + } + ); + + ne("BrowserComplianceError", + /** @id MochiKit.Async.BrowserComplianceError */ + function (msg) { + /*** + + Raised when the JavaScript runtime is not capable of performing + the given function. Technically, this should really never be + raised because a non-conforming JavaScript runtime probably + isn't going to support exceptions in the first place. + + ***/ + this.message = msg; + } + ); + + ne("GenericError", + /** @id MochiKit.Async.GenericError */ + function (msg) { + this.message = msg; + } + ); + + ne("XMLHttpRequestError", + /** @id MochiKit.Async.XMLHttpRequestError */ + function (req, msg) { + /*** + + Raised when an XMLHttpRequest does not complete for any reason. + + ***/ + this.req = req; + this.message = msg; + try { + // Strange but true that this can raise in some cases. + this.number = req.status; + } catch (e) { + // pass + } + } + ); + + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + +}; + +MochiKit.Async.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Async); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Base.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Base.js new file mode 100644 index 000000000..39b151c58 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Base.js @@ -0,0 +1,1489 @@ +/*** + +MochiKit.Base 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +if (typeof(dojo) != 'undefined') { + dojo.provide("MochiKit.Base"); +} +if (typeof(MochiKit) == 'undefined') { + MochiKit = {}; +} +if (typeof(MochiKit.Base) == 'undefined') { + MochiKit.Base = {}; +} +if (typeof(MochiKit.__export__) == "undefined") { + MochiKit.__export__ = (MochiKit.__compat__ || + (typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined') + ); +} + +MochiKit.Base.VERSION = "1.4.2"; +MochiKit.Base.NAME = "MochiKit.Base"; +/** @id MochiKit.Base.update */ +MochiKit.Base.update = function (self, obj/*, ... */) { + if (self === null || self === undefined) { + self = {}; + } + for (var i = 1; i < arguments.length; i++) { + var o = arguments[i]; + if (typeof(o) != 'undefined' && o !== null) { + for (var k in o) { + self[k] = o[k]; + } + } + } + return self; +}; + +MochiKit.Base.update(MochiKit.Base, { + __repr__: function () { + return "[" + this.NAME + " " + this.VERSION + "]"; + }, + + toString: function () { + return this.__repr__(); + }, + + /** @id MochiKit.Base.camelize */ + camelize: function (selector) { + /* from dojo.style.toCamelCase */ + var arr = selector.split('-'); + var cc = arr[0]; + for (var i = 1; i < arr.length; i++) { + cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1); + } + return cc; + }, + + /** @id MochiKit.Base.counter */ + counter: function (n/* = 1 */) { + if (arguments.length === 0) { + n = 1; + } + return function () { + return n++; + }; + }, + + /** @id MochiKit.Base.clone */ + clone: function (obj) { + var me = arguments.callee; + if (arguments.length == 1) { + me.prototype = obj; + return new me(); + } + }, + + _deps: function(module, deps) { + if (!(module in MochiKit)) { + MochiKit[module] = {}; + } + + if (typeof(dojo) != 'undefined') { + dojo.provide('MochiKit.' + module); + } + for (var i = 0; i < deps.length; i++) { + if (typeof(dojo) != 'undefined') { + dojo.require('MochiKit.' + deps[i]); + } + if (typeof(JSAN) != 'undefined') { + JSAN.use('MochiKit.' + deps[i], []); + } + if (!(deps[i] in MochiKit)) { + throw 'MochiKit.' + module + ' depends on MochiKit.' + deps[i] + '!' + } + } + }, + + _flattenArray: function (res, lst) { + for (var i = 0; i < lst.length; i++) { + var o = lst[i]; + if (o instanceof Array) { + arguments.callee(res, o); + } else { + res.push(o); + } + } + return res; + }, + + /** @id MochiKit.Base.flattenArray */ + flattenArray: function (lst) { + return MochiKit.Base._flattenArray([], lst); + }, + + /** @id MochiKit.Base.flattenArguments */ + flattenArguments: function (lst/* ...*/) { + var res = []; + var m = MochiKit.Base; + var args = m.extend(null, arguments); + while (args.length) { + var o = args.shift(); + if (o && typeof(o) == "object" && typeof(o.length) == "number") { + for (var i = o.length - 1; i >= 0; i--) { + args.unshift(o[i]); + } + } else { + res.push(o); + } + } + return res; + }, + + /** @id MochiKit.Base.extend */ + extend: function (self, obj, /* optional */skip) { + // Extend an array with an array-like object starting + // from the skip index + if (!skip) { + skip = 0; + } + if (obj) { + // allow iterable fall-through, but skip the full isArrayLike + // check for speed, this is called often. + var l = obj.length; + if (typeof(l) != 'number' /* !isArrayLike(obj) */) { + if (typeof(MochiKit.Iter) != "undefined") { + obj = MochiKit.Iter.list(obj); + l = obj.length; + } else { + throw new TypeError("Argument not an array-like and MochiKit.Iter not present"); + } + } + if (!self) { + self = []; + } + for (var i = skip; i < l; i++) { + self.push(obj[i]); + } + } + // This mutates, but it's convenient to return because + // it's often used like a constructor when turning some + // ghetto array-like to a real array + return self; + }, + + + /** @id MochiKit.Base.updatetree */ + updatetree: function (self, obj/*, ...*/) { + if (self === null || self === undefined) { + self = {}; + } + for (var i = 1; i < arguments.length; i++) { + var o = arguments[i]; + if (typeof(o) != 'undefined' && o !== null) { + for (var k in o) { + var v = o[k]; + if (typeof(self[k]) == 'object' && typeof(v) == 'object') { + arguments.callee(self[k], v); + } else { + self[k] = v; + } + } + } + } + return self; + }, + + /** @id MochiKit.Base.setdefault */ + setdefault: function (self, obj/*, ...*/) { + if (self === null || self === undefined) { + self = {}; + } + for (var i = 1; i < arguments.length; i++) { + var o = arguments[i]; + for (var k in o) { + if (!(k in self)) { + self[k] = o[k]; + } + } + } + return self; + }, + + /** @id MochiKit.Base.keys */ + keys: function (obj) { + var rval = []; + for (var prop in obj) { + rval.push(prop); + } + return rval; + }, + + /** @id MochiKit.Base.values */ + values: function (obj) { + var rval = []; + for (var prop in obj) { + rval.push(obj[prop]); + } + return rval; + }, + + /** @id MochiKit.Base.items */ + items: function (obj) { + var rval = []; + var e; + for (var prop in obj) { + var v; + try { + v = obj[prop]; + } catch (e) { + continue; + } + rval.push([prop, v]); + } + return rval; + }, + + + _newNamedError: function (module, name, func) { + func.prototype = new MochiKit.Base.NamedError(module.NAME + "." + name); + module[name] = func; + }, + + + /** @id MochiKit.Base.operator */ + operator: { + // unary logic operators + /** @id MochiKit.Base.truth */ + truth: function (a) { return !!a; }, + /** @id MochiKit.Base.lognot */ + lognot: function (a) { return !a; }, + /** @id MochiKit.Base.identity */ + identity: function (a) { return a; }, + + // bitwise unary operators + /** @id MochiKit.Base.not */ + not: function (a) { return ~a; }, + /** @id MochiKit.Base.neg */ + neg: function (a) { return -a; }, + + // binary operators + /** @id MochiKit.Base.add */ + add: function (a, b) { return a + b; }, + /** @id MochiKit.Base.sub */ + sub: function (a, b) { return a - b; }, + /** @id MochiKit.Base.div */ + div: function (a, b) { return a / b; }, + /** @id MochiKit.Base.mod */ + mod: function (a, b) { return a % b; }, + /** @id MochiKit.Base.mul */ + mul: function (a, b) { return a * b; }, + + // bitwise binary operators + /** @id MochiKit.Base.and */ + and: function (a, b) { return a & b; }, + /** @id MochiKit.Base.or */ + or: function (a, b) { return a | b; }, + /** @id MochiKit.Base.xor */ + xor: function (a, b) { return a ^ b; }, + /** @id MochiKit.Base.lshift */ + lshift: function (a, b) { return a << b; }, + /** @id MochiKit.Base.rshift */ + rshift: function (a, b) { return a >> b; }, + /** @id MochiKit.Base.zrshift */ + zrshift: function (a, b) { return a >>> b; }, + + // near-worthless built-in comparators + /** @id MochiKit.Base.eq */ + eq: function (a, b) { return a == b; }, + /** @id MochiKit.Base.ne */ + ne: function (a, b) { return a != b; }, + /** @id MochiKit.Base.gt */ + gt: function (a, b) { return a > b; }, + /** @id MochiKit.Base.ge */ + ge: function (a, b) { return a >= b; }, + /** @id MochiKit.Base.lt */ + lt: function (a, b) { return a < b; }, + /** @id MochiKit.Base.le */ + le: function (a, b) { return a <= b; }, + + // strict built-in comparators + seq: function (a, b) { return a === b; }, + sne: function (a, b) { return a !== b; }, + + // compare comparators + /** @id MochiKit.Base.ceq */ + ceq: function (a, b) { return MochiKit.Base.compare(a, b) === 0; }, + /** @id MochiKit.Base.cne */ + cne: function (a, b) { return MochiKit.Base.compare(a, b) !== 0; }, + /** @id MochiKit.Base.cgt */ + cgt: function (a, b) { return MochiKit.Base.compare(a, b) == 1; }, + /** @id MochiKit.Base.cge */ + cge: function (a, b) { return MochiKit.Base.compare(a, b) != -1; }, + /** @id MochiKit.Base.clt */ + clt: function (a, b) { return MochiKit.Base.compare(a, b) == -1; }, + /** @id MochiKit.Base.cle */ + cle: function (a, b) { return MochiKit.Base.compare(a, b) != 1; }, + + // binary logical operators + /** @id MochiKit.Base.logand */ + logand: function (a, b) { return a && b; }, + /** @id MochiKit.Base.logor */ + logor: function (a, b) { return a || b; }, + /** @id MochiKit.Base.contains */ + contains: function (a, b) { return b in a; } + }, + + /** @id MochiKit.Base.forwardCall */ + forwardCall: function (func) { + return function () { + return this[func].apply(this, arguments); + }; + }, + + /** @id MochiKit.Base.itemgetter */ + itemgetter: function (func) { + return function (arg) { + return arg[func]; + }; + }, + + /** @id MochiKit.Base.typeMatcher */ + typeMatcher: function (/* typ */) { + var types = {}; + for (var i = 0; i < arguments.length; i++) { + var typ = arguments[i]; + types[typ] = typ; + } + return function () { + for (var i = 0; i < arguments.length; i++) { + if (!(typeof(arguments[i]) in types)) { + return false; + } + } + return true; + }; + }, + + /** @id MochiKit.Base.isNull */ + isNull: function (/* ... */) { + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] !== null) { + return false; + } + } + return true; + }, + + /** @id MochiKit.Base.isUndefinedOrNull */ + isUndefinedOrNull: function (/* ... */) { + for (var i = 0; i < arguments.length; i++) { + var o = arguments[i]; + if (!(typeof(o) == 'undefined' || o === null)) { + return false; + } + } + return true; + }, + + /** @id MochiKit.Base.isEmpty */ + isEmpty: function (obj) { + return !MochiKit.Base.isNotEmpty.apply(this, arguments); + }, + + /** @id MochiKit.Base.isNotEmpty */ + isNotEmpty: function (obj) { + for (var i = 0; i < arguments.length; i++) { + var o = arguments[i]; + if (!(o && o.length)) { + return false; + } + } + return true; + }, + + /** @id MochiKit.Base.isArrayLike */ + isArrayLike: function () { + for (var i = 0; i < arguments.length; i++) { + var o = arguments[i]; + var typ = typeof(o); + if ( + (typ != 'object' && !(typ == 'function' && typeof(o.item) == 'function')) || + o === null || + typeof(o.length) != 'number' || + o.nodeType === 3 || + o.nodeType === 4 + ) { + return false; + } + } + return true; + }, + + /** @id MochiKit.Base.isDateLike */ + isDateLike: function () { + for (var i = 0; i < arguments.length; i++) { + var o = arguments[i]; + if (typeof(o) != "object" || o === null + || typeof(o.getTime) != 'function') { + return false; + } + } + return true; + }, + + + /** @id MochiKit.Base.xmap */ + xmap: function (fn/*, obj... */) { + if (fn === null) { + return MochiKit.Base.extend(null, arguments, 1); + } + var rval = []; + for (var i = 1; i < arguments.length; i++) { + rval.push(fn(arguments[i])); + } + return rval; + }, + + /** @id MochiKit.Base.map */ + map: function (fn, lst/*, lst... */) { + var m = MochiKit.Base; + var itr = MochiKit.Iter; + var isArrayLike = m.isArrayLike; + if (arguments.length <= 2) { + // allow an iterable to be passed + if (!isArrayLike(lst)) { + if (itr) { + // fast path for map(null, iterable) + lst = itr.list(lst); + if (fn === null) { + return lst; + } + } else { + throw new TypeError("Argument not an array-like and MochiKit.Iter not present"); + } + } + // fast path for map(null, lst) + if (fn === null) { + return m.extend(null, lst); + } + // disabled fast path for map(fn, lst) + /* + if (false && typeof(Array.prototype.map) == 'function') { + // Mozilla fast-path + return Array.prototype.map.call(lst, fn); + } + */ + var rval = []; + for (var i = 0; i < lst.length; i++) { + rval.push(fn(lst[i])); + } + return rval; + } else { + // default for map(null, ...) is zip(...) + if (fn === null) { + fn = Array; + } + var length = null; + for (i = 1; i < arguments.length; i++) { + // allow iterables to be passed + if (!isArrayLike(arguments[i])) { + if (itr) { + return itr.list(itr.imap.apply(null, arguments)); + } else { + throw new TypeError("Argument not an array-like and MochiKit.Iter not present"); + } + } + // find the minimum length + var l = arguments[i].length; + if (length === null || length > l) { + length = l; + } + } + rval = []; + for (i = 0; i < length; i++) { + var args = []; + for (var j = 1; j < arguments.length; j++) { + args.push(arguments[j][i]); + } + rval.push(fn.apply(this, args)); + } + return rval; + } + }, + + /** @id MochiKit.Base.xfilter */ + xfilter: function (fn/*, obj... */) { + var rval = []; + if (fn === null) { + fn = MochiKit.Base.operator.truth; + } + for (var i = 1; i < arguments.length; i++) { + var o = arguments[i]; + if (fn(o)) { + rval.push(o); + } + } + return rval; + }, + + /** @id MochiKit.Base.filter */ + filter: function (fn, lst, self) { + var rval = []; + // allow an iterable to be passed + var m = MochiKit.Base; + if (!m.isArrayLike(lst)) { + if (MochiKit.Iter) { + lst = MochiKit.Iter.list(lst); + } else { + throw new TypeError("Argument not an array-like and MochiKit.Iter not present"); + } + } + if (fn === null) { + fn = m.operator.truth; + } + if (typeof(Array.prototype.filter) == 'function') { + // Mozilla fast-path + return Array.prototype.filter.call(lst, fn, self); + } else if (typeof(self) == 'undefined' || self === null) { + for (var i = 0; i < lst.length; i++) { + var o = lst[i]; + if (fn(o)) { + rval.push(o); + } + } + } else { + for (i = 0; i < lst.length; i++) { + o = lst[i]; + if (fn.call(self, o)) { + rval.push(o); + } + } + } + return rval; + }, + + + _wrapDumbFunction: function (func) { + return function () { + // fast path! + switch (arguments.length) { + case 0: return func(); + case 1: return func(arguments[0]); + case 2: return func(arguments[0], arguments[1]); + case 3: return func(arguments[0], arguments[1], arguments[2]); + } + var args = []; + for (var i = 0; i < arguments.length; i++) { + args.push("arguments[" + i + "]"); + } + return eval("(func(" + args.join(",") + "))"); + }; + }, + + /** @id MochiKit.Base.methodcaller */ + methodcaller: function (func/*, args... */) { + var args = MochiKit.Base.extend(null, arguments, 1); + if (typeof(func) == "function") { + return function (obj) { + return func.apply(obj, args); + }; + } else { + return function (obj) { + return obj[func].apply(obj, args); + }; + } + }, + + /** @id MochiKit.Base.method */ + method: function (self, func) { + var m = MochiKit.Base; + return m.bind.apply(this, m.extend([func, self], arguments, 2)); + }, + + /** @id MochiKit.Base.compose */ + compose: function (f1, f2/*, f3, ... fN */) { + var fnlist = []; + var m = MochiKit.Base; + if (arguments.length === 0) { + throw new TypeError("compose() requires at least one argument"); + } + for (var i = 0; i < arguments.length; i++) { + var fn = arguments[i]; + if (typeof(fn) != "function") { + throw new TypeError(m.repr(fn) + " is not a function"); + } + fnlist.push(fn); + } + return function () { + var args = arguments; + for (var i = fnlist.length - 1; i >= 0; i--) { + args = [fnlist[i].apply(this, args)]; + } + return args[0]; + }; + }, + + /** @id MochiKit.Base.bind */ + bind: function (func, self/* args... */) { + if (typeof(func) == "string") { + func = self[func]; + } + var im_func = func.im_func; + var im_preargs = func.im_preargs; + var im_self = func.im_self; + var m = MochiKit.Base; + if (typeof(func) == "function" && typeof(func.apply) == "undefined") { + // this is for cases where JavaScript sucks ass and gives you a + // really dumb built-in function like alert() that doesn't have + // an apply + func = m._wrapDumbFunction(func); + } + if (typeof(im_func) != 'function') { + im_func = func; + } + if (typeof(self) != 'undefined') { + im_self = self; + } + if (typeof(im_preargs) == 'undefined') { + im_preargs = []; + } else { + im_preargs = im_preargs.slice(); + } + m.extend(im_preargs, arguments, 2); + var newfunc = function () { + var args = arguments; + var me = arguments.callee; + if (me.im_preargs.length > 0) { + args = m.concat(me.im_preargs, args); + } + var self = me.im_self; + if (!self) { + self = this; + } + return me.im_func.apply(self, args); + }; + newfunc.im_self = im_self; + newfunc.im_func = im_func; + newfunc.im_preargs = im_preargs; + return newfunc; + }, + + /** @id MochiKit.Base.bindLate */ + bindLate: function (func, self/* args... */) { + var m = MochiKit.Base; + if (typeof(func) != "string") { + return m.bind.apply(this, arguments); + } + var im_preargs = m.extend([], arguments, 2); + var newfunc = function () { + var args = arguments; + var me = arguments.callee; + if (me.im_preargs.length > 0) { + args = m.concat(me.im_preargs, args); + } + var self = me.im_self; + if (!self) { + self = this; + } + return self[me.im_func].apply(self, args); + }; + newfunc.im_self = self; + newfunc.im_func = func; + newfunc.im_preargs = im_preargs; + return newfunc; + }, + + /** @id MochiKit.Base.bindMethods */ + bindMethods: function (self) { + var bind = MochiKit.Base.bind; + for (var k in self) { + var func = self[k]; + if (typeof(func) == 'function') { + self[k] = bind(func, self); + } + } + }, + + /** @id MochiKit.Base.registerComparator */ + registerComparator: function (name, check, comparator, /* optional */ override) { + MochiKit.Base.comparatorRegistry.register(name, check, comparator, override); + }, + + _primitives: {'boolean': true, 'string': true, 'number': true}, + + /** @id MochiKit.Base.compare */ + compare: function (a, b) { + if (a == b) { + return 0; + } + var aIsNull = (typeof(a) == 'undefined' || a === null); + var bIsNull = (typeof(b) == 'undefined' || b === null); + if (aIsNull && bIsNull) { + return 0; + } else if (aIsNull) { + return -1; + } else if (bIsNull) { + return 1; + } + var m = MochiKit.Base; + // bool, number, string have meaningful comparisons + var prim = m._primitives; + if (!(typeof(a) in prim && typeof(b) in prim)) { + try { + return m.comparatorRegistry.match(a, b); + } catch (e) { + if (e != m.NotFound) { + throw e; + } + } + } + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + // These types can't be compared + var repr = m.repr; + throw new TypeError(repr(a) + " and " + repr(b) + " can not be compared"); + }, + + /** @id MochiKit.Base.compareDateLike */ + compareDateLike: function (a, b) { + return MochiKit.Base.compare(a.getTime(), b.getTime()); + }, + + /** @id MochiKit.Base.compareArrayLike */ + compareArrayLike: function (a, b) { + var compare = MochiKit.Base.compare; + var count = a.length; + var rval = 0; + if (count > b.length) { + rval = 1; + count = b.length; + } else if (count < b.length) { + rval = -1; + } + for (var i = 0; i < count; i++) { + var cmp = compare(a[i], b[i]); + if (cmp) { + return cmp; + } + } + return rval; + }, + + /** @id MochiKit.Base.registerRepr */ + registerRepr: function (name, check, wrap, /* optional */override) { + MochiKit.Base.reprRegistry.register(name, check, wrap, override); + }, + + /** @id MochiKit.Base.repr */ + repr: function (o) { + if (typeof(o) == "undefined") { + return "undefined"; + } else if (o === null) { + return "null"; + } + try { + if (typeof(o.__repr__) == 'function') { + return o.__repr__(); + } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) { + return o.repr(); + } + return MochiKit.Base.reprRegistry.match(o); + } catch (e) { + if (typeof(o.NAME) == 'string' && (
+ o.toString == Function.prototype.toString ||
+ o.toString == Object.prototype.toString
+ )) {
+ return o.NAME;
+ } + } + try { + var ostring = (o + ""); + } catch (e) { + return "[" + typeof(o) + "]"; + } + if (typeof(o) == "function") { + ostring = ostring.replace(/^\s+/, "").replace(/\s+/g, " "); + ostring = ostring.replace(/,(\S)/, ", $1"); + var idx = ostring.indexOf("{"); + if (idx != -1) { + ostring = ostring.substr(0, idx) + "{...}"; + } + } + return ostring; + }, + + /** @id MochiKit.Base.reprArrayLike */ + reprArrayLike: function (o) { + var m = MochiKit.Base; + return "[" + m.map(m.repr, o).join(", ") + "]"; + }, + + /** @id MochiKit.Base.reprString */ + reprString: function (o) { + return ('"' + o.replace(/(["\\])/g, '\\$1') + '"' + ).replace(/[\f]/g, "\\f" + ).replace(/[\b]/g, "\\b" + ).replace(/[\n]/g, "\\n" + ).replace(/[\t]/g, "\\t" + ).replace(/[\v]/g, "\\v" + ).replace(/[\r]/g, "\\r"); + }, + + /** @id MochiKit.Base.reprNumber */ + reprNumber: function (o) { + return o + ""; + }, + + /** @id MochiKit.Base.registerJSON */ + registerJSON: function (name, check, wrap, /* optional */override) { + MochiKit.Base.jsonRegistry.register(name, check, wrap, override); + }, + + + /** @id MochiKit.Base.evalJSON */ + evalJSON: function () { + return eval("(" + MochiKit.Base._filterJSON(arguments[0]) + ")"); + }, + + _filterJSON: function (s) { + var m = s.match(/^\s*\/\*(.*)\*\/\s*$/); + if (m) { + return m[1]; + } + return s; + }, + + /** @id MochiKit.Base.serializeJSON */ + serializeJSON: function (o) { + var objtype = typeof(o); + if (objtype == "number" || objtype == "boolean") { + return o + ""; + } else if (o === null) { + return "null"; + } else if (objtype == "string") { + var res = ""; + for (var i = 0; i < o.length; i++) { + var c = o.charAt(i); + if (c == '\"') { + res += '\\"'; + } else if (c == '\\') { + res += '\\\\'; + } else if (c == '\b') { + res += '\\b'; + } else if (c == '\f') { + res += '\\f'; + } else if (c == '\n') { + res += '\\n'; + } else if (c == '\r') { + res += '\\r'; + } else if (c == '\t') { + res += '\\t'; + } else if (o.charCodeAt(i) <= 0x1F) { + var hex = o.charCodeAt(i).toString(16); + if (hex.length < 2) { + hex = '0' + hex; + } + res += '\\u00' + hex.toUpperCase(); + } else { + res += c; + } + } + return '"' + res + '"'; + } + // recurse + var me = arguments.callee; + // short-circuit for objects that support "json" serialization + // if they return "self" then just pass-through... + var newObj; + if (typeof(o.__json__) == "function") { + newObj = o.__json__(); + if (o !== newObj) { + return me(newObj); + } + } + if (typeof(o.json) == "function") { + newObj = o.json(); + if (o !== newObj) { + return me(newObj); + } + } + // array + if (objtype != "function" && typeof(o.length) == "number") { + var res = []; + for (var i = 0; i < o.length; i++) { + var val = me(o[i]); + if (typeof(val) != "string") { + // skip non-serializable values + continue; + } + res.push(val); + } + return "[" + res.join(", ") + "]"; + } + // look in the registry + var m = MochiKit.Base; + try { + newObj = m.jsonRegistry.match(o); + if (o !== newObj) { + return me(newObj); + } + } catch (e) { + if (e != m.NotFound) { + // something really bad happened + throw e; + } + } + // undefined is outside of the spec + if (objtype == "undefined") { + throw new TypeError("undefined can not be serialized as JSON"); + } + // it's a function with no adapter, bad + if (objtype == "function") { + return null; + } + // generic object code path + res = []; + for (var k in o) { + var useKey; + if (typeof(k) == "number") { + useKey = '"' + k + '"'; + } else if (typeof(k) == "string") { + useKey = me(k); + } else { + // skip non-string or number keys + continue; + } + val = me(o[k]); + if (typeof(val) != "string") { + // skip non-serializable values + continue; + } + res.push(useKey + ":" + val); + } + return "{" + res.join(", ") + "}"; + }, + + + /** @id MochiKit.Base.objEqual */ + objEqual: function (a, b) { + return (MochiKit.Base.compare(a, b) === 0); + }, + + /** @id MochiKit.Base.arrayEqual */ + arrayEqual: function (self, arr) { + if (self.length != arr.length) { + return false; + } + return (MochiKit.Base.compare(self, arr) === 0); + }, + + /** @id MochiKit.Base.concat */ + concat: function (/* lst... */) { + var rval = []; + var extend = MochiKit.Base.extend; + for (var i = 0; i < arguments.length; i++) { + extend(rval, arguments[i]); + } + return rval; + }, + + /** @id MochiKit.Base.keyComparator */ + keyComparator: function (key/* ... */) { + // fast-path for single key comparisons + var m = MochiKit.Base; + var compare = m.compare; + if (arguments.length == 1) { + return function (a, b) { + return compare(a[key], b[key]); + }; + } + var compareKeys = m.extend(null, arguments); + return function (a, b) { + var rval = 0; + // keep comparing until something is inequal or we run out of + // keys to compare + for (var i = 0; (rval === 0) && (i < compareKeys.length); i++) { + var key = compareKeys[i]; + rval = compare(a[key], b[key]); + } + return rval; + }; + }, + + /** @id MochiKit.Base.reverseKeyComparator */ + reverseKeyComparator: function (key) { + var comparator = MochiKit.Base.keyComparator.apply(this, arguments); + return function (a, b) { + return comparator(b, a); + }; + }, + + /** @id MochiKit.Base.partial */ + partial: function (func) { + var m = MochiKit.Base; + return m.bind.apply(this, m.extend([func, undefined], arguments, 1)); + }, + + /** @id MochiKit.Base.listMinMax */ + listMinMax: function (which, lst) { + if (lst.length === 0) { + return null; + } + var cur = lst[0]; + var compare = MochiKit.Base.compare; + for (var i = 1; i < lst.length; i++) { + var o = lst[i]; + if (compare(o, cur) == which) { + cur = o; + } + } + return cur; + }, + + /** @id MochiKit.Base.objMax */ + objMax: function (/* obj... */) { + return MochiKit.Base.listMinMax(1, arguments); + }, + + /** @id MochiKit.Base.objMin */ + objMin: function (/* obj... */) { + return MochiKit.Base.listMinMax(-1, arguments); + }, + + /** @id MochiKit.Base.findIdentical */ + findIdentical: function (lst, value, start/* = 0 */, /* optional */end) { + if (typeof(end) == "undefined" || end === null) { + end = lst.length; + } + if (typeof(start) == "undefined" || start === null) { + start = 0; + } + for (var i = start; i < end; i++) { + if (lst[i] === value) { + return i; + } + } + return -1; + }, + + /** @id MochiKit.Base.mean */ + mean: function(/* lst... */) { + /* http://www.nist.gov/dads/HTML/mean.html */ + var sum = 0; + + var m = MochiKit.Base; + var args = m.extend(null, arguments); + var count = args.length; + + while (args.length) { + var o = args.shift(); + if (o && typeof(o) == "object" && typeof(o.length) == "number") { + count += o.length - 1; + for (var i = o.length - 1; i >= 0; i--) { + sum += o[i]; + } + } else { + sum += o; + } + } + + if (count <= 0) { + throw new TypeError('mean() requires at least one argument'); + } + + return sum/count; + }, + + /** @id MochiKit.Base.median */ + median: function(/* lst... */) { + /* http://www.nist.gov/dads/HTML/median.html */ + var data = MochiKit.Base.flattenArguments(arguments); + if (data.length === 0) { + throw new TypeError('median() requires at least one argument'); + } + data.sort(compare); + if (data.length % 2 == 0) { + var upper = data.length / 2; + return (data[upper] + data[upper - 1]) / 2; + } else { + return data[(data.length - 1) / 2]; + } + }, + + /** @id MochiKit.Base.findValue */ + findValue: function (lst, value, start/* = 0 */, /* optional */end) { + if (typeof(end) == "undefined" || end === null) { + end = lst.length; + } + if (typeof(start) == "undefined" || start === null) { + start = 0; + } + var cmp = MochiKit.Base.compare; + for (var i = start; i < end; i++) { + if (cmp(lst[i], value) === 0) { + return i; + } + } + return -1; + }, + + /** @id MochiKit.Base.nodeWalk */ + nodeWalk: function (node, visitor) { + var nodes = [node]; + var extend = MochiKit.Base.extend; + while (nodes.length) { + var res = visitor(nodes.shift()); + if (res) { + extend(nodes, res); + } + } + }, + + + /** @id MochiKit.Base.nameFunctions */ + nameFunctions: function (namespace) { + var base = namespace.NAME; + if (typeof(base) == 'undefined') { + base = ''; + } else { + base = base + '.'; + } + for (var name in namespace) { + var o = namespace[name]; + if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') { + try { + o.NAME = base + name; + } catch (e) { + // pass + } + } + } + }, + + + /** @id MochiKit.Base.queryString */ + queryString: function (names, values) { + // check to see if names is a string or a DOM element, and if + // MochiKit.DOM is available. If so, drop it like it's a form + // Ugliest conditional in MochiKit? Probably! + if (typeof(MochiKit.DOM) != "undefined" && arguments.length == 1 + && (typeof(names) == "string" || ( + typeof(names.nodeType) != "undefined" && names.nodeType > 0 + )) + ) { + var kv = MochiKit.DOM.formContents(names); + names = kv[0]; + values = kv[1]; + } else if (arguments.length == 1) { + // Allow the return value of formContents to be passed directly + if (typeof(names.length) == "number" && names.length == 2) { + return arguments.callee(names[0], names[1]); + } + var o = names; + names = []; + values = []; + for (var k in o) { + var v = o[k]; + if (typeof(v) == "function") { + continue; + } else if (MochiKit.Base.isArrayLike(v)){ + for (var i = 0; i < v.length; i++) { + names.push(k); + values.push(v[i]); + } + } else { + names.push(k); + values.push(v); + } + } + } + var rval = []; + var len = Math.min(names.length, values.length); + var urlEncode = MochiKit.Base.urlEncode; + for (var i = 0; i < len; i++) { + v = values[i]; + if (typeof(v) != 'undefined' && v !== null) { + rval.push(urlEncode(names[i]) + "=" + urlEncode(v)); + } + } + return rval.join("&"); + }, + + + /** @id MochiKit.Base.parseQueryString */ + parseQueryString: function (encodedString, useArrays) { + // strip a leading '?' from the encoded string + var qstr = (encodedString.charAt(0) == "?") + ? encodedString.substring(1) + : encodedString; + var pairs = qstr.replace(/\+/g, "%20").split(/\&\;|\&\#38\;|\&|\&/); + var o = {}; + var decode; + if (typeof(decodeURIComponent) != "undefined") { + decode = decodeURIComponent; + } else { + decode = unescape; + } + if (useArrays) { + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split("="); + var name = decode(pair.shift()); + if (!name) { + continue; + } + var arr = o[name]; + if (!(arr instanceof Array)) { + arr = []; + o[name] = arr; + } + arr.push(decode(pair.join("="))); + } + } else { + for (i = 0; i < pairs.length; i++) { + pair = pairs[i].split("="); + var name = pair.shift(); + if (!name) { + continue; + } + o[decode(name)] = decode(pair.join("=")); + } + } + return o; + } +}); + +/** @id MochiKit.Base.AdapterRegistry */ +MochiKit.Base.AdapterRegistry = function () { + this.pairs = []; +}; + +MochiKit.Base.AdapterRegistry.prototype = { + /** @id MochiKit.Base.AdapterRegistry.prototype.register */ + register: function (name, check, wrap, /* optional */ override) { + if (override) { + this.pairs.unshift([name, check, wrap]); + } else { + this.pairs.push([name, check, wrap]); + } + }, + + /** @id MochiKit.Base.AdapterRegistry.prototype.match */ + match: function (/* ... */) { + for (var i = 0; i < this.pairs.length; i++) { + var pair = this.pairs[i]; + if (pair[1].apply(this, arguments)) { + return pair[2].apply(this, arguments); + } + } + throw MochiKit.Base.NotFound; + }, + + /** @id MochiKit.Base.AdapterRegistry.prototype.unregister */ + unregister: function (name) { + for (var i = 0; i < this.pairs.length; i++) { + var pair = this.pairs[i]; + if (pair[0] == name) { + this.pairs.splice(i, 1); + return true; + } + } + return false; + } +}; + + +MochiKit.Base.EXPORT = [ + "flattenArray", + "noop", + "camelize", + "counter", + "clone", + "extend", + "update", + "updatetree", + "setdefault", + "keys", + "values", + "items", + "NamedError", + "operator", + "forwardCall", + "itemgetter", + "typeMatcher", + "isCallable", + "isUndefined", + "isUndefinedOrNull", + "isNull", + "isEmpty", + "isNotEmpty", + "isArrayLike", + "isDateLike", + "xmap", + "map", + "xfilter", + "filter", + "methodcaller", + "compose", + "bind", + "bindLate", + "bindMethods", + "NotFound", + "AdapterRegistry", + "registerComparator", + "compare", + "registerRepr", + "repr", + "objEqual", + "arrayEqual", + "concat", + "keyComparator", + "reverseKeyComparator", + "partial", + "merge", + "listMinMax", + "listMax", + "listMin", + "objMax", + "objMin", + "nodeWalk", + "zip", + "urlEncode", + "queryString", + "serializeJSON", + "registerJSON", + "evalJSON", + "parseQueryString", + "findValue", + "findIdentical", + "flattenArguments", + "method", + "average", + "mean", + "median" +]; + +MochiKit.Base.EXPORT_OK = [ + "nameFunctions", + "comparatorRegistry", + "reprRegistry", + "jsonRegistry", + "compareDateLike", + "compareArrayLike", + "reprArrayLike", + "reprString", + "reprNumber" +]; + +MochiKit.Base._exportSymbols = function (globals, module) { + if (!MochiKit.__export__) { + return; + } + var all = module.EXPORT_TAGS[":all"]; + for (var i = 0; i < all.length; i++) { + globals[all[i]] = module[all[i]]; + } +}; + +MochiKit.Base.__new__ = function () { + // A singleton raised when no suitable adapter is found + var m = this; + + // convenience + /** @id MochiKit.Base.noop */ + m.noop = m.operator.identity; + + // Backwards compat + m.forward = m.forwardCall; + m.find = m.findValue; + + if (typeof(encodeURIComponent) != "undefined") { + /** @id MochiKit.Base.urlEncode */ + m.urlEncode = function (unencoded) { + return encodeURIComponent(unencoded).replace(/\'/g, '%27'); + }; + } else { + m.urlEncode = function (unencoded) { + return escape(unencoded + ).replace(/\+/g, '%2B' + ).replace(/\"/g,'%22' + ).rval.replace(/\'/g, '%27'); + }; + } + + /** @id MochiKit.Base.NamedError */ + m.NamedError = function (name) { + this.message = name; + this.name = name; + }; + m.NamedError.prototype = new Error(); + m.update(m.NamedError.prototype, { + repr: function () { + if (this.message && this.message != this.name) { + return this.name + "(" + m.repr(this.message) + ")"; + } else { + return this.name + "()"; + } + }, + toString: m.forwardCall("repr") + }); + + /** @id MochiKit.Base.NotFound */ + m.NotFound = new m.NamedError("MochiKit.Base.NotFound"); + + + /** @id MochiKit.Base.listMax */ + m.listMax = m.partial(m.listMinMax, 1); + /** @id MochiKit.Base.listMin */ + m.listMin = m.partial(m.listMinMax, -1); + + /** @id MochiKit.Base.isCallable */ + m.isCallable = m.typeMatcher('function'); + /** @id MochiKit.Base.isUndefined */ + m.isUndefined = m.typeMatcher('undefined'); + + /** @id MochiKit.Base.merge */ + m.merge = m.partial(m.update, null); + /** @id MochiKit.Base.zip */ + m.zip = m.partial(m.map, null); + + /** @id MochiKit.Base.average */ + m.average = m.mean; + + /** @id MochiKit.Base.comparatorRegistry */ + m.comparatorRegistry = new m.AdapterRegistry(); + m.registerComparator("dateLike", m.isDateLike, m.compareDateLike); + m.registerComparator("arrayLike", m.isArrayLike, m.compareArrayLike); + + /** @id MochiKit.Base.reprRegistry */ + m.reprRegistry = new m.AdapterRegistry(); + m.registerRepr("arrayLike", m.isArrayLike, m.reprArrayLike); + m.registerRepr("string", m.typeMatcher("string"), m.reprString); + m.registerRepr("numbers", m.typeMatcher("number", "boolean"), m.reprNumber); + + /** @id MochiKit.Base.jsonRegistry */ + m.jsonRegistry = new m.AdapterRegistry(); + + var all = m.concat(m.EXPORT, m.EXPORT_OK); + m.EXPORT_TAGS = { + ":common": m.concat(m.EXPORT_OK), + ":all": all + }; + + m.nameFunctions(this); + +}; + +MochiKit.Base.__new__(); + +// +// XXX: Internet Explorer blows +// +if (MochiKit.__export__) { + compare = MochiKit.Base.compare; + compose = MochiKit.Base.compose; + serializeJSON = MochiKit.Base.serializeJSON; + mean = MochiKit.Base.mean; + median = MochiKit.Base.median; +} + +MochiKit.Base._exportSymbols(this, MochiKit.Base); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Color.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Color.js new file mode 100644 index 000000000..a0d5bd8c3 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Color.js @@ -0,0 +1,863 @@ +/*** + +MochiKit.Color 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito and others. All rights Reserved. + +***/ + +MochiKit.Base._deps('Color', ['Base', 'DOM', 'Style']); + +MochiKit.Color.NAME = "MochiKit.Color"; +MochiKit.Color.VERSION = "1.4.2"; + +MochiKit.Color.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +MochiKit.Color.toString = function () { + return this.__repr__(); +}; + + +/** @id MochiKit.Color.Color */ +MochiKit.Color.Color = function (red, green, blue, alpha) { + if (typeof(alpha) == 'undefined' || alpha === null) { + alpha = 1.0; + } + this.rgb = { + r: red, + g: green, + b: blue, + a: alpha + }; +}; + + +// Prototype methods + +MochiKit.Color.Color.prototype = { + + __class__: MochiKit.Color.Color, + + /** @id MochiKit.Color.Color.prototype.colorWithAlpha */ + colorWithAlpha: function (alpha) { + var rgb = this.rgb; + var m = MochiKit.Color; + return m.Color.fromRGB(rgb.r, rgb.g, rgb.b, alpha); + }, + + /** @id MochiKit.Color.Color.prototype.colorWithHue */ + colorWithHue: function (hue) { + // get an HSL model, and set the new hue... + var hsl = this.asHSL(); + hsl.h = hue; + var m = MochiKit.Color; + // convert back to RGB... + return m.Color.fromHSL(hsl); + }, + + /** @id MochiKit.Color.Color.prototype.colorWithSaturation */ + colorWithSaturation: function (saturation) { + // get an HSL model, and set the new hue... + var hsl = this.asHSL(); + hsl.s = saturation; + var m = MochiKit.Color; + // convert back to RGB... + return m.Color.fromHSL(hsl); + }, + + /** @id MochiKit.Color.Color.prototype.colorWithLightness */ + colorWithLightness: function (lightness) { + // get an HSL model, and set the new hue... + var hsl = this.asHSL(); + hsl.l = lightness; + var m = MochiKit.Color; + // convert back to RGB... + return m.Color.fromHSL(hsl); + }, + + /** @id MochiKit.Color.Color.prototype.darkerColorWithLevel */ + darkerColorWithLevel: function (level) { + var hsl = this.asHSL(); + hsl.l = Math.max(hsl.l - level, 0); + var m = MochiKit.Color; + return m.Color.fromHSL(hsl); + }, + + /** @id MochiKit.Color.Color.prototype.lighterColorWithLevel */ + lighterColorWithLevel: function (level) { + var hsl = this.asHSL(); + hsl.l = Math.min(hsl.l + level, 1); + var m = MochiKit.Color; + return m.Color.fromHSL(hsl); + }, + + /** @id MochiKit.Color.Color.prototype.blendedColor */ + blendedColor: function (other, /* optional */ fraction) { + if (typeof(fraction) == 'undefined' || fraction === null) { + fraction = 0.5; + } + var sf = 1.0 - fraction; + var s = this.rgb; + var d = other.rgb; + var df = fraction; + return MochiKit.Color.Color.fromRGB( + (s.r * sf) + (d.r * df), + (s.g * sf) + (d.g * df), + (s.b * sf) + (d.b * df), + (s.a * sf) + (d.a * df) + ); + }, + + /** @id MochiKit.Color.Color.prototype.compareRGB */ + compareRGB: function (other) { + var a = this.asRGB(); + var b = other.asRGB(); + return MochiKit.Base.compare( + [a.r, a.g, a.b, a.a], + [b.r, b.g, b.b, b.a] + ); + }, + + /** @id MochiKit.Color.Color.prototype.isLight */ + isLight: function () { + return this.asHSL().b > 0.5; + }, + + /** @id MochiKit.Color.Color.prototype.isDark */ + isDark: function () { + return (!this.isLight()); + }, + + /** @id MochiKit.Color.Color.prototype.toHSLString */ + toHSLString: function () { + var c = this.asHSL(); + var ccc = MochiKit.Color.clampColorComponent; + var rval = this._hslString; + if (!rval) { + var mid = ( + ccc(c.h, 360).toFixed(0) + + "," + ccc(c.s, 100).toPrecision(4) + "%" + + "," + ccc(c.l, 100).toPrecision(4) + "%" + ); + var a = c.a; + if (a >= 1) { + a = 1; + rval = "hsl(" + mid + ")"; + } else { + if (a <= 0) { + a = 0; + } + rval = "hsla(" + mid + "," + a + ")"; + } + this._hslString = rval; + } + return rval; + }, + + /** @id MochiKit.Color.Color.prototype.toRGBString */ + toRGBString: function () { + var c = this.rgb; + var ccc = MochiKit.Color.clampColorComponent; + var rval = this._rgbString; + if (!rval) { + var mid = ( + ccc(c.r, 255).toFixed(0) + + "," + ccc(c.g, 255).toFixed(0) + + "," + ccc(c.b, 255).toFixed(0) + ); + if (c.a != 1) { + rval = "rgba(" + mid + "," + c.a + ")"; + } else { + rval = "rgb(" + mid + ")"; + } + this._rgbString = rval; + } + return rval; + }, + + /** @id MochiKit.Color.Color.prototype.asRGB */ + asRGB: function () { + return MochiKit.Base.clone(this.rgb); + }, + + /** @id MochiKit.Color.Color.prototype.toHexString */ + toHexString: function () { + var m = MochiKit.Color; + var c = this.rgb; + var ccc = MochiKit.Color.clampColorComponent; + var rval = this._hexString; + if (!rval) { + rval = ("#" + + m.toColorPart(ccc(c.r, 255)) + + m.toColorPart(ccc(c.g, 255)) + + m.toColorPart(ccc(c.b, 255)) + ); + this._hexString = rval; + } + return rval; + }, + + /** @id MochiKit.Color.Color.prototype.asHSV */ + asHSV: function () { + var hsv = this.hsv; + var c = this.rgb; + if (typeof(hsv) == 'undefined' || hsv === null) { + hsv = MochiKit.Color.rgbToHSV(this.rgb); + this.hsv = hsv; + } + return MochiKit.Base.clone(hsv); + }, + + /** @id MochiKit.Color.Color.prototype.asHSL */ + asHSL: function () { + var hsl = this.hsl; + var c = this.rgb; + if (typeof(hsl) == 'undefined' || hsl === null) { + hsl = MochiKit.Color.rgbToHSL(this.rgb); + this.hsl = hsl; + } + return MochiKit.Base.clone(hsl); + }, + + /** @id MochiKit.Color.Color.prototype.toString */ + toString: function () { + return this.toRGBString(); + }, + + /** @id MochiKit.Color.Color.prototype.repr */ + repr: function () { + var c = this.rgb; + var col = [c.r, c.g, c.b, c.a]; + return this.__class__.NAME + "(" + col.join(", ") + ")"; + } + +}; + +// Constructor methods + +MochiKit.Base.update(MochiKit.Color.Color, { + /** @id MochiKit.Color.Color.fromRGB */ + fromRGB: function (red, green, blue, alpha) { + // designated initializer + var Color = MochiKit.Color.Color; + if (arguments.length == 1) { + var rgb = red; + red = rgb.r; + green = rgb.g; + blue = rgb.b; + if (typeof(rgb.a) == 'undefined') { + alpha = undefined; + } else { + alpha = rgb.a; + } + } + return new Color(red, green, blue, alpha); + }, + + /** @id MochiKit.Color.Color.fromHSL */ + fromHSL: function (hue, saturation, lightness, alpha) { + var m = MochiKit.Color; + return m.Color.fromRGB(m.hslToRGB.apply(m, arguments)); + }, + + /** @id MochiKit.Color.Color.fromHSV */ + fromHSV: function (hue, saturation, value, alpha) { + var m = MochiKit.Color; + return m.Color.fromRGB(m.hsvToRGB.apply(m, arguments)); + }, + + /** @id MochiKit.Color.Color.fromName */ + fromName: function (name) { + var Color = MochiKit.Color.Color; + // Opera 9 seems to "quote" named colors(?!) + if (name.charAt(0) == '"') { + name = name.substr(1, name.length - 2); + } + var htmlColor = Color._namedColors[name.toLowerCase()]; + if (typeof(htmlColor) == 'string') { + return Color.fromHexString(htmlColor); + } else if (name == "transparent") { + return Color.transparentColor(); + } + return null; + }, + + /** @id MochiKit.Color.Color.fromString */ + fromString: function (colorString) { + var self = MochiKit.Color.Color; + var three = colorString.substr(0, 3); + if (three == "rgb") { + return self.fromRGBString(colorString); + } else if (three == "hsl") { + return self.fromHSLString(colorString); + } else if (colorString.charAt(0) == "#") { + return self.fromHexString(colorString); + } + return self.fromName(colorString); + }, + + + /** @id MochiKit.Color.Color.fromHexString */ + fromHexString: function (hexCode) { + if (hexCode.charAt(0) == '#') { + hexCode = hexCode.substring(1); + } + var components = []; + var i, hex; + if (hexCode.length == 3) { + for (i = 0; i < 3; i++) { + hex = hexCode.substr(i, 1); + components.push(parseInt(hex + hex, 16) / 255.0); + } + } else { + for (i = 0; i < 6; i += 2) { + hex = hexCode.substr(i, 2); + components.push(parseInt(hex, 16) / 255.0); + } + } + var Color = MochiKit.Color.Color; + return Color.fromRGB.apply(Color, components); + }, + + + _fromColorString: function (pre, method, scales, colorCode) { + // parses either HSL or RGB + if (colorCode.indexOf(pre) === 0) { + colorCode = colorCode.substring(colorCode.indexOf("(", 3) + 1, colorCode.length - 1); + } + var colorChunks = colorCode.split(/\s*,\s*/); + var colorFloats = []; + for (var i = 0; i < colorChunks.length; i++) { + var c = colorChunks[i]; + var val; + var three = c.substring(c.length - 3); + if (c.charAt(c.length - 1) == '%') { + val = 0.01 * parseFloat(c.substring(0, c.length - 1)); + } else if (three == "deg") { + val = parseFloat(c) / 360.0; + } else if (three == "rad") { + val = parseFloat(c) / (Math.PI * 2); + } else { + val = scales[i] * parseFloat(c); + } + colorFloats.push(val); + } + return this[method].apply(this, colorFloats); + }, + + /** @id MochiKit.Color.Color.fromComputedStyle */ + fromComputedStyle: function (elem, style) { + var d = MochiKit.DOM; + var cls = MochiKit.Color.Color; + for (elem = d.getElement(elem); elem; elem = elem.parentNode) { + var actualColor = MochiKit.Style.getStyle.apply(d, arguments); + if (!actualColor) { + continue; + } + var color = cls.fromString(actualColor); + if (!color) { + break; + } + if (color.asRGB().a > 0) { + return color; + } + } + return null; + }, + + /** @id MochiKit.Color.Color.fromBackground */ + fromBackground: function (elem) { + var cls = MochiKit.Color.Color; + return cls.fromComputedStyle( + elem, "backgroundColor", "background-color") || cls.whiteColor(); + }, + + /** @id MochiKit.Color.Color.fromText */ + fromText: function (elem) { + var cls = MochiKit.Color.Color; + return cls.fromComputedStyle( + elem, "color", "color") || cls.blackColor(); + }, + + /** @id MochiKit.Color.Color.namedColors */ + namedColors: function () { + return MochiKit.Base.clone(MochiKit.Color.Color._namedColors); + } +}); + + +// Module level functions + +MochiKit.Base.update(MochiKit.Color, { + /** @id MochiKit.Color.clampColorComponent */ + clampColorComponent: function (v, scale) { + v *= scale; + if (v < 0) { + return 0; + } else if (v > scale) { + return scale; + } else { + return v; + } + }, + + _hslValue: function (n1, n2, hue) { + if (hue > 6.0) { + hue -= 6.0; + } else if (hue < 0.0) { + hue += 6.0; + } + var val; + if (hue < 1.0) { + val = n1 + (n2 - n1) * hue; + } else if (hue < 3.0) { + val = n2; + } else if (hue < 4.0) { + val = n1 + (n2 - n1) * (4.0 - hue); + } else { + val = n1; + } + return val; + }, + + /** @id MochiKit.Color.hsvToRGB */ + hsvToRGB: function (hue, saturation, value, alpha) { + if (arguments.length == 1) { + var hsv = hue; + hue = hsv.h; + saturation = hsv.s; + value = hsv.v; + alpha = hsv.a; + } + var red; + var green; + var blue; + if (saturation === 0) { + red = value; + green = value; + blue = value; + } else { + var i = Math.floor(hue * 6); + var f = (hue * 6) - i; + var p = value * (1 - saturation); + var q = value * (1 - (saturation * f)); + var t = value * (1 - (saturation * (1 - f))); + switch (i) { + case 1: red = q; green = value; blue = p; break; + case 2: red = p; green = value; blue = t; break; + case 3: red = p; green = q; blue = value; break; + case 4: red = t; green = p; blue = value; break; + case 5: red = value; green = p; blue = q; break; + case 6: // fall through + case 0: red = value; green = t; blue = p; break; + } + } + return { + r: red, + g: green, + b: blue, + a: alpha + }; + }, + + /** @id MochiKit.Color.hslToRGB */ + hslToRGB: function (hue, saturation, lightness, alpha) { + if (arguments.length == 1) { + var hsl = hue; + hue = hsl.h; + saturation = hsl.s; + lightness = hsl.l; + alpha = hsl.a; + } + var red; + var green; + var blue; + if (saturation === 0) { + red = lightness; + green = lightness; + blue = lightness; + } else { + var m2; + if (lightness <= 0.5) { + m2 = lightness * (1.0 + saturation); + } else { + m2 = lightness + saturation - (lightness * saturation); + } + var m1 = (2.0 * lightness) - m2; + var f = MochiKit.Color._hslValue; + var h6 = hue * 6.0; + red = f(m1, m2, h6 + 2); + green = f(m1, m2, h6); + blue = f(m1, m2, h6 - 2); + } + return { + r: red, + g: green, + b: blue, + a: alpha + }; + }, + + /** @id MochiKit.Color.rgbToHSV */ + rgbToHSV: function (red, green, blue, alpha) { + if (arguments.length == 1) { + var rgb = red; + red = rgb.r; + green = rgb.g; + blue = rgb.b; + alpha = rgb.a; + } + var max = Math.max(Math.max(red, green), blue); + var min = Math.min(Math.min(red, green), blue); + var hue; + var saturation; + var value = max; + if (min == max) { + hue = 0; + saturation = 0; + } else { + var delta = (max - min); + saturation = delta / max; + + if (red == max) { + hue = (green - blue) / delta; + } else if (green == max) { + hue = 2 + ((blue - red) / delta); + } else { + hue = 4 + ((red - green) / delta); + } + hue /= 6; + if (hue < 0) { + hue += 1; + } + if (hue > 1) { + hue -= 1; + } + } + return { + h: hue, + s: saturation, + v: value, + a: alpha + }; + }, + + /** @id MochiKit.Color.rgbToHSL */ + rgbToHSL: function (red, green, blue, alpha) { + if (arguments.length == 1) { + var rgb = red; + red = rgb.r; + green = rgb.g; + blue = rgb.b; + alpha = rgb.a; + } + var max = Math.max(red, Math.max(green, blue)); + var min = Math.min(red, Math.min(green, blue)); + var hue; + var saturation; + var lightness = (max + min) / 2.0; + var delta = max - min; + if (delta === 0) { + hue = 0; + saturation = 0; + } else { + if (lightness <= 0.5) { + saturation = delta / (max + min); + } else { + saturation = delta / (2 - max - min); + } + if (red == max) { + hue = (green - blue) / delta; + } else if (green == max) { + hue = 2 + ((blue - red) / delta); + } else { + hue = 4 + ((red - green) / delta); + } + hue /= 6; + if (hue < 0) { + hue += 1; + } + if (hue > 1) { + hue -= 1; + } + + } + return { + h: hue, + s: saturation, + l: lightness, + a: alpha + }; + }, + + /** @id MochiKit.Color.toColorPart */ + toColorPart: function (num) { + num = Math.round(num); + var digits = num.toString(16); + if (num < 16) { + return '0' + digits; + } + return digits; + }, + + __new__: function () { + var m = MochiKit.Base; + /** @id MochiKit.Color.fromRGBString */ + this.Color.fromRGBString = m.bind( + this.Color._fromColorString, this.Color, "rgb", "fromRGB", + [1.0/255.0, 1.0/255.0, 1.0/255.0, 1] + ); + /** @id MochiKit.Color.fromHSLString */ + this.Color.fromHSLString = m.bind( + this.Color._fromColorString, this.Color, "hsl", "fromHSL", + [1.0/360.0, 0.01, 0.01, 1] + ); + + var third = 1.0 / 3.0; + /** @id MochiKit.Color.colors */ + var colors = { + // NSColor colors plus transparent + /** @id MochiKit.Color.blackColor */ + black: [0, 0, 0], + /** @id MochiKit.Color.blueColor */ + blue: [0, 0, 1], + /** @id MochiKit.Color.brownColor */ + brown: [0.6, 0.4, 0.2], + /** @id MochiKit.Color.cyanColor */ + cyan: [0, 1, 1], + /** @id MochiKit.Color.darkGrayColor */ + darkGray: [third, third, third], + /** @id MochiKit.Color.grayColor */ + gray: [0.5, 0.5, 0.5], + /** @id MochiKit.Color.greenColor */ + green: [0, 1, 0], + /** @id MochiKit.Color.lightGrayColor */ + lightGray: [2 * third, 2 * third, 2 * third], + /** @id MochiKit.Color.magentaColor */ + magenta: [1, 0, 1], + /** @id MochiKit.Color.orangeColor */ + orange: [1, 0.5, 0], + /** @id MochiKit.Color.purpleColor */ + purple: [0.5, 0, 0.5], + /** @id MochiKit.Color.redColor */ + red: [1, 0, 0], + /** @id MochiKit.Color.transparentColor */ + transparent: [0, 0, 0, 0], + /** @id MochiKit.Color.whiteColor */ + white: [1, 1, 1], + /** @id MochiKit.Color.yellowColor */ + yellow: [1, 1, 0] + }; + + var makeColor = function (name, r, g, b, a) { + var rval = this.fromRGB(r, g, b, a); + this[name] = function () { return rval; }; + return rval; + }; + + for (var k in colors) { + var name = k + "Color"; + var bindArgs = m.concat( + [makeColor, this.Color, name], + colors[k] + ); + this.Color[name] = m.bind.apply(null, bindArgs); + } + + var isColor = function () { + for (var i = 0; i < arguments.length; i++) { + if (!(arguments[i] instanceof MochiKit.Color.Color)) { + return false; + } + } + return true; + }; + + var compareColor = function (a, b) { + return a.compareRGB(b); + }; + + m.nameFunctions(this); + + m.registerComparator(this.Color.NAME, isColor, compareColor); + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + } +}); + +MochiKit.Color.EXPORT = [ + "Color" +]; + +MochiKit.Color.EXPORT_OK = [ + "clampColorComponent", + "rgbToHSL", + "hslToRGB", + "rgbToHSV", + "hsvToRGB", + "toColorPart" +]; + +MochiKit.Color.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Color); + +// Full table of css3 X11 colors <http://www.w3.org/TR/css3-color/#X11COLORS> + +MochiKit.Color.Color._namedColors = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DOM.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DOM.js new file mode 100644 index 000000000..b43c7baa4 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DOM.js @@ -0,0 +1,1256 @@ +/*** + +MochiKit.DOM 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('DOM', ['Base']); + +MochiKit.DOM.NAME = "MochiKit.DOM"; +MochiKit.DOM.VERSION = "1.4.2"; +MochiKit.DOM.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; +MochiKit.DOM.toString = function () { + return this.__repr__(); +}; + +MochiKit.DOM.EXPORT = [ + "removeEmptyTextNodes", + "formContents", + "currentWindow", + "currentDocument", + "withWindow", + "withDocument", + "registerDOMConverter", + "coerceToDOM", + "createDOM", + "createDOMFunc", + "isChildNode", + "getNodeAttribute", + "removeNodeAttribute", + "setNodeAttribute", + "updateNodeAttributes", + "appendChildNodes", + "insertSiblingNodesAfter", + "insertSiblingNodesBefore", + "replaceChildNodes", + "removeElement", + "swapDOM", + "BUTTON", + "TT", + "PRE", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "BR", + "CANVAS", + "HR", + "LABEL", + "TEXTAREA", + "FORM", + "STRONG", + "SELECT", + "OPTION", + "OPTGROUP", + "LEGEND", + "FIELDSET", + "P", + "UL", + "OL", + "LI", + "DL", + "DT", + "DD", + "TD", + "TR", + "THEAD", + "TBODY", + "TFOOT", + "TABLE", + "TH", + "INPUT", + "SPAN", + "A", + "DIV", + "IMG", + "getElement", + "$", + "getElementsByTagAndClassName", + "addToCallStack", + "addLoadEvent", + "focusOnLoad", + "setElementClass", + "toggleElementClass", + "addElementClass", + "removeElementClass", + "swapElementClass", + "hasElementClass", + "computedStyle", // deprecated in 1.4 + "escapeHTML", + "toHTML", + "emitHTML", + "scrapeText", + "getFirstParentByTagAndClassName", + "getFirstElementByTagAndClassName" +]; + +MochiKit.DOM.EXPORT_OK = [ + "domConverters" +]; + +MochiKit.DOM.DEPRECATED = [ + /** @id MochiKit.DOM.computedStyle */ + ['computedStyle', 'MochiKit.Style.getStyle', '1.4'], + /** @id MochiKit.DOM.elementDimensions */ + ['elementDimensions', 'MochiKit.Style.getElementDimensions', '1.4'], + /** @id MochiKit.DOM.elementPosition */ + ['elementPosition', 'MochiKit.Style.getElementPosition', '1.4'], + /** @id MochiKit.DOM.getViewportDimensions */ + ['getViewportDimensions', 'MochiKit.Style.getViewportDimensions', '1.4'], + /** @id MochiKit.DOM.hideElement */ + ['hideElement', 'MochiKit.Style.hideElement', '1.4'], + /** @id MochiKit.DOM.makeClipping */ + ['makeClipping', 'MochiKit.Style.makeClipping', '1.4.1'], + /** @id MochiKit.DOM.makePositioned */ + ['makePositioned', 'MochiKit.Style.makePositioned', '1.4.1'], + /** @id MochiKit.DOM.setElementDimensions */ + ['setElementDimensions', 'MochiKit.Style.setElementDimensions', '1.4'], + /** @id MochiKit.DOM.setElementPosition */ + ['setElementPosition', 'MochiKit.Style.setElementPosition', '1.4'], + /** @id MochiKit.DOM.setDisplayForElement */ + ['setDisplayForElement', 'MochiKit.Style.setDisplayForElement', '1.4'], + /** @id MochiKit.DOM.setOpacity */ + ['setOpacity', 'MochiKit.Style.setOpacity', '1.4'], + /** @id MochiKit.DOM.showElement */ + ['showElement', 'MochiKit.Style.showElement', '1.4'], + /** @id MochiKit.DOM.undoClipping */ + ['undoClipping', 'MochiKit.Style.undoClipping', '1.4.1'], + /** @id MochiKit.DOM.undoPositioned */ + ['undoPositioned', 'MochiKit.Style.undoPositioned', '1.4.1'], + /** @id MochiKit.DOM.Coordinates */ + ['Coordinates', 'MochiKit.Style.Coordinates', '1.4'], // FIXME: broken + /** @id MochiKit.DOM.Dimensions */ + ['Dimensions', 'MochiKit.Style.Dimensions', '1.4'] // FIXME: broken +]; + +MochiKit.Base.update(MochiKit.DOM, { + + /** @id MochiKit.DOM.currentWindow */ + currentWindow: function () { + return MochiKit.DOM._window; + }, + + /** @id MochiKit.DOM.currentDocument */ + currentDocument: function () { + return MochiKit.DOM._document; + }, + + /** @id MochiKit.DOM.withWindow */ + withWindow: function (win, func) { + var self = MochiKit.DOM; + var oldDoc = self._document; + var oldWin = self._window; + var rval; + try { + self._window = win; + self._document = win.document; + rval = func(); + } catch (e) { + self._window = oldWin; + self._document = oldDoc; + throw e; + } + self._window = oldWin; + self._document = oldDoc; + return rval; + }, + + /** @id MochiKit.DOM.formContents */ + formContents: function (elem/* = document.body */) { + var names = []; + var values = []; + var m = MochiKit.Base; + var self = MochiKit.DOM; + if (typeof(elem) == "undefined" || elem === null) { + elem = self._document.body; + } else { + elem = self.getElement(elem); + } + m.nodeWalk(elem, function (elem) { + var name = elem.name; + if (m.isNotEmpty(name)) { + var tagName = elem.tagName.toUpperCase(); + if (tagName === "INPUT" + && (elem.type == "radio" || elem.type == "checkbox") + && !elem.checked + ) { + return null; + } + if (tagName === "SELECT") { + if (elem.type == "select-one") { + if (elem.selectedIndex >= 0) { + var opt = elem.options[elem.selectedIndex]; + var v = opt.value; + if (!v) { + var h = opt.outerHTML; + // internet explorer sure does suck. + if (h && !h.match(/^[^>]+\svalue\s*=/i)) { + v = opt.text; + } + } + names.push(name); + values.push(v); + return null; + } + // no form elements? + names.push(name); + values.push(""); + return null; + } else { + var opts = elem.options; + if (!opts.length) { + names.push(name); + values.push(""); + return null; + } + for (var i = 0; i < opts.length; i++) { + var opt = opts[i]; + if (!opt.selected) { + continue; + } + var v = opt.value; + if (!v) { + var h = opt.outerHTML; + // internet explorer sure does suck. + if (h && !h.match(/^[^>]+\svalue\s*=/i)) { + v = opt.text; + } + } + names.push(name); + values.push(v); + } + return null; + } + } + if (tagName === "FORM" || tagName === "P" || tagName === "SPAN" + || tagName === "DIV" + ) { + return elem.childNodes; + } + names.push(name); + values.push(elem.value || ''); + return null; + } + return elem.childNodes; + }); + return [names, values]; + }, + + /** @id MochiKit.DOM.withDocument */ + withDocument: function (doc, func) { + var self = MochiKit.DOM; + var oldDoc = self._document; + var rval; + try { + self._document = doc; + rval = func(); + } catch (e) { + self._document = oldDoc; + throw e; + } + self._document = oldDoc; + return rval; + }, + + /** @id MochiKit.DOM.registerDOMConverter */ + registerDOMConverter: function (name, check, wrap, /* optional */override) { + MochiKit.DOM.domConverters.register(name, check, wrap, override); + }, + + /** @id MochiKit.DOM.coerceToDOM */ + coerceToDOM: function (node, ctx) { + var m = MochiKit.Base; + var im = MochiKit.Iter; + var self = MochiKit.DOM; + if (im) { + var iter = im.iter; + var repeat = im.repeat; + } + var map = m.map; + var domConverters = self.domConverters; + var coerceToDOM = arguments.callee; + var NotFound = m.NotFound; + while (true) { + if (typeof(node) == 'undefined' || node === null) { + return null; + } + // this is a safari childNodes object, avoiding crashes w/ attr + // lookup + if (typeof(node) == "function" && + typeof(node.length) == "number" && + !(node instanceof Function)) { + node = im ? im.list(node) : m.extend(null, node); + } + if (typeof(node.nodeType) != 'undefined' && node.nodeType > 0) { + return node; + } + if (typeof(node) == 'number' || typeof(node) == 'boolean') { + node = node.toString(); + // FALL THROUGH + } + if (typeof(node) == 'string') { + return self._document.createTextNode(node); + } + if (typeof(node.__dom__) == 'function') { + node = node.__dom__(ctx); + continue; + } + if (typeof(node.dom) == 'function') { + node = node.dom(ctx); + continue; + } + if (typeof(node) == 'function') { + node = node.apply(ctx, [ctx]); + continue; + } + + if (im) { + // iterable + var iterNodes = null; + try { + iterNodes = iter(node); + } catch (e) { + // pass + } + if (iterNodes) { + return map(coerceToDOM, iterNodes, repeat(ctx)); + } + } else if (m.isArrayLike(node)) { + var func = function (n) { return coerceToDOM(n, ctx); }; + return map(func, node); + } + + // adapter + try { + node = domConverters.match(node, ctx); + continue; + } catch (e) { + if (e != NotFound) { + throw e; + } + } + + // fallback + return self._document.createTextNode(node.toString()); + } + // mozilla warnings aren't too bright + return undefined; + }, + + /** @id MochiKit.DOM.isChildNode */ + isChildNode: function (node, maybeparent) { + var self = MochiKit.DOM; + if (typeof(node) == "string") { + node = self.getElement(node); + } + if (typeof(maybeparent) == "string") { + maybeparent = self.getElement(maybeparent); + } + if (typeof(node) == 'undefined' || node === null) { + return false; + } + while (node != null && node !== self._document) { + if (node === maybeparent) { + return true; + } + node = node.parentNode; + } + return false; + }, + + /** @id MochiKit.DOM.setNodeAttribute */ + setNodeAttribute: function (node, attr, value) { + var o = {}; + o[attr] = value; + try { + return MochiKit.DOM.updateNodeAttributes(node, o); + } catch (e) { + // pass + } + return null; + }, + + /** @id MochiKit.DOM.getNodeAttribute */ + getNodeAttribute: function (node, attr) { + var self = MochiKit.DOM; + var rename = self.attributeArray.renames[attr]; + var ignoreValue = self.attributeArray.ignoreAttr[attr]; + node = self.getElement(node); + try { + if (rename) { + return node[rename]; + } + var value = node.getAttribute(attr); + if (value != ignoreValue) { + return value; + } + } catch (e) { + // pass + } + return null; + }, + + /** @id MochiKit.DOM.removeNodeAttribute */ + removeNodeAttribute: function (node, attr) { + var self = MochiKit.DOM; + var rename = self.attributeArray.renames[attr]; + node = self.getElement(node); + try { + if (rename) { + return node[rename]; + } + return node.removeAttribute(attr); + } catch (e) { + // pass + } + return null; + }, + + /** @id MochiKit.DOM.updateNodeAttributes */ + updateNodeAttributes: function (node, attrs) { + var elem = node; + var self = MochiKit.DOM; + if (typeof(node) == 'string') { + elem = self.getElement(node); + } + if (attrs) { + var updatetree = MochiKit.Base.updatetree; + if (self.attributeArray.compliant) { + // not IE, good. + for (var k in attrs) { + var v = attrs[k]; + if (typeof(v) == 'object' && typeof(elem[k]) == 'object') { + if (k == "style" && MochiKit.Style) { + MochiKit.Style.setStyle(elem, v); + } else { + updatetree(elem[k], v); + } + } else if (k.substring(0, 2) == "on") { + if (typeof(v) == "string") { + v = new Function(v); + } + elem[k] = v; + } else { + elem.setAttribute(k, v); + } + if (typeof(elem[k]) == "string" && elem[k] != v) { + // Also set property for weird attributes (see #302) + elem[k] = v; + } + } + } else { + // IE is insane in the membrane + var renames = self.attributeArray.renames; + for (var k in attrs) { + v = attrs[k]; + var renamed = renames[k]; + if (k == "style" && typeof(v) == "string") { + elem.style.cssText = v; + } else if (typeof(renamed) == "string") { + elem[renamed] = v; + } else if (typeof(elem[k]) == 'object' + && typeof(v) == 'object') { + if (k == "style" && MochiKit.Style) { + MochiKit.Style.setStyle(elem, v); + } else { + updatetree(elem[k], v); + } + } else if (k.substring(0, 2) == "on") { + if (typeof(v) == "string") { + v = new Function(v); + } + elem[k] = v; + } else { + elem.setAttribute(k, v); + } + if (typeof(elem[k]) == "string" && elem[k] != v) { + // Also set property for weird attributes (see #302) + elem[k] = v; + } + } + } + } + return elem; + }, + + /** @id MochiKit.DOM.appendChildNodes */ + appendChildNodes: function (node/*, nodes...*/) { + var elem = node; + var self = MochiKit.DOM; + if (typeof(node) == 'string') { + elem = self.getElement(node); + } + var nodeStack = [ + self.coerceToDOM( + MochiKit.Base.extend(null, arguments, 1), + elem + ) + ]; + var concat = MochiKit.Base.concat; + while (nodeStack.length) { + var n = nodeStack.shift(); + if (typeof(n) == 'undefined' || n === null) { + // pass + } else if (typeof(n.nodeType) == 'number') { + elem.appendChild(n); + } else { + nodeStack = concat(n, nodeStack); + } + } + return elem; + }, + + + /** @id MochiKit.DOM.insertSiblingNodesBefore */ + insertSiblingNodesBefore: function (node/*, nodes...*/) { + var elem = node; + var self = MochiKit.DOM; + if (typeof(node) == 'string') { + elem = self.getElement(node); + } + var nodeStack = [ + self.coerceToDOM( + MochiKit.Base.extend(null, arguments, 1), + elem + ) + ]; + var parentnode = elem.parentNode; + var concat = MochiKit.Base.concat; + while (nodeStack.length) { + var n = nodeStack.shift(); + if (typeof(n) == 'undefined' || n === null) { + // pass + } else if (typeof(n.nodeType) == 'number') { + parentnode.insertBefore(n, elem); + } else { + nodeStack = concat(n, nodeStack); + } + } + return parentnode; + }, + + /** @id MochiKit.DOM.insertSiblingNodesAfter */ + insertSiblingNodesAfter: function (node/*, nodes...*/) { + var elem = node; + var self = MochiKit.DOM; + + if (typeof(node) == 'string') { + elem = self.getElement(node); + } + var nodeStack = [ + self.coerceToDOM( + MochiKit.Base.extend(null, arguments, 1), + elem + ) + ]; + + if (elem.nextSibling) { + return self.insertSiblingNodesBefore(elem.nextSibling, nodeStack); + } + else { + return self.appendChildNodes(elem.parentNode, nodeStack); + } + }, + + /** @id MochiKit.DOM.replaceChildNodes */ + replaceChildNodes: function (node/*, nodes...*/) { + var elem = node; + var self = MochiKit.DOM; + if (typeof(node) == 'string') { + elem = self.getElement(node); + arguments[0] = elem; + } + var child; + while ((child = elem.firstChild)) { + elem.removeChild(child); + } + if (arguments.length < 2) { + return elem; + } else { + return self.appendChildNodes.apply(this, arguments); + } + }, + + /** @id MochiKit.DOM.createDOM */ + createDOM: function (name, attrs/*, nodes... */) { + var elem; + var self = MochiKit.DOM; + var m = MochiKit.Base; + if (typeof(attrs) == "string" || typeof(attrs) == "number") { + var args = m.extend([name, null], arguments, 1); + return arguments.callee.apply(this, args); + } + if (typeof(name) == 'string') { + // Internet Explorer is dumb + var xhtml = self._xhtml; + if (attrs && !self.attributeArray.compliant) { + // http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp + var contents = ""; + if ('name' in attrs) { + contents += ' name="' + self.escapeHTML(attrs.name) + '"'; + } + if (name == 'input' && 'type' in attrs) { + contents += ' type="' + self.escapeHTML(attrs.type) + '"'; + } + if (contents) { + name = "<" + name + contents + ">"; + xhtml = false; + } + } + var d = self._document; + if (xhtml && d === document) { + elem = d.createElementNS("http://www.w3.org/1999/xhtml", name); + } else { + elem = d.createElement(name); + } + } else { + elem = name; + } + if (attrs) { + self.updateNodeAttributes(elem, attrs); + } + if (arguments.length <= 2) { + return elem; + } else { + var args = m.extend([elem], arguments, 2); + return self.appendChildNodes.apply(this, args); + } + }, + + /** @id MochiKit.DOM.createDOMFunc */ + createDOMFunc: function (/* tag, attrs, *nodes */) { + var m = MochiKit.Base; + return m.partial.apply( + this, + m.extend([MochiKit.DOM.createDOM], arguments) + ); + }, + + /** @id MochiKit.DOM.removeElement */ + removeElement: function (elem) { + var self = MochiKit.DOM; + var e = self.coerceToDOM(self.getElement(elem)); + e.parentNode.removeChild(e); + return e; + }, + + /** @id MochiKit.DOM.swapDOM */ + swapDOM: function (dest, src) { + var self = MochiKit.DOM; + dest = self.getElement(dest); + var parent = dest.parentNode; + if (src) { + src = self.coerceToDOM(self.getElement(src), parent); + parent.replaceChild(src, dest); + } else { + parent.removeChild(dest); + } + return src; + }, + + /** @id MochiKit.DOM.getElement */ + getElement: function (id) { + var self = MochiKit.DOM; + if (arguments.length == 1) { + return ((typeof(id) == "string") ? + self._document.getElementById(id) : id); + } else { + return MochiKit.Base.map(self.getElement, arguments); + } + }, + + /** @id MochiKit.DOM.getElementsByTagAndClassName */ + getElementsByTagAndClassName: function (tagName, className, + /* optional */parent) { + var self = MochiKit.DOM; + if (typeof(tagName) == 'undefined' || tagName === null) { + tagName = '*'; + } + if (typeof(parent) == 'undefined' || parent === null) { + parent = self._document; + } + parent = self.getElement(parent); + if (parent == null) { + return []; + } + var children = (parent.getElementsByTagName(tagName) + || self._document.all); + if (typeof(className) == 'undefined' || className === null) { + return MochiKit.Base.extend(null, children); + } + + var elements = []; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var cls = child.className; + if (typeof(cls) != "string") { + cls = child.getAttribute("class"); + } + if (typeof(cls) == "string") { + var classNames = cls.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + elements.push(child); + break; + } + } + } + } + + return elements; + }, + + _newCallStack: function (path, once) { + var rval = function () { + var callStack = arguments.callee.callStack; + for (var i = 0; i < callStack.length; i++) { + if (callStack[i].apply(this, arguments) === false) { + break; + } + } + if (once) { + try { + this[path] = null; + } catch (e) { + // pass + } + } + }; + rval.callStack = []; + return rval; + }, + + /** @id MochiKit.DOM.addToCallStack */ + addToCallStack: function (target, path, func, once) { + var self = MochiKit.DOM; + var existing = target[path]; + var regfunc = existing; + if (!(typeof(existing) == 'function' + && typeof(existing.callStack) == "object" + && existing.callStack !== null)) { + regfunc = self._newCallStack(path, once); + if (typeof(existing) == 'function') { + regfunc.callStack.push(existing); + } + target[path] = regfunc; + } + regfunc.callStack.push(func); + }, + + /** @id MochiKit.DOM.addLoadEvent */ + addLoadEvent: function (func) { + var self = MochiKit.DOM; + self.addToCallStack(self._window, "onload", func, true); + + }, + + /** @id MochiKit.DOM.focusOnLoad */ + focusOnLoad: function (element) { + var self = MochiKit.DOM; + self.addLoadEvent(function () { + element = self.getElement(element); + if (element) { + element.focus(); + } + }); + }, + + /** @id MochiKit.DOM.setElementClass */ + setElementClass: function (element, className) { + var self = MochiKit.DOM; + var obj = self.getElement(element); + if (self.attributeArray.compliant) { + obj.setAttribute("class", className); + } else { + obj.setAttribute("className", className); + } + }, + + /** @id MochiKit.DOM.toggleElementClass */ + toggleElementClass: function (className/*, element... */) { + var self = MochiKit.DOM; + for (var i = 1; i < arguments.length; i++) { + var obj = self.getElement(arguments[i]); + if (!self.addElementClass(obj, className)) { + self.removeElementClass(obj, className); + } + } + }, + + /** @id MochiKit.DOM.addElementClass */ + addElementClass: function (element, className) { + var self = MochiKit.DOM; + var obj = self.getElement(element); + var cls = obj.className; + if (typeof(cls) != "string") { + cls = obj.getAttribute("class"); + } + // trivial case, no className yet + if (typeof(cls) != "string" || cls.length === 0) { + self.setElementClass(obj, className); + return true; + } + // the other trivial case, already set as the only class + if (cls == className) { + return false; + } + var classes = cls.split(" "); + for (var i = 0; i < classes.length; i++) { + // already present + if (classes[i] == className) { + return false; + } + } + // append class + self.setElementClass(obj, cls + " " + className); + return true; + }, + + /** @id MochiKit.DOM.removeElementClass */ + removeElementClass: function (element, className) { + var self = MochiKit.DOM; + var obj = self.getElement(element); + var cls = obj.className; + if (typeof(cls) != "string") { + cls = obj.getAttribute("class"); + } + // trivial case, no className yet + if (typeof(cls) != "string" || cls.length === 0) { + return false; + } + // other trivial case, set only to className + if (cls == className) { + self.setElementClass(obj, ""); + return true; + } + var classes = cls.split(" "); + for (var i = 0; i < classes.length; i++) { + // already present + if (classes[i] == className) { + // only check sane case where the class is used once + classes.splice(i, 1); + self.setElementClass(obj, classes.join(" ")); + return true; + } + } + // not found + return false; + }, + + /** @id MochiKit.DOM.swapElementClass */ + swapElementClass: function (element, fromClass, toClass) { + var obj = MochiKit.DOM.getElement(element); + var res = MochiKit.DOM.removeElementClass(obj, fromClass); + if (res) { + MochiKit.DOM.addElementClass(obj, toClass); + } + return res; + }, + + /** @id MochiKit.DOM.hasElementClass */ + hasElementClass: function (element, className/*...*/) { + var obj = MochiKit.DOM.getElement(element); + if (obj == null) { + return false; + } + var cls = obj.className; + if (typeof(cls) != "string") { + cls = obj.getAttribute("class"); + } + if (typeof(cls) != "string") { + return false; + } + var classes = cls.split(" "); + for (var i = 1; i < arguments.length; i++) { + var good = false; + for (var j = 0; j < classes.length; j++) { + if (classes[j] == arguments[i]) { + good = true; + break; + } + } + if (!good) { + return false; + } + } + return true; + }, + + /** @id MochiKit.DOM.escapeHTML */ + escapeHTML: function (s) { + return s.replace(/&/g, "&" + ).replace(/"/g, """ + ).replace(/</g, "<" + ).replace(/>/g, ">"); + }, + + /** @id MochiKit.DOM.toHTML */ + toHTML: function (dom) { + return MochiKit.DOM.emitHTML(dom).join(""); + }, + + /** @id MochiKit.DOM.emitHTML */ + emitHTML: function (dom, /* optional */lst) { + if (typeof(lst) == 'undefined' || lst === null) { + lst = []; + } + // queue is the call stack, we're doing this non-recursively + var queue = [dom]; + var self = MochiKit.DOM; + var escapeHTML = self.escapeHTML; + var attributeArray = self.attributeArray; + while (queue.length) { + dom = queue.pop(); + if (typeof(dom) == 'string') { + lst.push(dom); + } else if (dom.nodeType == 1) { + // we're not using higher order stuff here + // because safari has heisenbugs.. argh. + // + // I think it might have something to do with + // garbage collection and function calls. + lst.push('<' + dom.tagName.toLowerCase()); + var attributes = []; + var domAttr = attributeArray(dom); + for (var i = 0; i < domAttr.length; i++) { + var a = domAttr[i]; + attributes.push([ + " ", + a.name, + '="', + escapeHTML(a.value), + '"' + ]); + } + attributes.sort(); + for (i = 0; i < attributes.length; i++) { + var attrs = attributes[i]; + for (var j = 0; j < attrs.length; j++) { + lst.push(attrs[j]); + } + } + if (dom.hasChildNodes()) { + lst.push(">"); + // queue is the FILO call stack, so we put the close tag + // on first + queue.push("</" + dom.tagName.toLowerCase() + ">"); + var cnodes = dom.childNodes; + for (i = cnodes.length - 1; i >= 0; i--) { + queue.push(cnodes[i]); + } + } else { + lst.push('/>'); + } + } else if (dom.nodeType == 3) { + lst.push(escapeHTML(dom.nodeValue)); + } + } + return lst; + }, + + /** @id MochiKit.DOM.scrapeText */ + scrapeText: function (node, /* optional */asArray) { + var rval = []; + (function (node) { + var cn = node.childNodes; + if (cn) { + for (var i = 0; i < cn.length; i++) { + arguments.callee.call(this, cn[i]); + } + } + var nodeValue = node.nodeValue; + if (typeof(nodeValue) == 'string') { + rval.push(nodeValue); + } + })(MochiKit.DOM.getElement(node)); + if (asArray) { + return rval; + } else { + return rval.join(""); + } + }, + + /** @id MochiKit.DOM.removeEmptyTextNodes */ + removeEmptyTextNodes: function (element) { + element = MochiKit.DOM.getElement(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) { + node.parentNode.removeChild(node); + } + } + }, + + /** @id MochiKit.DOM.getFirstElementByTagAndClassName */ + getFirstElementByTagAndClassName: function (tagName, className, + /* optional */parent) { + var self = MochiKit.DOM; + if (typeof(tagName) == 'undefined' || tagName === null) { + tagName = '*'; + } + if (typeof(parent) == 'undefined' || parent === null) { + parent = self._document; + } + parent = self.getElement(parent); + if (parent == null) { + return null; + } + var children = (parent.getElementsByTagName(tagName) + || self._document.all); + if (children.length <= 0) { + return null; + } else if (typeof(className) == 'undefined' || className === null) { + return children[0]; + } + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var cls = child.className; + if (typeof(cls) != "string") { + cls = child.getAttribute("class"); + } + if (typeof(cls) == "string") { + var classNames = cls.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + return child; + } + } + } + } + return null; + }, + + /** @id MochiKit.DOM.getFirstParentByTagAndClassName */ + getFirstParentByTagAndClassName: function (elem, tagName, className) { + var self = MochiKit.DOM; + elem = self.getElement(elem); + if (typeof(tagName) == 'undefined' || tagName === null) { + tagName = '*'; + } else { + tagName = tagName.toUpperCase(); + } + if (typeof(className) == 'undefined' || className === null) { + className = null; + } + if (elem) { + elem = elem.parentNode; + } + while (elem && elem.tagName) { + var curTagName = elem.tagName.toUpperCase(); + if ((tagName === '*' || tagName == curTagName) && + (className === null || self.hasElementClass(elem, className))) { + return elem; + } + elem = elem.parentNode; + } + return null; + }, + + __new__: function (win) { + + var m = MochiKit.Base; + if (typeof(document) != "undefined") { + this._document = document; + var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ this._xhtml = (document.documentElement &&
+ document.createElementNS && + document.documentElement.namespaceURI === kXULNSURI);
+ } else if (MochiKit.MockDOM) { + this._document = MochiKit.MockDOM.document; + } + this._window = win; + + this.domConverters = new m.AdapterRegistry(); + + var __tmpElement = this._document.createElement("span"); + var attributeArray; + if (__tmpElement && __tmpElement.attributes && + __tmpElement.attributes.length > 0) { + // for braindead browsers (IE) that insert extra junk + var filter = m.filter; + attributeArray = function (node) { + /*** + + Return an array of attributes for a given node, + filtering out attributes that don't belong for + that are inserted by "Certain Browsers". + + ***/ + return filter(attributeArray.ignoreAttrFilter, node.attributes); + }; + attributeArray.ignoreAttr = {}; + var attrs = __tmpElement.attributes; + var ignoreAttr = attributeArray.ignoreAttr; + for (var i = 0; i < attrs.length; i++) { + var a = attrs[i]; + ignoreAttr[a.name] = a.value; + } + attributeArray.ignoreAttrFilter = function (a) { + return (attributeArray.ignoreAttr[a.name] != a.value); + }; + attributeArray.compliant = false; + attributeArray.renames = { + "class": "className", + "checked": "defaultChecked", + "usemap": "useMap", + "for": "htmlFor", + "readonly": "readOnly", + "colspan": "colSpan", + "bgcolor": "bgColor", + "cellspacing": "cellSpacing", + "cellpadding": "cellPadding" + }; + } else { + attributeArray = function (node) { + return node.attributes; + }; + attributeArray.compliant = true; + attributeArray.ignoreAttr = {}; + attributeArray.renames = {}; + } + this.attributeArray = attributeArray; + + // FIXME: this really belongs in Base, and could probably be cleaner + var _deprecated = function(fromModule, arr) { + var fromName = arr[0]; + var toName = arr[1]; + var toModule = toName.split('.')[1]; + var str = ''; + + str += 'if (!MochiKit.' + toModule + ') { throw new Error("'; + str += 'This function has been deprecated and depends on MochiKit.'; + str += toModule + '.");}'; + str += 'return ' + toName + '.apply(this, arguments);'; + MochiKit[fromModule][fromName] = new Function(str); + } + for (var i = 0; i < MochiKit.DOM.DEPRECATED.length; i++) { + _deprecated('DOM', MochiKit.DOM.DEPRECATED[i]); + } + + // shorthand for createDOM syntax + var createDOMFunc = this.createDOMFunc; + /** @id MochiKit.DOM.UL */ + this.UL = createDOMFunc("ul"); + /** @id MochiKit.DOM.OL */ + this.OL = createDOMFunc("ol"); + /** @id MochiKit.DOM.LI */ + this.LI = createDOMFunc("li"); + /** @id MochiKit.DOM.DL */ + this.DL = createDOMFunc("dl"); + /** @id MochiKit.DOM.DT */ + this.DT = createDOMFunc("dt"); + /** @id MochiKit.DOM.DD */ + this.DD = createDOMFunc("dd"); + /** @id MochiKit.DOM.TD */ + this.TD = createDOMFunc("td"); + /** @id MochiKit.DOM.TR */ + this.TR = createDOMFunc("tr"); + /** @id MochiKit.DOM.TBODY */ + this.TBODY = createDOMFunc("tbody"); + /** @id MochiKit.DOM.THEAD */ + this.THEAD = createDOMFunc("thead"); + /** @id MochiKit.DOM.TFOOT */ + this.TFOOT = createDOMFunc("tfoot"); + /** @id MochiKit.DOM.TABLE */ + this.TABLE = createDOMFunc("table"); + /** @id MochiKit.DOM.TH */ + this.TH = createDOMFunc("th"); + /** @id MochiKit.DOM.INPUT */ + this.INPUT = createDOMFunc("input"); + /** @id MochiKit.DOM.SPAN */ + this.SPAN = createDOMFunc("span"); + /** @id MochiKit.DOM.A */ + this.A = createDOMFunc("a"); + /** @id MochiKit.DOM.DIV */ + this.DIV = createDOMFunc("div"); + /** @id MochiKit.DOM.IMG */ + this.IMG = createDOMFunc("img"); + /** @id MochiKit.DOM.BUTTON */ + this.BUTTON = createDOMFunc("button"); + /** @id MochiKit.DOM.TT */ + this.TT = createDOMFunc("tt"); + /** @id MochiKit.DOM.PRE */ + this.PRE = createDOMFunc("pre"); + /** @id MochiKit.DOM.H1 */ + this.H1 = createDOMFunc("h1"); + /** @id MochiKit.DOM.H2 */ + this.H2 = createDOMFunc("h2"); + /** @id MochiKit.DOM.H3 */ + this.H3 = createDOMFunc("h3"); + /** @id MochiKit.DOM.H4 */ + this.H4 = createDOMFunc("h4"); + /** @id MochiKit.DOM.H5 */ + this.H5 = createDOMFunc("h5"); + /** @id MochiKit.DOM.H6 */ + this.H6 = createDOMFunc("h6"); + /** @id MochiKit.DOM.BR */ + this.BR = createDOMFunc("br"); + /** @id MochiKit.DOM.HR */ + this.HR = createDOMFunc("hr"); + /** @id MochiKit.DOM.LABEL */ + this.LABEL = createDOMFunc("label"); + /** @id MochiKit.DOM.TEXTAREA */ + this.TEXTAREA = createDOMFunc("textarea"); + /** @id MochiKit.DOM.FORM */ + this.FORM = createDOMFunc("form"); + /** @id MochiKit.DOM.P */ + this.P = createDOMFunc("p"); + /** @id MochiKit.DOM.SELECT */ + this.SELECT = createDOMFunc("select"); + /** @id MochiKit.DOM.OPTION */ + this.OPTION = createDOMFunc("option"); + /** @id MochiKit.DOM.OPTGROUP */ + this.OPTGROUP = createDOMFunc("optgroup"); + /** @id MochiKit.DOM.LEGEND */ + this.LEGEND = createDOMFunc("legend"); + /** @id MochiKit.DOM.FIELDSET */ + this.FIELDSET = createDOMFunc("fieldset"); + /** @id MochiKit.DOM.STRONG */ + this.STRONG = createDOMFunc("strong"); + /** @id MochiKit.DOM.CANVAS */ + this.CANVAS = createDOMFunc("canvas"); + + /** @id MochiKit.DOM.$ */ + this.$ = this.getElement; + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + + } +}); + + +MochiKit.DOM.__new__(((typeof(window) == "undefined") ? this : window)); + +// +// XXX: Internet Explorer blows +// +if (MochiKit.__export__) { + withWindow = MochiKit.DOM.withWindow; + withDocument = MochiKit.DOM.withDocument; +} + +MochiKit.Base._exportSymbols(this, MochiKit.DOM); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DateTime.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DateTime.js new file mode 100644 index 000000000..cbb3c91b6 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DateTime.js @@ -0,0 +1,222 @@ +/*** + +MochiKit.DateTime 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('DateTime', ['Base']); + +MochiKit.DateTime.NAME = "MochiKit.DateTime"; +MochiKit.DateTime.VERSION = "1.4.2"; +MochiKit.DateTime.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; +MochiKit.DateTime.toString = function () { + return this.__repr__(); +}; + +/** @id MochiKit.DateTime.isoDate */ +MochiKit.DateTime.isoDate = function (str) { + str = str + ""; + if (typeof(str) != "string" || str.length === 0) { + return null; + } + var iso = str.split('-'); + if (iso.length === 0) { + return null; + } + var date = new Date(iso[0], iso[1] - 1, iso[2]); + date.setFullYear(iso[0]); + date.setMonth(iso[1] - 1); + date.setDate(iso[2]); + return date; +}; + +MochiKit.DateTime._isoRegexp = /(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/; + +/** @id MochiKit.DateTime.isoTimestamp */ +MochiKit.DateTime.isoTimestamp = function (str) { + str = str + ""; + if (typeof(str) != "string" || str.length === 0) { + return null; + } + var res = str.match(MochiKit.DateTime._isoRegexp); + if (typeof(res) == "undefined" || res === null) { + return null; + } + var year, month, day, hour, min, sec, msec; + year = parseInt(res[1], 10); + if (typeof(res[2]) == "undefined" || res[2] === '') { + return new Date(year); + } + month = parseInt(res[2], 10) - 1; + day = parseInt(res[3], 10); + if (typeof(res[4]) == "undefined" || res[4] === '') { + return new Date(year, month, day); + } + hour = parseInt(res[4], 10); + min = parseInt(res[5], 10); + sec = (typeof(res[6]) != "undefined" && res[6] !== '') ? parseInt(res[6], 10) : 0; + if (typeof(res[7]) != "undefined" && res[7] !== '') { + msec = Math.round(1000.0 * parseFloat("0." + res[7])); + } else { + msec = 0; + } + if ((typeof(res[8]) == "undefined" || res[8] === '') && (typeof(res[9]) == "undefined" || res[9] === '')) { + return new Date(year, month, day, hour, min, sec, msec); + } + var ofs; + if (typeof(res[9]) != "undefined" && res[9] !== '') { + ofs = parseInt(res[10], 10) * 3600000; + if (typeof(res[11]) != "undefined" && res[11] !== '') { + ofs += parseInt(res[11], 10) * 60000; + } + if (res[9] == "-") { + ofs = -ofs; + } + } else { + ofs = 0; + } + return new Date(Date.UTC(year, month, day, hour, min, sec, msec) - ofs); +}; + +/** @id MochiKit.DateTime.toISOTime */ +MochiKit.DateTime.toISOTime = function (date, realISO/* = false */) { + if (typeof(date) == "undefined" || date === null) { + return null; + } + var hh = date.getHours(); + var mm = date.getMinutes(); + var ss = date.getSeconds(); + var lst = [ + ((realISO && (hh < 10)) ? "0" + hh : hh), + ((mm < 10) ? "0" + mm : mm), + ((ss < 10) ? "0" + ss : ss) + ]; + return lst.join(":"); +}; + +/** @id MochiKit.DateTime.toISOTimeStamp */ +MochiKit.DateTime.toISOTimestamp = function (date, realISO/* = false*/) { + if (typeof(date) == "undefined" || date === null) { + return null; + } + var sep = realISO ? "T" : " "; + var foot = realISO ? "Z" : ""; + if (realISO) { + date = new Date(date.getTime() + (date.getTimezoneOffset() * 60000)); + } + return MochiKit.DateTime.toISODate(date) + sep + MochiKit.DateTime.toISOTime(date, realISO) + foot; +}; + +/** @id MochiKit.DateTime.toISODate */ +MochiKit.DateTime.toISODate = function (date) { + if (typeof(date) == "undefined" || date === null) { + return null; + } + var _padTwo = MochiKit.DateTime._padTwo; + var _padFour = MochiKit.DateTime._padFour; + return [ + _padFour(date.getFullYear()), + _padTwo(date.getMonth() + 1), + _padTwo(date.getDate()) + ].join("-"); +}; + +/** @id MochiKit.DateTime.americanDate */ +MochiKit.DateTime.americanDate = function (d) { + d = d + ""; + if (typeof(d) != "string" || d.length === 0) { + return null; + } + var a = d.split('/'); + return new Date(a[2], a[0] - 1, a[1]); +}; + +MochiKit.DateTime._padTwo = function (n) { + return (n > 9) ? n : "0" + n; +}; + +MochiKit.DateTime._padFour = function(n) { + switch(n.toString().length) { + case 1: return "000" + n; break; + case 2: return "00" + n; break; + case 3: return "0" + n; break; + case 4: + default: + return n; + } +}; + +/** @id MochiKit.DateTime.toPaddedAmericanDate */ +MochiKit.DateTime.toPaddedAmericanDate = function (d) { + if (typeof(d) == "undefined" || d === null) { + return null; + } + var _padTwo = MochiKit.DateTime._padTwo; + return [ + _padTwo(d.getMonth() + 1), + _padTwo(d.getDate()), + d.getFullYear() + ].join('/'); +}; + +/** @id MochiKit.DateTime.toAmericanDate */ +MochiKit.DateTime.toAmericanDate = function (d) { + if (typeof(d) == "undefined" || d === null) { + return null; + } + return [d.getMonth() + 1, d.getDate(), d.getFullYear()].join('/'); +}; + +MochiKit.DateTime.EXPORT = [ + "isoDate", + "isoTimestamp", + "toISOTime", + "toISOTimestamp", + "toISODate", + "americanDate", + "toPaddedAmericanDate", + "toAmericanDate" +]; + +MochiKit.DateTime.EXPORT_OK = []; +MochiKit.DateTime.EXPORT_TAGS = { + ":common": MochiKit.DateTime.EXPORT, + ":all": MochiKit.DateTime.EXPORT +}; + +MochiKit.DateTime.__new__ = function () { + // MochiKit.Base.nameFunctions(this); + var base = this.NAME + "."; + for (var k in this) { + var o = this[k]; + if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') { + try { + o.NAME = base + k; + } catch (e) { + // pass + } + } + } +}; + +MochiKit.DateTime.__new__(); + +if (typeof(MochiKit.Base) != "undefined") { + MochiKit.Base._exportSymbols(this, MochiKit.DateTime); +} else { + (function (globals, module) { + if ((typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined') + || (MochiKit.__export__ === false)) { + var all = module.EXPORT_TAGS[":all"]; + for (var i = 0; i < all.length; i++) { + globals[all[i]] = module[all[i]]; + } + } + })(this, MochiKit.DateTime); +} diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DragAndDrop.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DragAndDrop.js new file mode 100644 index 000000000..b23b10208 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/DragAndDrop.js @@ -0,0 +1,793 @@ +/*** +MochiKit.DragAndDrop 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) + Mochi-ized By Thomas Herve (_firstname_@nimail.org) + +***/ + +MochiKit.Base._deps('DragAndDrop', ['Base', 'Iter', 'DOM', 'Signal', 'Visual', 'Position']); + +MochiKit.DragAndDrop.NAME = 'MochiKit.DragAndDrop'; +MochiKit.DragAndDrop.VERSION = '1.4.2'; + +MochiKit.DragAndDrop.__repr__ = function () { + return '[' + this.NAME + ' ' + this.VERSION + ']'; +}; + +MochiKit.DragAndDrop.toString = function () { + return this.__repr__(); +}; + +MochiKit.DragAndDrop.EXPORT = [ + "Droppable", + "Draggable" +]; + +MochiKit.DragAndDrop.EXPORT_OK = [ + "Droppables", + "Draggables" +]; + +MochiKit.DragAndDrop.Droppables = { + /*** + + Manage all droppables. Shouldn't be used, use the Droppable object instead. + + ***/ + drops: [], + + remove: function (element) { + this.drops = MochiKit.Base.filter(function (d) { + return d.element != MochiKit.DOM.getElement(element); + }, this.drops); + }, + + register: function (drop) { + this.drops.push(drop); + }, + + unregister: function (drop) { + this.drops = MochiKit.Base.filter(function (d) { + return d != drop; + }, this.drops); + }, + + prepare: function (element) { + MochiKit.Base.map(function (drop) { + if (drop.isAccepted(element)) { + if (drop.options.activeclass) { + MochiKit.DOM.addElementClass(drop.element, + drop.options.activeclass); + } + drop.options.onactive(drop.element, element); + } + }, this.drops); + }, + + findDeepestChild: function (drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) { + if (MochiKit.DOM.isChildNode(drops[i].element, deepest.element)) { + deepest = drops[i]; + } + } + return deepest; + }, + + show: function (point, element) { + if (!this.drops.length) { + return; + } + var affected = []; + + if (this.last_active) { + this.last_active.deactivate(); + } + MochiKit.Iter.forEach(this.drops, function (drop) { + if (drop.isAffected(point, element)) { + affected.push(drop); + } + }); + if (affected.length > 0) { + drop = this.findDeepestChild(affected); + MochiKit.Position.within(drop.element, point.page.x, point.page.y); + drop.options.onhover(element, drop.element, + MochiKit.Position.overlap(drop.options.overlap, drop.element)); + drop.activate(); + } + }, + + fire: function (event, element) { + if (!this.last_active) { + return; + } + MochiKit.Position.prepare(); + + if (this.last_active.isAffected(event.mouse(), element)) { + this.last_active.options.ondrop(element, + this.last_active.element, event); + } + }, + + reset: function (element) { + MochiKit.Base.map(function (drop) { + if (drop.options.activeclass) { + MochiKit.DOM.removeElementClass(drop.element, + drop.options.activeclass); + } + drop.options.ondesactive(drop.element, element); + }, this.drops); + if (this.last_active) { + this.last_active.deactivate(); + } + } +}; + +/** @id MochiKit.DragAndDrop.Droppable */ +MochiKit.DragAndDrop.Droppable = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.DragAndDrop.Droppable.prototype = { + /*** + + A droppable object. Simple use is to create giving an element: + + new MochiKit.DragAndDrop.Droppable('myelement'); + + Generally you'll want to define the 'ondrop' function and maybe the + 'accept' option to filter draggables. + + ***/ + __class__: MochiKit.DragAndDrop.Droppable, + + __init__: function (element, /* optional */options) { + var d = MochiKit.DOM; + var b = MochiKit.Base; + this.element = d.getElement(element); + this.options = b.update({ + + /** @id MochiKit.DragAndDrop.greedy */ + greedy: true, + + /** @id MochiKit.DragAndDrop.hoverclass */ + hoverclass: null, + + /** @id MochiKit.DragAndDrop.activeclass */ + activeclass: null, + + /** @id MochiKit.DragAndDrop.hoverfunc */ + hoverfunc: b.noop, + + /** @id MochiKit.DragAndDrop.accept */ + accept: null, + + /** @id MochiKit.DragAndDrop.onactive */ + onactive: b.noop, + + /** @id MochiKit.DragAndDrop.ondesactive */ + ondesactive: b.noop, + + /** @id MochiKit.DragAndDrop.onhover */ + onhover: b.noop, + + /** @id MochiKit.DragAndDrop.ondrop */ + ondrop: b.noop, + + /** @id MochiKit.DragAndDrop.containment */ + containment: [], + tree: false + }, options); + + // cache containers + this.options._containers = []; + b.map(MochiKit.Base.bind(function (c) { + this.options._containers.push(d.getElement(c)); + }, this), this.options.containment); + + MochiKit.Style.makePositioned(this.element); // fix IE + + MochiKit.DragAndDrop.Droppables.register(this); + }, + + /** @id MochiKit.DragAndDrop.isContained */ + isContained: function (element) { + if (this.options._containers.length) { + var containmentNode; + if (this.options.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return MochiKit.Iter.some(this.options._containers, function (c) { + return containmentNode == c; + }); + } else { + return true; + } + }, + + /** @id MochiKit.DragAndDrop.isAccepted */ + isAccepted: function (element) { + return ((!this.options.accept) || MochiKit.Iter.some( + this.options.accept, function (c) { + return MochiKit.DOM.hasElementClass(element, c); + })); + }, + + /** @id MochiKit.DragAndDrop.isAffected */ + isAffected: function (point, element) { + return ((this.element != element) && + this.isContained(element) && + this.isAccepted(element) && + MochiKit.Position.within(this.element, point.page.x, + point.page.y)); + }, + + /** @id MochiKit.DragAndDrop.deactivate */ + deactivate: function () { + /*** + + A droppable is deactivate when a draggable has been over it and left. + + ***/ + if (this.options.hoverclass) { + MochiKit.DOM.removeElementClass(this.element, + this.options.hoverclass); + } + this.options.hoverfunc(this.element, false); + MochiKit.DragAndDrop.Droppables.last_active = null; + }, + + /** @id MochiKit.DragAndDrop.activate */ + activate: function () { + /*** + + A droppable is active when a draggable is over it. + + ***/ + if (this.options.hoverclass) { + MochiKit.DOM.addElementClass(this.element, this.options.hoverclass); + } + this.options.hoverfunc(this.element, true); + MochiKit.DragAndDrop.Droppables.last_active = this; + }, + + /** @id MochiKit.DragAndDrop.destroy */ + destroy: function () { + /*** + + Delete this droppable. + + ***/ + MochiKit.DragAndDrop.Droppables.unregister(this); + }, + + /** @id MochiKit.DragAndDrop.repr */ + repr: function () { + return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]"; + } +}; + +MochiKit.DragAndDrop.Draggables = { + /*** + + Manage draggables elements. Not intended to direct use. + + ***/ + drags: [], + + register: function (draggable) { + if (this.drags.length === 0) { + var conn = MochiKit.Signal.connect; + this.eventMouseUp = conn(document, 'onmouseup', this, this.endDrag); + this.eventMouseMove = conn(document, 'onmousemove', this, + this.updateDrag); + this.eventKeypress = conn(document, 'onkeypress', this, + this.keyPress); + } + this.drags.push(draggable); + }, + + unregister: function (draggable) { + this.drags = MochiKit.Base.filter(function (d) { + return d != draggable; + }, this.drags); + if (this.drags.length === 0) { + var disc = MochiKit.Signal.disconnect; + disc(this.eventMouseUp); + disc(this.eventMouseMove); + disc(this.eventKeypress); + } + }, + + activate: function (draggable) { + // allows keypress events if window is not currently focused + // fails for Safari + window.focus(); + this.activeDraggable = draggable; + }, + + deactivate: function () { + this.activeDraggable = null; + }, + + updateDrag: function (event) { + if (!this.activeDraggable) { + return; + } + var pointer = event.mouse(); + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if (this._lastPointer && (MochiKit.Base.repr(this._lastPointer.page) == + MochiKit.Base.repr(pointer.page))) { + return; + } + this._lastPointer = pointer; + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function (event) { + if (!this.activeDraggable) { + return; + } + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function (event) { + if (this.activeDraggable) { + this.activeDraggable.keyPress(event); + } + }, + + notify: function (eventName, draggable, event) { + MochiKit.Signal.signal(this, eventName, draggable, event); + } +}; + +/** @id MochiKit.DragAndDrop.Draggable */ +MochiKit.DragAndDrop.Draggable = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.DragAndDrop.Draggable.prototype = { + /*** + + A draggable object. Simple instantiate : + + new MochiKit.DragAndDrop.Draggable('myelement'); + + ***/ + __class__ : MochiKit.DragAndDrop.Draggable, + + __init__: function (element, /* optional */options) { + var v = MochiKit.Visual; + var b = MochiKit.Base; + options = b.update({ + + /** @id MochiKit.DragAndDrop.handle */ + handle: false, + + /** @id MochiKit.DragAndDrop.starteffect */ + starteffect: function (innerelement) { + this._savedOpacity = MochiKit.Style.getStyle(innerelement, 'opacity') || 1.0; + new v.Opacity(innerelement, {duration:0.2, from:this._savedOpacity, to:0.7}); + }, + /** @id MochiKit.DragAndDrop.reverteffect */ + reverteffect: function (innerelement, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2) + + Math.abs(left_offset^2))*0.02; + return new v.Move(innerelement, + {x: -left_offset, y: -top_offset, duration: dur}); + }, + + /** @id MochiKit.DragAndDrop.endeffect */ + endeffect: function (innerelement) { + new v.Opacity(innerelement, {duration:0.2, from:0.7, to:this._savedOpacity}); + }, + + /** @id MochiKit.DragAndDrop.onchange */ + onchange: b.noop, + + /** @id MochiKit.DragAndDrop.zindex */ + zindex: 1000, + + /** @id MochiKit.DragAndDrop.revert */ + revert: false, + + /** @id MochiKit.DragAndDrop.scroll */ + scroll: false, + + /** @id MochiKit.DragAndDrop.scrollSensitivity */ + scrollSensitivity: 20, + + /** @id MochiKit.DragAndDrop.scrollSpeed */ + scrollSpeed: 15, + // false, or xy or [x, y] or function (x, y){return [x, y];} + + /** @id MochiKit.DragAndDrop.snap */ + snap: false + }, options); + + var d = MochiKit.DOM; + this.element = d.getElement(element); + + if (options.handle && (typeof(options.handle) == 'string')) { + this.handle = d.getFirstElementByTagAndClassName(null, + options.handle, this.element); + } + if (!this.handle) { + this.handle = d.getElement(options.handle); + } + if (!this.handle) { + this.handle = this.element; + } + + if (options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = d.getElement(options.scroll); + this._isScrollChild = MochiKit.DOM.isChildNode(this.element, options.scroll); + } + + MochiKit.Style.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = MochiKit.Signal.connect(this.handle, + 'onmousedown', this, this.initDrag); + MochiKit.DragAndDrop.Draggables.register(this); + }, + + /** @id MochiKit.DragAndDrop.destroy */ + destroy: function () { + MochiKit.Signal.disconnect(this.eventMouseDown); + MochiKit.DragAndDrop.Draggables.unregister(this); + }, + + /** @id MochiKit.DragAndDrop.currentDelta */ + currentDelta: function () { + var s = MochiKit.Style.getStyle; + return [ + parseInt(s(this.element, 'left') || '0'), + parseInt(s(this.element, 'top') || '0')]; + }, + + /** @id MochiKit.DragAndDrop.initDrag */ + initDrag: function (event) { + if (!event.mouse().button.left) { + return; + } + // abort on form elements, fixes a Firefox issue + var src = event.target(); + var tagName = (src.tagName || '').toUpperCase(); + if (tagName === 'INPUT' || tagName === 'SELECT' || + tagName === 'OPTION' || tagName === 'BUTTON' || + tagName === 'TEXTAREA') { + return; + } + + if (this._revert) { + this._revert.cancel(); + this._revert = null; + } + + var pointer = event.mouse(); + var pos = MochiKit.Position.cumulativeOffset(this.element); + this.offset = [pointer.page.x - pos.x, pointer.page.y - pos.y]; + + MochiKit.DragAndDrop.Draggables.activate(this); + event.stop(); + }, + + /** @id MochiKit.DragAndDrop.startDrag */ + startDrag: function (event) { + this.dragging = true; + if (this.options.selectclass) { + MochiKit.DOM.addElementClass(this.element, + this.options.selectclass); + } + if (this.options.zindex) { + this.originalZ = parseInt(MochiKit.Style.getStyle(this.element, + 'z-index') || '0'); + this.element.style.zIndex = this.options.zindex; + } + + if (this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this.ghostPosition = MochiKit.Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if (this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + MochiKit.DragAndDrop.Droppables.prepare(this.element); + MochiKit.DragAndDrop.Draggables.notify('start', this, event); + if (this.options.starteffect) { + this.options.starteffect(this.element); + } + }, + + /** @id MochiKit.DragAndDrop.updateDrag */ + updateDrag: function (event, pointer) { + if (!this.dragging) { + this.startDrag(event); + } + MochiKit.Position.prepare(); + MochiKit.DragAndDrop.Droppables.show(pointer, this.element); + MochiKit.DragAndDrop.Draggables.notify('drag', this, event); + this.draw(pointer); + this.options.onchange(this); + + if (this.options.scroll) { + this.stopScrolling(); + var p, q; + if (this.options.scroll == window) { + var s = this._getWindowScroll(this.options.scroll); + p = new MochiKit.Style.Coordinates(s.left, s.top); + q = new MochiKit.Style.Coordinates(s.left + s.width, + s.top + s.height); + } else { + p = MochiKit.Position.page(this.options.scroll); + p.x += this.options.scroll.scrollLeft; + p.y += this.options.scroll.scrollTop; + p.x += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0); + p.y += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); + q = new MochiKit.Style.Coordinates(p.x + this.options.scroll.offsetWidth, + p.y + this.options.scroll.offsetHeight); + } + var speed = [0, 0]; + if (pointer.page.x > (q.x - this.options.scrollSensitivity)) { + speed[0] = pointer.page.x - (q.x - this.options.scrollSensitivity); + } else if (pointer.page.x < (p.x + this.options.scrollSensitivity)) { + speed[0] = pointer.page.x - (p.x + this.options.scrollSensitivity); + } + if (pointer.page.y > (q.y - this.options.scrollSensitivity)) { + speed[1] = pointer.page.y - (q.y - this.options.scrollSensitivity); + } else if (pointer.page.y < (p.y + this.options.scrollSensitivity)) { + speed[1] = pointer.page.y - (p.y + this.options.scrollSensitivity); + } + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if (/AppleWebKit/.test(navigator.appVersion)) { + window.scrollBy(0, 0); + } + event.stop(); + }, + + /** @id MochiKit.DragAndDrop.finishDrag */ + finishDrag: function (event, success) { + var dr = MochiKit.DragAndDrop; + this.dragging = false; + if (this.options.selectclass) { + MochiKit.DOM.removeElementClass(this.element, + this.options.selectclass); + } + + if (this.options.ghosting) { + // XXX: from a user point of view, it would be better to remove + // the node only *after* the MochiKit.Visual.Move end when used + // with revert. + MochiKit.Position.relativize(this.element, this.ghostPosition); + MochiKit.DOM.removeElement(this._clone); + this._clone = null; + } + + if (success) { + dr.Droppables.fire(event, this.element); + } + dr.Draggables.notify('end', this, event); + + var revert = this.options.revert; + if (revert && typeof(revert) == 'function') { + revert = revert(this.element); + } + + var d = this.currentDelta(); + if (revert && this.options.reverteffect) { + this._revert = this.options.reverteffect(this.element, + d[1] - this.delta[1], d[0] - this.delta[0]); + } else { + this.delta = d; + } + + if (this.options.zindex) { + this.element.style.zIndex = this.originalZ; + } + + if (this.options.endeffect) { + this.options.endeffect(this.element); + } + + dr.Draggables.deactivate(); + dr.Droppables.reset(this.element); + }, + + /** @id MochiKit.DragAndDrop.keyPress */ + keyPress: function (event) { + if (event.key().string != "KEY_ESCAPE") { + return; + } + this.finishDrag(event, false); + event.stop(); + }, + + /** @id MochiKit.DragAndDrop.endDrag */ + endDrag: function (event) { + if (!this.dragging) { + return; + } + this.stopScrolling(); + this.finishDrag(event, true); + event.stop(); + }, + + /** @id MochiKit.DragAndDrop.draw */ + draw: function (point) { + var pos = MochiKit.Position.cumulativeOffset(this.element); + var d = this.currentDelta(); + pos.x -= d[0]; + pos.y -= d[1]; + + if (this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos.x -= this.options.scroll.scrollLeft - this.originalScrollLeft; + pos.y -= this.options.scroll.scrollTop - this.originalScrollTop; + } + + var p = [point.page.x - pos.x - this.offset[0], + point.page.y - pos.y - this.offset[1]]; + + if (this.options.snap) { + if (typeof(this.options.snap) == 'function') { + p = this.options.snap(p[0], p[1]); + } else { + if (this.options.snap instanceof Array) { + var i = -1; + p = MochiKit.Base.map(MochiKit.Base.bind(function (v) { + i += 1; + return Math.round(v/this.options.snap[i]) * + this.options.snap[i]; + }, this), p); + } else { + p = MochiKit.Base.map(MochiKit.Base.bind(function (v) { + return Math.round(v/this.options.snap) * + this.options.snap; + }, this), p); + } + } + } + var style = this.element.style; + if ((!this.options.constraint) || + (this.options.constraint == 'horizontal')) { + style.left = p[0] + 'px'; + } + if ((!this.options.constraint) || + (this.options.constraint == 'vertical')) { + style.top = p[1] + 'px'; + } + if (style.visibility == 'hidden') { + style.visibility = ''; // fix gecko rendering + } + }, + + /** @id MochiKit.DragAndDrop.stopScrolling */ + stopScrolling: function () { + if (this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + MochiKit.DragAndDrop.Draggables._lastScrollPointer = null; + } + }, + + /** @id MochiKit.DragAndDrop.startScrolling */ + startScrolling: function (speed) { + if (!speed[0] && !speed[1]) { + return; + } + this.scrollSpeed = [speed[0] * this.options.scrollSpeed, + speed[1] * this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(MochiKit.Base.bind(this.scroll, this), 10); + }, + + /** @id MochiKit.DragAndDrop.scroll */ + scroll: function () { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + + if (this.options.scroll == window) { + var s = this._getWindowScroll(this.options.scroll); + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var dm = delta / 1000; + this.options.scroll.scrollTo(s.left + dm * this.scrollSpeed[0], + s.top + dm * this.scrollSpeed[1]); + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + var d = MochiKit.DragAndDrop; + + MochiKit.Position.prepare(); + d.Droppables.show(d.Draggables._lastPointer, this.element); + d.Draggables.notify('drag', this); + if (this._isScrollChild) { + d.Draggables._lastScrollPointer = d.Draggables._lastScrollPointer || d.Draggables._lastPointer; + d.Draggables._lastScrollPointer.x += this.scrollSpeed[0] * delta / 1000; + d.Draggables._lastScrollPointer.y += this.scrollSpeed[1] * delta / 1000; + if (d.Draggables._lastScrollPointer.x < 0) { + d.Draggables._lastScrollPointer.x = 0; + } + if (d.Draggables._lastScrollPointer.y < 0) { + d.Draggables._lastScrollPointer.y = 0; + } + this.draw(d.Draggables._lastScrollPointer); + } + + this.options.onchange(this); + }, + + _getWindowScroll: function (win) { + var vp, w, h; + MochiKit.DOM.withWindow(win, function () { + vp = MochiKit.Style.getViewportPosition(win.document); + }); + if (win.innerWidth) { + w = win.innerWidth; + h = win.innerHeight; + } else if (win.document.documentElement && win.document.documentElement.clientWidth) { + w = win.document.documentElement.clientWidth; + h = win.document.documentElement.clientHeight; + } else { + w = win.document.body.offsetWidth; + h = win.document.body.offsetHeight; + } + return {top: vp.y, left: vp.x, width: w, height: h}; + }, + + /** @id MochiKit.DragAndDrop.repr */ + repr: function () { + return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]"; + } +}; + +MochiKit.DragAndDrop.__new__ = function () { + MochiKit.Base.nameFunctions(this); + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK) + }; +}; + +MochiKit.DragAndDrop.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.DragAndDrop); + diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Format.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Format.js new file mode 100644 index 000000000..36b71537c --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Format.js @@ -0,0 +1,304 @@ +/*** + +MochiKit.Format 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('Format', ['Base']); + +MochiKit.Format.NAME = "MochiKit.Format"; +MochiKit.Format.VERSION = "1.4.2"; +MochiKit.Format.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; +MochiKit.Format.toString = function () { + return this.__repr__(); +}; + +MochiKit.Format._numberFormatter = function (placeholder, header, footer, locale, isPercent, precision, leadingZeros, separatorAt, trailingZeros) { + return function (num) { + num = parseFloat(num); + if (typeof(num) == "undefined" || num === null || isNaN(num)) { + return placeholder; + } + var curheader = header; + var curfooter = footer; + if (num < 0) { + num = -num; + } else { + curheader = curheader.replace(/-/, ""); + } + var me = arguments.callee; + var fmt = MochiKit.Format.formatLocale(locale); + if (isPercent) { + num = num * 100.0; + curfooter = fmt.percent + curfooter; + } + num = MochiKit.Format.roundToFixed(num, precision); + var parts = num.split(/\./); + var whole = parts[0]; + var frac = (parts.length == 1) ? "" : parts[1]; + var res = ""; + while (whole.length < leadingZeros) { + whole = "0" + whole; + } + if (separatorAt) { + while (whole.length > separatorAt) { + var i = whole.length - separatorAt; + //res = res + fmt.separator + whole.substring(i, whole.length); + res = fmt.separator + whole.substring(i, whole.length) + res; + whole = whole.substring(0, i); + } + } + res = whole + res; + if (precision > 0) { + while (frac.length < trailingZeros) { + frac = frac + "0"; + } + res = res + fmt.decimal + frac; + } + return curheader + res + curfooter; + }; +}; + +/** @id MochiKit.Format.numberFormatter */ +MochiKit.Format.numberFormatter = function (pattern, placeholder/* = "" */, locale/* = "default" */) { + // http://java.sun.com/docs/books/tutorial/i18n/format/numberpattern.html + // | 0 | leading or trailing zeros + // | # | just the number + // | , | separator + // | . | decimal separator + // | % | Multiply by 100 and format as percent + if (typeof(placeholder) == "undefined") { + placeholder = ""; + } + var match = pattern.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/); + if (!match) { + throw TypeError("Invalid pattern"); + } + var header = pattern.substr(0, match.index); + var footer = pattern.substr(match.index + match[0].length); + if (header.search(/-/) == -1) { + header = header + "-"; + } + var whole = match[1]; + var frac = (typeof(match[2]) == "string" && match[2] != "") ? match[2] : ""; + var isPercent = (typeof(match[3]) == "string" && match[3] != ""); + var tmp = whole.split(/,/); + var separatorAt; + if (typeof(locale) == "undefined") { + locale = "default"; + } + if (tmp.length == 1) { + separatorAt = null; + } else { + separatorAt = tmp[1].length; + } + var leadingZeros = whole.length - whole.replace(/0/g, "").length; + var trailingZeros = frac.length - frac.replace(/0/g, "").length; + var precision = frac.length; + var rval = MochiKit.Format._numberFormatter( + placeholder, header, footer, locale, isPercent, precision, + leadingZeros, separatorAt, trailingZeros + ); + var m = MochiKit.Base; + if (m) { + var fn = arguments.callee; + var args = m.concat(arguments); + rval.repr = function () { + return [ + self.NAME, + "(", + map(m.repr, args).join(", "), + ")" + ].join(""); + }; + } + return rval; +}; + +/** @id MochiKit.Format.formatLocale */ +MochiKit.Format.formatLocale = function (locale) { + if (typeof(locale) == "undefined" || locale === null) { + locale = "default"; + } + if (typeof(locale) == "string") { + var rval = MochiKit.Format.LOCALE[locale]; + if (typeof(rval) == "string") { + rval = arguments.callee(rval); + MochiKit.Format.LOCALE[locale] = rval; + } + return rval; + } else { + return locale; + } +}; + +/** @id MochiKit.Format.twoDigitAverage */ +MochiKit.Format.twoDigitAverage = function (numerator, denominator) { + if (denominator) { + var res = numerator / denominator; + if (!isNaN(res)) { + return MochiKit.Format.twoDigitFloat(res); + } + } + return "0"; +}; + +/** @id MochiKit.Format.twoDigitFloat */ +MochiKit.Format.twoDigitFloat = function (aNumber) { + var res = roundToFixed(aNumber, 2); + if (res.indexOf(".00") > 0) { + return res.substring(0, res.length - 3); + } else if (res.charAt(res.length - 1) == "0") { + return res.substring(0, res.length - 1); + } else { + return res; + } +}; + +/** @id MochiKit.Format.lstrip */ +MochiKit.Format.lstrip = function (str, /* optional */chars) { + str = str + ""; + if (typeof(str) != "string") { + return null; + } + if (!chars) { + return str.replace(/^\s+/, ""); + } else { + return str.replace(new RegExp("^[" + chars + "]+"), ""); + } +}; + +/** @id MochiKit.Format.rstrip */ +MochiKit.Format.rstrip = function (str, /* optional */chars) { + str = str + ""; + if (typeof(str) != "string") { + return null; + } + if (!chars) { + return str.replace(/\s+$/, ""); + } else { + return str.replace(new RegExp("[" + chars + "]+$"), ""); + } +}; + +/** @id MochiKit.Format.strip */ +MochiKit.Format.strip = function (str, /* optional */chars) { + var self = MochiKit.Format; + return self.rstrip(self.lstrip(str, chars), chars); +}; + +/** @id MochiKit.Format.truncToFixed */ +MochiKit.Format.truncToFixed = function (aNumber, precision) { + var res = Math.floor(aNumber).toFixed(0); + if (aNumber < 0) { + res = Math.ceil(aNumber).toFixed(0); + if (res.charAt(0) != "-" && precision > 0) { + res = "-" + res; + } + } + if (res.indexOf("e") < 0 && precision > 0) { + var tail = aNumber.toString(); + if (tail.indexOf("e") > 0) { + tail = "."; + } else if (tail.indexOf(".") < 0) { + tail = "."; + } else { + tail = tail.substring(tail.indexOf(".")); + } + if (tail.length - 1 > precision) { + tail = tail.substring(0, precision + 1); + } + while (tail.length - 1 < precision) { + tail += "0"; + } + res += tail; + } + return res; +}; + +/** @id MochiKit.Format.roundToFixed */ +MochiKit.Format.roundToFixed = function (aNumber, precision) { + var upper = Math.abs(aNumber) + 0.5 * Math.pow(10, -precision); + var res = MochiKit.Format.truncToFixed(upper, precision); + if (aNumber < 0) { + res = "-" + res; + } + return res; +}; + +/** @id MochiKit.Format.percentFormat */ +MochiKit.Format.percentFormat = function (aNumber) { + return MochiKit.Format.twoDigitFloat(100 * aNumber) + '%'; +}; + +MochiKit.Format.EXPORT = [ + "truncToFixed", + "roundToFixed", + "numberFormatter", + "formatLocale", + "twoDigitAverage", + "twoDigitFloat", + "percentFormat", + "lstrip", + "rstrip", + "strip" +]; + +MochiKit.Format.LOCALE = { + en_US: {separator: ",", decimal: ".", percent: "%"}, + de_DE: {separator: ".", decimal: ",", percent: "%"}, + pt_BR: {separator: ".", decimal: ",", percent: "%"}, + fr_FR: {separator: " ", decimal: ",", percent: "%"}, + "default": "en_US" +}; + +MochiKit.Format.EXPORT_OK = []; +MochiKit.Format.EXPORT_TAGS = { + ':all': MochiKit.Format.EXPORT, + ':common': MochiKit.Format.EXPORT +}; + +MochiKit.Format.__new__ = function () { + // MochiKit.Base.nameFunctions(this); + var base = this.NAME + "."; + var k, v, o; + for (k in this.LOCALE) { + o = this.LOCALE[k]; + if (typeof(o) == "object") { + o.repr = function () { return this.NAME; }; + o.NAME = base + "LOCALE." + k; + } + } + for (k in this) { + o = this[k]; + if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') { + try { + o.NAME = base + k; + } catch (e) { + // pass + } + } + } +}; + +MochiKit.Format.__new__(); + +if (typeof(MochiKit.Base) != "undefined") { + MochiKit.Base._exportSymbols(this, MochiKit.Format); +} else { + (function (globals, module) { + if ((typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined') + || (MochiKit.__export__ === false)) { + var all = module.EXPORT_TAGS[":all"]; + for (var i = 0; i < all.length; i++) { + globals[all[i]] = module[all[i]]; + } + } + })(this, MochiKit.Format); +} diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Iter.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Iter.js new file mode 100644 index 000000000..99f3155b8 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Iter.js @@ -0,0 +1,844 @@ +/*** + +MochiKit.Iter 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('Iter', ['Base']); + +MochiKit.Iter.NAME = "MochiKit.Iter"; +MochiKit.Iter.VERSION = "1.4.2"; +MochiKit.Base.update(MochiKit.Iter, { + __repr__: function () { + return "[" + this.NAME + " " + this.VERSION + "]"; + }, + toString: function () { + return this.__repr__(); + }, + + /** @id MochiKit.Iter.registerIteratorFactory */ + registerIteratorFactory: function (name, check, iterfactory, /* optional */ override) { + MochiKit.Iter.iteratorRegistry.register(name, check, iterfactory, override); + }, + + /** @id MochiKit.Iter.isIterable */ + isIterable: function(o) { + return o != null && + (typeof(o.next) == "function" || typeof(o.iter) == "function"); + }, + + /** @id MochiKit.Iter.iter */ + iter: function (iterable, /* optional */ sentinel) { + var self = MochiKit.Iter; + if (arguments.length == 2) { + return self.takewhile( + function (a) { return a != sentinel; }, + iterable + ); + } + if (typeof(iterable.next) == 'function') { + return iterable; + } else if (typeof(iterable.iter) == 'function') { + return iterable.iter(); + /* + } else if (typeof(iterable.__iterator__) == 'function') { + // + // XXX: We can't support JavaScript 1.7 __iterator__ directly + // because of Object.prototype.__iterator__ + // + return iterable.__iterator__(); + */ + } + + try { + return self.iteratorRegistry.match(iterable); + } catch (e) { + var m = MochiKit.Base; + if (e == m.NotFound) { + e = new TypeError(typeof(iterable) + ": " + m.repr(iterable) + " is not iterable"); + } + throw e; + } + }, + + /** @id MochiKit.Iter.count */ + count: function (n) { + if (!n) { + n = 0; + } + var m = MochiKit.Base; + return { + repr: function () { return "count(" + n + ")"; }, + toString: m.forwardCall("repr"), + next: m.counter(n) + }; + }, + + /** @id MochiKit.Iter.cycle */ + cycle: function (p) { + var self = MochiKit.Iter; + var m = MochiKit.Base; + var lst = []; + var iterator = self.iter(p); + return { + repr: function () { return "cycle(...)"; }, + toString: m.forwardCall("repr"), + next: function () { + try { + var rval = iterator.next(); + lst.push(rval); + return rval; + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + if (lst.length === 0) { + this.next = function () { + throw self.StopIteration; + }; + } else { + var i = -1; + this.next = function () { + i = (i + 1) % lst.length; + return lst[i]; + }; + } + return this.next(); + } + } + }; + }, + + /** @id MochiKit.Iter.repeat */ + repeat: function (elem, /* optional */n) { + var m = MochiKit.Base; + if (typeof(n) == 'undefined') { + return { + repr: function () { + return "repeat(" + m.repr(elem) + ")"; + }, + toString: m.forwardCall("repr"), + next: function () { + return elem; + } + }; + } + return { + repr: function () { + return "repeat(" + m.repr(elem) + ", " + n + ")"; + }, + toString: m.forwardCall("repr"), + next: function () { + if (n <= 0) { + throw MochiKit.Iter.StopIteration; + } + n -= 1; + return elem; + } + }; + }, + + /** @id MochiKit.Iter.next */ + next: function (iterator) { + return iterator.next(); + }, + + /** @id MochiKit.Iter.izip */ + izip: function (p, q/*, ...*/) { + var m = MochiKit.Base; + var self = MochiKit.Iter; + var next = self.next; + var iterables = m.map(self.iter, arguments); + return { + repr: function () { return "izip(...)"; }, + toString: m.forwardCall("repr"), + next: function () { return m.map(next, iterables); } + }; + }, + + /** @id MochiKit.Iter.ifilter */ + ifilter: function (pred, seq) { + var m = MochiKit.Base; + seq = MochiKit.Iter.iter(seq); + if (pred === null) { + pred = m.operator.truth; + } + return { + repr: function () { return "ifilter(...)"; }, + toString: m.forwardCall("repr"), + next: function () { + while (true) { + var rval = seq.next(); + if (pred(rval)) { + return rval; + } + } + // mozilla warnings aren't too bright + return undefined; + } + }; + }, + + /** @id MochiKit.Iter.ifilterfalse */ + ifilterfalse: function (pred, seq) { + var m = MochiKit.Base; + seq = MochiKit.Iter.iter(seq); + if (pred === null) { + pred = m.operator.truth; + } + return { + repr: function () { return "ifilterfalse(...)"; }, + toString: m.forwardCall("repr"), + next: function () { + while (true) { + var rval = seq.next(); + if (!pred(rval)) { + return rval; + } + } + // mozilla warnings aren't too bright + return undefined; + } + }; + }, + + /** @id MochiKit.Iter.islice */ + islice: function (seq/*, [start,] stop[, step] */) { + var self = MochiKit.Iter; + var m = MochiKit.Base; + seq = self.iter(seq); + var start = 0; + var stop = 0; + var step = 1; + var i = -1; + if (arguments.length == 2) { + stop = arguments[1]; + } else if (arguments.length == 3) { + start = arguments[1]; + stop = arguments[2]; + } else { + start = arguments[1]; + stop = arguments[2]; + step = arguments[3]; + } + return { + repr: function () { + return "islice(" + ["...", start, stop, step].join(", ") + ")"; + }, + toString: m.forwardCall("repr"), + next: function () { + var rval; + while (i < start) { + rval = seq.next(); + i++; + } + if (start >= stop) { + throw self.StopIteration; + } + start += step; + return rval; + } + }; + }, + + /** @id MochiKit.Iter.imap */ + imap: function (fun, p, q/*, ...*/) { + var m = MochiKit.Base; + var self = MochiKit.Iter; + var iterables = m.map(self.iter, m.extend(null, arguments, 1)); + var map = m.map; + var next = self.next; + return { + repr: function () { return "imap(...)"; }, + toString: m.forwardCall("repr"), + next: function () { + return fun.apply(this, map(next, iterables)); + } + }; + }, + + /** @id MochiKit.Iter.applymap */ + applymap: function (fun, seq, self) { + seq = MochiKit.Iter.iter(seq); + var m = MochiKit.Base; + return { + repr: function () { return "applymap(...)"; }, + toString: m.forwardCall("repr"), + next: function () { + return fun.apply(self, seq.next()); + } + }; + }, + + /** @id MochiKit.Iter.chain */ + chain: function (p, q/*, ...*/) { + // dumb fast path + var self = MochiKit.Iter; + var m = MochiKit.Base; + if (arguments.length == 1) { + return self.iter(arguments[0]); + } + var argiter = m.map(self.iter, arguments); + return { + repr: function () { return "chain(...)"; }, + toString: m.forwardCall("repr"), + next: function () { + while (argiter.length > 1) { + try { + var result = argiter[0].next(); + return result; + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + argiter.shift(); + var result = argiter[0].next(); + return result; + } + } + if (argiter.length == 1) { + // optimize last element + var arg = argiter.shift(); + this.next = m.bind("next", arg); + return this.next(); + } + throw self.StopIteration; + } + }; + }, + + /** @id MochiKit.Iter.takewhile */ + takewhile: function (pred, seq) { + var self = MochiKit.Iter; + seq = self.iter(seq); + return { + repr: function () { return "takewhile(...)"; }, + toString: MochiKit.Base.forwardCall("repr"), + next: function () { + var rval = seq.next(); + if (!pred(rval)) { + this.next = function () { + throw self.StopIteration; + }; + this.next(); + } + return rval; + } + }; + }, + + /** @id MochiKit.Iter.dropwhile */ + dropwhile: function (pred, seq) { + seq = MochiKit.Iter.iter(seq); + var m = MochiKit.Base; + var bind = m.bind; + return { + "repr": function () { return "dropwhile(...)"; }, + "toString": m.forwardCall("repr"), + "next": function () { + while (true) { + var rval = seq.next(); + if (!pred(rval)) { + break; + } + } + this.next = bind("next", seq); + return rval; + } + }; + }, + + _tee: function (ident, sync, iterable) { + sync.pos[ident] = -1; + var m = MochiKit.Base; + var listMin = m.listMin; + return { + repr: function () { return "tee(" + ident + ", ...)"; }, + toString: m.forwardCall("repr"), + next: function () { + var rval; + var i = sync.pos[ident]; + + if (i == sync.max) { + rval = iterable.next(); + sync.deque.push(rval); + sync.max += 1; + sync.pos[ident] += 1; + } else { + rval = sync.deque[i - sync.min]; + sync.pos[ident] += 1; + if (i == sync.min && listMin(sync.pos) != sync.min) { + sync.min += 1; + sync.deque.shift(); + } + } + return rval; + } + }; + }, + + /** @id MochiKit.Iter.tee */ + tee: function (iterable, n/* = 2 */) { + var rval = []; + var sync = { + "pos": [], + "deque": [], + "max": -1, + "min": -1 + }; + if (arguments.length == 1 || typeof(n) == "undefined" || n === null) { + n = 2; + } + var self = MochiKit.Iter; + iterable = self.iter(iterable); + var _tee = self._tee; + for (var i = 0; i < n; i++) { + rval.push(_tee(i, sync, iterable)); + } + return rval; + }, + + /** @id MochiKit.Iter.list */ + list: function (iterable) { + // Fast-path for Array and Array-like + var rval; + if (iterable instanceof Array) { + return iterable.slice(); + } + // this is necessary to avoid a Safari crash + if (typeof(iterable) == "function" && + !(iterable instanceof Function) && + typeof(iterable.length) == 'number') { + rval = []; + for (var i = 0; i < iterable.length; i++) { + rval.push(iterable[i]); + } + return rval; + } + + var self = MochiKit.Iter; + iterable = self.iter(iterable); + var rval = []; + var a_val; + try { + while (true) { + a_val = iterable.next(); + rval.push(a_val); + } + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + return rval; + } + // mozilla warnings aren't too bright + return undefined; + }, + + + /** @id MochiKit.Iter.reduce */ + reduce: function (fn, iterable, /* optional */initial) { + var i = 0; + var x = initial; + var self = MochiKit.Iter; + iterable = self.iter(iterable); + if (arguments.length < 3) { + try { + x = iterable.next(); + } catch (e) { + if (e == self.StopIteration) { + e = new TypeError("reduce() of empty sequence with no initial value"); + } + throw e; + } + i++; + } + try { + while (true) { + x = fn(x, iterable.next()); + } + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + } + return x; + }, + + /** @id MochiKit.Iter.range */ + range: function (/* [start,] stop[, step] */) { + var start = 0; + var stop = 0; + var step = 1; + if (arguments.length == 1) { + stop = arguments[0]; + } else if (arguments.length == 2) { + start = arguments[0]; + stop = arguments[1]; + } else if (arguments.length == 3) { + start = arguments[0]; + stop = arguments[1]; + step = arguments[2]; + } else { + throw new TypeError("range() takes 1, 2, or 3 arguments!"); + } + if (step === 0) { + throw new TypeError("range() step must not be 0"); + } + return { + next: function () { + if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) { + throw MochiKit.Iter.StopIteration; + } + var rval = start; + start += step; + return rval; + }, + repr: function () { + return "range(" + [start, stop, step].join(", ") + ")"; + }, + toString: MochiKit.Base.forwardCall("repr") + }; + }, + + /** @id MochiKit.Iter.sum */ + sum: function (iterable, start/* = 0 */) { + if (typeof(start) == "undefined" || start === null) { + start = 0; + } + var x = start; + var self = MochiKit.Iter; + iterable = self.iter(iterable); + try { + while (true) { + x += iterable.next(); + } + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + } + return x; + }, + + /** @id MochiKit.Iter.exhaust */ + exhaust: function (iterable) { + var self = MochiKit.Iter; + iterable = self.iter(iterable); + try { + while (true) { + iterable.next(); + } + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + } + }, + + /** @id MochiKit.Iter.forEach */ + forEach: function (iterable, func, /* optional */obj) { + var m = MochiKit.Base; + var self = MochiKit.Iter; + if (arguments.length > 2) { + func = m.bind(func, obj); + } + // fast path for array + if (m.isArrayLike(iterable) && !self.isIterable(iterable)) { + try { + for (var i = 0; i < iterable.length; i++) { + func(iterable[i]); + } + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + } + } else { + self.exhaust(self.imap(func, iterable)); + } + }, + + /** @id MochiKit.Iter.every */ + every: function (iterable, func) { + var self = MochiKit.Iter; + try { + self.ifilterfalse(func, iterable).next(); + return false; + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + return true; + } + }, + + /** @id MochiKit.Iter.sorted */ + sorted: function (iterable, /* optional */cmp) { + var rval = MochiKit.Iter.list(iterable); + if (arguments.length == 1) { + cmp = MochiKit.Base.compare; + } + rval.sort(cmp); + return rval; + }, + + /** @id MochiKit.Iter.reversed */ + reversed: function (iterable) { + var rval = MochiKit.Iter.list(iterable); + rval.reverse(); + return rval; + }, + + /** @id MochiKit.Iter.some */ + some: function (iterable, func) { + var self = MochiKit.Iter; + try { + self.ifilter(func, iterable).next(); + return true; + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + return false; + } + }, + + /** @id MochiKit.Iter.iextend */ + iextend: function (lst, iterable) { + var m = MochiKit.Base; + var self = MochiKit.Iter; + if (m.isArrayLike(iterable) && !self.isIterable(iterable)) { + // fast-path for array-like + for (var i = 0; i < iterable.length; i++) { + lst.push(iterable[i]); + } + } else { + iterable = self.iter(iterable); + try { + while (true) { + lst.push(iterable.next()); + } + } catch (e) { + if (e != self.StopIteration) { + throw e; + } + } + } + return lst; + }, + + /** @id MochiKit.Iter.groupby */ + groupby: function(iterable, /* optional */ keyfunc) { + var m = MochiKit.Base; + var self = MochiKit.Iter; + if (arguments.length < 2) { + keyfunc = m.operator.identity; + } + iterable = self.iter(iterable); + + // shared + var pk = undefined; + var k = undefined; + var v; + + function fetch() { + v = iterable.next(); + k = keyfunc(v); + }; + + function eat() { + var ret = v; + v = undefined; + return ret; + }; + + var first = true; + var compare = m.compare; + return { + repr: function () { return "groupby(...)"; }, + next: function() { + // iterator-next + + // iterate until meet next group + while (compare(k, pk) === 0) { + fetch(); + if (first) { + first = false; + break; + } + } + pk = k; + return [k, { + next: function() { + // subiterator-next + if (v == undefined) { // Is there something to eat? + fetch(); + } + if (compare(k, pk) !== 0) { + throw self.StopIteration; + } + return eat(); + } + }]; + } + }; + }, + + /** @id MochiKit.Iter.groupby_as_array */ + groupby_as_array: function (iterable, /* optional */ keyfunc) { + var m = MochiKit.Base; + var self = MochiKit.Iter; + if (arguments.length < 2) { + keyfunc = m.operator.identity; + } + + iterable = self.iter(iterable); + var result = []; + var first = true; + var prev_key; + var compare = m.compare; + while (true) { + try { + var value = iterable.next(); + var key = keyfunc(value); + } catch (e) { + if (e == self.StopIteration) { + break; + } + throw e; + } + if (first || compare(key, prev_key) !== 0) { + var values = []; + result.push([key, values]); + } + values.push(value); + first = false; + prev_key = key; + } + return result; + }, + + /** @id MochiKit.Iter.arrayLikeIter */ + arrayLikeIter: function (iterable) { + var i = 0; + return { + repr: function () { return "arrayLikeIter(...)"; }, + toString: MochiKit.Base.forwardCall("repr"), + next: function () { + if (i >= iterable.length) { + throw MochiKit.Iter.StopIteration; + } + return iterable[i++]; + } + }; + }, + + /** @id MochiKit.Iter.hasIterateNext */ + hasIterateNext: function (iterable) { + return (iterable && typeof(iterable.iterateNext) == "function"); + }, + + /** @id MochiKit.Iter.iterateNextIter */ + iterateNextIter: function (iterable) { + return { + repr: function () { return "iterateNextIter(...)"; }, + toString: MochiKit.Base.forwardCall("repr"), + next: function () { + var rval = iterable.iterateNext(); + if (rval === null || rval === undefined) { + throw MochiKit.Iter.StopIteration; + } + return rval; + } + }; + } +}); + + +MochiKit.Iter.EXPORT_OK = [ + "iteratorRegistry", + "arrayLikeIter", + "hasIterateNext", + "iterateNextIter" +]; + +MochiKit.Iter.EXPORT = [ + "StopIteration", + "registerIteratorFactory", + "iter", + "count", + "cycle", + "repeat", + "next", + "izip", + "ifilter", + "ifilterfalse", + "islice", + "imap", + "applymap", + "chain", + "takewhile", + "dropwhile", + "tee", + "list", + "reduce", + "range", + "sum", + "exhaust", + "forEach", + "every", + "sorted", + "reversed", + "some", + "iextend", + "groupby", + "groupby_as_array" +]; + +MochiKit.Iter.__new__ = function () { + var m = MochiKit.Base; + // Re-use StopIteration if exists (e.g. SpiderMonkey) + if (typeof(StopIteration) != "undefined") { + this.StopIteration = StopIteration; + } else { + /** @id MochiKit.Iter.StopIteration */ + this.StopIteration = new m.NamedError("StopIteration"); + } + this.iteratorRegistry = new m.AdapterRegistry(); + // Register the iterator factory for arrays + this.registerIteratorFactory( + "arrayLike", + m.isArrayLike, + this.arrayLikeIter + ); + + this.registerIteratorFactory( + "iterateNext", + this.hasIterateNext, + this.iterateNextIter + ); + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + +}; + +MochiKit.Iter.__new__(); + +// +// XXX: Internet Explorer blows +// +if (MochiKit.__export__) { + reduce = MochiKit.Iter.reduce; +} + +MochiKit.Base._exportSymbols(this, MochiKit.Iter); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Logging.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Logging.js new file mode 100644 index 000000000..463ccd0b7 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Logging.js @@ -0,0 +1,315 @@ +/*** + +MochiKit.Logging 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('Logging', ['Base']); + +MochiKit.Logging.NAME = "MochiKit.Logging"; +MochiKit.Logging.VERSION = "1.4.2"; +MochiKit.Logging.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +MochiKit.Logging.toString = function () { + return this.__repr__(); +}; + + +MochiKit.Logging.EXPORT = [ + "LogLevel", + "LogMessage", + "Logger", + "alertListener", + "logger", + "log", + "logError", + "logDebug", + "logFatal", + "logWarning" +]; + + +MochiKit.Logging.EXPORT_OK = [ + "logLevelAtLeast", + "isLogMessage", + "compareLogMessage" +]; + + +/** @id MochiKit.Logging.LogMessage */ +MochiKit.Logging.LogMessage = function (num, level, info) { + this.num = num; + this.level = level; + this.info = info; + this.timestamp = new Date(); +}; + +MochiKit.Logging.LogMessage.prototype = { + /** @id MochiKit.Logging.LogMessage.prototype.repr */ + repr: function () { + var m = MochiKit.Base; + return 'LogMessage(' + + m.map( + m.repr, + [this.num, this.level, this.info] + ).join(', ') + ')'; + }, + /** @id MochiKit.Logging.LogMessage.prototype.toString */ + toString: MochiKit.Base.forwardCall("repr") +}; + +MochiKit.Base.update(MochiKit.Logging, { + /** @id MochiKit.Logging.logLevelAtLeast */ + logLevelAtLeast: function (minLevel) { + var self = MochiKit.Logging; + if (typeof(minLevel) == 'string') { + minLevel = self.LogLevel[minLevel]; + } + return function (msg) { + var msgLevel = msg.level; + if (typeof(msgLevel) == 'string') { + msgLevel = self.LogLevel[msgLevel]; + } + return msgLevel >= minLevel; + }; + }, + + /** @id MochiKit.Logging.isLogMessage */ + isLogMessage: function (/* ... */) { + var LogMessage = MochiKit.Logging.LogMessage; + for (var i = 0; i < arguments.length; i++) { + if (!(arguments[i] instanceof LogMessage)) { + return false; + } + } + return true; + }, + + /** @id MochiKit.Logging.compareLogMessage */ + compareLogMessage: function (a, b) { + return MochiKit.Base.compare([a.level, a.info], [b.level, b.info]); + }, + + /** @id MochiKit.Logging.alertListener */ + alertListener: function (msg) { + alert( + "num: " + msg.num + + "\nlevel: " + msg.level + + "\ninfo: " + msg.info.join(" ") + ); + } + +}); + +/** @id MochiKit.Logging.Logger */ +MochiKit.Logging.Logger = function (/* optional */maxSize) { + this.counter = 0; + if (typeof(maxSize) == 'undefined' || maxSize === null) { + maxSize = -1; + } + this.maxSize = maxSize; + this._messages = []; + this.listeners = {}; + this.useNativeConsole = false; +}; + +MochiKit.Logging.Logger.prototype = { + /** @id MochiKit.Logging.Logger.prototype.clear */ + clear: function () { + this._messages.splice(0, this._messages.length); + }, + + /** @id MochiKit.Logging.Logger.prototype.logToConsole */ + logToConsole: function (msg) { + if (typeof(window) != "undefined" && window.console + && window.console.log) { + // Safari and FireBug 0.4 + // Percent replacement is a workaround for cute Safari crashing bug + window.console.log(msg.replace(/%/g, '\uFF05')); + } else if (typeof(opera) != "undefined" && opera.postError) { + // Opera + opera.postError(msg); + } else if (typeof(printfire) == "function") { + // FireBug 0.3 and earlier + printfire(msg); + } else if (typeof(Debug) != "undefined" && Debug.writeln) { + // IE Web Development Helper (?) + // http://www.nikhilk.net/Entry.aspx?id=93 + Debug.writeln(msg); + } else if (typeof(debug) != "undefined" && debug.trace) { + // Atlas framework (?) + // http://www.nikhilk.net/Entry.aspx?id=93 + debug.trace(msg); + } + }, + + /** @id MochiKit.Logging.Logger.prototype.dispatchListeners */ + dispatchListeners: function (msg) { + for (var k in this.listeners) { + var pair = this.listeners[k]; + if (pair.ident != k || (pair[0] && !pair[0](msg))) { + continue; + } + pair[1](msg); + } + }, + + /** @id MochiKit.Logging.Logger.prototype.addListener */ + addListener: function (ident, filter, listener) { + if (typeof(filter) == 'string') { + filter = MochiKit.Logging.logLevelAtLeast(filter); + } + var entry = [filter, listener]; + entry.ident = ident; + this.listeners[ident] = entry; + }, + + /** @id MochiKit.Logging.Logger.prototype.removeListener */ + removeListener: function (ident) { + delete this.listeners[ident]; + }, + + /** @id MochiKit.Logging.Logger.prototype.baseLog */ + baseLog: function (level, message/*, ...*/) { + if (typeof(level) == "number") { + if (level >= MochiKit.Logging.LogLevel.FATAL) { + level = 'FATAL'; + } else if (level >= MochiKit.Logging.LogLevel.ERROR) { + level = 'ERROR'; + } else if (level >= MochiKit.Logging.LogLevel.WARNING) { + level = 'WARNING'; + } else if (level >= MochiKit.Logging.LogLevel.INFO) { + level = 'INFO'; + } else { + level = 'DEBUG'; + } + } + var msg = new MochiKit.Logging.LogMessage( + this.counter, + level, + MochiKit.Base.extend(null, arguments, 1) + ); + this._messages.push(msg); + this.dispatchListeners(msg); + if (this.useNativeConsole) { + this.logToConsole(msg.level + ": " + msg.info.join(" ")); + } + this.counter += 1; + while (this.maxSize >= 0 && this._messages.length > this.maxSize) { + this._messages.shift(); + } + }, + + /** @id MochiKit.Logging.Logger.prototype.getMessages */ + getMessages: function (howMany) { + var firstMsg = 0; + if (!(typeof(howMany) == 'undefined' || howMany === null)) { + firstMsg = Math.max(0, this._messages.length - howMany); + } + return this._messages.slice(firstMsg); + }, + + /** @id MochiKit.Logging.Logger.prototype.getMessageText */ + getMessageText: function (howMany) { + if (typeof(howMany) == 'undefined' || howMany === null) { + howMany = 30; + } + var messages = this.getMessages(howMany); + if (messages.length) { + var lst = map(function (m) { + return '\n [' + m.num + '] ' + m.level + ': ' + m.info.join(' '); + }, messages); + lst.unshift('LAST ' + messages.length + ' MESSAGES:'); + return lst.join(''); + } + return ''; + }, + + /** @id MochiKit.Logging.Logger.prototype.debuggingBookmarklet */ + debuggingBookmarklet: function (inline) { + if (typeof(MochiKit.LoggingPane) == "undefined") { + alert(this.getMessageText()); + } else { + MochiKit.LoggingPane.createLoggingPane(inline || false); + } + } +}; + +MochiKit.Logging.__new__ = function () { + this.LogLevel = { + ERROR: 40, + FATAL: 50, + WARNING: 30, + INFO: 20, + DEBUG: 10 + }; + + var m = MochiKit.Base; + m.registerComparator("LogMessage", + this.isLogMessage, + this.compareLogMessage + ); + + var partial = m.partial; + + var Logger = this.Logger; + var baseLog = Logger.prototype.baseLog; + m.update(this.Logger.prototype, { + debug: partial(baseLog, 'DEBUG'), + log: partial(baseLog, 'INFO'), + error: partial(baseLog, 'ERROR'), + fatal: partial(baseLog, 'FATAL'), + warning: partial(baseLog, 'WARNING') + }); + + // indirectly find logger so it can be replaced + var self = this; + var connectLog = function (name) { + return function () { + self.logger[name].apply(self.logger, arguments); + }; + }; + + /** @id MochiKit.Logging.log */ + this.log = connectLog('log'); + /** @id MochiKit.Logging.logError */ + this.logError = connectLog('error'); + /** @id MochiKit.Logging.logDebug */ + this.logDebug = connectLog('debug'); + /** @id MochiKit.Logging.logFatal */ + this.logFatal = connectLog('fatal'); + /** @id MochiKit.Logging.logWarning */ + this.logWarning = connectLog('warning'); + this.logger = new Logger(); + this.logger.useNativeConsole = true; + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + +}; + +if (typeof(printfire) == "undefined" && + typeof(document) != "undefined" && document.createEvent && + typeof(dispatchEvent) != "undefined") { + // FireBug really should be less lame about this global function + printfire = function () { + printfire.args = arguments; + var ev = document.createEvent("Events"); + ev.initEvent("printfire", false, true); + dispatchEvent(ev); + }; +} + +MochiKit.Logging.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Logging); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/LoggingPane.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/LoggingPane.js new file mode 100644 index 000000000..95c4fe590 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/LoggingPane.js @@ -0,0 +1,353 @@ +/*** + +MochiKit.LoggingPane 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('LoggingPane', ['Base', 'Logging']); + +MochiKit.LoggingPane.NAME = "MochiKit.LoggingPane"; +MochiKit.LoggingPane.VERSION = "1.4.2"; +MochiKit.LoggingPane.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +MochiKit.LoggingPane.toString = function () { + return this.__repr__(); +}; + +/** @id MochiKit.LoggingPane.createLoggingPane */ +MochiKit.LoggingPane.createLoggingPane = function (inline/* = false */) { + var m = MochiKit.LoggingPane; + inline = !(!inline); + if (m._loggingPane && m._loggingPane.inline != inline) { + m._loggingPane.closePane(); + m._loggingPane = null; + } + if (!m._loggingPane || m._loggingPane.closed) { + m._loggingPane = new m.LoggingPane(inline, MochiKit.Logging.logger); + } + return m._loggingPane; +}; + +/** @id MochiKit.LoggingPane.LoggingPane */ +MochiKit.LoggingPane.LoggingPane = function (inline/* = false */, logger/* = MochiKit.Logging.logger */) { + + /* Use a div if inline, pop up a window if not */ + /* Create the elements */ + if (typeof(logger) == "undefined" || logger === null) { + logger = MochiKit.Logging.logger; + } + this.logger = logger; + var update = MochiKit.Base.update; + var updatetree = MochiKit.Base.updatetree; + var bind = MochiKit.Base.bind; + var clone = MochiKit.Base.clone; + var win = window; + var uid = "_MochiKit_LoggingPane"; + if (typeof(MochiKit.DOM) != "undefined") { + win = MochiKit.DOM.currentWindow(); + } + if (!inline) { + // name the popup with the base URL for uniqueness + var url = win.location.href.split("?")[0].replace(/[#:\/.><&%-]/g, "_"); + var name = uid + "_" + url; + var nwin = win.open("", name, "dependent,resizable,height=200"); + if (!nwin) { + alert("Not able to open debugging window due to pop-up blocking."); + return undefined; + } + nwin.document.write( + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ' + + '"http://www.w3.org/TR/html4/loose.dtd">' + + '<html><head><title>[MochiKit.LoggingPane]</title></head>' + + '<body></body></html>' + ); + nwin.document.close(); + nwin.document.title += ' ' + win.document.title; + win = nwin; + } + var doc = win.document; + this.doc = doc; + + // Connect to the debug pane if it already exists (i.e. in a window orphaned by the page being refreshed) + var debugPane = doc.getElementById(uid); + var existing_pane = !!debugPane; + if (debugPane && typeof(debugPane.loggingPane) != "undefined") { + debugPane.loggingPane.logger = this.logger; + debugPane.loggingPane.buildAndApplyFilter(); + return debugPane.loggingPane; + } + + if (existing_pane) { + // clear any existing contents + var child; + while ((child = debugPane.firstChild)) { + debugPane.removeChild(child); + } + } else { + debugPane = doc.createElement("div"); + debugPane.id = uid; + } + debugPane.loggingPane = this; + var levelFilterField = doc.createElement("input"); + var infoFilterField = doc.createElement("input"); + var filterButton = doc.createElement("button"); + var loadButton = doc.createElement("button"); + var clearButton = doc.createElement("button"); + var closeButton = doc.createElement("button"); + var logPaneArea = doc.createElement("div"); + var logPane = doc.createElement("div"); + + /* Set up the functions */ + var listenerId = uid + "_Listener"; + this.colorTable = clone(this.colorTable); + var messages = []; + var messageFilter = null; + + /** @id MochiKit.LoggingPane.messageLevel */ + var messageLevel = function (msg) { + var level = msg.level; + if (typeof(level) == "number") { + level = MochiKit.Logging.LogLevel[level]; + } + return level; + }; + + /** @id MochiKit.LoggingPane.messageText */ + var messageText = function (msg) { + return msg.info.join(" "); + }; + + /** @id MochiKit.LoggingPane.addMessageText */ + var addMessageText = bind(function (msg) { + var level = messageLevel(msg); + var text = messageText(msg); + var c = this.colorTable[level]; + var p = doc.createElement("span"); + p.className = "MochiKit-LogMessage MochiKit-LogLevel-" + level; + p.style.cssText = "margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: " + c; + p.appendChild(doc.createTextNode(level + ": " + text)); + logPane.appendChild(p); + logPane.appendChild(doc.createElement("br")); + if (logPaneArea.offsetHeight > logPaneArea.scrollHeight) { + logPaneArea.scrollTop = 0; + } else { + logPaneArea.scrollTop = logPaneArea.scrollHeight; + } + }, this); + + /** @id MochiKit.LoggingPane.addMessage */ + var addMessage = function (msg) { + messages[messages.length] = msg; + addMessageText(msg); + }; + + /** @id MochiKit.LoggingPane.buildMessageFilter */ + var buildMessageFilter = function () { + var levelre, infore; + try { + /* Catch any exceptions that might arise due to invalid regexes */ + levelre = new RegExp(levelFilterField.value); + infore = new RegExp(infoFilterField.value); + } catch(e) { + /* If there was an error with the regexes, do no filtering */ + logDebug("Error in filter regex: " + e.message); + return null; + } + + return function (msg) { + return ( + levelre.test(messageLevel(msg)) && + infore.test(messageText(msg)) + ); + }; + }; + + /** @id MochiKit.LoggingPane.clearMessagePane */ + var clearMessagePane = function () { + while (logPane.firstChild) { + logPane.removeChild(logPane.firstChild); + } + }; + + /** @id MochiKit.LoggingPane.clearMessages */ + var clearMessages = function () { + messages = []; + clearMessagePane(); + }; + + /** @id MochiKit.LoggingPane.closePane */ + var closePane = bind(function () { + if (this.closed) { + return; + } + this.closed = true; + if (MochiKit.LoggingPane._loggingPane == this) { + MochiKit.LoggingPane._loggingPane = null; + } + this.logger.removeListener(listenerId); + try { + try { + debugPane.loggingPane = null; + } catch(e) { logFatal("Bookmarklet was closed incorrectly."); } + if (inline) { + debugPane.parentNode.removeChild(debugPane); + } else { + this.win.close(); + } + } catch(e) {} + }, this); + + /** @id MochiKit.LoggingPane.filterMessages */ + var filterMessages = function () { + clearMessagePane(); + + for (var i = 0; i < messages.length; i++) { + var msg = messages[i]; + if (messageFilter === null || messageFilter(msg)) { + addMessageText(msg); + } + } + }; + + this.buildAndApplyFilter = function () { + messageFilter = buildMessageFilter(); + + filterMessages(); + + this.logger.removeListener(listenerId); + this.logger.addListener(listenerId, messageFilter, addMessage); + }; + + + /** @id MochiKit.LoggingPane.loadMessages */ + var loadMessages = bind(function () { + messages = this.logger.getMessages(); + filterMessages(); + }, this); + + /** @id MochiKit.LoggingPane.filterOnEnter */ + var filterOnEnter = bind(function (event) { + event = event || window.event; + key = event.which || event.keyCode; + if (key == 13) { + this.buildAndApplyFilter(); + } + }, this); + + /* Create the debug pane */ + var style = "display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: " + this.logFont; + if (inline) { + style += "; height: 10em; border-top: 2px solid black"; + } else { + style += "; height: 100%;"; + } + debugPane.style.cssText = style; + + if (!existing_pane) { + doc.body.appendChild(debugPane); + } + + /* Create the filter fields */ + style = {"cssText": "width: 33%; display: inline; font: " + this.logFont}; + + updatetree(levelFilterField, { + "value": "FATAL|ERROR|WARNING|INFO|DEBUG", + "onkeypress": filterOnEnter, + "style": style + }); + debugPane.appendChild(levelFilterField); + + updatetree(infoFilterField, { + "value": ".*", + "onkeypress": filterOnEnter, + "style": style + }); + debugPane.appendChild(infoFilterField); + + /* Create the buttons */ + style = "width: 8%; display:inline; font: " + this.logFont; + + filterButton.appendChild(doc.createTextNode("Filter")); + filterButton.onclick = bind("buildAndApplyFilter", this); + filterButton.style.cssText = style; + debugPane.appendChild(filterButton); + + loadButton.appendChild(doc.createTextNode("Load")); + loadButton.onclick = loadMessages; + loadButton.style.cssText = style; + debugPane.appendChild(loadButton); + + clearButton.appendChild(doc.createTextNode("Clear")); + clearButton.onclick = clearMessages; + clearButton.style.cssText = style; + debugPane.appendChild(clearButton); + + closeButton.appendChild(doc.createTextNode("Close")); + closeButton.onclick = closePane; + closeButton.style.cssText = style; + debugPane.appendChild(closeButton); + + /* Create the logging pane */ + logPaneArea.style.cssText = "overflow: auto; width: 100%"; + logPane.style.cssText = "width: 100%; height: " + (inline ? "8em" : "100%"); + + logPaneArea.appendChild(logPane); + debugPane.appendChild(logPaneArea); + + this.buildAndApplyFilter(); + loadMessages(); + + if (inline) { + this.win = undefined; + } else { + this.win = win; + } + this.inline = inline; + this.closePane = closePane; + this.closed = false; + + + return this; +}; + +MochiKit.LoggingPane.LoggingPane.prototype = { + "logFont": "8pt Verdana,sans-serif", + "colorTable": { + "ERROR": "red", + "FATAL": "darkred", + "WARNING": "blue", + "INFO": "black", + "DEBUG": "green" + } +}; + + +MochiKit.LoggingPane.EXPORT_OK = [ + "LoggingPane" +]; + +MochiKit.LoggingPane.EXPORT = [ + "createLoggingPane" +]; + +MochiKit.LoggingPane.__new__ = function () { + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK) + }; + + MochiKit.Base.nameFunctions(this); + + MochiKit.LoggingPane._loggingPane = null; + +}; + +MochiKit.LoggingPane.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.LoggingPane); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/MochiKit.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/MochiKit.js new file mode 100644 index 000000000..1ec157ed2 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/MochiKit.js @@ -0,0 +1,188 @@ +/*** + +MochiKit.MochiKit 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +if (typeof(MochiKit) == 'undefined') { + MochiKit = {}; +} + +if (typeof(MochiKit.MochiKit) == 'undefined') { + /** @id MochiKit.MochiKit */ + MochiKit.MochiKit = {}; +} + +MochiKit.MochiKit.NAME = "MochiKit.MochiKit"; +MochiKit.MochiKit.VERSION = "1.4.2"; +MochiKit.MochiKit.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +/** @id MochiKit.MochiKit.toString */ +MochiKit.MochiKit.toString = function () { + return this.__repr__(); +}; + +/** @id MochiKit.MochiKit.SUBMODULES */ +MochiKit.MochiKit.SUBMODULES = [ + "Base", + "Iter", + "Logging", + "DateTime", + "Format", + "Async", + "DOM", + "Selector", + "Style", + "LoggingPane", + "Color", + "Signal", + "Position", + "Visual", + "DragAndDrop", + "Sortable" +]; + +if (typeof(JSAN) != 'undefined' || typeof(dojo) != 'undefined') { + if (typeof(dojo) != 'undefined') { + dojo.provide('MochiKit.MochiKit'); + (function (lst) { + for (var i = 0; i < lst.length; i++) { + dojo.require("MochiKit." + lst[i]); + } + })(MochiKit.MochiKit.SUBMODULES); + } + if (typeof(JSAN) != 'undefined') { + (function (lst) { + for (var i = 0; i < lst.length; i++) { + JSAN.use("MochiKit." + lst[i], []); + } + })(MochiKit.MochiKit.SUBMODULES); + } + (function () { + var extend = MochiKit.Base.extend; + var self = MochiKit.MochiKit; + var modules = self.SUBMODULES; + var EXPORT = []; + var EXPORT_OK = []; + var EXPORT_TAGS = {}; + var i, k, m, all; + for (i = 0; i < modules.length; i++) { + m = MochiKit[modules[i]]; + extend(EXPORT, m.EXPORT); + extend(EXPORT_OK, m.EXPORT_OK); + for (k in m.EXPORT_TAGS) { + EXPORT_TAGS[k] = extend(EXPORT_TAGS[k], m.EXPORT_TAGS[k]); + } + all = m.EXPORT_TAGS[":all"]; + if (!all) { + all = extend(null, m.EXPORT, m.EXPORT_OK); + } + var j; + for (j = 0; j < all.length; j++) { + k = all[j]; + self[k] = m[k]; + } + } + self.EXPORT = EXPORT; + self.EXPORT_OK = EXPORT_OK; + self.EXPORT_TAGS = EXPORT_TAGS; + }()); + +} else { + if (typeof(MochiKit.__compat__) == 'undefined') { + MochiKit.__compat__ = true; + } + (function () { + if (typeof(document) == "undefined") { + return; + } + var scripts = document.getElementsByTagName("script"); + var kXHTMLNSURI = "http://www.w3.org/1999/xhtml"; + var kSVGNSURI = "http://www.w3.org/2000/svg"; + var kXLINKNSURI = "http://www.w3.org/1999/xlink"; + var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var base = null; + var baseElem = null; + var allScripts = {}; + var i; + var src; + for (i = 0; i < scripts.length; i++) { + src = null; + switch (scripts[i].namespaceURI) { + case kSVGNSURI: + src = scripts[i].getAttributeNS(kXLINKNSURI, "href"); + break; + /* + case null: // HTML + case '': // HTML + case kXHTMLNSURI: + case kXULNSURI: + */ + default: + src = scripts[i].getAttribute("src"); + break; + } + if (!src) { + continue; + } + allScripts[src] = true; + if (src.match(/MochiKit.js(\?.*)?$/)) { + base = src.substring(0, src.lastIndexOf('MochiKit.js')); + baseElem = scripts[i]; + } + } + if (base === null) { + return; + } + var modules = MochiKit.MochiKit.SUBMODULES; + for (var i = 0; i < modules.length; i++) { + if (MochiKit[modules[i]]) { + continue; + } + var uri = base + modules[i] + '.js'; + if (uri in allScripts) { + continue; + } + if (baseElem.namespaceURI == kSVGNSURI || + baseElem.namespaceURI == kXULNSURI) { + // SVG, XUL + /* + SVG does not support document.write, so if Safari wants to + support SVG tests it should fix its deferred loading bug + (see following below). + + */ + var s = document.createElementNS(baseElem.namespaceURI, 'script'); + s.setAttribute("id", "MochiKit_" + base + modules[i]); + if (baseElem.namespaceURI == kSVGNSURI) { + s.setAttributeNS(kXLINKNSURI, 'href', uri); + } else { + s.setAttribute('src', uri); + } + s.setAttribute("type", "application/x-javascript"); + baseElem.parentNode.appendChild(s); + } else { + // HTML, XHTML + /* + DOM can not be used here because Safari does + deferred loading of scripts unless they are + in the document or inserted with document.write + + This is not XHTML compliant. If you want XHTML + compliance then you must use the packed version of MochiKit + or include each script individually (basically unroll + these document.write calls into your XHTML source) + + */ + document.write('<' + baseElem.nodeName + ' src="' + uri + + '" type="text/javascript"></script>'); + } + }; + })(); +} diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/MockDOM.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/MockDOM.js new file mode 100644 index 000000000..250d12eed --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/MockDOM.js @@ -0,0 +1,115 @@ +/*** + +MochiKit.MockDOM 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +if (typeof(MochiKit) == "undefined") { + MochiKit = {}; +} + +if (typeof(MochiKit.MockDOM) == "undefined") { + MochiKit.MockDOM = {}; +} + +MochiKit.MockDOM.NAME = "MochiKit.MockDOM"; +MochiKit.MockDOM.VERSION = "1.4.2"; + +MochiKit.MockDOM.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +/** @id MochiKit.MockDOM.toString */ +MochiKit.MockDOM.toString = function () { + return this.__repr__(); +}; + +/** @id MochiKit.MockDOM.createDocument */ +MochiKit.MockDOM.createDocument = function () { + var doc = new MochiKit.MockDOM.MockElement("DOCUMENT"); + doc.body = doc.createElement("BODY"); + doc.appendChild(doc.body); + return doc; +}; + +/** @id MochiKit.MockDOM.MockElement */ +MochiKit.MockDOM.MockElement = function (name, data, ownerDocument) { + this.tagName = this.nodeName = name.toUpperCase(); + this.ownerDocument = ownerDocument || null; + if (name == "DOCUMENT") { + this.nodeType = 9; + this.childNodes = []; + } else if (typeof(data) == "string") { + this.nodeValue = data; + this.nodeType = 3; + } else { + this.nodeType = 1; + this.childNodes = []; + } + if (name.substring(0, 1) == "<") { + var nameattr = name.substring( + name.indexOf('"') + 1, name.lastIndexOf('"')); + name = name.substring(1, name.indexOf(" ")); + this.tagName = this.nodeName = name.toUpperCase(); + this.setAttribute("name", nameattr); + } +}; + +MochiKit.MockDOM.MockElement.prototype = { + /** @id MochiKit.MockDOM.MockElement.prototype.createElement */ + createElement: function (tagName) { + return new MochiKit.MockDOM.MockElement(tagName, null, this.nodeType == 9 ? this : this.ownerDocument); + }, + /** @id MochiKit.MockDOM.MockElement.prototype.createTextNode */ + createTextNode: function (text) { + return new MochiKit.MockDOM.MockElement("text", text, this.nodeType == 9 ? this : this.ownerDocument); + }, + /** @id MochiKit.MockDOM.MockElement.prototype.setAttribute */ + setAttribute: function (name, value) { + this[name] = value; + }, + /** @id MochiKit.MockDOM.MockElement.prototype.getAttribute */ + getAttribute: function (name) { + return this[name]; + }, + /** @id MochiKit.MockDOM.MockElement.prototype.appendChild */ + appendChild: function (child) { + this.childNodes.push(child); + }, + /** @id MochiKit.MockDOM.MockElement.prototype.toString */ + toString: function () { + return "MockElement(" + this.tagName + ")"; + }, + /** @id MochiKit.MockDOM.MockElement.prototype.getElementsByTagName */ + getElementsByTagName: function (tagName) { + var foundElements = []; + MochiKit.Base.nodeWalk(this, function(node){ + if (tagName == '*' || tagName == node.tagName) { + foundElements.push(node); + return node.childNodes; + } + }); + return foundElements; + } +}; + + /** @id MochiKit.MockDOM.EXPORT_OK */ +MochiKit.MockDOM.EXPORT_OK = [ + "mockElement", + "createDocument" +]; + + /** @id MochiKit.MockDOM.EXPORT */ +MochiKit.MockDOM.EXPORT = [ + "document" +]; + +MochiKit.MockDOM.__new__ = function () { + this.document = this.createDocument(); +}; + +MochiKit.MockDOM.__new__(); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Position.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Position.js new file mode 100644 index 000000000..70288c7c1 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Position.js @@ -0,0 +1,236 @@ +/*** + +MochiKit.Position 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005-2006 Bob Ippolito and others. All rights Reserved. + +***/ + +MochiKit.Base._deps('Position', ['Base', 'DOM', 'Style']); + +MochiKit.Position.NAME = 'MochiKit.Position'; +MochiKit.Position.VERSION = '1.4.2'; +MochiKit.Position.__repr__ = function () { + return '[' + this.NAME + ' ' + this.VERSION + ']'; +}; +MochiKit.Position.toString = function () { + return this.__repr__(); +}; + +MochiKit.Position.EXPORT_OK = []; + +MochiKit.Position.EXPORT = [ +]; + + +MochiKit.Base.update(MochiKit.Position, { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + /** @id MochiKit.Position.prepare */ + prepare: function () { + var deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + var deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + this.windowOffset = new MochiKit.Style.Coordinates(deltaX, deltaY); + }, + + /** @id MochiKit.Position.cumulativeOffset */ + cumulativeOffset: function (element) { + var valueT = 0; + var valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new MochiKit.Style.Coordinates(valueL, valueT); + }, + + /** @id MochiKit.Position.realOffset */ + realOffset: function (element) { + var valueT = 0; + var valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new MochiKit.Style.Coordinates(valueL, valueT); + }, + + /** @id MochiKit.Position.within */ + within: function (element, x, y) { + if (this.includeScrollOffsets) { + return this.withinIncludingScrolloffsets(element, x, y); + } + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + if (element.style.position == "fixed") { + this.offset.x += this.windowOffset.x; + this.offset.y += this.windowOffset.y; + } + + return (y >= this.offset.y && + y < this.offset.y + element.offsetHeight && + x >= this.offset.x && + x < this.offset.x + element.offsetWidth); + }, + + /** @id MochiKit.Position.withinIncludingScrolloffsets */ + withinIncludingScrolloffsets: function (element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache.x - this.windowOffset.x; + this.ycomp = y + offsetcache.y - this.windowOffset.y; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset.y && + this.ycomp < this.offset.y + element.offsetHeight && + this.xcomp >= this.offset.x && + this.xcomp < this.offset.x + element.offsetWidth); + }, + + // within must be called directly before + /** @id MochiKit.Position.overlap */ + overlap: function (mode, element) { + if (!mode) { + return 0; + } + if (mode == 'vertical') { + return ((this.offset.y + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + } + if (mode == 'horizontal') { + return ((this.offset.x + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + } + }, + + /** @id MochiKit.Position.absolutize */ + absolutize: function (element) { + element = MochiKit.DOM.getElement(element); + if (element.style.position == 'absolute') { + return; + } + MochiKit.Position.prepare(); + + var offsets = MochiKit.Position.positionedOffset(element); + var width = element.clientWidth; + var height = element.clientHeight; + + var oldStyle = { + 'position': element.style.position, + 'left': offsets.x - parseFloat(element.style.left || 0), + 'top': offsets.y - parseFloat(element.style.top || 0), + 'width': element.style.width, + 'height': element.style.height + }; + + element.style.position = 'absolute'; + element.style.top = offsets.y + 'px'; + element.style.left = offsets.x + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + + return oldStyle; + }, + + /** @id MochiKit.Position.positionedOffset */ + positionedOffset: function (element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = MochiKit.Style.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') { + break; + } + } + } while (element); + return new MochiKit.Style.Coordinates(valueL, valueT); + }, + + /** @id MochiKit.Position.relativize */ + relativize: function (element, oldPos) { + element = MochiKit.DOM.getElement(element); + if (element.style.position == 'relative') { + return; + } + MochiKit.Position.prepare(); + + var top = parseFloat(element.style.top || 0) - + (oldPos['top'] || 0); + var left = parseFloat(element.style.left || 0) - + (oldPos['left'] || 0); + + element.style.position = oldPos['position']; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = oldPos['width']; + element.style.height = oldPos['height']; + }, + + /** @id MochiKit.Position.clone */ + clone: function (source, target) { + source = MochiKit.DOM.getElement(source); + target = MochiKit.DOM.getElement(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets.y + 'px'; + target.style.left = offsets.x + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + /** @id MochiKit.Position.page */ + page: function (forElement) { + var valueT = 0; + var valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && MochiKit.Style.getStyle(element, 'position') == 'absolute') { + break; + } + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return new MochiKit.Style.Coordinates(valueL, valueT); + } +}); + +MochiKit.Position.__new__ = function (win) { + var m = MochiKit.Base; + this.EXPORT_TAGS = { + ':common': this.EXPORT, + ':all': m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); +}; + +MochiKit.Position.__new__(this); + +MochiKit.Base._exportSymbols(this, MochiKit.Position);
\ No newline at end of file diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Selector.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Selector.js new file mode 100644 index 000000000..e428cad16 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Selector.js @@ -0,0 +1,415 @@ +/*** + +MochiKit.Selector 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito and others. All rights Reserved. + +***/ + +MochiKit.Base._deps('Selector', ['Base', 'DOM', 'Iter']); + +MochiKit.Selector.NAME = "MochiKit.Selector"; +MochiKit.Selector.VERSION = "1.4.2"; + +MochiKit.Selector.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +MochiKit.Selector.toString = function () { + return this.__repr__(); +}; + +MochiKit.Selector.EXPORT = [ + "Selector", + "findChildElements", + "findDocElements", + "$$" +]; + +MochiKit.Selector.EXPORT_OK = [ +]; + +MochiKit.Selector.Selector = function (expression) { + this.params = {classNames: [], pseudoClassNames: []}; + this.expression = expression.toString().replace(/(^\s+|\s+$)/g, ''); + this.parseExpression(); + this.compileMatcher(); +}; + +MochiKit.Selector.Selector.prototype = { + /*** + + Selector class: convenient object to make CSS selections. + + ***/ + __class__: MochiKit.Selector.Selector, + + /** @id MochiKit.Selector.Selector.prototype.parseExpression */ + parseExpression: function () { + function abort(message) { + throw 'Parse error in selector: ' + message; + } + + if (this.expression == '') { + abort('empty expression'); + } + + var repr = MochiKit.Base.repr; + var params = this.params; + var expr = this.expression; + var match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!^$*]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') { + return this.params.wildcard = true; + } + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+(?:\([^)]*\))?)(.*)/i)) { + modifier = match[1]; + clause = match[2]; + rest = match[3]; + switch (modifier) { + case '#': + params.id = clause; + break; + case '.': + params.classNames.push(clause); + break; + case ':': + params.pseudoClassNames.push(clause); + break; + case '': + case undefined: + params.tagName = clause.toUpperCase(); + break; + default: + abort(repr(expr)); + } + expr = rest; + } + + if (expr.length > 0) { + abort(repr(expr)); + } + }, + + /** @id MochiKit.Selector.Selector.prototype.buildMatchExpression */ + buildMatchExpression: function () { + var repr = MochiKit.Base.repr; + var params = this.params; + var conditions = []; + var clause, i; + + function childElements(element) { + return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)"; + } + + if (params.wildcard) { + conditions.push('true'); + } + if (clause = params.id) { + conditions.push('element.id == ' + repr(clause)); + } + if (clause = params.tagName) { + conditions.push('element.tagName.toUpperCase() == ' + repr(clause)); + } + if ((clause = params.classNames).length > 0) { + for (i = 0; i < clause.length; i++) { + conditions.push('MochiKit.DOM.hasElementClass(element, ' + repr(clause[i]) + ')'); + } + } + if ((clause = params.pseudoClassNames).length > 0) { + for (i = 0; i < clause.length; i++) { + var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/); + var pseudoClass = match[1]; + var pseudoClassArgument = match[2]; + switch (pseudoClass) { + case 'root': + conditions.push('element.nodeType == 9 || element === element.ownerDocument.documentElement'); break; + case 'nth-child': + case 'nth-last-child': + case 'nth-of-type': + case 'nth-last-of-type': + match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/); + if (!match) { + throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument; + } + var a, b; + if (match[0] == 'odd') { + a = 2; + b = 1; + } else if (match[0] == 'even') { + a = 2; + b = 0; + } else { + a = match[2] && parseInt(match) || null; + b = parseInt(match[3]); + } + conditions.push('this.nthChild(element,' + a + ',' + b + + ',' + !!pseudoClass.match('^nth-last') // Reverse + + ',' + !!pseudoClass.match('of-type$') // Restrict to same tagName + + ')'); + break; + case 'first-child': + conditions.push('this.nthChild(element, null, 1)'); + break; + case 'last-child': + conditions.push('this.nthChild(element, null, 1, true)'); + break; + case 'first-of-type': + conditions.push('this.nthChild(element, null, 1, false, true)'); + break; + case 'last-of-type': + conditions.push('this.nthChild(element, null, 1, true, true)'); + break; + case 'only-child': + conditions.push(childElements('element.parentNode') + '.length == 1'); + break; + case 'only-of-type': + conditions.push('MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, ' + childElements('element.parentNode') + ').length == 1'); + break; + case 'empty': + conditions.push('element.childNodes.length == 0'); + break; + case 'enabled': + conditions.push('(this.isUIElement(element) && element.disabled === false)'); + break; + case 'disabled': + conditions.push('(this.isUIElement(element) && element.disabled === true)'); + break; + case 'checked': + conditions.push('(this.isUIElement(element) && element.checked === true)'); + break; + case 'not': + var subselector = new MochiKit.Selector.Selector(pseudoClassArgument); + conditions.push('!( ' + subselector.buildMatchExpression() + ')') + break; + } + } + } + if (clause = params.attributes) { + MochiKit.Base.map(function (attribute) { + var value = 'MochiKit.DOM.getNodeAttribute(element, ' + repr(attribute.name) + ')'; + var splitValueBy = function (delimiter) { + return value + '.split(' + repr(delimiter) + ')'; + } + conditions.push(value + ' != null'); + switch (attribute.operator) { + case '=': + conditions.push(value + ' == ' + repr(attribute.value)); + break; + case '~=': + conditions.push('MochiKit.Base.findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1'); + break; + case '^=': + conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value)); + break; + case '$=': + conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value)); + break; + case '*=': + conditions.push(value + '.match(' + repr(attribute.value) + ')'); + break; + case '|=': + conditions.push(splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase())); + break; + case '!=': + conditions.push(value + ' != ' + repr(attribute.value)); + break; + case '': + case undefined: + // Condition already added above + break; + default: + throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }, clause); + } + + return conditions.join(' && '); + }, + + /** @id MochiKit.Selector.Selector.prototype.compileMatcher */ + compileMatcher: function () { + var code = 'return (!element.tagName) ? false : ' + + this.buildMatchExpression() + ';'; + this.match = new Function('element', code); + }, + + /** @id MochiKit.Selector.Selector.prototype.nthChild */ + nthChild: function (element, a, b, reverse, sametag){ + var siblings = MochiKit.Base.filter(function (node) { + return node.nodeType == 1; + }, element.parentNode.childNodes); + if (sametag) { + siblings = MochiKit.Base.filter(function (node) { + return node.tagName == element.tagName; + }, siblings); + } + if (reverse) { + siblings = MochiKit.Iter.reversed(siblings); + } + if (a) { + var actualIndex = MochiKit.Base.findIdentical(siblings, element); + return ((actualIndex + 1 - b) / a) % 1 == 0; + } else { + return b == MochiKit.Base.findIdentical(siblings, element) + 1; + } + }, + + /** @id MochiKit.Selector.Selector.prototype.isUIElement */ + isUIElement: function (element) { + return MochiKit.Base.findValue(['input', 'button', 'select', 'option', 'textarea', 'object'], + element.tagName.toLowerCase()) > -1; + }, + + /** @id MochiKit.Selector.Selector.prototype.findElements */ + findElements: function (scope, axis) { + var element; + + if (axis == undefined) { + axis = ""; + } + + function inScope(element, scope) { + if (axis == "") { + return MochiKit.DOM.isChildNode(element, scope); + } else if (axis == ">") { + return element.parentNode === scope; + } else if (axis == "+") { + return element === nextSiblingElement(scope); + } else if (axis == "~") { + var sibling = scope; + while (sibling = nextSiblingElement(sibling)) { + if (element === sibling) { + return true; + } + } + return false; + } else { + throw "Invalid axis: " + axis; + } + } + + if (element = MochiKit.DOM.getElement(this.params.id)) { + if (this.match(element)) { + if (!scope || inScope(element, scope)) { + return [element]; + } + } + } + + function nextSiblingElement(node) { + node = node.nextSibling; + while (node && node.nodeType != 1) { + node = node.nextSibling; + } + return node; + } + + if (axis == "") { + scope = (scope || MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName || '*'); + } else if (axis == ">") { + if (!scope) { + throw "> combinator not allowed without preceding expression"; + } + scope = MochiKit.Base.filter(function (node) { + return node.nodeType == 1; + }, scope.childNodes); + } else if (axis == "+") { + if (!scope) { + throw "+ combinator not allowed without preceding expression"; + } + scope = nextSiblingElement(scope) && [nextSiblingElement(scope)]; + } else if (axis == "~") { + if (!scope) { + throw "~ combinator not allowed without preceding expression"; + } + var newscope = []; + while (nextSiblingElement(scope)) { + scope = nextSiblingElement(scope); + newscope.push(scope); + } + scope = newscope; + } + + if (!scope) { + return []; + } + + var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) { + return this.match(scopeElt); + }, this), scope); + + return results; + }, + + /** @id MochiKit.Selector.Selector.prototype.repr */ + repr: function () { + return 'Selector(' + this.expression + ')'; + }, + + toString: MochiKit.Base.forwardCall("repr") +}; + +MochiKit.Base.update(MochiKit.Selector, { + + /** @id MochiKit.Selector.findChildElements */ + findChildElements: function (element, expressions) { + var uniq = function(arr) { + var res = []; + for (var i = 0; i < arr.length; i++) { + if (MochiKit.Base.findIdentical(res, arr[i]) < 0) { + res.push(arr[i]); + } + } + return res; + }; + return MochiKit.Base.flattenArray(MochiKit.Base.map(function (expression) { + var nextScope = ""; + var reducer = function (results, expr) { + if (match = expr.match(/^[>+~]$/)) { + nextScope = match[0]; + return results; + } else { + var selector = new MochiKit.Selector.Selector(expr); + var elements = MochiKit.Iter.reduce(function (elements, result) { + return MochiKit.Base.extend(elements, selector.findElements(result || element, nextScope)); + }, results, []); + nextScope = ""; + return elements; + } + }; + var exprs = expression.replace(/(^\s+|\s+$)/g, '').split(/\s+/); + return uniq(MochiKit.Iter.reduce(reducer, exprs, [null])); + }, expressions)); + }, + + findDocElements: function () { + return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments); + }, + + __new__: function () { + var m = MochiKit.Base; + + this.$$ = this.findDocElements; + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + } +}); + +MochiKit.Selector.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Selector); + diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Signal.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Signal.js new file mode 100644 index 000000000..d49513c45 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Signal.js @@ -0,0 +1,897 @@ +/*** + +MochiKit.Signal 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('Signal', ['Base', 'DOM', 'Style']); + +MochiKit.Signal.NAME = 'MochiKit.Signal'; +MochiKit.Signal.VERSION = '1.4.2'; + +MochiKit.Signal._observers = []; + +/** @id MochiKit.Signal.Event */ +MochiKit.Signal.Event = function (src, e) { + this._event = e || window.event; + this._src = src; +}; + +MochiKit.Base.update(MochiKit.Signal.Event.prototype, { + + __repr__: function () { + var repr = MochiKit.Base.repr; + var str = '{event(): ' + repr(this.event()) + + ', src(): ' + repr(this.src()) + + ', type(): ' + repr(this.type()) + + ', target(): ' + repr(this.target()); + + if (this.type() && + this.type().indexOf('key') === 0 || + this.type().indexOf('mouse') === 0 || + this.type().indexOf('click') != -1 || + this.type() == 'contextmenu') { + str += ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) + + ', ctrl: ' + repr(this.modifier().ctrl) + + ', meta: ' + repr(this.modifier().meta) + + ', shift: ' + repr(this.modifier().shift) + + ', any: ' + repr(this.modifier().any) + '}'; + } + + if (this.type() && this.type().indexOf('key') === 0) { + str += ', key(): {code: ' + repr(this.key().code) + + ', string: ' + repr(this.key().string) + '}'; + } + + if (this.type() && ( + this.type().indexOf('mouse') === 0 || + this.type().indexOf('click') != -1 || + this.type() == 'contextmenu')) { + + str += ', mouse(): {page: ' + repr(this.mouse().page) + + ', client: ' + repr(this.mouse().client); + + if (this.type() != 'mousemove' && this.type() != 'mousewheel') { + str += ', button: {left: ' + repr(this.mouse().button.left) + + ', middle: ' + repr(this.mouse().button.middle) + + ', right: ' + repr(this.mouse().button.right) + '}'; + } + if (this.type() == 'mousewheel') { + str += ', wheel: ' + repr(this.mouse().wheel); + } + str += '}'; + } + if (this.type() == 'mouseover' || this.type() == 'mouseout' || + this.type() == 'mouseenter' || this.type() == 'mouseleave') { + str += ', relatedTarget(): ' + repr(this.relatedTarget()); + } + str += '}'; + return str; + }, + + /** @id MochiKit.Signal.Event.prototype.toString */ + toString: function () { + return this.__repr__(); + }, + + /** @id MochiKit.Signal.Event.prototype.src */ + src: function () { + return this._src; + }, + + /** @id MochiKit.Signal.Event.prototype.event */ + event: function () { + return this._event; + }, + + /** @id MochiKit.Signal.Event.prototype.type */ + type: function () { + if (this._event.type === "DOMMouseScroll") { + return "mousewheel"; + } else { + return this._event.type || undefined; + } + }, + + /** @id MochiKit.Signal.Event.prototype.target */ + target: function () { + return this._event.target || this._event.srcElement; + }, + + _relatedTarget: null, + /** @id MochiKit.Signal.Event.prototype.relatedTarget */ + relatedTarget: function () { + if (this._relatedTarget !== null) { + return this._relatedTarget; + } + + var elem = null; + if (this.type() == 'mouseover' || this.type() == 'mouseenter') { + elem = (this._event.relatedTarget || + this._event.fromElement); + } else if (this.type() == 'mouseout' || this.type() == 'mouseleave') { + elem = (this._event.relatedTarget || + this._event.toElement); + } + try { + if (elem !== null && elem.nodeType !== null) { + this._relatedTarget = elem; + return elem; + } + } catch (ignore) { + // Firefox 3 throws a permission denied error when accessing + // any property on XUL elements (e.g. scrollbars)... + } + + return undefined; + }, + + _modifier: null, + /** @id MochiKit.Signal.Event.prototype.modifier */ + modifier: function () { + if (this._modifier !== null) { + return this._modifier; + } + var m = {}; + m.alt = this._event.altKey; + m.ctrl = this._event.ctrlKey; + m.meta = this._event.metaKey || false; // IE and Opera punt here + m.shift = this._event.shiftKey; + m.any = m.alt || m.ctrl || m.shift || m.meta; + this._modifier = m; + return m; + }, + + _key: null, + /** @id MochiKit.Signal.Event.prototype.key */ + key: function () { + if (this._key !== null) { + return this._key; + } + var k = {}; + if (this.type() && this.type().indexOf('key') === 0) { + + /* + + If you're looking for a special key, look for it in keydown or + keyup, but never keypress. If you're looking for a Unicode + chracter, look for it with keypress, but never keyup or + keydown. + + Notes: + + FF key event behavior: + key event charCode keyCode + DOWN ku,kd 0 40 + DOWN kp 0 40 + ESC ku,kd 0 27 + ESC kp 0 27 + a ku,kd 0 65 + a kp 97 0 + shift+a ku,kd 0 65 + shift+a kp 65 0 + 1 ku,kd 0 49 + 1 kp 49 0 + shift+1 ku,kd 0 0 + shift+1 kp 33 0 + + IE key event behavior: + (IE doesn't fire keypress events for special keys.) + key event keyCode + DOWN ku,kd 40 + DOWN kp undefined + ESC ku,kd 27 + ESC kp 27 + a ku,kd 65 + a kp 97 + shift+a ku,kd 65 + shift+a kp 65 + 1 ku,kd 49 + 1 kp 49 + shift+1 ku,kd 49 + shift+1 kp 33 + + Safari key event behavior: + (Safari sets charCode and keyCode to something crazy for + special keys.) + key event charCode keyCode + DOWN ku,kd 63233 40 + DOWN kp 63233 63233 + ESC ku,kd 27 27 + ESC kp 27 27 + a ku,kd 97 65 + a kp 97 97 + shift+a ku,kd 65 65 + shift+a kp 65 65 + 1 ku,kd 49 49 + 1 kp 49 49 + shift+1 ku,kd 33 49 + shift+1 kp 33 33 + + */ + + /* look for special keys here */ + if (this.type() == 'keydown' || this.type() == 'keyup') { + k.code = this._event.keyCode; + k.string = (MochiKit.Signal._specialKeys[k.code] || + 'KEY_UNKNOWN'); + this._key = k; + return k; + + /* look for characters here */ + } else if (this.type() == 'keypress') { + + /* + + Special key behavior: + + IE: does not fire keypress events for special keys + FF: sets charCode to 0, and sets the correct keyCode + Safari: sets keyCode and charCode to something stupid + + */ + + k.code = 0; + k.string = ''; + + if (typeof(this._event.charCode) != 'undefined' && + this._event.charCode !== 0 && + !MochiKit.Signal._specialMacKeys[this._event.charCode]) { + k.code = this._event.charCode; + k.string = String.fromCharCode(k.code); + } else if (this._event.keyCode && + typeof(this._event.charCode) == 'undefined') { // IE + k.code = this._event.keyCode; + k.string = String.fromCharCode(k.code); + } + + this._key = k; + return k; + } + } + return undefined; + }, + + _mouse: null, + /** @id MochiKit.Signal.Event.prototype.mouse */ + mouse: function () { + if (this._mouse !== null) { + return this._mouse; + } + + var m = {}; + var e = this._event; + + if (this.type() && ( + this.type().indexOf('mouse') === 0 || + this.type().indexOf('click') != -1 || + this.type() == 'contextmenu')) { + + m.client = new MochiKit.Style.Coordinates(0, 0); + if (e.clientX || e.clientY) { + m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX; + m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY; + } + + m.page = new MochiKit.Style.Coordinates(0, 0); + if (e.pageX || e.pageY) { + m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX; + m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY; + } else { + /* + + The IE shortcut can be off by two. We fix it. See: + http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp + + This is similar to the method used in + MochiKit.Style.getElementPosition(). + + */ + var de = MochiKit.DOM._document.documentElement; + var b = MochiKit.DOM._document.body; + + m.page.x = e.clientX + + (de.scrollLeft || b.scrollLeft) - + (de.clientLeft || 0); + + m.page.y = e.clientY + + (de.scrollTop || b.scrollTop) - + (de.clientTop || 0); + + } + if (this.type() != 'mousemove' && this.type() != 'mousewheel') { + m.button = {}; + m.button.left = false; + m.button.right = false; + m.button.middle = false; + + /* we could check e.button, but which is more consistent */ + if (e.which) { + m.button.left = (e.which == 1); + m.button.middle = (e.which == 2); + m.button.right = (e.which == 3); + + /* + + Mac browsers and right click: + + - Safari doesn't fire any click events on a right + click: + http://bugs.webkit.org/show_bug.cgi?id=6595 + + - Firefox fires the event, and sets ctrlKey = true + + - Opera fires the event, and sets metaKey = true + + oncontextmenu is fired on right clicks between + browsers and across platforms. + + */ + + } else { + m.button.left = !!(e.button & 1); + m.button.right = !!(e.button & 2); + m.button.middle = !!(e.button & 4); + } + } + if (this.type() == 'mousewheel') { + m.wheel = new MochiKit.Style.Coordinates(0, 0); + if (e.wheelDeltaX || e.wheelDeltaY) { + m.wheel.x = e.wheelDeltaX / -40 || 0; + m.wheel.y = e.wheelDeltaY / -40 || 0; + } else if (e.wheelDelta) { + m.wheel.y = e.wheelDelta / -40; + } else { + m.wheel.y = e.detail || 0; + } + } + this._mouse = m; + return m; + } + return undefined; + }, + + /** @id MochiKit.Signal.Event.prototype.stop */ + stop: function () { + this.stopPropagation(); + this.preventDefault(); + }, + + /** @id MochiKit.Signal.Event.prototype.stopPropagation */ + stopPropagation: function () { + if (this._event.stopPropagation) { + this._event.stopPropagation(); + } else { + this._event.cancelBubble = true; + } + }, + + /** @id MochiKit.Signal.Event.prototype.preventDefault */ + preventDefault: function () { + if (this._event.preventDefault) { + this._event.preventDefault(); + } else if (this._confirmUnload === null) { + this._event.returnValue = false; + } + }, + + _confirmUnload: null, + + /** @id MochiKit.Signal.Event.prototype.confirmUnload */ + confirmUnload: function (msg) { + if (this.type() == 'beforeunload') { + this._confirmUnload = msg; + this._event.returnValue = msg; + } + } +}); + +/* Safari sets keyCode to these special values onkeypress. */ +MochiKit.Signal._specialMacKeys = { + 3: 'KEY_ENTER', + 63289: 'KEY_NUM_PAD_CLEAR', + 63276: 'KEY_PAGE_UP', + 63277: 'KEY_PAGE_DOWN', + 63275: 'KEY_END', + 63273: 'KEY_HOME', + 63234: 'KEY_ARROW_LEFT', + 63232: 'KEY_ARROW_UP', + 63235: 'KEY_ARROW_RIGHT', + 63233: 'KEY_ARROW_DOWN', + 63302: 'KEY_INSERT', + 63272: 'KEY_DELETE' +}; + +/* for KEY_F1 - KEY_F12 */ +(function () { + var _specialMacKeys = MochiKit.Signal._specialMacKeys; + for (i = 63236; i <= 63242; i++) { + // no F0 + _specialMacKeys[i] = 'KEY_F' + (i - 63236 + 1); + } +})(); + +/* Standard keyboard key codes. */ +MochiKit.Signal._specialKeys = { + 8: 'KEY_BACKSPACE', + 9: 'KEY_TAB', + 12: 'KEY_NUM_PAD_CLEAR', // weird, for Safari and Mac FF only + 13: 'KEY_ENTER', + 16: 'KEY_SHIFT', + 17: 'KEY_CTRL', + 18: 'KEY_ALT', + 19: 'KEY_PAUSE', + 20: 'KEY_CAPS_LOCK', + 27: 'KEY_ESCAPE', + 32: 'KEY_SPACEBAR', + 33: 'KEY_PAGE_UP', + 34: 'KEY_PAGE_DOWN', + 35: 'KEY_END', + 36: 'KEY_HOME', + 37: 'KEY_ARROW_LEFT', + 38: 'KEY_ARROW_UP', + 39: 'KEY_ARROW_RIGHT', + 40: 'KEY_ARROW_DOWN', + 44: 'KEY_PRINT_SCREEN', + 45: 'KEY_INSERT', + 46: 'KEY_DELETE', + 59: 'KEY_SEMICOLON', // weird, for Safari and IE only + 91: 'KEY_WINDOWS_LEFT', + 92: 'KEY_WINDOWS_RIGHT', + 93: 'KEY_SELECT', + 106: 'KEY_NUM_PAD_ASTERISK', + 107: 'KEY_NUM_PAD_PLUS_SIGN', + 109: 'KEY_NUM_PAD_HYPHEN-MINUS', + 110: 'KEY_NUM_PAD_FULL_STOP', + 111: 'KEY_NUM_PAD_SOLIDUS', + 144: 'KEY_NUM_LOCK', + 145: 'KEY_SCROLL_LOCK', + 186: 'KEY_SEMICOLON', + 187: 'KEY_EQUALS_SIGN', + 188: 'KEY_COMMA', + 189: 'KEY_HYPHEN-MINUS', + 190: 'KEY_FULL_STOP', + 191: 'KEY_SOLIDUS', + 192: 'KEY_GRAVE_ACCENT', + 219: 'KEY_LEFT_SQUARE_BRACKET', + 220: 'KEY_REVERSE_SOLIDUS', + 221: 'KEY_RIGHT_SQUARE_BRACKET', + 222: 'KEY_APOSTROPHE' + // undefined: 'KEY_UNKNOWN' +}; + +(function () { + /* for KEY_0 - KEY_9 */ + var _specialKeys = MochiKit.Signal._specialKeys; + for (var i = 48; i <= 57; i++) { + _specialKeys[i] = 'KEY_' + (i - 48); + } + + /* for KEY_A - KEY_Z */ + for (i = 65; i <= 90; i++) { + _specialKeys[i] = 'KEY_' + String.fromCharCode(i); + } + + /* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */ + for (i = 96; i <= 105; i++) { + _specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96); + } + + /* for KEY_F1 - KEY_F12 */ + for (i = 112; i <= 123; i++) { + // no F0 + _specialKeys[i] = 'KEY_F' + (i - 112 + 1); + } +})(); + +/* Internal object to keep track of created signals. */ +MochiKit.Signal.Ident = function (ident) { + this.source = ident.source; + this.signal = ident.signal; + this.listener = ident.listener; + this.isDOM = ident.isDOM; + this.objOrFunc = ident.objOrFunc; + this.funcOrStr = ident.funcOrStr; + this.connected = ident.connected; +}; + +MochiKit.Signal.Ident.prototype = {}; + +MochiKit.Base.update(MochiKit.Signal, { + + __repr__: function () { + return '[' + this.NAME + ' ' + this.VERSION + ']'; + }, + + toString: function () { + return this.__repr__(); + }, + + _unloadCache: function () { + var self = MochiKit.Signal; + var observers = self._observers; + + for (var i = 0; i < observers.length; i++) { + if (observers[i].signal !== 'onload' && observers[i].signal !== 'onunload') { + self._disconnect(observers[i]); + } + } + }, + + _listener: function (src, sig, func, obj, isDOM) { + var self = MochiKit.Signal; + var E = self.Event; + if (!isDOM) { + /* We don't want to re-bind already bound methods */ + if (typeof(func.im_self) == 'undefined') { + return MochiKit.Base.bindLate(func, obj); + } else { + return func; + } + } + obj = obj || src; + if (typeof(func) == "string") { + if (sig === 'onload' || sig === 'onunload') { + return function (nativeEvent) { + obj[func].apply(obj, [new E(src, nativeEvent)]); + + var ident = new MochiKit.Signal.Ident({ + source: src, signal: sig, objOrFunc: obj, funcOrStr: func}); + + MochiKit.Signal._disconnect(ident); + }; + } else { + return function (nativeEvent) { + obj[func].apply(obj, [new E(src, nativeEvent)]); + }; + } + } else { + if (sig === 'onload' || sig === 'onunload') { + return function (nativeEvent) { + func.apply(obj, [new E(src, nativeEvent)]); + + var ident = new MochiKit.Signal.Ident({ + source: src, signal: sig, objOrFunc: func}); + + MochiKit.Signal._disconnect(ident); + }; + } else { + return function (nativeEvent) { + func.apply(obj, [new E(src, nativeEvent)]); + }; + } + } + }, + + _browserAlreadyHasMouseEnterAndLeave: function () { + return /MSIE/.test(navigator.userAgent); + }, + + _browserLacksMouseWheelEvent: function () { + return /Gecko\//.test(navigator.userAgent); + }, + + _mouseEnterListener: function (src, sig, func, obj) { + var E = MochiKit.Signal.Event; + return function (nativeEvent) { + var e = new E(src, nativeEvent); + try { + e.relatedTarget().nodeName; + } catch (err) { + /* probably hit a permission denied error; possibly one of + * firefox's screwy anonymous DIVs inside an input element. + * Allow this event to propogate up. + */ + return; + } + e.stop(); + if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) { + /* We've moved between our node and a child. Ignore. */ + return; + } + e.type = function () { return sig; }; + if (typeof(func) == "string") { + return obj[func].apply(obj, [e]); + } else { + return func.apply(obj, [e]); + } + }; + }, + + _getDestPair: function (objOrFunc, funcOrStr) { + var obj = null; + var func = null; + if (typeof(funcOrStr) != 'undefined') { + obj = objOrFunc; + func = funcOrStr; + if (typeof(funcOrStr) == 'string') { + if (typeof(objOrFunc[funcOrStr]) != "function") { + throw new Error("'funcOrStr' must be a function on 'objOrFunc'"); + } + } else if (typeof(funcOrStr) != 'function') { + throw new Error("'funcOrStr' must be a function or string"); + } + } else if (typeof(objOrFunc) != "function") { + throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given"); + } else { + func = objOrFunc; + } + return [obj, func]; + }, + + /** @id MochiKit.Signal.connect */ + connect: function (src, sig, objOrFunc/* optional */, funcOrStr) { + src = MochiKit.DOM.getElement(src); + var self = MochiKit.Signal; + + if (typeof(sig) != 'string') { + throw new Error("'sig' must be a string"); + } + + var destPair = self._getDestPair(objOrFunc, funcOrStr); + var obj = destPair[0]; + var func = destPair[1]; + if (typeof(obj) == 'undefined' || obj === null) { + obj = src; + } + + var isDOM = !!(src.addEventListener || src.attachEvent); + if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave") + && !self._browserAlreadyHasMouseEnterAndLeave()) { + var listener = self._mouseEnterListener(src, sig.substr(2), func, obj); + if (sig === "onmouseenter") { + sig = "onmouseover"; + } else { + sig = "onmouseout"; + } + } else if (isDOM && sig == "onmousewheel" && self._browserLacksMouseWheelEvent()) { + var listener = self._listener(src, sig, func, obj, isDOM); + sig = "onDOMMouseScroll"; + } else { + var listener = self._listener(src, sig, func, obj, isDOM); + } + + if (src.addEventListener) { + src.addEventListener(sig.substr(2), listener, false); + } else if (src.attachEvent) { + src.attachEvent(sig, listener); // useCapture unsupported + } + + var ident = new MochiKit.Signal.Ident({ + source: src, + signal: sig, + listener: listener, + isDOM: isDOM, + objOrFunc: objOrFunc, + funcOrStr: funcOrStr, + connected: true + }); + self._observers.push(ident); + + if (!isDOM && typeof(src.__connect__) == 'function') { + var args = MochiKit.Base.extend([ident], arguments, 1); + src.__connect__.apply(src, args); + } + + return ident; + }, + + _disconnect: function (ident) { + // already disconnected + if (!ident.connected) { + return; + } + ident.connected = false; + var src = ident.source; + var sig = ident.signal; + var listener = ident.listener; + // check isDOM + if (!ident.isDOM) { + if (typeof(src.__disconnect__) == 'function') { + src.__disconnect__(ident, sig, ident.objOrFunc, ident.funcOrStr); + } + return; + } + if (src.removeEventListener) { + src.removeEventListener(sig.substr(2), listener, false); + } else if (src.detachEvent) { + src.detachEvent(sig, listener); // useCapture unsupported + } else { + throw new Error("'src' must be a DOM element"); + } + }, + + /** @id MochiKit.Signal.disconnect */ + disconnect: function (ident) { + var self = MochiKit.Signal; + var observers = self._observers; + var m = MochiKit.Base; + if (arguments.length > 1) { + // compatibility API + var src = MochiKit.DOM.getElement(arguments[0]); + var sig = arguments[1]; + var obj = arguments[2]; + var func = arguments[3]; + for (var i = observers.length - 1; i >= 0; i--) { + var o = observers[i]; + if (o.source === src && o.signal === sig && o.objOrFunc === obj && o.funcOrStr === func) { + self._disconnect(o); + if (!self._lock) { + observers.splice(i, 1); + } else { + self._dirty = true; + } + return true; + } + } + } else { + var idx = m.findIdentical(observers, ident); + if (idx >= 0) { + self._disconnect(ident); + if (!self._lock) { + observers.splice(idx, 1); + } else { + self._dirty = true; + } + return true; + } + } + return false; + }, + + /** @id MochiKit.Signal.disconnectAllTo */ + disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) { + var self = MochiKit.Signal; + var observers = self._observers; + var disconnect = self._disconnect; + var locked = self._lock; + var dirty = self._dirty; + if (typeof(funcOrStr) === 'undefined') { + funcOrStr = null; + } + for (var i = observers.length - 1; i >= 0; i--) { + var ident = observers[i]; + if (ident.objOrFunc === objOrFunc && + (funcOrStr === null || ident.funcOrStr === funcOrStr)) { + disconnect(ident); + if (locked) { + dirty = true; + } else { + observers.splice(i, 1); + } + } + } + self._dirty = dirty; + }, + + /** @id MochiKit.Signal.disconnectAll */ + disconnectAll: function (src/* optional */, sig) { + src = MochiKit.DOM.getElement(src); + var m = MochiKit.Base; + var signals = m.flattenArguments(m.extend(null, arguments, 1)); + var self = MochiKit.Signal; + var disconnect = self._disconnect; + var observers = self._observers; + var i, ident; + var locked = self._lock; + var dirty = self._dirty; + if (signals.length === 0) { + // disconnect all + for (i = observers.length - 1; i >= 0; i--) { + ident = observers[i]; + if (ident.source === src) { + disconnect(ident); + if (!locked) { + observers.splice(i, 1); + } else { + dirty = true; + } + } + } + } else { + var sigs = {}; + for (i = 0; i < signals.length; i++) { + sigs[signals[i]] = true; + } + for (i = observers.length - 1; i >= 0; i--) { + ident = observers[i]; + if (ident.source === src && ident.signal in sigs) { + disconnect(ident); + if (!locked) { + observers.splice(i, 1); + } else { + dirty = true; + } + } + } + } + self._dirty = dirty; + }, + + /** @id MochiKit.Signal.signal */ + signal: function (src, sig) { + var self = MochiKit.Signal; + var observers = self._observers; + src = MochiKit.DOM.getElement(src); + var args = MochiKit.Base.extend(null, arguments, 2); + var errors = []; + self._lock = true; + for (var i = 0; i < observers.length; i++) { + var ident = observers[i]; + if (ident.source === src && ident.signal === sig && + ident.connected) { + try { + ident.listener.apply(src, args); + } catch (e) { + errors.push(e); + } + } + } + self._lock = false; + if (self._dirty) { + self._dirty = false; + for (var i = observers.length - 1; i >= 0; i--) { + if (!observers[i].connected) { + observers.splice(i, 1); + } + } + } + if (errors.length == 1) { + throw errors[0]; + } else if (errors.length > 1) { + var e = new Error("Multiple errors thrown in handling 'sig', see errors property"); + e.errors = errors; + throw e; + } + } + +}); + +MochiKit.Signal.EXPORT_OK = []; + +MochiKit.Signal.EXPORT = [ + 'connect', + 'disconnect', + 'signal', + 'disconnectAll', + 'disconnectAllTo' +]; + +MochiKit.Signal.__new__ = function (win) { + var m = MochiKit.Base; + this._document = document; + this._window = win; + this._lock = false; + this._dirty = false; + + try { + this.connect(window, 'onunload', this._unloadCache); + } catch (e) { + // pass: might not be a browser + } + + this.EXPORT_TAGS = { + ':common': this.EXPORT, + ':all': m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); +}; + +MochiKit.Signal.__new__(this); + +// +// XXX: Internet Explorer blows +// +if (MochiKit.__export__) { + connect = MochiKit.Signal.connect; + disconnect = MochiKit.Signal.disconnect; + disconnectAll = MochiKit.Signal.disconnectAll; + signal = MochiKit.Signal.signal; +} + +MochiKit.Base._exportSymbols(this, MochiKit.Signal); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Sortable.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Sortable.js new file mode 100644 index 000000000..463cce2fd --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Sortable.js @@ -0,0 +1,589 @@ +/*** +Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) + Mochi-ized By Thomas Herve (_firstname_@nimail.org) + +See scriptaculous.js for full license. + +***/ + +MochiKit.Base._deps('Sortable', ['Base', 'Iter', 'DOM', 'Position', 'DragAndDrop']); + +MochiKit.Sortable.NAME = 'MochiKit.Sortable'; +MochiKit.Sortable.VERSION = '1.4.2'; + +MochiKit.Sortable.__repr__ = function () { + return '[' + this.NAME + ' ' + this.VERSION + ']'; +}; + +MochiKit.Sortable.toString = function () { + return this.__repr__(); +}; + +MochiKit.Sortable.EXPORT = [ +]; + +MochiKit.Sortable.EXPORT_OK = [ +]; + +MochiKit.Base.update(MochiKit.Sortable, { + /*** + + Manage sortables. Mainly use the create function to add a sortable. + + ***/ + sortables: {}, + + _findRootElement: function (element) { + while (element.tagName.toUpperCase() != "BODY") { + if (element.id && MochiKit.Sortable.sortables[element.id]) { + return element; + } + element = element.parentNode; + } + }, + + _createElementId: function(element) { + if (element.id == null || element.id == "") { + var d = MochiKit.DOM; + var id; + var count = 1; + while (d.getElement(id = "sortable" + count) != null) { + count += 1; + } + d.setNodeAttribute(element, "id", id); + } + }, + + /** @id MochiKit.Sortable.options */ + options: function (element) { + element = MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(element)); + if (!element) { + return; + } + return MochiKit.Sortable.sortables[element.id]; + }, + + /** @id MochiKit.Sortable.destroy */ + destroy: function (element){ + var s = MochiKit.Sortable.options(element); + var b = MochiKit.Base; + var d = MochiKit.DragAndDrop; + + if (s) { + MochiKit.Signal.disconnect(s.startHandle); + MochiKit.Signal.disconnect(s.endHandle); + b.map(function (dr) { + d.Droppables.remove(dr); + }, s.droppables); + b.map(function (dr) { + dr.destroy(); + }, s.draggables); + + delete MochiKit.Sortable.sortables[s.element.id]; + } + }, + + /** @id MochiKit.Sortable.create */ + create: function (element, options) { + element = MochiKit.DOM.getElement(element); + var self = MochiKit.Sortable; + self._createElementId(element); + + /** @id MochiKit.Sortable.options */ + options = MochiKit.Base.update({ + + /** @id MochiKit.Sortable.element */ + element: element, + + /** @id MochiKit.Sortable.tag */ + tag: 'li', // assumes li children, override with tag: 'tagname' + + /** @id MochiKit.Sortable.dropOnEmpty */ + dropOnEmpty: false, + + /** @id MochiKit.Sortable.tree */ + tree: false, + + /** @id MochiKit.Sortable.treeTag */ + treeTag: 'ul', + + /** @id MochiKit.Sortable.overlap */ + overlap: 'vertical', // one of 'vertical', 'horizontal' + + /** @id MochiKit.Sortable.constraint */ + constraint: 'vertical', // one of 'vertical', 'horizontal', false + // also takes array of elements (or ids); or false + + /** @id MochiKit.Sortable.containment */ + containment: [element], + + /** @id MochiKit.Sortable.handle */ + handle: false, // or a CSS class + + /** @id MochiKit.Sortable.only */ + only: false, + + /** @id MochiKit.Sortable.hoverclass */ + hoverclass: null, + + /** @id MochiKit.Sortable.ghosting */ + ghosting: false, + + /** @id MochiKit.Sortable.scroll */ + scroll: false, + + /** @id MochiKit.Sortable.scrollSensitivity */ + scrollSensitivity: 20, + + /** @id MochiKit.Sortable.scrollSpeed */ + scrollSpeed: 15, + + /** @id MochiKit.Sortable.format */ + format: /^[^_]*_(.*)$/, + + /** @id MochiKit.Sortable.onChange */ + onChange: MochiKit.Base.noop, + + /** @id MochiKit.Sortable.onUpdate */ + onUpdate: MochiKit.Base.noop, + + /** @id MochiKit.Sortable.accept */ + accept: null + }, options); + + // clear any old sortable with same element + self.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + ghosting: options.ghosting, + scroll: options.scroll, + scrollSensitivity: options.scrollSensitivity, + scrollSpeed: options.scrollSpeed, + constraint: options.constraint, + handle: options.handle + }; + + if (options.starteffect) { + options_for_draggable.starteffect = options.starteffect; + } + + if (options.reverteffect) { + options_for_draggable.reverteffect = options.reverteffect; + } else if (options.ghosting) { + options_for_draggable.reverteffect = function (innerelement) { + innerelement.style.top = 0; + innerelement.style.left = 0; + }; + } + + if (options.endeffect) { + options_for_draggable.endeffect = options.endeffect; + } + + if (options.zindex) { + options_for_draggable.zindex = options.zindex; + } + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass, + onhover: self.onHover, + tree: options.tree, + accept: options.accept + } + + var options_for_tree = { + onhover: self.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass, + accept: options.accept + } + + // fix for gecko engine + MochiKit.DOM.removeEmptyTextNodes(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if (options.dropOnEmpty || options.tree) { + new MochiKit.DragAndDrop.Droppable(element, options_for_tree); + options.droppables.push(element); + } + MochiKit.Base.map(function (e) { + // handles are per-draggable + var handle = options.handle ? + MochiKit.DOM.getFirstElementByTagAndClassName(null, + options.handle, e) : e; + options.draggables.push( + new MochiKit.DragAndDrop.Draggable(e, + MochiKit.Base.update(options_for_draggable, + {handle: handle}))); + new MochiKit.DragAndDrop.Droppable(e, options_for_droppable); + if (options.tree) { + e.treeNode = element; + } + options.droppables.push(e); + }, (self.findElements(element, options) || [])); + + if (options.tree) { + MochiKit.Base.map(function (e) { + new MochiKit.DragAndDrop.Droppable(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }, (self.findTreeElements(element, options) || [])); + } + + // keep reference + self.sortables[element.id] = options; + + options.lastValue = self.serialize(element); + options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start', + MochiKit.Base.partial(self.onStart, element)); + options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end', + MochiKit.Base.partial(self.onEnd, element)); + }, + + /** @id MochiKit.Sortable.onStart */ + onStart: function (element, draggable) { + var self = MochiKit.Sortable; + var options = self.options(element); + options.lastValue = self.serialize(options.element); + }, + + /** @id MochiKit.Sortable.onEnd */ + onEnd: function (element, draggable) { + var self = MochiKit.Sortable; + self.unmark(); + var options = self.options(element); + if (options.lastValue != self.serialize(options.element)) { + options.onUpdate(options.element); + } + }, + + // return all suitable-for-sortable elements in a guaranteed order + + /** @id MochiKit.Sortable.findElements */ + findElements: function (element, options) { + return MochiKit.Sortable.findChildren(element, options.only, options.tree, options.tag); + }, + + /** @id MochiKit.Sortable.findTreeElements */ + findTreeElements: function (element, options) { + return MochiKit.Sortable.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + /** @id MochiKit.Sortable.findChildren */ + findChildren: function (element, only, recursive, tagName) { + if (!element.hasChildNodes()) { + return null; + } + tagName = tagName.toUpperCase(); + if (only) { + only = MochiKit.Base.flattenArray([only]); + } + var elements = []; + MochiKit.Base.map(function (e) { + if (e.tagName && + e.tagName.toUpperCase() == tagName && + (!only || + MochiKit.Iter.some(only, function (c) { + return MochiKit.DOM.hasElementClass(e, c); + }))) { + elements.push(e); + } + if (recursive) { + var grandchildren = MochiKit.Sortable.findChildren(e, only, recursive, tagName); + if (grandchildren && grandchildren.length > 0) { + elements = elements.concat(grandchildren); + } + } + }, element.childNodes); + return elements; + }, + + /** @id MochiKit.Sortable.onHover */ + onHover: function (element, dropon, overlap) { + if (MochiKit.DOM.isChildNode(dropon, element)) { + return; + } + var self = MochiKit.Sortable; + + if (overlap > .33 && overlap < .66 && self.options(dropon).tree) { + return; + } else if (overlap > 0.5) { + self.mark(dropon, 'before'); + if (dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = 'hidden'; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if (dropon.parentNode != oldParentNode) { + self.options(oldParentNode).onChange(element); + } + self.options(dropon.parentNode).onChange(element); + } + } else { + self.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if (nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = 'hidden'; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if (dropon.parentNode != oldParentNode) { + self.options(oldParentNode).onChange(element); + } + self.options(dropon.parentNode).onChange(element); + } + } + }, + + _offsetSize: function (element, type) { + if (type == 'vertical' || type == 'height') { + return element.offsetHeight; + } else { + return element.offsetWidth; + } + }, + + /** @id MochiKit.Sortable.onEmptyHover */ + onEmptyHover: function (element, dropon, overlap) { + var oldParentNode = element.parentNode; + var self = MochiKit.Sortable; + var droponOptions = self.options(dropon); + + if (!MochiKit.DOM.isChildNode(dropon, element)) { + var index; + + var children = self.findElements(dropon, {tag: droponOptions.tag, + only: droponOptions.only}); + var child = null; + + if (children) { + var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) { + offset -= self._offsetSize(children[index], droponOptions.overlap); + } else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + self.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + /** @id MochiKit.Sortable.unmark */ + unmark: function () { + var m = MochiKit.Sortable._marker; + if (m) { + MochiKit.Style.hideElement(m); + } + }, + + /** @id MochiKit.Sortable.mark */ + mark: function (dropon, position) { + // mark on ghosting only + var d = MochiKit.DOM; + var self = MochiKit.Sortable; + var sortable = self.options(dropon.parentNode); + if (sortable && !sortable.ghosting) { + return; + } + + if (!self._marker) { + self._marker = d.getElement('dropmarker') || + document.createElement('DIV'); + MochiKit.Style.hideElement(self._marker); + d.addElementClass(self._marker, 'dropmarker'); + self._marker.style.position = 'absolute'; + document.getElementsByTagName('body').item(0).appendChild(self._marker); + } + var offsets = MochiKit.Position.cumulativeOffset(dropon); + self._marker.style.left = offsets.x + 'px'; + self._marker.style.top = offsets.y + 'px'; + + if (position == 'after') { + if (sortable.overlap == 'horizontal') { + self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px'; + } else { + self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px'; + } + } + MochiKit.Style.showElement(self._marker); + }, + + _tree: function (element, options, parent) { + var self = MochiKit.Sortable; + var children = self.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) { + continue; + } + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: self._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) { + self._tree(child.container, options, child) + } + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) { + containerTag = containerTag.toUpperCase(); + for (var i = 0; i < element.childNodes.length; ++i) { + if (element.childNodes[i].tagName.toUpperCase() == containerTag) { + return element.childNodes[i]; + } + } + } + return null; + }, + + /** @id MochiKit.Sortable.tree */ + tree: function (element, options) { + element = MochiKit.DOM.getElement(element); + var sortableOptions = MochiKit.Sortable.options(element); + options = MochiKit.Base.update({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, options || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return MochiKit.Sortable._tree(element, options, root); + }, + + /** + * Specifies the sequence for the Sortable. + * @param {Node} element Element to use as the Sortable. + * @param {Object} newSequence New sequence to use. + * @param {Object} options Options to use fro the Sortable. + */ + setSequence: function (element, newSequence, options) { + var self = MochiKit.Sortable; + var b = MochiKit.Base; + element = MochiKit.DOM.getElement(element); + options = b.update(self.options(element), options || {}); + + var nodeMap = {}; + b.map(function (n) { + var m = n.id.match(options.format); + if (m) { + nodeMap[m[1]] = [n, n.parentNode]; + } + n.parentNode.removeChild(n); + }, self.findElements(element, options)); + + b.map(function (ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }, newSequence); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function (node) { + var index = ''; + do { + if (node.id) { + index = '[' + node.position + ']' + index; + } + } while ((node = node.parent) != null); + return index; + }, + + /** @id MochiKit.Sortable.sequence */ + sequence: function (element, options) { + element = MochiKit.DOM.getElement(element); + var self = MochiKit.Sortable; + var options = MochiKit.Base.update(self.options(element), options || {}); + + return MochiKit.Base.map(function (item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }, MochiKit.DOM.getElement(self.findElements(element, options) || [])); + }, + + /** + * Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest. + * These options override the Sortable options for the serialization only. + * @param {Node} element Element to serialize. + * @param {Object} options Serialization options. + */ + serialize: function (element, options) { + element = MochiKit.DOM.getElement(element); + var self = MochiKit.Sortable; + options = MochiKit.Base.update(self.options(element), options || {}); + var name = encodeURIComponent(options.name || element.id); + + if (options.tree) { + return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) { + return [name + self._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }, self.tree(element, options).children)).join('&'); + } else { + return MochiKit.Base.map(function (item) { + return name + "[]=" + encodeURIComponent(item); + }, self.sequence(element, options)).join('&'); + } + } +}); + +// trunk compatibility +MochiKit.Sortable.Sortable = MochiKit.Sortable; + +MochiKit.Sortable.__new__ = function () { + MochiKit.Base.nameFunctions(this); + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK) + }; +}; + +MochiKit.Sortable.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Sortable); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Style.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Style.js new file mode 100644 index 000000000..1488110c4 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Style.js @@ -0,0 +1,594 @@ +/*** + +MochiKit.Style 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005-2006 Bob Ippolito, Beau Hartshorne. All rights Reserved. + +***/ + +MochiKit.Base._deps('Style', ['Base', 'DOM']); + +MochiKit.Style.NAME = 'MochiKit.Style'; +MochiKit.Style.VERSION = '1.4.2'; +MochiKit.Style.__repr__ = function () { + return '[' + this.NAME + ' ' + this.VERSION + ']'; +}; +MochiKit.Style.toString = function () { + return this.__repr__(); +}; + +MochiKit.Style.EXPORT_OK = []; + +MochiKit.Style.EXPORT = [ + 'setStyle', + 'setOpacity', + 'getStyle', + 'getElementDimensions', + 'elementDimensions', // deprecated + 'setElementDimensions', + 'getElementPosition', + 'elementPosition', // deprecated + 'setElementPosition', + "makePositioned", + "undoPositioned", + "makeClipping", + "undoClipping", + 'setDisplayForElement', + 'hideElement', + 'showElement', + 'getViewportDimensions', + 'getViewportPosition', + 'Dimensions', + 'Coordinates' +]; + + +/* + + Dimensions + +*/ +/** @id MochiKit.Style.Dimensions */ +MochiKit.Style.Dimensions = function (w, h) { + this.w = w; + this.h = h; +}; + +MochiKit.Style.Dimensions.prototype.__repr__ = function () { + var repr = MochiKit.Base.repr; + return '{w: ' + repr(this.w) + ', h: ' + repr(this.h) + '}'; +}; + +MochiKit.Style.Dimensions.prototype.toString = function () { + return this.__repr__(); +}; + + +/* + + Coordinates + +*/ +/** @id MochiKit.Style.Coordinates */ +MochiKit.Style.Coordinates = function (x, y) { + this.x = x; + this.y = y; +}; + +MochiKit.Style.Coordinates.prototype.__repr__ = function () { + var repr = MochiKit.Base.repr; + return '{x: ' + repr(this.x) + ', y: ' + repr(this.y) + '}'; +}; + +MochiKit.Style.Coordinates.prototype.toString = function () { + return this.__repr__(); +}; + + +MochiKit.Base.update(MochiKit.Style, { + + /** @id MochiKit.Style.getStyle */ + getStyle: function (elem, cssProperty) { + var dom = MochiKit.DOM; + var d = dom._document; + + elem = dom.getElement(elem); + cssProperty = MochiKit.Base.camelize(cssProperty); + + if (!elem || elem == d) { + return undefined; + } + if (cssProperty == 'opacity' && typeof(elem.filters) != 'undefined') { + var opacity = (MochiKit.Style.getStyle(elem, 'filter') || '').match(/alpha\(opacity=(.*)\)/); + if (opacity && opacity[1]) { + return parseFloat(opacity[1]) / 100; + } + return 1.0; + } + if (cssProperty == 'float' || cssProperty == 'cssFloat' || cssProperty == 'styleFloat') { + if (elem.style["float"]) { + return elem.style["float"]; + } else if (elem.style.cssFloat) { + return elem.style.cssFloat; + } else if (elem.style.styleFloat) { + return elem.style.styleFloat; + } else { + return "none"; + } + } + var value = elem.style ? elem.style[cssProperty] : null; + if (!value) { + if (d.defaultView && d.defaultView.getComputedStyle) { + var css = d.defaultView.getComputedStyle(elem, null); + cssProperty = cssProperty.replace(/([A-Z])/g, '-$1' + ).toLowerCase(); // from dojo.style.toSelectorCase + value = css ? css.getPropertyValue(cssProperty) : null; + } else if (elem.currentStyle) { + value = elem.currentStyle[cssProperty]; + if (/^\d/.test(value) && !/px$/.test(value) && cssProperty != 'fontWeight') { + /* Convert to px using an hack from Dean Edwards */ + var left = elem.style.left; + var rsLeft = elem.runtimeStyle.left; + elem.runtimeStyle.left = elem.currentStyle.left; + elem.style.left = value || 0; + value = elem.style.pixelLeft + "px"; + elem.style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + } + if (cssProperty == 'opacity') { + value = parseFloat(value); + } + + if (/Opera/.test(navigator.userAgent) && (MochiKit.Base.findValue(['left', 'top', 'right', 'bottom'], cssProperty) != -1)) { + if (MochiKit.Style.getStyle(elem, 'position') == 'static') { + value = 'auto'; + } + } + + return value == 'auto' ? null : value; + }, + + /** @id MochiKit.Style.setStyle */ + setStyle: function (elem, style) { + elem = MochiKit.DOM.getElement(elem); + for (var name in style) { + switch (name) { + case 'opacity': + MochiKit.Style.setOpacity(elem, style[name]); + break; + case 'float': + case 'cssFloat': + case 'styleFloat': + if (typeof(elem.style["float"]) != "undefined") { + elem.style["float"] = style[name]; + } else if (typeof(elem.style.cssFloat) != "undefined") { + elem.style.cssFloat = style[name]; + } else { + elem.style.styleFloat = style[name]; + } + break; + default: + elem.style[MochiKit.Base.camelize(name)] = style[name]; + } + } + }, + + /** @id MochiKit.Style.setOpacity */ + setOpacity: function (elem, o) { + elem = MochiKit.DOM.getElement(elem); + var self = MochiKit.Style; + if (o == 1) { + var toSet = /Gecko/.test(navigator.userAgent) && !(/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)); + elem.style["opacity"] = toSet ? 0.999999 : 1.0; + if (/MSIE/.test(navigator.userAgent)) { + elem.style['filter'] = + self.getStyle(elem, 'filter').replace(/alpha\([^\)]*\)/gi, ''); + } + } else { + if (o < 0.00001) { + o = 0; + } + elem.style["opacity"] = o; + if (/MSIE/.test(navigator.userAgent)) { + elem.style['filter'] = + self.getStyle(elem, 'filter').replace(/alpha\([^\)]*\)/gi, '') + 'alpha(opacity=' + o * 100 + ')'; + } + } + }, + + /* + + getElementPosition is adapted from YAHOO.util.Dom.getXY v0.9.0. + Copyright: Copyright (c) 2006, Yahoo! Inc. All rights reserved. + License: BSD, http://developer.yahoo.net/yui/license.txt + + */ + + /** @id MochiKit.Style.getElementPosition */ + getElementPosition: function (elem, /* optional */relativeTo) { + var self = MochiKit.Style; + var dom = MochiKit.DOM; + elem = dom.getElement(elem); + + if (!elem || + (!(elem.x && elem.y) && + (!elem.parentNode === null || + self.getStyle(elem, 'display') == 'none'))) { + return undefined; + } + + var c = new self.Coordinates(0, 0); + var box = null; + var parent = null; + + var d = MochiKit.DOM._document; + var de = d.documentElement; + var b = d.body; + + if (!elem.parentNode && elem.x && elem.y) { + /* it's just a MochiKit.Style.Coordinates object */ + c.x += elem.x || 0; + c.y += elem.y || 0; + } else if (elem.getBoundingClientRect) { // IE shortcut + /* + + The IE shortcut can be off by two. We fix it. See: + http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp + + This is similar to the method used in + MochiKit.Signal.Event.mouse(). + + */ + box = elem.getBoundingClientRect(); + + c.x += box.left + + (de.scrollLeft || b.scrollLeft) - + (de.clientLeft || 0); + + c.y += box.top + + (de.scrollTop || b.scrollTop) - + (de.clientTop || 0); + + } else if (elem.offsetParent) { + c.x += elem.offsetLeft; + c.y += elem.offsetTop; + parent = elem.offsetParent; + + if (parent != elem) { + while (parent) { + c.x += parseInt(parent.style.borderLeftWidth) || 0; + c.y += parseInt(parent.style.borderTopWidth) || 0; + c.x += parent.offsetLeft; + c.y += parent.offsetTop; + parent = parent.offsetParent; + } + } + + /* + + Opera < 9 and old Safari (absolute) incorrectly account for + body offsetTop and offsetLeft. + + */ + var ua = navigator.userAgent.toLowerCase(); + if ((typeof(opera) != 'undefined' && + parseFloat(opera.version()) < 9) || + (ua.indexOf('AppleWebKit') != -1 && + self.getStyle(elem, 'position') == 'absolute')) { + + c.x -= b.offsetLeft; + c.y -= b.offsetTop; + + } + + // Adjust position for strange Opera scroll bug + if (elem.parentNode) { + parent = elem.parentNode; + } else { + parent = null; + } + while (parent) { + var tagName = parent.tagName.toUpperCase(); + if (tagName === 'BODY' || tagName === 'HTML') { + break; + } + var disp = self.getStyle(parent, 'display'); + // Handle strange Opera bug for some display + if (disp.search(/^inline|table-row.*$/i)) { + c.x -= parent.scrollLeft; + c.y -= parent.scrollTop; + } + if (parent.parentNode) { + parent = parent.parentNode; + } else { + parent = null; + } + } + } + + if (typeof(relativeTo) != 'undefined') { + relativeTo = arguments.callee(relativeTo); + if (relativeTo) { + c.x -= (relativeTo.x || 0); + c.y -= (relativeTo.y || 0); + } + } + + return c; + }, + + /** @id MochiKit.Style.setElementPosition */ + setElementPosition: function (elem, newPos/* optional */, units) { + elem = MochiKit.DOM.getElement(elem); + if (typeof(units) == 'undefined') { + units = 'px'; + } + var newStyle = {}; + var isUndefNull = MochiKit.Base.isUndefinedOrNull; + if (!isUndefNull(newPos.x)) { + newStyle['left'] = newPos.x + units; + } + if (!isUndefNull(newPos.y)) { + newStyle['top'] = newPos.y + units; + } + MochiKit.DOM.updateNodeAttributes(elem, {'style': newStyle}); + }, + + /** @id MochiKit.Style.makePositioned */ + makePositioned: function (element) { + element = MochiKit.DOM.getElement(element); + var pos = MochiKit.Style.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, + // when an element is position relative but top and left have + // not been defined + if (/Opera/.test(navigator.userAgent)) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + /** @id MochiKit.Style.undoPositioned */ + undoPositioned: function (element) { + element = MochiKit.DOM.getElement(element); + if (element.style.position == 'relative') { + element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; + } + }, + + /** @id MochiKit.Style.makeClipping */ + makeClipping: function (element) { + element = MochiKit.DOM.getElement(element); + var s = element.style; + var oldOverflow = { 'overflow': s.overflow, + 'overflow-x': s.overflowX, + 'overflow-y': s.overflowY }; + if ((MochiKit.Style.getStyle(element, 'overflow') || 'visible') != 'hidden') { + element.style.overflow = 'hidden'; + element.style.overflowX = 'hidden'; + element.style.overflowY = 'hidden'; + } + return oldOverflow; + }, + + /** @id MochiKit.Style.undoClipping */ + undoClipping: function (element, overflow) { + element = MochiKit.DOM.getElement(element); + if (typeof(overflow) == 'string') { + element.style.overflow = overflow; + } else if (overflow != null) { + element.style.overflow = overflow['overflow']; + element.style.overflowX = overflow['overflow-x']; + element.style.overflowY = overflow['overflow-y']; + } + }, + + /** @id MochiKit.Style.getElementDimensions */ + getElementDimensions: function (elem, contentSize/*optional*/) { + var self = MochiKit.Style; + var dom = MochiKit.DOM; + if (typeof(elem.w) == 'number' || typeof(elem.h) == 'number') { + return new self.Dimensions(elem.w || 0, elem.h || 0); + } + elem = dom.getElement(elem); + if (!elem) { + return undefined; + } + var disp = self.getStyle(elem, 'display'); + // display can be empty/undefined on WebKit/KHTML + if (disp == 'none' || disp == '' || typeof(disp) == 'undefined') { + var s = elem.style; + var originalVisibility = s.visibility; + var originalPosition = s.position; + var originalDisplay = s.display; + s.visibility = 'hidden'; + s.position = 'absolute'; + s.display = self._getDefaultDisplay(elem); + var originalWidth = elem.offsetWidth; + var originalHeight = elem.offsetHeight; + s.display = originalDisplay; + s.position = originalPosition; + s.visibility = originalVisibility; + } else { + originalWidth = elem.offsetWidth || 0; + originalHeight = elem.offsetHeight || 0; + } + if (contentSize) { + var tableCell = 'colSpan' in elem && 'rowSpan' in elem; + var collapse = (tableCell && elem.parentNode && self.getStyle( + elem.parentNode, 'borderCollapse') == 'collapse') + if (collapse) { + if (/MSIE/.test(navigator.userAgent)) { + var borderLeftQuota = elem.previousSibling? 0.5 : 1; + var borderRightQuota = elem.nextSibling? 0.5 : 1; + } + else { + var borderLeftQuota = 0.5; + var borderRightQuota = 0.5; + } + } else { + var borderLeftQuota = 1; + var borderRightQuota = 1; + } + originalWidth -= Math.round( + (parseFloat(self.getStyle(elem, 'paddingLeft')) || 0) + + (parseFloat(self.getStyle(elem, 'paddingRight')) || 0) + + borderLeftQuota * + (parseFloat(self.getStyle(elem, 'borderLeftWidth')) || 0) + + borderRightQuota * + (parseFloat(self.getStyle(elem, 'borderRightWidth')) || 0) + ); + if (tableCell) { + if (/Opera/.test(navigator.userAgent) + && !/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)) { + var borderHeightQuota = 0; + } else if (/MSIE/.test(navigator.userAgent)) { + var borderHeightQuota = 1; + } else { + var borderHeightQuota = collapse? 0.5 : 1; + } + } else { + var borderHeightQuota = 1; + } + originalHeight -= Math.round( + (parseFloat(self.getStyle(elem, 'paddingTop')) || 0) + + (parseFloat(self.getStyle(elem, 'paddingBottom')) || 0) + + borderHeightQuota * ( + (parseFloat(self.getStyle(elem, 'borderTopWidth')) || 0) + + (parseFloat(self.getStyle(elem, 'borderBottomWidth')) || 0)) + ); + } + return new self.Dimensions(originalWidth, originalHeight); + }, + + /** @id MochiKit.Style.setElementDimensions */ + setElementDimensions: function (elem, newSize/* optional */, units) { + elem = MochiKit.DOM.getElement(elem); + if (typeof(units) == 'undefined') { + units = 'px'; + } + var newStyle = {}; + var isUndefNull = MochiKit.Base.isUndefinedOrNull; + if (!isUndefNull(newSize.w)) { + newStyle['width'] = newSize.w + units; + } + if (!isUndefNull(newSize.h)) { + newStyle['height'] = newSize.h + units; + } + MochiKit.DOM.updateNodeAttributes(elem, {'style': newStyle}); + }, + + _getDefaultDisplay: function (elem) { + var self = MochiKit.Style; + var dom = MochiKit.DOM; + elem = dom.getElement(elem); + if (!elem) { + return undefined; + } + var tagName = elem.tagName.toUpperCase(); + return self._defaultDisplay[tagName] || 'block'; + }, + + /** @id MochiKit.Style.setDisplayForElement */ + setDisplayForElement: function (display, element/*, ...*/) { + var elements = MochiKit.Base.extend(null, arguments, 1); + var getElement = MochiKit.DOM.getElement; + for (var i = 0; i < elements.length; i++) { + element = getElement(elements[i]); + if (element) { + element.style.display = display; + } + } + }, + + /** @id MochiKit.Style.getViewportDimensions */ + getViewportDimensions: function () { + var d = new MochiKit.Style.Dimensions(); + var w = MochiKit.DOM._window; + var b = MochiKit.DOM._document.body; + if (w.innerWidth) { + d.w = w.innerWidth; + d.h = w.innerHeight; + } else if (b && b.parentElement && b.parentElement.clientWidth) { + d.w = b.parentElement.clientWidth; + d.h = b.parentElement.clientHeight; + } else if (b && b.clientWidth) { + d.w = b.clientWidth; + d.h = b.clientHeight; + } + return d; + }, + + /** @id MochiKit.Style.getViewportPosition */ + getViewportPosition: function () { + var c = new MochiKit.Style.Coordinates(0, 0); + var d = MochiKit.DOM._document; + var de = d.documentElement; + var db = d.body; + if (de && (de.scrollTop || de.scrollLeft)) { + c.x = de.scrollLeft; + c.y = de.scrollTop; + } else if (db) { + c.x = db.scrollLeft; + c.y = db.scrollTop; + } + return c; + }, + + __new__: function () { + var m = MochiKit.Base; + + var inlines = ['A','ABBR','ACRONYM','B','BASEFONT','BDO','BIG','BR', + 'CITE','CODE','DFN','EM','FONT','I','IMG','KBD','LABEL', + 'Q','S','SAMP','SMALL','SPAN','STRIKE','STRONG','SUB', + 'SUP','TEXTAREA','TT','U','VAR']; + this._defaultDisplay = { 'TABLE': 'table', + 'THEAD': 'table-header-group', + 'TBODY': 'table-row-group', + 'TFOOT': 'table-footer-group', + 'COLGROUP': 'table-column-group', + 'COL': 'table-column', + 'TR': 'table-row', + 'TD': 'table-cell', + 'TH': 'table-cell', + 'CAPTION': 'table-caption', + 'LI': 'list-item', + 'INPUT': 'inline-block', + 'SELECT': 'inline-block' }; + // CSS 'display' support in IE6/7 is just broken... + if (/MSIE/.test(navigator.userAgent)) { + for (var k in this._defaultDisplay) { + var v = this._defaultDisplay[k]; + if (v.indexOf('table') == 0) { + this._defaultDisplay[k] = 'block'; + } + } + } + for (var i = 0; i < inlines.length; i++) { + this._defaultDisplay[inlines[i]] = 'inline'; + } + + this.elementPosition = this.getElementPosition; + this.elementDimensions = this.getElementDimensions; + + this.hideElement = m.partial(this.setDisplayForElement, 'none'); + // TODO: showElement could be improved by using getDefaultDisplay. + this.showElement = m.partial(this.setDisplayForElement, 'block'); + + this.EXPORT_TAGS = { + ':common': this.EXPORT, + ':all': m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + } +}); + +MochiKit.Style.__new__(); +MochiKit.Base._exportSymbols(this, MochiKit.Style); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Test.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Test.js new file mode 100644 index 000000000..30bfb4c88 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Test.js @@ -0,0 +1,162 @@ +/*** + +MochiKit.Test 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +MochiKit.Base._deps('Test', ['Base']); + +MochiKit.Test.NAME = "MochiKit.Test"; +MochiKit.Test.VERSION = "1.4.2"; +MochiKit.Test.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +MochiKit.Test.toString = function () { + return this.__repr__(); +}; + + +MochiKit.Test.EXPORT = ["runTests"]; +MochiKit.Test.EXPORT_OK = []; + +MochiKit.Test.runTests = function (obj) { + if (typeof(obj) == "string") { + obj = JSAN.use(obj); + } + var suite = new MochiKit.Test.Suite(); + suite.run(obj); +}; + +MochiKit.Test.Suite = function () { + this.testIndex = 0; + MochiKit.Base.bindMethods(this); +}; + +MochiKit.Test.Suite.prototype = { + run: function (obj) { + try { + obj(this); + } catch (e) { + this.traceback(e); + } + }, + traceback: function (e) { + var items = MochiKit.Iter.sorted(MochiKit.Base.items(e)); + print("not ok " + this.testIndex + " - Error thrown"); + for (var i = 0; i < items.length; i++) { + var kv = items[i]; + if (kv[0] == "stack") { + kv[1] = kv[1].split(/\n/)[0]; + } + this.print("# " + kv.join(": ")); + } + }, + print: function (s) { + print(s); + }, + is: function (got, expected, /* optional */message) { + var res = 1; + var msg = null; + try { + res = MochiKit.Base.compare(got, expected); + } catch (e) { + msg = "Can not compare " + typeof(got) + ":" + typeof(expected); + } + if (res) { + msg = "Expected value did not compare equal"; + } + if (!res) { + return this.testResult(true, message); + } + return this.testResult(false, message, + [[msg], ["got:", got], ["expected:", expected]]); + }, + + testResult: function (pass, msg, failures) { + this.testIndex += 1; + if (pass) { + this.print("ok " + this.testIndex + " - " + msg); + return; + } + this.print("not ok " + this.testIndex + " - " + msg); + if (failures) { + for (var i = 0; i < failures.length; i++) { + this.print("# " + failures[i].join(" ")); + } + } + }, + + isDeeply: function (got, expected, /* optional */message) { + var m = MochiKit.Base; + var res = 1; + try { + res = m.compare(got, expected); + } catch (e) { + // pass + } + if (res === 0) { + return this.ok(true, message); + } + var gk = m.keys(got); + var ek = m.keys(expected); + gk.sort(); + ek.sort(); + if (m.compare(gk, ek)) { + // differing keys + var cmp = {}; + var i; + for (i = 0; i < gk.length; i++) { + cmp[gk[i]] = "got"; + } + for (i = 0; i < ek.length; i++) { + if (ek[i] in cmp) { + delete cmp[ek[i]]; + } else { + cmp[ek[i]] = "expected"; + } + } + var diffkeys = m.keys(cmp); + diffkeys.sort(); + var gotkeys = []; + var expkeys = []; + while (diffkeys.length) { + var k = diffkeys.shift(); + if (k in Object.prototype) { + continue; + } + (cmp[k] == "got" ? gotkeys : expkeys).push(k); + } + + + } + + return this.testResult((!res), msg, + (msg ? [["got:", got], ["expected:", expected]] : undefined) + ); + }, + + ok: function (res, message) { + return this.testResult(res, message); + } +}; + +MochiKit.Test.__new__ = function () { + var m = MochiKit.Base; + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + + m.nameFunctions(this); + +}; + +MochiKit.Test.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Test); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Visual.js b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Visual.js new file mode 100644 index 000000000..df975544d --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Visual.js @@ -0,0 +1,2026 @@ +/*** + +MochiKit.Visual 1.4.2 + +See <http://mochikit.com/> for documentation, downloads, license, etc. + +(c) 2005 Bob Ippolito and others. All rights Reserved. + +***/ + +MochiKit.Base._deps('Visual', ['Base', 'DOM', 'Style', 'Color', 'Position']); + +MochiKit.Visual.NAME = "MochiKit.Visual"; +MochiKit.Visual.VERSION = "1.4.2"; + +MochiKit.Visual.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +MochiKit.Visual.toString = function () { + return this.__repr__(); +}; + +MochiKit.Visual._RoundCorners = function (e, options) { + e = MochiKit.DOM.getElement(e); + this._setOptions(options); + if (this.options.__unstable__wrapElement) { + e = this._doWrap(e); + } + + var color = this.options.color; + var C = MochiKit.Color.Color; + if (this.options.color === "fromElement") { + color = C.fromBackground(e); + } else if (!(color instanceof C)) { + color = C.fromString(color); + } + this.isTransparent = (color.asRGB().a <= 0); + + var bgColor = this.options.bgColor; + if (this.options.bgColor === "fromParent") { + bgColor = C.fromBackground(e.offsetParent); + } else if (!(bgColor instanceof C)) { + bgColor = C.fromString(bgColor); + } + + this._roundCornersImpl(e, color, bgColor); +}; + +MochiKit.Visual._RoundCorners.prototype = { + _doWrap: function (e) { + var parent = e.parentNode; + var doc = MochiKit.DOM.currentDocument(); + if (typeof(doc.defaultView) === "undefined" + || doc.defaultView === null) { + return e; + } + var style = doc.defaultView.getComputedStyle(e, null); + if (typeof(style) === "undefined" || style === null) { + return e; + } + var wrapper = MochiKit.DOM.DIV({"style": { + display: "block", + // convert padding to margin + marginTop: style.getPropertyValue("padding-top"), + marginRight: style.getPropertyValue("padding-right"), + marginBottom: style.getPropertyValue("padding-bottom"), + marginLeft: style.getPropertyValue("padding-left"), + // remove padding so the rounding looks right + padding: "0px" + /* + paddingRight: "0px", + paddingLeft: "0px" + */ + }}); + wrapper.innerHTML = e.innerHTML; + e.innerHTML = ""; + e.appendChild(wrapper); + return e; + }, + + _roundCornersImpl: function (e, color, bgColor) { + if (this.options.border) { + this._renderBorder(e, bgColor); + } + if (this._isTopRounded()) { + this._roundTopCorners(e, color, bgColor); + } + if (this._isBottomRounded()) { + this._roundBottomCorners(e, color, bgColor); + } + }, + + _renderBorder: function (el, bgColor) { + var borderValue = "1px solid " + this._borderColor(bgColor); + var borderL = "border-left: " + borderValue; + var borderR = "border-right: " + borderValue; + var style = "style='" + borderL + ";" + borderR + "'"; + el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"; + }, + + _roundTopCorners: function (el, color, bgColor) { + var corner = this._createCorner(bgColor); + for (var i = 0; i < this.options.numSlices; i++) { + corner.appendChild( + this._createCornerSlice(color, bgColor, i, "top") + ); + } + el.style.paddingTop = 0; + el.insertBefore(corner, el.firstChild); + }, + + _roundBottomCorners: function (el, color, bgColor) { + var corner = this._createCorner(bgColor); + for (var i = (this.options.numSlices - 1); i >= 0; i--) { + corner.appendChild( + this._createCornerSlice(color, bgColor, i, "bottom") + ); + } + el.style.paddingBottom = 0; + el.appendChild(corner); + }, + + _createCorner: function (bgColor) { + var dom = MochiKit.DOM; + return dom.DIV({style: {backgroundColor: bgColor.toString()}}); + }, + + _createCornerSlice: function (color, bgColor, n, position) { + var slice = MochiKit.DOM.SPAN(); + + var inStyle = slice.style; + inStyle.backgroundColor = color.toString(); + inStyle.display = "block"; + inStyle.height = "1px"; + inStyle.overflow = "hidden"; + inStyle.fontSize = "1px"; + + var borderColor = this._borderColor(color, bgColor); + if (this.options.border && n === 0) { + inStyle.borderTopStyle = "solid"; + inStyle.borderTopWidth = "1px"; + inStyle.borderLeftWidth = "0px"; + inStyle.borderRightWidth = "0px"; + inStyle.borderBottomWidth = "0px"; + // assumes css compliant box model + inStyle.height = "0px"; + inStyle.borderColor = borderColor.toString(); + } else if (borderColor) { + inStyle.borderColor = borderColor.toString(); + inStyle.borderStyle = "solid"; + inStyle.borderWidth = "0px 1px"; + } + + if (!this.options.compact && (n == (this.options.numSlices - 1))) { + inStyle.height = "2px"; + } + + this._setMargin(slice, n, position); + this._setBorder(slice, n, position); + + return slice; + }, + + _setOptions: function (options) { + this.options = { + corners: "all", + color: "fromElement", + bgColor: "fromParent", + blend: true, + border: false, + compact: false, + __unstable__wrapElement: false + }; + MochiKit.Base.update(this.options, options); + + this.options.numSlices = (this.options.compact ? 2 : 4); + }, + + _whichSideTop: function () { + var corners = this.options.corners; + if (this._hasString(corners, "all", "top")) { + return ""; + } + + var has_tl = (corners.indexOf("tl") != -1); + var has_tr = (corners.indexOf("tr") != -1); + if (has_tl && has_tr) { + return ""; + } + if (has_tl) { + return "left"; + } + if (has_tr) { + return "right"; + } + return ""; + }, + + _whichSideBottom: function () { + var corners = this.options.corners; + if (this._hasString(corners, "all", "bottom")) { + return ""; + } + + var has_bl = (corners.indexOf('bl') != -1); + var has_br = (corners.indexOf('br') != -1); + if (has_bl && has_br) { + return ""; + } + if (has_bl) { + return "left"; + } + if (has_br) { + return "right"; + } + return ""; + }, + + _borderColor: function (color, bgColor) { + if (color == "transparent") { + return bgColor; + } else if (this.options.border) { + return this.options.border; + } else if (this.options.blend) { + return bgColor.blendedColor(color); + } + return ""; + }, + + + _setMargin: function (el, n, corners) { + var marginSize = this._marginSize(n) + "px"; + var whichSide = ( + corners == "top" ? this._whichSideTop() : this._whichSideBottom() + ); + var style = el.style; + + if (whichSide == "left") { + style.marginLeft = marginSize; + style.marginRight = "0px"; + } else if (whichSide == "right") { + style.marginRight = marginSize; + style.marginLeft = "0px"; + } else { + style.marginLeft = marginSize; + style.marginRight = marginSize; + } + }, + + _setBorder: function (el, n, corners) { + var borderSize = this._borderSize(n) + "px"; + var whichSide = ( + corners == "top" ? this._whichSideTop() : this._whichSideBottom() + ); + + var style = el.style; + if (whichSide == "left") { + style.borderLeftWidth = borderSize; + style.borderRightWidth = "0px"; + } else if (whichSide == "right") { + style.borderRightWidth = borderSize; + style.borderLeftWidth = "0px"; + } else { + style.borderLeftWidth = borderSize; + style.borderRightWidth = borderSize; + } + }, + + _marginSize: function (n) { + if (this.isTransparent) { + return 0; + } + + var o = this.options; + if (o.compact && o.blend) { + var smBlendedMarginSizes = [1, 0]; + return smBlendedMarginSizes[n]; + } else if (o.compact) { + var compactMarginSizes = [2, 1]; + return compactMarginSizes[n]; + } else if (o.blend) { + var blendedMarginSizes = [3, 2, 1, 0]; + return blendedMarginSizes[n]; + } else { + var marginSizes = [5, 3, 2, 1]; + return marginSizes[n]; + } + }, + + _borderSize: function (n) { + var o = this.options; + var borderSizes; + if (o.compact && (o.blend || this.isTransparent)) { + return 1; + } else if (o.compact) { + borderSizes = [1, 0]; + } else if (o.blend) { + borderSizes = [2, 1, 1, 1]; + } else if (o.border) { + borderSizes = [0, 2, 0, 0]; + } else if (this.isTransparent) { + borderSizes = [5, 3, 2, 1]; + } else { + return 0; + } + return borderSizes[n]; + }, + + _hasString: function (str) { + for (var i = 1; i< arguments.length; i++) { + if (str.indexOf(arguments[i]) != -1) { + return true; + } + } + return false; + }, + + _isTopRounded: function () { + return this._hasString(this.options.corners, + "all", "top", "tl", "tr" + ); + }, + + _isBottomRounded: function () { + return this._hasString(this.options.corners, + "all", "bottom", "bl", "br" + ); + }, + + _hasSingleTextChild: function (el) { + return (el.childNodes.length == 1 && el.childNodes[0].nodeType == 3); + } +}; + +/** @id MochiKit.Visual.roundElement */ +MochiKit.Visual.roundElement = function (e, options) { + new MochiKit.Visual._RoundCorners(e, options); +}; + +/** @id MochiKit.Visual.roundClass */ +MochiKit.Visual.roundClass = function (tagName, className, options) { + var elements = MochiKit.DOM.getElementsByTagAndClassName( + tagName, className + ); + for (var i = 0; i < elements.length; i++) { + MochiKit.Visual.roundElement(elements[i], options); + } +}; + +/** @id MochiKit.Visual.tagifyText */ +MochiKit.Visual.tagifyText = function (element, /* optional */tagifyStyle) { + /*** + + Change a node text to character in tags. + + @param tagifyStyle: the style to apply to character nodes, default to + 'position: relative'. + + ***/ + tagifyStyle = tagifyStyle || 'position:relative'; + if (/MSIE/.test(navigator.userAgent)) { + tagifyStyle += ';zoom:1'; + } + element = MochiKit.DOM.getElement(element); + var ma = MochiKit.Base.map; + ma(function (child) { + if (child.nodeType == 3) { + ma(function (character) { + element.insertBefore( + MochiKit.DOM.SPAN({style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), child); + }, child.nodeValue.split('')); + MochiKit.DOM.removeElement(child); + } + }, element.childNodes); +}; + +/** @id MochiKit.Visual.forceRerendering */ +MochiKit.Visual.forceRerendering = function (element) { + try { + element = MochiKit.DOM.getElement(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { + } +}; + +/** @id MochiKit.Visual.multiple */ +MochiKit.Visual.multiple = function (elements, effect, /* optional */options) { + /*** + + Launch the same effect subsequently on given elements. + + ***/ + options = MochiKit.Base.update({ + speed: 0.1, delay: 0.0 + }, options); + var masterDelay = options.delay; + var index = 0; + MochiKit.Base.map(function (innerelement) { + options.delay = index * options.speed + masterDelay; + new effect(innerelement, options); + index += 1; + }, elements); +}; + +MochiKit.Visual.PAIRS = { + 'slide': ['slideDown', 'slideUp'], + 'blind': ['blindDown', 'blindUp'], + 'appear': ['appear', 'fade'], + 'size': ['grow', 'shrink'] +}; + +/** @id MochiKit.Visual.toggle */ +MochiKit.Visual.toggle = function (element, /* optional */effect, /* optional */options) { + /*** + + Toggle an item between two state depending of its visibility, making + a effect between these states. Default effect is 'appear', can be + 'slide' or 'blind'. + + ***/ + element = MochiKit.DOM.getElement(element); + effect = (effect || 'appear').toLowerCase(); + options = MochiKit.Base.update({ + queue: {position: 'end', scope: (element.id || 'global'), limit: 1} + }, options); + var v = MochiKit.Visual; + v[MochiKit.Style.getStyle(element, 'display') != 'none' ? + v.PAIRS[effect][1] : v.PAIRS[effect][0]](element, options); +}; + +/*** + +Transitions: define functions calculating variations depending of a position. + +***/ + +MochiKit.Visual.Transitions = {}; + +/** @id MochiKit.Visual.Transitions.linear */ +MochiKit.Visual.Transitions.linear = function (pos) { + return pos; +}; + +/** @id MochiKit.Visual.Transitions.sinoidal */ +MochiKit.Visual.Transitions.sinoidal = function (pos) { + return 0.5 - Math.cos(pos*Math.PI)/2; +}; + +/** @id MochiKit.Visual.Transitions.reverse */ +MochiKit.Visual.Transitions.reverse = function (pos) { + return 1 - pos; +}; + +/** @id MochiKit.Visual.Transitions.flicker */ +MochiKit.Visual.Transitions.flicker = function (pos) { + return 0.25 - Math.cos(pos*Math.PI)/4 + Math.random()/2; +}; + +/** @id MochiKit.Visual.Transitions.wobble */ +MochiKit.Visual.Transitions.wobble = function (pos) { + return 0.5 - Math.cos(9*pos*Math.PI)/2; +}; + +/** @id MochiKit.Visual.Transitions.pulse */ +MochiKit.Visual.Transitions.pulse = function (pos, pulses) { + if (pulses) { + pos *= 2 * pulses; + } else { + pos *= 10; + } + var decimals = pos - Math.floor(pos); + return (Math.floor(pos) % 2 == 0) ? decimals : 1 - decimals; +}; + +/** @id MochiKit.Visual.Transitions.parabolic */ +MochiKit.Visual.Transitions.parabolic = function (pos) { + return pos * pos; +}; + +/** @id MochiKit.Visual.Transitions.none */ +MochiKit.Visual.Transitions.none = function (pos) { + return 0; +}; + +/** @id MochiKit.Visual.Transitions.full */ +MochiKit.Visual.Transitions.full = function (pos) { + return 1; +}; + +/*** + +Core effects + +***/ + +MochiKit.Visual.ScopedQueue = function () { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(); + } + this.__init__(); +}; + +MochiKit.Base.update(MochiKit.Visual.ScopedQueue.prototype, { + __init__: function () { + this.effects = []; + this.interval = null; + }, + + /** @id MochiKit.Visual.ScopedQueue.prototype.add */ + add: function (effect) { + var timestamp = new Date().getTime(); + + var position = (typeof(effect.options.queue) == 'string') ? + effect.options.queue : effect.options.queue.position; + + var ma = MochiKit.Base.map; + switch (position) { + case 'front': + // move unstarted effects after this effect + ma(function (e) { + if (e.state == 'idle') { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + } + }, this.effects); + break; + case 'end': + var finish; + // start effect after last queued effect has finished + ma(function (e) { + var i = e.finishOn; + if (i >= (finish || i)) { + finish = i; + } + }, this.effects); + timestamp = finish || timestamp; + break; + case 'break': + ma(function (e) { + e.finalize(); + }, this.effects); + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + if (!effect.options.queue.limit || + this.effects.length < effect.options.queue.limit) { + this.effects.push(effect); + } + + if (!this.interval) { + this.interval = this.startLoop(MochiKit.Base.bind(this.loop, this), + 40); + } + }, + + /** @id MochiKit.Visual.ScopedQueue.prototype.startLoop */ + startLoop: function (func, interval) { + return setInterval(func, interval); + }, + + /** @id MochiKit.Visual.ScopedQueue.prototype.remove */ + remove: function (effect) { + this.effects = MochiKit.Base.filter(function (e) { + return e != effect; + }, this.effects); + if (!this.effects.length) { + this.stopLoop(this.interval); + this.interval = null; + } + }, + + /** @id MochiKit.Visual.ScopedQueue.prototype.stopLoop */ + stopLoop: function (interval) { + clearInterval(interval); + }, + + /** @id MochiKit.Visual.ScopedQueue.prototype.loop */ + loop: function () { + var timePos = new Date().getTime(); + MochiKit.Base.map(function (effect) { + effect.loop(timePos); + }, this.effects); + } +}); + +MochiKit.Visual.Queues = { + instances: {}, + + get: function (queueName) { + if (typeof(queueName) != 'string') { + return queueName; + } + + if (!this.instances[queueName]) { + this.instances[queueName] = new MochiKit.Visual.ScopedQueue(); + } + return this.instances[queueName]; + } +}; + +MochiKit.Visual.Queue = MochiKit.Visual.Queues.get('global'); + +MochiKit.Visual.DefaultOptions = { + transition: MochiKit.Visual.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to MochiKit.Visual.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +}; + +MochiKit.Visual.Base = function () {}; + +MochiKit.Visual.Base.prototype = { + /*** + + Basic class for all Effects. Define a looping mechanism called for each step + of an effect. Don't instantiate it, only subclass it. + + ***/ + + __class__ : MochiKit.Visual.Base, + + /** @id MochiKit.Visual.Base.prototype.start */ + start: function (options) { + var v = MochiKit.Visual; + this.options = MochiKit.Base.setdefault(options, + v.DefaultOptions); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if (!this.options.sync) { + v.Queues.get(typeof(this.options.queue) == 'string' ? + 'global' : this.options.queue.scope).add(this); + } + }, + + /** @id MochiKit.Visual.Base.prototype.loop */ + loop: function (timePos) { + if (timePos >= this.startOn) { + if (timePos >= this.finishOn) { + return this.finalize(); + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = + Math.round(pos * this.options.fps * this.options.duration); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + + /** @id MochiKit.Visual.Base.prototype.render */ + render: function (pos) { + if (this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + this.setup(); + this.event('afterSetup'); + } + if (this.state == 'running') { + if (this.options.transition) { + pos = this.options.transition(pos); + } + pos *= (this.options.to - this.options.from); + pos += this.options.from; + this.event('beforeUpdate'); + this.update(pos); + this.event('afterUpdate'); + } + }, + + /** @id MochiKit.Visual.Base.prototype.cancel */ + cancel: function () { + if (!this.options.sync) { + MochiKit.Visual.Queues.get(typeof(this.options.queue) == 'string' ? + 'global' : this.options.queue.scope).remove(this); + } + this.state = 'finished'; + }, + + /** @id MochiKit.Visual.Base.prototype.finalize */ + finalize: function () { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + this.finish(); + this.event('afterFinish'); + }, + + setup: function () { + }, + + finish: function () { + }, + + update: function (position) { + }, + + /** @id MochiKit.Visual.Base.prototype.event */ + event: function (eventName) { + if (this.options[eventName + 'Internal']) { + this.options[eventName + 'Internal'](this); + } + if (this.options[eventName]) { + this.options[eventName](this); + } + }, + + /** @id MochiKit.Visual.Base.prototype.repr */ + repr: function () { + return '[' + this.__class__.NAME + ', options:' + + MochiKit.Base.repr(this.options) + ']'; + } +}; + +/** @id MochiKit.Visual.Parallel */ +MochiKit.Visual.Parallel = function (effects, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(effects, options); + } + + this.__init__(effects, options); +}; + +MochiKit.Visual.Parallel.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Parallel.prototype, { + /*** + + Run multiple effects at the same time. + + ***/ + + __class__ : MochiKit.Visual.Parallel, + + __init__: function (effects, options) { + this.effects = effects || []; + this.start(options); + }, + + /** @id MochiKit.Visual.Parallel.prototype.update */ + update: function (position) { + MochiKit.Base.map(function (effect) { + effect.render(position); + }, this.effects); + }, + + /** @id MochiKit.Visual.Parallel.prototype.finish */ + finish: function () { + MochiKit.Base.map(function (effect) { + effect.finalize(); + }, this.effects); + } +}); + +/** @id MochiKit.Visual.Sequence */ +MochiKit.Visual.Sequence = function (effects, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(effects, options); + } + this.__init__(effects, options); +}; + +MochiKit.Visual.Sequence.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Sequence.prototype, { + + __class__ : MochiKit.Visual.Sequence, + + __init__: function (effects, options) { + var defs = { transition: MochiKit.Visual.Transitions.linear, + duration: 0 }; + this.effects = effects || []; + MochiKit.Base.map(function (effect) { + defs.duration += effect.options.duration; + }, this.effects); + MochiKit.Base.setdefault(options, defs); + this.start(options); + }, + + /** @id MochiKit.Visual.Sequence.prototype.update */ + update: function (position) { + var time = position * this.options.duration; + for (var i = 0; i < this.effects.length; i++) { + var effect = this.effects[i]; + if (time <= effect.options.duration) { + effect.render(time / effect.options.duration); + break; + } else { + time -= effect.options.duration; + } + } + }, + + /** @id MochiKit.Visual.Sequence.prototype.finish */ + finish: function () { + MochiKit.Base.map(function (effect) { + effect.finalize(); + }, this.effects); + } +}); + +/** @id MochiKit.Visual.Opacity */ +MochiKit.Visual.Opacity = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.Visual.Opacity.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Opacity.prototype, { + /*** + + Change the opacity of an element. + + @param options: 'from' and 'to' change the starting and ending opacities. + Must be between 0.0 and 1.0. Default to current opacity and 1.0. + + ***/ + + __class__ : MochiKit.Visual.Opacity, + + __init__: function (element, /* optional */options) { + var b = MochiKit.Base; + var s = MochiKit.Style; + this.element = MochiKit.DOM.getElement(element); + // make this work on IE on elements without 'layout' + if (this.element.currentStyle && + (!this.element.currentStyle.hasLayout)) { + s.setStyle(this.element, {zoom: 1}); + } + options = b.update({ + from: s.getStyle(this.element, 'opacity') || 0.0, + to: 1.0 + }, options); + this.start(options); + }, + + /** @id MochiKit.Visual.Opacity.prototype.update */ + update: function (position) { + MochiKit.Style.setStyle(this.element, {'opacity': position}); + } +}); + +/** @id MochiKit.Visual.Move.prototype */ +MochiKit.Visual.Move = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.Visual.Move.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Move.prototype, { + /*** + + Move an element between its current position to a defined position + + @param options: 'x' and 'y' for final positions, default to 0, 0. + + ***/ + + __class__ : MochiKit.Visual.Move, + + __init__: function (element, /* optional */options) { + this.element = MochiKit.DOM.getElement(element); + options = MochiKit.Base.update({ + x: 0, + y: 0, + mode: 'relative' + }, options); + this.start(options); + }, + + /** @id MochiKit.Visual.Move.prototype.setup */ + setup: function () { + // Bug in Opera: Opera returns the 'real' position of a static element + // or relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your + // stylesheets (to 0 if you do not need them) + MochiKit.Style.makePositioned(this.element); + + var s = this.element.style; + var originalVisibility = s.visibility; + var originalDisplay = s.display; + if (originalDisplay == 'none') { + s.visibility = 'hidden'; + s.display = ''; + } + + this.originalLeft = parseFloat(MochiKit.Style.getStyle(this.element, 'left') || '0'); + this.originalTop = parseFloat(MochiKit.Style.getStyle(this.element, 'top') || '0'); + + if (this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x -= this.originalLeft; + this.options.y -= this.originalTop; + } + if (originalDisplay == 'none') { + s.visibility = originalVisibility; + s.display = originalDisplay; + } + }, + + /** @id MochiKit.Visual.Move.prototype.update */ + update: function (position) { + MochiKit.Style.setStyle(this.element, { + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +/** @id MochiKit.Visual.Scale */ +MochiKit.Visual.Scale = function (element, percent, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, percent, options); + } + this.__init__(element, percent, options); +}; + +MochiKit.Visual.Scale.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Scale.prototype, { + /*** + + Change the size of an element. + + @param percent: final_size = percent*original_size + + @param options: several options changing scale behaviour + + ***/ + + __class__ : MochiKit.Visual.Scale, + + __init__: function (element, percent, /* optional */options) { + this.element = MochiKit.DOM.getElement(element); + options = MochiKit.Base.update({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, options); + this.start(options); + }, + + /** @id MochiKit.Visual.Scale.prototype.setup */ + setup: function () { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = MochiKit.Style.getStyle(this.element, + 'position'); + + var ma = MochiKit.Base.map; + var b = MochiKit.Base.bind; + this.originalStyle = {}; + ma(b(function (k) { + this.originalStyle[k] = this.element.style[k]; + }, this), ['top', 'left', 'width', 'height', 'fontSize']); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = MochiKit.Style.getStyle(this.element, + 'font-size') || '100%'; + ma(b(function (fontSizeType) { + if (fontSize.indexOf(fontSizeType) > 0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }, this), ['em', 'px', '%']); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + if (/^content/.test(this.options.scaleMode)) { + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + } else if (this.options.scaleMode == 'box') { + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + } else { + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + } + }, + + /** @id MochiKit.Visual.Scale.prototype.update */ + update: function (position) { + var currentScale = (this.options.scaleFrom/100.0) + + (this.factor * position); + if (this.options.scaleContent && this.fontSize) { + MochiKit.Style.setStyle(this.element, { + fontSize: this.fontSize * currentScale + this.fontSizeType + }); + } + this.setDimensions(this.dims[0] * currentScale, + this.dims[1] * currentScale); + }, + + /** @id MochiKit.Visual.Scale.prototype.finish */ + finish: function () { + if (this.restoreAfterFinish) { + MochiKit.Style.setStyle(this.element, this.originalStyle); + } + }, + + /** @id MochiKit.Visual.Scale.prototype.setDimensions */ + setDimensions: function (height, width) { + var d = {}; + var r = Math.round; + if (/MSIE/.test(navigator.userAgent)) { + r = Math.ceil; + } + if (this.options.scaleX) { + d.width = r(width) + 'px'; + } + if (this.options.scaleY) { + d.height = r(height) + 'px'; + } + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) { + d.top = this.originalTop - topd + 'px'; + } + if (this.options.scaleX) { + d.left = this.originalLeft - leftd + 'px'; + } + } else { + if (this.options.scaleY) { + d.top = -topd + 'px'; + } + if (this.options.scaleX) { + d.left = -leftd + 'px'; + } + } + } + MochiKit.Style.setStyle(this.element, d); + } +}); + +/** @id MochiKit.Visual.Highlight */ +MochiKit.Visual.Highlight = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.Visual.Highlight.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Highlight.prototype, { + /*** + + Highlight an item of the page. + + @param options: 'startcolor' for choosing highlighting color, default + to '#ffff99'. + + ***/ + + __class__ : MochiKit.Visual.Highlight, + + __init__: function (element, /* optional */options) { + this.element = MochiKit.DOM.getElement(element); + options = MochiKit.Base.update({ + startcolor: '#ffff99' + }, options); + this.start(options); + }, + + /** @id MochiKit.Visual.Highlight.prototype.setup */ + setup: function () { + var b = MochiKit.Base; + var s = MochiKit.Style; + // Prevent executing on elements not in the layout flow + if (s.getStyle(this.element, 'display') == 'none') { + this.cancel(); + return; + } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: s.getStyle(this.element, 'background-image') + }; + s.setStyle(this.element, { + backgroundImage: 'none' + }); + + if (!this.options.endcolor) { + this.options.endcolor = + MochiKit.Color.Color.fromBackground(this.element).toHexString(); + } + if (b.isUndefinedOrNull(this.options.restorecolor)) { + this.options.restorecolor = s.getStyle(this.element, + 'background-color'); + } + // init color calculations + this._base = b.map(b.bind(function (i) { + return parseInt( + this.options.startcolor.slice(i*2 + 1, i*2 + 3), 16); + }, this), [0, 1, 2]); + this._delta = b.map(b.bind(function (i) { + return parseInt(this.options.endcolor.slice(i*2 + 1, i*2 + 3), 16) + - this._base[i]; + }, this), [0, 1, 2]); + }, + + /** @id MochiKit.Visual.Highlight.prototype.update */ + update: function (position) { + var m = '#'; + MochiKit.Base.map(MochiKit.Base.bind(function (i) { + m += MochiKit.Color.toColorPart(Math.round(this._base[i] + + this._delta[i]*position)); + }, this), [0, 1, 2]); + MochiKit.Style.setStyle(this.element, { + backgroundColor: m + }); + }, + + /** @id MochiKit.Visual.Highlight.prototype.finish */ + finish: function () { + MochiKit.Style.setStyle(this.element, + MochiKit.Base.update(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +/** @id MochiKit.Visual.ScrollTo */ +MochiKit.Visual.ScrollTo = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.Visual.ScrollTo.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.ScrollTo.prototype, { + /*** + + Scroll to an element in the page. + + ***/ + + __class__ : MochiKit.Visual.ScrollTo, + + __init__: function (element, /* optional */options) { + this.element = MochiKit.DOM.getElement(element); + this.start(options); + }, + + /** @id MochiKit.Visual.ScrollTo.prototype.setup */ + setup: function () { + var p = MochiKit.Position; + p.prepare(); + var offsets = p.cumulativeOffset(this.element); + if (this.options.offset) { + offsets.y += this.options.offset; + } + var max; + if (window.innerHeight) { + max = window.innerHeight - window.height; + } else if (document.documentElement && + document.documentElement.clientHeight) { + max = document.documentElement.clientHeight - + document.body.scrollHeight; + } else if (document.body) { + max = document.body.clientHeight - document.body.scrollHeight; + } + this.scrollStart = p.windowOffset.y; + this.delta = (offsets.y > max ? max : offsets.y) - this.scrollStart; + }, + + /** @id MochiKit.Visual.ScrollTo.prototype.update */ + update: function (position) { + var p = MochiKit.Position; + p.prepare(); + window.scrollTo(p.windowOffset.x, this.scrollStart + (position * this.delta)); + } +}); + +MochiKit.Visual.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +MochiKit.Visual.Morph = function (element, options) { + var cls = arguments.callee; + if (!(this instanceof cls)) { + return new cls(element, options); + } + this.__init__(element, options); +}; + +MochiKit.Visual.Morph.prototype = new MochiKit.Visual.Base(); + +MochiKit.Base.update(MochiKit.Visual.Morph.prototype, { + /*** + + Morph effect: make a transformation from current style to the given style, + automatically making a transition between the two. + + ***/ + + __class__ : MochiKit.Visual.Morph, + + __init__: function (element, /* optional */options) { + this.element = MochiKit.DOM.getElement(element); + this.start(options); + }, + + /** @id MochiKit.Visual.Morph.prototype.setup */ + setup: function () { + var b = MochiKit.Base; + var style = this.options.style; + this.styleStart = {}; + this.styleEnd = {}; + this.units = {}; + var value, unit; + for (var s in style) { + value = style[s]; + s = b.camelize(s); + if (MochiKit.Visual.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + this.styleEnd[s] = value; + this.units[s] = unit; + value = MochiKit.Style.getStyle(this.element, s); + components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + this.styleStart[s] = value; + } else if (/[Cc]olor$/.test(s)) { + var c = MochiKit.Color.Color; + value = c.fromString(value); + if (value) { + this.units[s] = "color"; + this.styleEnd[s] = value.toHexString(); + value = MochiKit.Style.getStyle(this.element, s); + this.styleStart[s] = c.fromString(value).toHexString(); + + this.styleStart[s] = b.map(b.bind(function (i) { + return parseInt( + this.styleStart[s].slice(i*2 + 1, i*2 + 3), 16); + }, this), [0, 1, 2]); + this.styleEnd[s] = b.map(b.bind(function (i) { + return parseInt( + this.styleEnd[s].slice(i*2 + 1, i*2 + 3), 16); + }, this), [0, 1, 2]); + } + } else { + // For non-length & non-color properties, we just set the value + this.element.style[s] = value; + } + } + }, + + /** @id MochiKit.Visual.Morph.prototype.update */ + update: function (position) { + var value; + for (var s in this.styleStart) { + if (this.units[s] == "color") { + var m = '#'; + var start = this.styleStart[s]; + var end = this.styleEnd[s]; + MochiKit.Base.map(MochiKit.Base.bind(function (i) { + m += MochiKit.Color.toColorPart(Math.round(start[i] + + (end[i] - start[i])*position)); + }, this), [0, 1, 2]); + this.element.style[s] = m; + } else { + value = this.styleStart[s] + Math.round((this.styleEnd[s] - this.styleStart[s]) * position * 1000) / 1000 + this.units[s]; + this.element.style[s] = value; + } + } + } +}); + +/*** + +Combination effects. + +***/ + +/** @id MochiKit.Visual.fade */ +MochiKit.Visual.fade = function (element, /* optional */ options) { + /*** + + Fade a given element: change its opacity and hide it in the end. + + @param options: 'to' and 'from' to change opacity. + + ***/ + var s = MochiKit.Style; + var oldOpacity = s.getStyle(element, 'opacity'); + options = MochiKit.Base.update({ + from: s.getStyle(element, 'opacity') || 1.0, + to: 0.0, + afterFinishInternal: function (effect) { + if (effect.options.to !== 0) { + return; + } + s.hideElement(effect.element); + s.setStyle(effect.element, {'opacity': oldOpacity}); + } + }, options); + return new MochiKit.Visual.Opacity(element, options); +}; + +/** @id MochiKit.Visual.appear */ +MochiKit.Visual.appear = function (element, /* optional */ options) { + /*** + + Make an element appear. + + @param options: 'to' and 'from' to change opacity. + + ***/ + var s = MochiKit.Style; + var v = MochiKit.Visual; + options = MochiKit.Base.update({ + from: (s.getStyle(element, 'display') == 'none' ? 0.0 : + s.getStyle(element, 'opacity') || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function (effect) { + v.forceRerendering(effect.element); + }, + beforeSetupInternal: function (effect) { + s.setStyle(effect.element, {'opacity': effect.options.from}); + s.showElement(effect.element); + } + }, options); + return new v.Opacity(element, options); +}; + +/** @id MochiKit.Visual.puff */ +MochiKit.Visual.puff = function (element, /* optional */ options) { + /*** + + 'Puff' an element: grow it to double size, fading it and make it hidden. + + ***/ + var s = MochiKit.Style; + var v = MochiKit.Visual; + element = MochiKit.DOM.getElement(element); + var elementDimensions = MochiKit.Style.getElementDimensions(element, true); + var oldStyle = { + position: s.getStyle(element, 'position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height, + opacity: s.getStyle(element, 'opacity') + }; + options = MochiKit.Base.update({ + beforeSetupInternal: function (effect) { + MochiKit.Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function (effect) { + s.hideElement(effect.effects[0].element); + s.setStyle(effect.effects[0].element, oldStyle); + }, + scaleContent: true, + scaleFromCenter: true + }, options); + return new v.Parallel( + [new v.Scale(element, 200, + {sync: true, scaleFromCenter: options.scaleFromCenter, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + scaleContent: options.scaleContent, restoreAfterFinish: true}), + new v.Opacity(element, {sync: true, to: 0.0 })], + options); +}; + +/** @id MochiKit.Visual.blindUp */ +MochiKit.Visual.blindUp = function (element, /* optional */ options) { + /*** + + Blind an element up: change its vertical size to 0. + + ***/ + var d = MochiKit.DOM; + var s = MochiKit.Style; + element = d.getElement(element); + var elementDimensions = s.getElementDimensions(element, true); + var elemClip = s.makeClipping(element); + options = MochiKit.Base.update({ + scaleContent: false, + scaleX: false, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + restoreAfterFinish: true, + afterFinishInternal: function (effect) { + s.hideElement(effect.element); + s.undoClipping(effect.element, elemClip); + } + }, options); + return new MochiKit.Visual.Scale(element, 0, options); +}; + +/** @id MochiKit.Visual.blindDown */ +MochiKit.Visual.blindDown = function (element, /* optional */ options) { + /*** + + Blind an element down: restore its vertical size. + + ***/ + var d = MochiKit.DOM; + var s = MochiKit.Style; + element = d.getElement(element); + var elementDimensions = s.getElementDimensions(element, true); + var elemClip; + options = MochiKit.Base.update({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + restoreAfterFinish: true, + afterSetupInternal: function (effect) { + elemClip = s.makeClipping(effect.element); + s.setStyle(effect.element, {height: '0px'}); + s.showElement(effect.element); + }, + afterFinishInternal: function (effect) { + s.undoClipping(effect.element, elemClip); + } + }, options); + return new MochiKit.Visual.Scale(element, 100, options); +}; + +/** @id MochiKit.Visual.switchOff */ +MochiKit.Visual.switchOff = function (element, /* optional */ options) { + /*** + + Apply a switch-off-like effect. + + ***/ + var d = MochiKit.DOM; + var s = MochiKit.Style; + element = d.getElement(element); + var elementDimensions = s.getElementDimensions(element, true); + var oldOpacity = s.getStyle(element, 'opacity'); + var elemClip; + options = MochiKit.Base.update({ + duration: 0.7, + restoreAfterFinish: true, + beforeSetupInternal: function (effect) { + s.makePositioned(element); + elemClip = s.makeClipping(element); + }, + afterFinishInternal: function (effect) { + s.hideElement(element); + s.undoClipping(element, elemClip); + s.undoPositioned(element); + s.setStyle(element, {'opacity': oldOpacity}); + } + }, options); + var v = MochiKit.Visual; + return new v.Sequence( + [new v.appear(element, + { sync: true, duration: 0.57 * options.duration, + from: 0, transition: v.Transitions.flicker }), + new v.Scale(element, 1, + { sync: true, duration: 0.43 * options.duration, + scaleFromCenter: true, scaleX: false, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + scaleContent: false, restoreAfterFinish: true })], + options); +}; + +/** @id MochiKit.Visual.dropOut */ +MochiKit.Visual.dropOut = function (element, /* optional */ options) { + /*** + + Make an element fall and disappear. + + ***/ + var d = MochiKit.DOM; + var s = MochiKit.Style; + element = d.getElement(element); + var oldStyle = { + top: s.getStyle(element, 'top'), + left: s.getStyle(element, 'left'), + opacity: s.getStyle(element, 'opacity') + }; + + options = MochiKit.Base.update({ + duration: 0.5, + distance: 100, + beforeSetupInternal: function (effect) { + s.makePositioned(effect.effects[0].element); + }, + afterFinishInternal: function (effect) { + s.hideElement(effect.effects[0].element); + s.undoPositioned(effect.effects[0].element); + s.setStyle(effect.effects[0].element, oldStyle); + } + }, options); + var v = MochiKit.Visual; + return new v.Parallel( + [new v.Move(element, {x: 0, y: options.distance, sync: true}), + new v.Opacity(element, {sync: true, to: 0.0})], + options); +}; + +/** @id MochiKit.Visual.shake */ +MochiKit.Visual.shake = function (element, /* optional */ options) { + /*** + + Move an element from left to right several times. + + ***/ + var d = MochiKit.DOM; + var v = MochiKit.Visual; + var s = MochiKit.Style; + element = d.getElement(element); + var oldStyle = { + top: s.getStyle(element, 'top'), + left: s.getStyle(element, 'left') + }; + options = MochiKit.Base.update({ + duration: 0.5, + afterFinishInternal: function (effect) { + s.undoPositioned(element); + s.setStyle(element, oldStyle); + } + }, options); + return new v.Sequence( + [new v.Move(element, { sync: true, duration: 0.1 * options.duration, + x: 20, y: 0 }), + new v.Move(element, { sync: true, duration: 0.2 * options.duration, + x: -40, y: 0 }), + new v.Move(element, { sync: true, duration: 0.2 * options.duration, + x: 40, y: 0 }), + new v.Move(element, { sync: true, duration: 0.2 * options.duration, + x: -40, y: 0 }), + new v.Move(element, { sync: true, duration: 0.2 * options.duration, + x: 40, y: 0 }), + new v.Move(element, { sync: true, duration: 0.1 * options.duration, + x: -20, y: 0 })], + options); +}; + +/** @id MochiKit.Visual.slideDown */ +MochiKit.Visual.slideDown = function (element, /* optional */ options) { + /*** + + Slide an element down. + It needs to have the content of the element wrapped in a container + element with fixed height. + + ***/ + var d = MochiKit.DOM; + var b = MochiKit.Base; + var s = MochiKit.Style; + element = d.getElement(element); + if (!element.firstChild) { + throw new Error("MochiKit.Visual.slideDown must be used on a element with a child"); + } + d.removeEmptyTextNodes(element); + var oldInnerBottom = s.getStyle(element.firstChild, 'bottom') || 0; + var elementDimensions = s.getElementDimensions(element, true); + var elemClip; + options = b.update({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + restoreAfterFinish: true, + afterSetupInternal: function (effect) { + s.makePositioned(effect.element); + s.makePositioned(effect.element.firstChild); + if (/Opera/.test(navigator.userAgent)) { + s.setStyle(effect.element, {top: ''}); + } + elemClip = s.makeClipping(effect.element); + s.setStyle(effect.element, {height: '0px'}); + s.showElement(effect.element); + }, + afterUpdateInternal: function (effect) { + var elementDimensions = s.getElementDimensions(effect.element, true); + s.setStyle(effect.element.firstChild, + {bottom: (effect.dims[0] - elementDimensions.h) + 'px'}); + }, + afterFinishInternal: function (effect) { + s.undoClipping(effect.element, elemClip); + // IE will crash if child is undoPositioned first + if (/MSIE/.test(navigator.userAgent)) { + s.undoPositioned(effect.element); + s.undoPositioned(effect.element.firstChild); + } else { + s.undoPositioned(effect.element.firstChild); + s.undoPositioned(effect.element); + } + s.setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); + } + }, options); + + return new MochiKit.Visual.Scale(element, 100, options); +}; + +/** @id MochiKit.Visual.slideUp */ +MochiKit.Visual.slideUp = function (element, /* optional */ options) { + /*** + + Slide an element up. + It needs to have the content of the element wrapped in a container + element with fixed height. + + ***/ + var d = MochiKit.DOM; + var b = MochiKit.Base; + var s = MochiKit.Style; + element = d.getElement(element); + if (!element.firstChild) { + throw new Error("MochiKit.Visual.slideUp must be used on a element with a child"); + } + d.removeEmptyTextNodes(element); + var oldInnerBottom = s.getStyle(element.firstChild, 'bottom'); + var elementDimensions = s.getElementDimensions(element, true); + var elemClip; + options = b.update({ + scaleContent: false, + scaleX: false, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function (effect) { + s.makePositioned(effect.element); + s.makePositioned(effect.element.firstChild); + if (/Opera/.test(navigator.userAgent)) { + s.setStyle(effect.element, {top: ''}); + } + elemClip = s.makeClipping(effect.element); + s.showElement(effect.element); + }, + afterUpdateInternal: function (effect) { + var elementDimensions = s.getElementDimensions(effect.element, true); + s.setStyle(effect.element.firstChild, + {bottom: (effect.dims[0] - elementDimensions.h) + 'px'}); + }, + afterFinishInternal: function (effect) { + s.hideElement(effect.element); + s.undoClipping(effect.element, elemClip); + s.undoPositioned(effect.element.firstChild); + s.undoPositioned(effect.element); + s.setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); + } + }, options); + return new MochiKit.Visual.Scale(element, 0, options); +}; + +// Bug in opera makes the TD containing this element expand for a instance +// after finish +/** @id MochiKit.Visual.squish */ +MochiKit.Visual.squish = function (element, /* optional */ options) { + /*** + + Reduce an element and make it disappear. + + ***/ + var d = MochiKit.DOM; + var b = MochiKit.Base; + var s = MochiKit.Style; + var elementDimensions = s.getElementDimensions(element, true); + var elemClip; + options = b.update({ + restoreAfterFinish: true, + scaleMode: {originalHeight: elementDimensions.w, + originalWidth: elementDimensions.h}, + beforeSetupInternal: function (effect) { + elemClip = s.makeClipping(effect.element); + }, + afterFinishInternal: function (effect) { + s.hideElement(effect.element); + s.undoClipping(effect.element, elemClip); + } + }, options); + + return new MochiKit.Visual.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, options); +}; + +/** @id MochiKit.Visual.grow */ +MochiKit.Visual.grow = function (element, /* optional */ options) { + /*** + + Grow an element to its original size. Make it zero-sized before + if necessary. + + ***/ + var d = MochiKit.DOM; + var v = MochiKit.Visual; + var s = MochiKit.Style; + element = d.getElement(element); + options = MochiKit.Base.update({ + direction: 'center', + moveTransition: v.Transitions.sinoidal, + scaleTransition: v.Transitions.sinoidal, + opacityTransition: v.Transitions.full, + scaleContent: true, + scaleFromCenter: false + }, options); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: s.getStyle(element, 'opacity') + }; + var dims = s.getElementDimensions(element, true); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.w; + initialMoveY = moveY = 0; + moveX = -dims.w; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.h; + moveY = -dims.h; + break; + case 'bottom-right': + initialMoveX = dims.w; + initialMoveY = dims.h; + moveX = -dims.w; + moveY = -dims.h; + break; + case 'center': + initialMoveX = dims.w / 2; + initialMoveY = dims.h / 2; + moveX = -dims.w / 2; + moveY = -dims.h / 2; + break; + } + + var optionsParallel = MochiKit.Base.update({ + beforeSetupInternal: function (effect) { + s.setStyle(effect.effects[0].element, {height: '0px'}); + s.showElement(effect.effects[0].element); + }, + afterFinishInternal: function (effect) { + s.undoClipping(effect.effects[0].element); + s.undoPositioned(effect.effects[0].element); + s.setStyle(effect.effects[0].element, oldStyle); + } + }, options); + + return new v.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetupInternal: function (effect) { + s.hideElement(effect.element); + s.makeClipping(effect.element); + s.makePositioned(effect.element); + }, + afterFinishInternal: function (effect) { + new v.Parallel( + [new v.Opacity(effect.element, { + sync: true, to: 1.0, from: 0.0, + transition: options.opacityTransition + }), + new v.Move(effect.element, { + x: moveX, y: moveY, sync: true, + transition: options.moveTransition + }), + new v.Scale(effect.element, 100, { + scaleMode: {originalHeight: dims.h, + originalWidth: dims.w}, + sync: true, + scaleFrom: /Opera/.test(navigator.userAgent) ? 1 : 0, + transition: options.scaleTransition, + scaleContent: options.scaleContent, + scaleFromCenter: options.scaleFromCenter, + restoreAfterFinish: true + }) + ], optionsParallel + ); + } + }); +}; + +/** @id MochiKit.Visual.shrink */ +MochiKit.Visual.shrink = function (element, /* optional */ options) { + /*** + + Shrink an element and make it disappear. + + ***/ + var d = MochiKit.DOM; + var v = MochiKit.Visual; + var s = MochiKit.Style; + element = d.getElement(element); + options = MochiKit.Base.update({ + direction: 'center', + moveTransition: v.Transitions.sinoidal, + scaleTransition: v.Transitions.sinoidal, + opacityTransition: v.Transitions.none, + scaleContent: true, + scaleFromCenter: false + }, options); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: s.getStyle(element, 'opacity') + }; + + var dims = s.getElementDimensions(element, true); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.w; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.h; + break; + case 'bottom-right': + moveX = dims.w; + moveY = dims.h; + break; + case 'center': + moveX = dims.w / 2; + moveY = dims.h / 2; + break; + } + var elemClip; + + var optionsParallel = MochiKit.Base.update({ + beforeStartInternal: function (effect) { + s.makePositioned(effect.effects[0].element); + elemClip = s.makeClipping(effect.effects[0].element); + }, + afterFinishInternal: function (effect) { + s.hideElement(effect.effects[0].element); + s.undoClipping(effect.effects[0].element, elemClip); + s.undoPositioned(effect.effects[0].element); + s.setStyle(effect.effects[0].element, oldStyle); + } + }, options); + + return new v.Parallel( + [new v.Opacity(element, { + sync: true, to: 0.0, from: 1.0, + transition: options.opacityTransition + }), + new v.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, { + scaleMode: {originalHeight: dims.h, originalWidth: dims.w}, + sync: true, transition: options.scaleTransition, + scaleContent: options.scaleContent, + scaleFromCenter: options.scaleFromCenter, + restoreAfterFinish: true + }), + new v.Move(element, { + x: moveX, y: moveY, sync: true, transition: options.moveTransition + }) + ], optionsParallel + ); +}; + +/** @id MochiKit.Visual.pulsate */ +MochiKit.Visual.pulsate = function (element, /* optional */ options) { + /*** + + Pulse an element between appear/fade. + + ***/ + var d = MochiKit.DOM; + var v = MochiKit.Visual; + var b = MochiKit.Base; + var oldOpacity = MochiKit.Style.getStyle(element, 'opacity'); + options = b.update({ + duration: 3.0, + from: 0, + afterFinishInternal: function (effect) { + MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity}); + } + }, options); + var transition = options.transition || v.Transitions.sinoidal; + options.transition = function (pos) { + return transition(1 - v.Transitions.pulse(pos, options.pulses)); + }; + return new v.Opacity(element, options); +}; + +/** @id MochiKit.Visual.fold */ +MochiKit.Visual.fold = function (element, /* optional */ options) { + /*** + + Fold an element, first vertically, then horizontally. + + ***/ + var d = MochiKit.DOM; + var v = MochiKit.Visual; + var s = MochiKit.Style; + element = d.getElement(element); + var elementDimensions = s.getElementDimensions(element, true); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + var elemClip = s.makeClipping(element); + options = MochiKit.Base.update({ + scaleContent: false, + scaleX: false, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + afterFinishInternal: function (effect) { + new v.Scale(element, 1, { + scaleContent: false, + scaleY: false, + scaleMode: {originalHeight: elementDimensions.h, + originalWidth: elementDimensions.w}, + afterFinishInternal: function (effect) { + s.hideElement(effect.element); + s.undoClipping(effect.element, elemClip); + s.setStyle(effect.element, oldStyle); + } + }); + } + }, options); + return new v.Scale(element, 5, options); +}; + + +// Compatibility with MochiKit 1.0 +MochiKit.Visual.Color = MochiKit.Color.Color; +MochiKit.Visual.getElementsComputedStyle = MochiKit.DOM.computedStyle; + +/* end of Rico adaptation */ + +MochiKit.Visual.__new__ = function () { + var m = MochiKit.Base; + + m.nameFunctions(this); + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; + +}; + +MochiKit.Visual.EXPORT = [ + "roundElement", + "roundClass", + "tagifyText", + "multiple", + "toggle", + "Parallel", + "Sequence", + "Opacity", + "Move", + "Scale", + "Highlight", + "ScrollTo", + "Morph", + "fade", + "appear", + "puff", + "blindUp", + "blindDown", + "switchOff", + "dropOut", + "shake", + "slideDown", + "slideUp", + "squish", + "grow", + "shrink", + "pulsate", + "fold" +]; + +MochiKit.Visual.EXPORT_OK = [ + "Base", + "PAIRS" +]; + +MochiKit.Visual.__new__(); + +MochiKit.Base._exportSymbols(this, MochiKit.Visual); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/SimpleTest/test.css b/testing/mochitest/tests/MochiKit-1.4.2/tests/SimpleTest/test.css new file mode 100644 index 000000000..9fc0a3f8d --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/SimpleTest/test.css @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +@import url("../../../SimpleTest/test.css"); diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/mochitest.ini b/testing/mochitest/tests/MochiKit-1.4.2/tests/mochitest.ini new file mode 100644 index 000000000..ed8916049 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/mochitest.ini @@ -0,0 +1,32 @@ +[DEFAULT] +support-files = + SimpleTest/test.css + test_Base.js + test_Color.js + test_DateTime.js + test_DragAndDrop.js + test_Format.js + test_Iter.js + test_Logging.js + test_MochiKit-Async.json + test_Signal.js + +[test_MochiKit-Async.html] +[test_MochiKit-Base.html] +[test_MochiKit-Color.html] +[test_MochiKit-DateTime.html] +[test_MochiKit-DOM.html] +[test_MochiKit-DOM-Safari.html] +skip-if = (android_version == '18' && debug) # intermittent time-out +[test_MochiKit-DragAndDrop.html] +[test_MochiKit-Format.html] +[test_MochiKit-Iter.html] +[test_MochiKit-Logging.html] +[test_MochiKit-MochiKit.html] +[test_MochiKit-Selector.html] +[test_MochiKit-Signal.html] +[test_MochiKit-Style.html] +[test_MochiKit-Visual.html] +# This test is broken: "Error: JSAN is not defined ... Line: 10". +# (And is removed in future MochiKit v1.5) +#[test_MochiKit-JSAN.html] diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Base.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Base.js new file mode 100644 index 000000000..eb0c5f577 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Base.js @@ -0,0 +1,577 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Base'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Base'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_Base = function (t) { + // test bind + var not_self = {"toString": function () { return "not self"; } }; + var self = {"toString": function () { return "self"; } }; + var func = function (arg) { return this.toString() + " " + arg; }; + var boundFunc = bind(func, self); + not_self.boundFunc = boundFunc; + + t.is( boundFunc("foo"), "self foo", "boundFunc bound to self properly" ); + t.is( not_self.boundFunc("foo"), "self foo", "boundFunc bound to self on another obj" ); + t.is( bind(boundFunc, not_self)("foo"), "not self foo", "boundFunc successfully rebound!" ); + t.is( bind(boundFunc, undefined, "foo")(), "self foo", "boundFunc partial no self change" ); + t.is( bind(boundFunc, not_self, "foo")(), "not self foo", "boundFunc partial self change" ); + + // test method + not_self = {"toString": function () { return "not self"; } }; + self = {"toString": function () { return "self"; } }; + func = function (arg) { return this.toString() + " " + arg; }; + var boundMethod = method(self, func); + not_self.boundMethod = boundMethod; + + t.is( boundMethod("foo"), "self foo", "boundMethod bound to self properly" ); + t.is( not_self.boundMethod("foo"), "self foo", "boundMethod bound to self on another obj" ); + t.is( method(not_self, boundMethod)("foo"), "not self foo", "boundMethod successfully rebound!" ); + t.is( method(undefined, boundMethod, "foo")(), "self foo", "boundMethod partial no self change" ); + t.is( method(not_self, boundMethod, "foo")(), "not self foo", "boundMethod partial self change" ); + + // test bindLate + self = {"toString": function () { return "self"; } }; + boundFunc = bindLate("toString", self); + t.is( boundFunc(), "self", "bindLate binds properly" ); + self.toString = function () { return "not self"; }; + t.is( boundFunc(), "not self", "bindLate late function lookup" ); + func = function (arg) { return this.toString() + " " + arg; }; + boundFunc = bindLate(func, self); + t.is( boundFunc("foo"), "not self foo", "bindLate fallback to standard bind" ); + + // test bindMethods + + var O = function (value) { + bindMethods(this); + this.value = value; + }; + O.prototype.func = function () { + return this.value; + }; + + var o = new O("boring"); + var p = {}; + p.func = o.func; + var func = o.func; + t.is( o.func(), "boring", "bindMethods doesn't break shit" ); + t.is( p.func(), "boring", "bindMethods works on other objects" ); + t.is( func(), "boring", "bindMethods works on functions" ); + + var p = clone(o); + t.ok( p instanceof O, "cloned correct inheritance" ); + var q = clone(p); + t.ok( q instanceof O, "clone-cloned correct inheritance" ); + q.foo = "bar"; + t.is( p.foo, undefined, "clone-clone is copy-on-write" ); + p.bar = "foo"; + t.is( o.bar, undefined, "clone is copy-on-write" ); + t.is( q.bar, "foo", "clone-clone has proper delegation" ); + // unbind + p.func = bind(p.func, null); + t.is( p.func(), "boring", "clone function calls correct" ); + q.value = "awesome"; + t.is( q.func(), "awesome", "clone really does work" ); + + // test boring boolean funcs + t.is( isNull(null), true, "isNull matches null" ); + t.is( isNull(undefined), false, "isNull doesn't match undefined" ); + t.is( isNull({}), false, "isNull doesn't match objects" ); + + t.is( isCallable(isCallable), true, "isCallable returns true on itself" ); + t.is( isCallable(1), false, "isCallable returns false on numbers" ); + + t.is( isUndefined(null), false, "null is not undefined" ); + t.is( isUndefined(""), false, "empty string is not undefined" ); + t.is( isUndefined(undefined), true, "undefined is undefined" ); + t.is( isUndefined({}.foo), true, "missing property is undefined" ); + + t.is( isUndefinedOrNull(null), true, "null is undefined or null" ); + t.is( isUndefinedOrNull(""), false, "empty string is not undefined or null" ); + t.is( isUndefinedOrNull(undefined), true, "undefined is undefined or null" ); + t.is( isUndefinedOrNull({}.foo), true, "missing property is undefined or null" ); + + t.is( isEmpty(null), true, "isEmpty null" ); + t.is( isEmpty([], [], ""), true, "isEmpty true" ); + t.is( isEmpty([], [1], ""), true, "isEmpty true" ); + t.is( isEmpty([1], [1], "1"), false, "isEmpty false" ); + t.is( isEmpty([1], [1], "1"), false, "isEmpty false" ); + + t.is( isNotEmpty(null), false, "isNotEmpty null" ); + t.is( isNotEmpty([], [], ""), false, "isNotEmpty false" ); + t.is( isNotEmpty([], [1], ""), false, "isNotEmpty false" ); + t.is( isNotEmpty([1], [1], "1"), true, "isNotEmpty true" ); + t.is( isNotEmpty([1], [1], "1"), true, "isNotEmpty true" ); + + t.is( isArrayLike(undefined), false, "isArrayLike(undefined)" ); + t.is( isArrayLike(null), false, "isArrayLike(null)" ); + t.is( isArrayLike([]), true, "isArrayLike([])" ); + + // test extension of arrays + var a = []; + var b = []; + var three = [1, 2, 3]; + + extend(a, three, 1); + t.ok( objEqual(a, [2, 3]), "extend to an empty array" ); + extend(a, three, 1) + t.ok( objEqual(a, [2, 3, 2, 3]), "extend to a non-empty array" ); + + extend(b, three); + t.ok( objEqual(b, three), "extend of an empty array" ); + + var c1 = extend(null, three); + t.ok( objEqual(c1, three), "extend null" ); + var c2 = extend(undefined, three); + t.ok( objEqual(c2, three), "extend undefined" ); + + + t.is( compare(1, 2), -1, "numbers compare lt" ); + t.is( compare(2, 1), 1, "numbers compare gt" ); + t.is( compare(1, 1), 0, "numbers compare eq" ); + t.is( compare([1], [1]), 0, "arrays compare eq" ); + t.is( compare([1], [1, 2]), -1, "arrays compare lt (length)" ); + t.is( compare([1, 2], [2, 1]), -1, "arrays compare lt (contents)" ); + t.is( compare([1, 2], [1]), 1, "arrays compare gt (length)" ); + t.is( compare([2, 1], [1, 1]), 1, "arrays compare gt (contents)" ); + + // test partial application + var a = []; + var func = function (a, b) { + if (arguments.length != 2) { + return "bad args"; + } else { + return this.value + a + b; + } + }; + var self = {"value": 1, "func": func}; + var self2 = {"value": 2}; + t.is( self.func(2, 3), 6, "setup for test is correct" ); + self.funcTwo = partial(self.func, 2); + t.is( self.funcTwo(3), 6, "partial application works" ); + t.is( self.funcTwo(3), 6, "partial application works still" ); + t.is( bind(self.funcTwo, self2)(3), 7, "rebinding partial works" ); + self.funcTwo = bind(bind(self.funcTwo, self2), null); + t.is( self.funcTwo(3), 6, "re-unbinding partial application works" ); + + + // nodeWalk test + // ... looks a lot like a DOM tree on purpose + var tree = { + "id": "nodeWalkTestTree", + "test:int": "1", + "childNodes": [ + { + "test:int": "2", + "childNodes": [ + {"test:int": "5"}, + "ignored string", + {"ignored": "object"}, + ["ignored", "list"], + { + "test:skipchildren": "1", + "childNodes": [{"test:int": 6}] + } + ] + }, + {"test:int": "3"}, + {"test:int": "4"} + ] + } + + var visitedNodes = []; + nodeWalk(tree, function (node) { + var attr = node["test:int"]; + if (attr) { + visitedNodes.push(attr); + } + if (node["test:skipchildren"]) { + return; + } + return node.childNodes; + }); + + t.ok( objEqual(visitedNodes, ["1", "2", "3", "4", "5"]), "nodeWalk looks like it works"); + + // test map + var minusOne = function (x) { return x - 1; }; + var res = map(minusOne, [1, 2, 3]); + t.ok( objEqual(res, [0, 1, 2]), "map works" ); + + var res2 = xmap(minusOne, 1, 2, 3); + t.ok( objEqual(res2, res), "xmap works" ); + + res = map(operator.add, [1, 2, 3], [2, 4, 6]); + t.ok( objEqual(res, [3, 6, 9]), "map(fn, p, q) works" ); + + res = map(operator.add, [1, 2, 3], [2, 4, 6, 8]); + t.ok( objEqual(res, [3, 6, 9]), "map(fn, p, q) works (q long)" ); + + res = map(operator.add, [1, 2, 3, 4], [2, 4, 6]); + t.ok( objEqual(res, [3, 6, 9]), "map(fn, p, q) works (p long)" ); + + res = map(null, [1, 2, 3], [2, 4, 6]); + t.ok( objEqual(res, [[1, 2], [2, 4], [3, 6]]), "map(null, p, q) works" ); + + res = zip([1, 2, 3], [2, 4, 6]); + t.ok( objEqual(res, [[1, 2], [2, 4], [3, 6]]), "zip(p, q) works" ); + + res = map(null, [1, 2, 3]); + t.ok( objEqual(res, [1, 2, 3]), "map(null, lst) works" ); + + + + + t.is( isNotEmpty("foo"), true, "3 char string is not empty" ); + t.is( isNotEmpty(""), false, "0 char string is empty" ); + t.is( isNotEmpty([1, 2, 3]), true, "3 element list is not empty" ); + t.is( isNotEmpty([]), false, "0 element list is empty" ); + + // test filter + var greaterThanThis = function (x) { return x > this; }; + var greaterThanOne = function (x) { return x > 1; }; + var res = filter(greaterThanOne, [-1, 0, 1, 2, 3]); + t.ok( objEqual(res, [2, 3]), "filter works" ); + var res = filter(greaterThanThis, [-1, 0, 1, 2, 3], 1); + t.ok( objEqual(res, [2, 3]), "filter self works" ); + var res2 = xfilter(greaterThanOne, -1, 0, 1, 2, 3); + t.ok( objEqual(res2, res), "xfilter works" ); + + t.is(objMax(1, 2, 9, 12, 42, -16, 16), 42, "objMax works (with numbers)"); + t.is(objMin(1, 2, 9, 12, 42, -16, 16), -16, "objMin works (with numbers)"); + + // test adapter registry + + var R = new AdapterRegistry(); + R.register("callable", isCallable, function () { return "callable"; }); + R.register("arrayLike", isArrayLike, function () { return "arrayLike"; }); + t.is( R.match(function () {}), "callable", "registry found callable" ); + t.is( R.match([]), "arrayLike", "registry found ArrayLike" ); + try { + R.match(null); + t.ok( false, "non-matching didn't raise!" ); + } catch (e) { + t.is( e, NotFound, "non-matching raised correctly" ); + } + R.register("undefinedOrNull", isUndefinedOrNull, function () { return "undefinedOrNull" }); + R.register("undefined", isUndefined, function () { return "undefined" }); + t.is( R.match(undefined), "undefinedOrNull", "priorities are as documented" ); + t.ok( R.unregister("undefinedOrNull"), "removed adapter" ); + t.is( R.match(undefined), "undefined", "adapter was removed" ); + R.register("undefinedOrNull", isUndefinedOrNull, function () { return "undefinedOrNull" }, true); + t.is( R.match(undefined), "undefinedOrNull", "override works" ); + + var a1 = {"a": 1, "b": 2, "c": 2}; + var a2 = {"a": 2, "b": 1, "c": 2}; + t.is( keyComparator("a")(a1, a2), -1, "keyComparator 1 lt" ); + t.is( keyComparator("c")(a1, a2), 0, "keyComparator 1 eq" ); + t.is( keyComparator("c", "b")(a1, a2), 1, "keyComparator 2 eq gt" ); + t.is( keyComparator("c", "a")(a1, a2), -1, "keyComparator 2 eq lt" ); + t.is( reverseKeyComparator("a")(a1, a2), 1, "reverseKeyComparator" ); + t.is( compare(concat([1], [2], [3]), [1, 2, 3]), 0, "concat" ); + t.is( repr("foo"), '"foo"', "string repr" ); + t.is( repr(1), '1', "number repr" ); + t.is( listMin([1, 3, 5, 3, -1]), -1, "listMin" ); + t.is( objMin(1, 3, 5, 3, -1), -1, "objMin" ); + t.is( listMax([1, 3, 5, 3, -1]), 5, "listMax" ); + t.is( objMax(1, 3, 5, 3, -1), 5, "objMax" ); + + var v = keys(a1); + v.sort(); + t.is( compare(v, ["a", "b", "c"]), 0, "keys" ); + v = items(a1); + v.sort(); + t.is( compare(v, [["a", 1], ["b", 2], ["c", 2]]), 0, "items" ); + + var StringMap = function() {}; + a = new StringMap(); + a.foo = "bar"; + b = new StringMap(); + b.foo = "bar"; + try { + compare(a, b); + t.ok( false, "bad comparison registered!?" ); + } catch (e) { + t.ok( e instanceof TypeError, "bad comparison raised TypeError" ); + } + + t.is( repr(a), "[object Object]", "default repr for StringMap" ); + var isStringMap = function () { + for (var i = 0; i < arguments.length; i++) { + if (!(arguments[i] instanceof StringMap)) { + return false; + } + } + return true; + }; + + registerRepr("stringMap", + isStringMap, + function (obj) { + return "StringMap(" + repr(items(obj)) + ")"; + } + ); + + t.is( repr(a), 'StringMap([["foo", "bar"]])', "repr worked" ); + + // not public API + MochiKit.Base.reprRegistry.unregister("stringMap"); + + t.is( repr(a), "[object Object]", "default repr for StringMap" ); + + registerComparator("stringMap", + isStringMap, + function (a, b) { + // no sorted(...) in base + a = items(a); + b = items(b); + a.sort(compare); + b.sort(compare); + return compare(a, b); + } + ); + + t.is( compare(a, b), 0, "registerComparator" ); + + update(a, {"foo": "bar"}, {"wibble": "baz"}, undefined, null, {"grr": 1}); + t.is( a.foo, "bar", "update worked (first obj)" ); + t.is( a.wibble, "baz", "update worked (second obj)" ); + t.is( a.grr, 1, "update worked (skipped undefined and null)" ); + t.is( compare(a, b), 1, "update worked (comparison)" ); + + + setdefault(a, {"foo": "unf"}, {"bar": "web taco"} ); + t.is( a.foo, "bar", "setdefault worked (skipped existing)" ); + t.is( a.bar, "web taco", "setdefault worked (set non-existing)" ); + + a = null; + a = setdefault(null, {"foo": "bar"}); + t.is( a.foo, "bar", "setdefault worked (self is null)" ); + + a = null; + a = setdefault(undefined, {"foo": "bar"}); + t.is( a.foo, "bar", "setdefault worked (self is undefined)" ); + + a = null; + a = update(null, {"foo": "bar"}, {"wibble": "baz"}, undefined, null, {"grr": 1}); + t.is( a.foo, "bar", "update worked (self is null, first obj)" ); + t.is( a.wibble, "baz", "update worked (self is null, second obj)" ); + t.is( a.grr, 1, "update worked (self is null, skipped undefined and null)" ); + + a = null; + a = update(undefined, {"foo": "bar"}, {"wibble": "baz"}, undefined, null, {"grr": 1}); + t.is( a.foo, "bar", "update worked (self is undefined, first obj)" ); + t.is( a.wibble, "baz", "update worked (self is undefined, second obj)" ); + t.is( a.grr, 1, "update worked (self is undefined, skipped undefined and null)" ); + + + var c = items(merge({"foo": "bar"}, {"wibble": "baz"})); + c.sort(compare); + t.is( compare(c, [["foo", "bar"], ["wibble", "baz"]]), 0, "merge worked" ); + + // not public API + MochiKit.Base.comparatorRegistry.unregister("stringMap"); + + try { + compare(a, b); + t.ok( false, "bad comparison registered!?" ); + } catch (e) { + t.ok( e instanceof TypeError, "bad comparison raised TypeError" ); + } + + var o = {"__repr__": function () { return "__repr__"; }}; + t.is( repr(o), "__repr__", "__repr__ protocol" ); + t.is( repr(MochiKit.Base), MochiKit.Base.__repr__(), "__repr__ protocol when repr is defined" ); + var o = {"NAME": "NAME"}; + t.is( repr(o), "NAME", "NAME protocol (obj)" ); + o = function () { return "TACO" }; + o.NAME = "NAME"; + t.is( repr(o), "NAME", "NAME protocol (func)" ); + + t.is( repr(MochiKit.Base.nameFunctions), "MochiKit.Base.nameFunctions", "test nameFunctions" ); + // Done! + + t.is( urlEncode("1+2=2").toUpperCase(), "1%2B2%3D2", "urlEncode" ); + t.is( queryString(["a", "b"], [1, "two"]), "a=1&b=two", "queryString"); + t.is( queryString({"a": 1}), "a=1", "one item alternate form queryString" ); + var o = {"a": 1, "b": 2, "c": function() {}}; + var res = queryString(o).split("&"); + res.sort(); + t.is( res.join("&"), "a=1&b=2", "two item alternate form queryString, function skip" ); + var res = parseQueryString("1+1=2&b=3%3D2"); + t.is( res["1 1"], "2", "parseQueryString pathological name" ); + t.is( res.b, "3=2", "parseQueryString second name:value pair" ); + var res = parseQueryString("foo=one&foo=two", true); + t.is( res["foo"].join(" "), "one two", "parseQueryString useArrays" ); + var res = parseQueryString("?foo=2&bar=1"); + t.is( res["foo"], "2", "parseQueryString strip leading question mark"); + + var res = parseQueryString("x=1&y=2"); + t.is( typeof(res['&']), "undefined", "extra cruft in parseQueryString output"); + + t.is( serializeJSON("foo\n\r\b\f\t\u000B\u001B"), "\"foo\\n\\r\\b\\f\\t\\u000B\\u001B\"", "string JSON" ); + t.is( serializeJSON(null), "null", "null JSON"); + try { + serializeJSON(undefined); + t.ok(false, "undefined should not be serializable"); + } catch (e) { + t.ok(e instanceof TypeError, "undefined not serializable"); + } + t.is( serializeJSON(1), "1", "1 JSON"); + t.is( serializeJSON(1.23), "1.23", "1.23 JSON"); + t.is( serializeJSON(serializeJSON), null, "function JSON (null, not string)" ); + t.is( serializeJSON([1, "2", 3.3]), "[1, \"2\", 3.3]", "array JSON" ); + var res = evalJSON(serializeJSON({"a":1, "b":2})); + t.is( res.a, 1, "evalJSON on an object (1)" ); + t.is( res.b, 2, "evalJSON on an object (2)" ); + var res = {"a": 1, "b": 2, "json": function () { return this; }}; + var res = evalJSON(serializeJSON(res)); + t.is( res.a, 1, "evalJSON on an object that jsons self (1)" ); + t.is( res.b, 2, "evalJSON on an object that jsons self (2)" ); + var strJSON = {"a": 1, "b": 2, "json": function () { return "json"; }}; + t.is( serializeJSON(strJSON), "\"json\"", "json serialization calling" ); + t.is( serializeJSON([strJSON]), "[\"json\"]", "json serialization calling in a structure" ); + t.is( evalJSON('/* {"result": 1} */').result, 1, "json comment stripping" ); + t.is( evalJSON('/* {"*/ /*": 1} */')['*/ /*'], 1, "json comment stripping" ); + registerJSON("isDateLike", + isDateLike, + function (d) { + return "this was a date"; + } + ); + t.is( serializeJSON(new Date()), "\"this was a date\"", "json registry" ); + MochiKit.Base.jsonRegistry.unregister("isDateLike"); + + var a = {"foo": {"bar": 12, "wibble": 13}}; + var b = {"foo": {"baz": 4, "bar": 16}, "bar": 4}; + updatetree(a, b); + var expect = [["bar", 16], ["baz", 4], ["wibble", 13]]; + var got = items(a.foo); + got.sort(compare); + t.is( repr(got), repr(expect), "updatetree merge" ); + t.is( a.bar, 4, "updatetree insert" ); + + var aa = {"foo": {"bar": 12, "wibble": 13}}; + var bb = {"foo": {"baz": 4, "bar": 16}, "bar": 4}; + + cc = updatetree(null, aa, bb); + got = items(cc.foo); + got.sort(compare); + t.is( repr(got), repr(expect), "updatetree merge (self is null)" ); + t.is( cc.bar, 4, "updatetree insert (self is null)" ); + + cc = updatetree(undefined, aa, bb); + got = items(cc.foo); + got.sort(compare); + t.is( repr(got), repr(expect), "updatetree merge (self is undefined)" ); + t.is( cc.bar, 4, "updatetree insert (self is undefined)" ); + + var c = counter(); + t.is( c(), 1, "counter starts at 1" ); + t.is( c(), 2, "counter increases" ); + c = counter(2); + t.is( c(), 2, "counter starts at 2" ); + t.is( c(), 3, "counter increases" ); + + t.is( findValue([1, 2, 3], 4), -1, "findValue returns -1 on not found"); + t.is( findValue([1, 2, 3], 1), 0, "findValue returns correct index"); + t.is( findValue([1, 2, 3], 1, 1), -1, "findValue honors start"); + t.is( findValue([1, 2, 3], 2, 0, 1), -1, "findValue honors end"); + t.is( findIdentical([1, 2, 3], 4), -1, "findIdentical returns -1"); + t.is( findIdentical([1, 2, 3], 1), 0, "findIdentical returns correct index"); + t.is( findIdentical([1, 2, 3], 1, 1), -1, "findIdentical honors start"); + t.is( findIdentical([1, 2, 3], 2, 0, 1), -1, "findIdentical honors end"); + + var flat = flattenArguments(1, "2", 3, [4, [5, [6, 7], 8, [], 9]]); + var expect = [1, "2", 3, 4, 5, 6, 7, 8, 9]; + t.is( repr(flat), repr(expect), "flattenArguments" ); + + var fn = function () { + return [this, concat(arguments)]; + } + t.is( methodcaller("toLowerCase")("FOO"), "foo", "methodcaller with a method name" ); + t.is( repr(methodcaller(fn, 2, 3)(1)), "[1, [2, 3]]", "methodcaller with a function" ); + + var f1 = function (x) { return [1, x]; }; + var f2 = function (x) { return [2, x]; }; + var f3 = function (x) { return [3, x]; }; + t.is( repr(f1(f2(f3(4)))), "[1, [2, [3, 4]]]", "test the compose test" ); + t.is( repr(compose(f1,f2,f3)(4)), "[1, [2, [3, 4]]]", "three fn composition works" ); + t.is( repr(compose(compose(f1,f2),f3)(4)), "[1, [2, [3, 4]]]", "associative left" ); + t.is( repr(compose(f1,compose(f2,f3))(4)), "[1, [2, [3, 4]]]", "associative right" ); + + try { + compose(f1, "foo"); + t.ok( false, "wrong compose argument not raised!" ); + } catch (e) { + t.is( e.name, 'TypeError', "wrong compose argument raised correctly" ); + } + + t.is(camelize('one'), 'one', 'one word'); + t.is(camelize('one-two'), 'oneTwo', 'two words'); + t.is(camelize('one-two-three'), 'oneTwoThree', 'three words'); + t.is(camelize('1-one'), '1One', 'letter and word'); + t.is(camelize('one-'), 'one', 'trailing hyphen'); + t.is(camelize('-one'), 'One', 'starting hyphen'); + t.is(camelize('o-two'), 'oTwo', 'one character and word'); + + var flat = flattenArray([1, "2", 3, [4, [5, [6, 7], 8, [], 9]]]); + var expect = [1, "2", 3, 4, 5, 6, 7, 8, 9]; + t.is( repr(flat), repr(expect), "flattenArray" ); + + /* mean */ + try { + mean(); + t.ok( false, "mean no arguments didn't raise!" ); + } catch (e) { + t.is( e.name, 'TypeError', "no arguments raised correctly" ); + } + t.is( mean(1), 1, 'single argument (arg list)'); + t.is( mean([1]), 1, 'single argument (array)'); + t.is( mean(1,2,3), 2, 'three arguments (arg list)'); + t.is( mean([1,2,3]), 2, 'three arguments (array)'); + t.is( average(1), 1, 'test the average alias'); + + /* median */ + try { + median(); + t.ok( false, "median no arguments didn't raise!" ); + } catch (e) { + t.is( e.name, 'TypeError', "no arguments raised correctly" ); + } + t.is( median(1), 1, 'single argument (arg list)'); + t.is( median([1]), 1, 'single argument (array)'); + t.is( median(3,1,2), 2, 'three arguments (arg list)'); + t.is( median([3,1,2]), 2, 'three arguments (array)'); + t.is( median(3,1,2,4), 2.5, 'four arguments (arg list)'); + t.is( median([3,1,2,4]), 2.5, 'four arguments (array)'); + + /* #185 */ + t.is( serializeJSON(parseQueryString("")), "{}", "parseQueryString('')" ); + t.is( serializeJSON(parseQueryString("", true)), "{}", "parseQueryString('', true)" ); + + /* #109 */ + t.is( queryString({ids: [1,2,3]}), "ids=1&ids=2&ids=3", "queryString array value" ); + t.is( queryString({ids: "123"}), "ids=123", "queryString string value" ); + + /* test values */ + var o = {a: 1, b: 2, c: 4, d: -1}; + var got = values(o); + got.sort(); + t.is( repr(got), repr([-1, 1, 2, 4]), "values()" ); + + t.is( queryString([["foo", "bar"], ["baz", "wibble"]]), "foo=baz&bar=wibble" ); + o = parseQueryString("foo=1=1=1&bar=2&baz&wibble="); + t.is( o.foo, "1=1=1", "parseQueryString multiple = first" ); + t.is( o.bar, "2", "parseQueryString multiple = second" ); + t.is( o.baz, "", "parseQueryString multiple = third" ); + t.is( o.wibble, "", "parseQueryString multiple = fourth" ); + + /* queryString with null values */ + t.is( queryString(["a", "b"], [1, null]), "a=1", "queryString with null value" ); + t.is( queryString({"a": 1, "b": null}), "a=1", "queryString with null value" ); + + var reprFunc = function (a, b) { + return; + } + t.is( repr(reprFunc), "function (a, b) {...}", "repr of function" ); +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Color.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Color.js new file mode 100644 index 000000000..17d418d8c --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Color.js @@ -0,0 +1,137 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Color'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Color'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_Color = function (t) { + var approx = function (a, b, msg) { + return t.is(a.toPrecision(4), b.toPrecision(4), msg); + }; + + t.is( Color.whiteColor().toHexString(), "#ffffff", "whiteColor has right hex" ); + t.is( Color.blackColor().toHexString(), "#000000", "blackColor has right hex" ); + t.is( Color.blueColor().toHexString(), "#0000ff", "blueColor has right hex" ); + t.is( Color.redColor().toHexString(), "#ff0000", "redColor has right hex" ); + t.is( Color.greenColor().toHexString(), "#00ff00", "greenColor has right hex" ); + t.is( compare(Color.whiteColor(), Color.whiteColor()), 0, "default colors compare right" ); + t.ok( Color.whiteColor() == Color.whiteColor(), "default colors are interned" ); + t.ok( Color.whiteColor().toRGBString(), "rgb(255,255,255)", "toRGBString white" ); + t.ok( Color.blueColor().toRGBString(), "rgb(0,0,255)", "toRGBString blue" ); + t.is( Color.fromRGB(190/255, 222/255, 173/255).toHexString(), "#bedead", "fromRGB works" ); + t.is( Color.fromRGB(226/255, 15.9/255, 182/255).toHexString(), "#e210b6", "fromRGB < 16 works" ); + t.is( Color.fromRGB({r:190/255,g:222/255,b:173/255}).toHexString(), "#bedead", "alt fromRGB works" ); + t.is( Color.fromHexString("#bedead").toHexString(), "#bedead", "round-trip hex" ); + t.is( Color.fromString("#bedead").toHexString(), "#bedead", "round-trip string(hex)" ); + t.is( Color.fromRGBString("rgb(190,222,173)").toHexString(), "#bedead", "round-trip rgb" ); + t.is( Color.fromString("rgb(190,222,173)").toHexString(), "#bedead", "round-trip rgb" ); + + var hsl = Color.redColor().asHSL(); + approx( hsl.h, 0.0, "red hsl.h" ); + approx( hsl.s, 1.0, "red hsl.s" ); + approx( hsl.l, 0.5, "red hsl.l" ); + hsl = Color.fromRGB(0, 0, 0.5).asHSL(); + approx( hsl.h, 2/3, "darkblue hsl.h" ); + approx( hsl.s, 1.0, "darkblue hsl.s" ); + approx( hsl.l, 0.25, "darkblue hsl.l" ); + hsl = Color.fromString("#4169E1").asHSL(); + approx( hsl.h, (5/8), "4169e1 h"); + approx( hsl.s, (8/11), "4169e1 s"); + approx( hsl.l, (29/51), "4169e1 l"); + hsl = Color.fromString("#555544").asHSL(); + approx( hsl.h, (1/6), "555544 h" ); + approx( hsl.s, (1/9), "555544 s" ); + approx( hsl.l, (3/10), "555544 l" ); + hsl = Color.fromRGB(0.5, 1, 0.5).asHSL(); + approx( hsl.h, 1/3, "aqua hsl.h" ); + approx( hsl.s, 1.0, "aqua hsl.s" ); + approx( hsl.l, 0.75, "aqua hsl.l" ); + t.is( + Color.fromHSL(hsl.h, hsl.s, hsl.l).toHexString(), + Color.fromRGB(0.5, 1, 0.5).toHexString(), + "fromHSL works with components" + ); + t.is( + Color.fromHSL(hsl).toHexString(), + Color.fromRGB(0.5, 1, 0.5).toHexString(), + "fromHSL alt form" + ); + t.is( + Color.fromString("hsl(120,100%,75%)").toHexString(), + "#80ff80", + "fromHSLString" + ); + t.is( + Color.fromRGB(0.5, 1, 0.5).toHSLString(), + "hsl(120,100.0%,75.00%)", + "toHSLString" + ); + t.is( Color.fromHSL(0, 0, 0).toHexString(), "#000000", "fromHSL to black" ); + hsl = Color.blackColor().asHSL(); + approx( hsl.h, 0.0, "black hsl.h" ); + approx( hsl.s, 0.0, "black hsl.s" ); + approx( hsl.l, 0.0, "black hsl.l" ); + hsl.h = 1.0; + hsl = Color.blackColor().asHSL(); + approx( hsl.h, 0.0, "asHSL returns copy" ); + var rgb = Color.brownColor().asRGB(); + approx( rgb.r, 153/255, "brown rgb.r" ); + approx( rgb.g, 102/255, "brown rgb.g" ); + approx( rgb.b, 51/255, "brown rgb.b" ); + rgb.r = 0; + rgb = Color.brownColor().asRGB(); + approx( rgb.r, 153/255, "asRGB returns copy" ); + + t.is( Color.fromName("aqua").toHexString(), "#00ffff", "aqua fromName" ); + t.is( Color.fromString("aqua").toHexString(), "#00ffff", "aqua fromString" ); + t.is( Color.fromName("transparent"), Color.transparentColor(), "transparent fromName" ); + t.is( Color.fromString("transparent"), Color.transparentColor(), "transparent fromString" ); + t.is( Color.transparentColor().toRGBString(), "rgba(0,0,0,0)", "transparent toRGBString" ); + t.is( Color.fromRGBString("rgba( 0, 255, 255, 50%)").asRGB().a, 0.5, "rgba parsing alpha correctly" ); + t.is( Color.fromRGBString("rgba( 0, 255, 255, 50%)").toRGBString(), "rgba(0,255,255,0.5)", "rgba output correctly" ); + t.is( Color.fromRGBString("rgba( 0, 255, 255, 1)").toHexString(), "#00ffff", "fromRGBString with spaces and alpha" ); + t.is( Color.fromRGBString("rgb( 0, 255, 255)").toHexString(), "#00ffff", "fromRGBString with spaces" ); + t.is( Color.fromRGBString("rgb( 0, 100%, 255)").toHexString(), "#00ffff", "fromRGBString with percents" ); + + var hsv = Color.redColor().asHSV(); + approx( hsv.h, 0.0, "red hsv.h" ); + approx( hsv.s, 1.0, "red hsv.s" ); + approx( hsv.v, 1.0, "red hsv.v" ); + t.is( Color.fromHSV(hsv).toHexString(), Color.redColor().toHexString(), "red hexstring" ); + hsv = Color.fromRGB(0, 0, 0.5).asHSV(); + approx( hsv.h, 2/3, "darkblue hsv.h" ); + approx( hsv.s, 1.0, "darkblue hsv.s" ); + approx( hsv.v, 0.5, "darkblue hsv.v" ); + t.is( Color.fromHSV(hsv).toHexString(), Color.fromRGB(0, 0, 0.5).toHexString(), "darkblue hexstring" ); + hsv = Color.fromString("#4169E1").asHSV(); + approx( hsv.h, 5/8, "4169e1 h"); + approx( hsv.s, 32/45, "4169e1 s"); + approx( hsv.v, 15/17, "4169e1 l"); + t.is( Color.fromHSV(hsv).toHexString(), "#4169e1", "4169e1 hexstring" ); + hsv = Color.fromString("#555544").asHSV(); + approx( hsv.h, 1/6, "555544 h" ); + approx( hsv.s, 1/5, "555544 s" ); + approx( hsv.v, 1/3, "555544 l" ); + t.is( Color.fromHSV(hsv).toHexString(), "#555544", "555544 hexstring" ); + hsv = Color.fromRGB(0.5, 1, 0.5).asHSV(); + approx( hsv.h, 1/3, "aqua hsv.h" ); + approx( hsv.s, 0.5, "aqua hsv.s" ); + approx( hsv.v, 1, "aqua hsv.v" ); + t.is( + Color.fromHSV(hsv.h, hsv.s, hsv.v).toHexString(), + Color.fromRGB(0.5, 1, 0.5).toHexString(), + "fromHSV works with components" + ); + t.is( + Color.fromHSV(hsv).toHexString(), + Color.fromRGB(0.5, 1, 0.5).toHexString(), + "fromHSV alt form" + ); + hsv = Color.fromRGB(1, 1, 1).asHSV() + approx( hsv.h, 0, 'white hsv.h' ); + approx( hsv.s, 0, 'white hsv.s' ); + approx( hsv.v, 1, 'white hsv.v' ); + t.is( + Color.fromHSV(0, 0, 1).toHexString(), + '#ffffff', + 'HSV saturation' + ); +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_DateTime.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_DateTime.js new file mode 100644 index 000000000..a7178086d --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_DateTime.js @@ -0,0 +1,52 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.DateTime'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.DateTime'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_DateTime = function (t) { + var testDate = isoDate('2005-2-3'); + t.is(testDate.getFullYear(), 2005, "isoDate year ok"); + t.is(testDate.getDate(), 3, "isoDate day ok"); + t.is(testDate.getMonth(), 1, "isoDate month ok"); + t.ok(objEqual(testDate, new Date("February 3, 2005")), "matches string date"); + t.is(toISODate(testDate), '2005-02-03', 'toISODate ok'); + + var testDate = isoDate('2005-06-08'); + t.is(testDate.getFullYear(), 2005, "isoDate year ok"); + t.is(testDate.getDate(), 8, "isoDate day ok"); + t.is(testDate.getMonth(), 5, "isoDate month ok"); + t.ok(objEqual(testDate, new Date("June 8, 2005")), "matches string date"); + t.is(toISODate(testDate), '2005-06-08', 'toISODate ok'); + + var testDate = isoDate('0500-12-12'); + t.is(testDate.getFullYear(), 500, 'isoDate year ok for year < 1000'); + t.is(testDate.getDate(), 12, 'isoDate day ok for year < 1000'); + t.is(testDate.getMonth(), 11, 'isoDate month ok for year < 1000'); + t.ok(objEqual(testDate, new Date("December 12, 0500")), "matches string date for year < 1000"); + t.is(toISODate(testDate), '0500-12-12', 'toISODate ok for year < 1000'); + + t.is(compare(new Date("February 3, 2005"), new Date(2005, 1, 3)), 0, "dates compare eq"); + t.is(compare(new Date("February 3, 2005"), new Date(2005, 2, 3)), -1, "dates compare lt"); + t.is(compare(new Date("February 3, 2005"), new Date(2005, 0, 3)), 1, "dates compare gt"); + + var testDate = isoDate('2005-2-3'); + t.is(compare(americanDate('2/3/2005'), testDate), 0, "americanDate eq"); + t.is(compare('2/3/2005', toAmericanDate(testDate)), 0, "toAmericanDate eq"); + + var testTimestamp = isoTimestamp('2005-2-3 22:01:03'); + t.is(compare(testTimestamp, new Date(2005,1,3,22,1,3)), 0, "isoTimestamp eq"); + t.is(compare(testTimestamp, isoTimestamp('2005-2-3T22:01:03')), 0, "isoTimestamp (real ISO) eq"); + t.is(compare(toISOTimestamp(testTimestamp), '2005-02-03 22:01:03'), 0, "toISOTimestamp eq"); + testTimestamp = isoTimestamp('2005-2-3T22:01:03Z'); + t.is(toISOTimestamp(testTimestamp, true), '2005-02-03T22:01:03Z', "toISOTimestamp (real ISO) eq"); + + var localTZ = Math.round((new Date(2005,1,3,22,1,3)).getTimezoneOffset()/60) + var direction = (localTZ < 0) ? "+" : "-"; + localTZ = Math.abs(localTZ); + localTZ = direction + ((localTZ < 10) ? "0" : "") + localTZ; + testTimestamp = isoTimestamp("2005-2-3T22:01:03" + localTZ); + var testDateTimestamp = new Date(2005,1,3,22,1,3); + t.is(compare(testTimestamp, testDateTimestamp), 0, "equal with local tz"); + testTimestamp = isoTimestamp("2005-2-3T17:01:03-05"); + var testDateTimestamp = new Date(Date.UTC(2005,1,3,22,1,3)); + t.is(compare(testTimestamp, testDateTimestamp), 0, "equal with specific tz"); +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_DragAndDrop.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_DragAndDrop.js new file mode 100644 index 000000000..d3a3c5837 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_DragAndDrop.js @@ -0,0 +1,30 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Signal'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Signal'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_DragAndDrop = function (t) { + + var drag1 = new MochiKit.DragAndDrop.Draggable('drag1', {'revert': true, 'ghosting': true}); + + var drop1 = new MochiKit.DragAndDrop.Droppable('drop1', {'hoverclass': 'drop-hover'}); + drop1.activate(); + t.is(hasElementClass('drop1', 'drop-hover'), true, "hoverclass ok"); + drop1.deactivate(); + t.is(hasElementClass('drop1', 'drop-hover'), false, "remove hoverclass ok"); + drop1.destroy(); + + t.is( isEmpty(MochiKit.DragAndDrop.Droppables.drops), true, "Unregister droppable ok"); + + var onhover = function (element) { + t.is(element, getElement('drag1'), 'onhover ok'); + }; + var drop2 = new MochiKit.DragAndDrop.Droppable('drop1', {'onhover': onhover}); + var pos = getElementPosition('drop1'); + pos = {"x": pos.x + 5, "y": pos.y + 5}; + MochiKit.DragAndDrop.Droppables.show({"page": pos}, getElement('drag1')); + + drag1.destroy(); + t.is( isEmpty(MochiKit.DragAndDrop.Draggables.drops), true, "Unregister draggable ok"); + +}; + diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Format.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Format.js new file mode 100644 index 000000000..dd18a8ff7 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Format.js @@ -0,0 +1,89 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Format'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Format'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_Format = function (t) { + t.is( truncToFixed(0.1234, 3), "0.123", "truncToFixed truncate" ); + t.is( truncToFixed(0.12, 3), "0.120", "truncToFixed trailing zeros" ); + t.is( truncToFixed(0.15, 1), "0.1", "truncToFixed no round" ); + t.is( truncToFixed(0.15, 0), "0", "truncToFixed zero (edge case)" ); + t.is( truncToFixed(568.80, 2), "568.80", "truncToFixed 568.80, floating-point error" ); + t.is( truncToFixed(1.23e+20, 2), "123000000000000000000.00", "truncToFixed 1.23e+20" ); + t.is( truncToFixed(-1.23e+20, 2), "-123000000000000000000.00", "truncToFixed -1.23e+20" ); + t.is( truncToFixed(1.23e-10, 2), "0.00", "truncToFixed 1.23e-10" ); + + t.is( roundToFixed(0.1234, 3), "0.123", "roundToFixed truncate" ); + t.is( roundToFixed(0.12, 3), "0.120", "roundToFixed trailing zeros" ); + t.is( roundToFixed(0.15, 1), "0.2", "roundToFixed round" ); + t.is( roundToFixed(0.15, 0), "0", "roundToFixed zero (edge case)" ); + t.is( roundToFixed(568.80, 2), "568.80", "roundToFixed 568.80, floating-point error" ); + + t.is( twoDigitFloat(-0.1234), "-0.12", "twoDigitFloat -0.1234 correct"); + t.is( twoDigitFloat(-0.1), "-0.1", "twoDigitFloat -0.1 correct"); + t.is( twoDigitFloat(-0), "0", "twoDigitFloat -0 correct"); + t.is( twoDigitFloat(0), "0", "twoDigitFloat 0 correct"); + t.is( twoDigitFloat(1), "1", "twoDigitFloat 1 correct"); + t.is( twoDigitFloat(1.0), "1", "twoDigitFloat 1.0 correct"); + t.is( twoDigitFloat(1.2), "1.2", "twoDigitFloat 1.2 correct"); + t.is( twoDigitFloat(1.234), "1.23", "twoDigitFloat 1.234 correct"); + t.is( twoDigitFloat(0.23), "0.23", "twoDigitFloat 0.23 correct"); + t.is( twoDigitFloat(0.01), "0.01", "twoDigitFloat 0.01 correct"); + t.is( twoDigitFloat(568.80), "568.8", "twoDigitFloat, floating-point error"); + + t.is( percentFormat(123), "12300%", "percentFormat 123 correct"); + t.is( percentFormat(1.23), "123%", "percentFormat 123 correct"); + t.is( twoDigitAverage(1, 0), "0", "twoDigitAverage dbz correct"); + t.is( twoDigitAverage(1, 1), "1", "twoDigitAverage 1 correct"); + t.is( twoDigitAverage(1, 10), "0.1", "twoDigitAverage .1 correct"); + function reprIs(a, b) { + arguments[0] = repr(a); + arguments[1] = repr(b); + t.is.apply(this, arguments); + } + reprIs( lstrip("\r\t\n foo \n\t\r"), "foo \n\t\r", "lstrip whitespace chars" ); + reprIs( rstrip("\r\t\n foo \n\t\r"), "\r\t\n foo", "rstrip whitespace chars" ); + reprIs( strip("\r\t\n foo \n\t\r"), "foo", "strip whitespace chars" ); + reprIs( lstrip("\r\n\t \r", "\r"), "\n\t \r", "lstrip custom chars" ); + reprIs( rstrip("\r\n\t \r", "\r"), "\r\n\t ", "rstrip custom chars" ); + reprIs( strip("\r\n\t \r", "\r"), "\n\t ", "strip custom chars" ); + + var nf = numberFormatter("$###,###.00 footer"); + t.is( nf(1000.1), "$1,000.10 footer", "trailing zeros" ); + t.is( nf(1000000.1), "$1,000,000.10 footer", "two seps" ); + t.is( nf(100), "$100.00 footer", "shorter than sep" ); + t.is( nf(100.555), "$100.56 footer", "rounding" ); + t.is( nf(-100.555), "$-100.56 footer", "default neg" ); + nf = numberFormatter("-$###,###.00"); + t.is( nf(-100.555), "-$100.56", "custom neg" ); + nf = numberFormatter("0000.0000"); + t.is( nf(0), "0000.0000", "leading and trailing" ); + t.is( nf(1.1), "0001.1000", "leading and trailing" ); + t.is( nf(12345.12345), "12345.1235", "no need for leading/trailing" ); + nf = numberFormatter("0000.0000"); + t.is( nf("taco"), "", "default placeholder" ); + nf = numberFormatter("###,###.00", "foo", "de_DE"); + t.is( nf("taco"), "foo", "custom placeholder" ); + t.is( nf(12345.12345), "12.345,12", "de_DE locale" ); + nf = numberFormatter("#%"); + t.is( nf(1), "100%", "trivial percent" ); + t.is( nf(0.55), "55%", "percent" ); + + var customLocale = { + separator: " apples and ", + decimal: " bagels at ", + percent: "am for breakfast"}; + var customFormatter = numberFormatter("###,###.0%", "No breakfast", customLocale); + t.is( customFormatter(23.458), "2 apples and 345 bagels at 8am for breakfast", "custom locale" ); + + nf = numberFormatter("###,###"); + t.is( nf(123), "123", "large number format" ); + t.is( nf(1234), "1,234", "large number format" ); + t.is( nf(12345), "12,345", "large number format" ); + t.is( nf(123456), "123,456", "large number format" ); + t.is( nf(1234567), "1,234,567", "large number format" ); + t.is( nf(12345678), "12,345,678", "large number format" ); + t.is( nf(123456789), "123,456,789", "large number format" ); + t.is( nf(1234567890), "1,234,567,890", "large number format" ); + t.is( nf(12345678901), "12,345,678,901", "large number format" ); + t.is( nf(123456789012), "123,456,789,012", "large number format" ); +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Iter.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Iter.js new file mode 100644 index 000000000..15b7b8932 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Iter.js @@ -0,0 +1,186 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Iter'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Iter'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_Iter = function (t) { + t.is( sum([1, 2, 3, 4, 5]), 15, "sum works on Arrays" ); + t.is( compare(list([1, 2, 3]), [1, 2, 3]), 0, "list([x]) == [x]" ); + t.is( compare(list(range(6, 0, -1)), [6, 5, 4, 3, 2, 1]), 0, "list(range(6, 0, -1)"); + t.is( compare(list(range(6)), [0, 1, 2, 3, 4, 5]), 0, "list(range(6))" ); + var moreThanTwo = partial(operator.lt, 2); + t.is( sum(ifilter(moreThanTwo, range(6))), 12, "sum(ifilter(, range()))" ); + t.is( sum(ifilterfalse(moreThanTwo, range(6))), 3, "sum(ifilterfalse(, range()))" ); + + var c = count(10); + t.is( compare([c.next(), c.next(), c.next()], [10, 11, 12]), 0, "count()" ); + c = cycle([1, 2]); + t.is( compare([c.next(), c.next(), c.next()], [1, 2, 1]), 0, "cycle()" ); + c = repeat("foo", 3); + t.is( compare(list(c), ["foo", "foo", "foo"]), 0, "repeat()" ); + c = izip([1, 2], [3, 4, 5], repeat("foo")); + t.is( compare(list(c), [[1, 3, "foo"], [2, 4, "foo"]]), 0, "izip()" ); + + t.is( compare(list(range(5)), [0, 1, 2, 3, 4]), 0, "range(x)" ); + c = islice(range(10), 0, 10, 2); + t.is( compare(list(c), [0, 2, 4, 6, 8]), 0, "islice(x, y, z)" ); + + c = imap(operator.add, [1, 2, 3], [2, 4, 6]); + t.is( compare(list(c), [3, 6, 9]), 0, "imap(fn, p, q)" ); + + c = filter(partial(operator.lt, 1), iter([1, 2, 3])); + t.is( compare(c, [2, 3]), 0, "filter(fn, iterable)" ); + + c = map(partial(operator.add, -1), iter([1, 2, 3])); + t.is( compare(c, [0, 1, 2]), 0, "map(fn, iterable)" ); + + c = map(operator.add, iter([1, 2, 3]), [2, 4, 6]); + t.is( compare(c, [3, 6, 9]), 0, "map(fn, iterable, q)" ); + + c = map(operator.add, iter([1, 2, 3]), iter([2, 4, 6])); + t.is( compare(c, [3, 6, 9]), 0, "map(fn, iterable, iterable)" ); + + c = applymap(operator.add, [[1, 2], [2, 4], [3, 6]]); + t.is( compare(list(c), [3, 6, 9]), 0, "applymap()" ); + + c = applymap(function (a) { return [this, a]; }, [[1], [2]], 1); + t.is( compare(list(c), [[1, 1], [1, 2]]), 0, "applymap(self)" ); + + c = chain(range(2), range(3)); + t.is( compare(list(c), [0, 1, 0, 1, 2]), 0, "chain(p, q)" ); + + var lessThanFive = partial(operator.gt, 5); + c = takewhile(lessThanFive, count()); + t.is( compare(list(c), [0, 1, 2, 3, 4]), 0, "takewhile()" ); + + c = dropwhile(lessThanFive, range(10)); + t.is( compare(list(c), [5, 6, 7, 8, 9]), 0, "dropwhile()" ); + + c = tee(range(5), 3); + t.is( compare(list(c[0]), list(c[1])), 0, "tee(..., 3) p0 == p1" ); + t.is( compare(list(c[2]), [0, 1, 2, 3, 4]), 0, "tee(..., 3) p2 == fixed" ); + + t.is( compare(reduce(operator.add, range(10)), 45), 0, "reduce(op.add)" ); + + try { + reduce(operator.add, []); + t.ok( false, "reduce didn't raise anything with empty list and no start?!" ); + } catch (e) { + if (e instanceof TypeError) { + t.ok( true, "reduce raised TypeError correctly" ); + } else { + t.ok( false, "reduce raised the wrong exception?" ); + } + } + + t.is( reduce(operator.add, [], 10), 10, "range initial value OK empty" ); + t.is( reduce(operator.add, [1], 10), 11, "range initial value OK populated" ); + + t.is( compare(iextend([1], range(2)), [1, 0, 1]), 0, "iextend(...)" ); + var rval = []; + var o = [0, 1, 2, 3]; + o.next = range(2).next; + t.is( iextend([], o).length, 2, "iextend handles array-like iterables" ); + + var x = []; + exhaust(imap(bind(x.push, x), range(5))); + t.is( compare(x, [0, 1, 2, 3, 4]), 0, "exhaust(...)" ); + + t.is( every([1, 2, 3, 4, 5, 4], lessThanFive), false, "every false" ); + t.is( every([1, 2, 3, 4, 4], lessThanFive), true, "every true" ); + t.is( some([1, 2, 3, 4, 4], lessThanFive), true, "some true" ); + t.is( some([5, 6, 7, 8, 9], lessThanFive), false, "some false" ); + t.is( some([5, 6, 7, 8, 4], lessThanFive), true, "some true" ); + + var rval = []; + forEach(range(2), rval.push, rval); + t.is( compare(rval, [0, 1]), 0, "forEach works bound" ); + + function foo(o) { + rval.push(o); + } + forEach(range(2), foo); + t.is( compare(rval, [0, 1, 0, 1]), 0, "forEach works unbound" ); + + var rval = []; + var o = [0, 1, 2, 3]; + o.next = range(2).next; + forEach(o, rval.push, rval); + t.is( rval.length, 2, "forEach handles array-like iterables" ); + + t.is( compare(sorted([3, 2, 1]), [1, 2, 3]), 0, "sorted default" ); + rval = sorted(["aaa", "bb", "c"], keyComparator("length")); + t.is(compare(rval, ["c", "bb", "aaa"]), 0, "sorted custom"); + + t.is( compare(reversed(range(4)), [3, 2, 1, 0]), 0, "reversed iterator" ); + t.is( compare(reversed([5, 6, 7]), [7, 6, 5]), 0, "reversed list" ); + + var o = {lst: [1, 2, 3], iterateNext: function () { return this.lst.shift(); }}; + t.is( compare(list(o), [1, 2, 3]), 0, "iterateNext" ); + + + function except(exc, func) { + try { + func(); + t.ok(false, exc.name + " was not raised."); + } catch (e) { + if (e == exc) { + t.ok( true, "raised " + exc.name + " correctly" ); + } else { + t.ok( false, "raised the wrong exception?" ); + } + } + } + + odd = partial(operator.and, 1) + + // empty + grouped = groupby([]); + except(StopIteration, grouped.next); + + // exhaust sub-iterator + grouped = groupby([2,4,6,7], odd); + kv = grouped.next(); k = kv[0], subiter = kv[1]; + t.is(k, 0, "odd(2) = odd(4) = odd(6) == 0"); + t.is(subiter.next(), 2, "sub-iterator.next() == 2"); + t.is(subiter.next(), 4, "sub-iterator.next() == 4"); + t.is(subiter.next(), 6, "sub-iterator.next() == 6"); + except(StopIteration, subiter.next); + kv = grouped.next(); key = kv[0], subiter = kv[1]; + t.is(key, 1, "odd(7) == 1"); + t.is(subiter.next(), 7, "sub-iterator.next() == 7"); + except(StopIteration, subiter.next); + + // not consume sub-iterator + grouped = groupby([2,4,6,7], odd); + kv = grouped.next(); key = kv[0], subiter = kv[1]; + t.is(key, 0, "0 = odd(2) = odd(4) = odd(6)"); + kv = grouped.next(); key = kv[0], subiter = kv[1]; + t.is(key, 1, "1 = odd(7)"); + except(StopIteration, grouped.next); + + // consume sub-iterator partially + grouped = groupby([3,1,1,2], odd); + kv = grouped.next(); key = kv[0], subiter = kv[1]; + t.is(key, 1, "odd(1) == 1"); + t.is(subiter.next(), 3, "sub-iterator.next() == 3"); + kv = grouped.next(); key = kv[0], v = kv[1]; + t.is(key, 0, "skip (1,1), odd(2) == 0"); + except(StopIteration, grouped.next); + + // null + grouped = groupby([null,null]); + kv = grouped.next(); k = kv[0], v = kv[1]; + t.is(k, null, "null ok"); + + // groupby - array version + isEqual = (t.isDeeply || function (a, b, msg) { + return t.ok(compare(a, b) == 0, msg); + }); + isEqual(groupby_as_array([ ] ), [ ], "empty"); + isEqual(groupby_as_array([1,1,1]), [ [1,[1,1,1]] ], "[1,1,1]: [1,1,1]"); + isEqual(groupby_as_array([1,2,2]), [ [1,[1] ], [2,[2,2]] ], "[1,2,2]: [1], [2,2]"); + isEqual(groupby_as_array([1,1,2]), [ [1,[1,1] ], [2,[2 ]] ], "[1,1,2]: [1,1], [2]"); + isEqual(groupby_as_array([null,null] ), [ [null,[null,null]] ], "[null,null]: [null,null]"); + grouped = groupby_as_array([1,1,3,2,4,6,8], odd); + isEqual(grouped, [[1, [1,1,3]], [0,[2,4,6,8]]], "[1,1,3,2,4,6,7] odd: [1,1,3], [2,4,6,8]"); +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Logging.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Logging.js new file mode 100644 index 000000000..b368e58b4 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Logging.js @@ -0,0 +1,88 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Logging'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Logging'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_Logging = function (t) { + + // just in case + logger.clear(); + + t.is( logLevelAtLeast('DEBUG')('INFO'), false, 'logLevelAtLeast false' ); + t.is( logLevelAtLeast('WARNING')('INFO'), false, 'logLevelAtLeast true' ); + t.ok( logger instanceof Logger, "global logger installed" ); + + var allMessages = []; + logger.addListener("allMessages", null, + bind(allMessages.push, allMessages)); + + var fatalMessages = []; + logger.addListener("fatalMessages", "FATAL", + bind(fatalMessages.push, fatalMessages)); + + var firstTwo = []; + logger.addListener("firstTwo", null, + bind(firstTwo.push, firstTwo)); + + + log("foo"); + var msgs = logger.getMessages(); + t.is( msgs.length, 1, 'global log() put one message in queue' ); + t.is( compare(allMessages, msgs), 0, "allMessages listener" ); + var msg = msgs.pop(); + t.is( compare(msg.info, ["foo"]), 0, "info matches" ); + t.is( msg.level, "INFO", "level matches" ); + + logDebug("debugFoo"); + t.is( msgs.length, 0, 'getMessages() returns copy' ); + msgs = logger.getMessages(); + t.is( compare(allMessages, msgs), 0, "allMessages listener" ); + t.is( msgs.length, 2, 'logDebug()' ); + msg = msgs.pop(); + t.is( compare(msg.info, ["debugFoo"]), 0, "info matches" ); + t.is( msg.level, "DEBUG", "level matches" ); + + logger.removeListener("firstTwo"); + + logError("errorFoo"); + msgs = logger.getMessages(); + t.is( compare(allMessages, msgs), 0, "allMessages listener" ); + t.is( msgs.length, 3, 'logError()' ); + msg = msgs.pop(); + t.is( compare(msg.info, ["errorFoo"]), 0, "info matches" ); + t.is( msg.level, "ERROR", "level matches" ); + + logWarning("warningFoo"); + msgs = logger.getMessages(); + t.is( compare(allMessages, msgs), 0, "allMessages listener" ); + t.is( msgs.length, 4, 'logWarning()' ); + msg = msgs.pop(); + t.is( compare(msg.info, ["warningFoo"]), 0, "info matches" ); + t.is( msg.level, "WARNING", "level matches" ); + + logFatal("fatalFoo"); + msgs = logger.getMessages(); + t.is( compare(allMessages, msgs), 0, "allMessages listener" ); + t.is( msgs.length, 5, 'logFatal()' ); + msg = msgs.pop(); + t.is( compare(fatalMessages, [msg]), 0, "fatalMessages listener" ); + t.is( compare(msg.info, ["fatalFoo"]), 0, "info matches" ); + t.is( msg.level, "FATAL", "level matches" ); + msgs = logger.getMessages(1); + t.is( compare(fatalMessages, msgs), 0, "getMessages with limit returns latest" ); + + logger.removeListener("allMessages"); + logger.removeListener("fatalMessages"); + + t.is( compare(firstTwo, logger.getMessages().slice(0, 2)), 0, "firstTwo" ); + + logger.clear(); + msgs = logger.getMessages(); + t.is(msgs.length, 0, "clear removes existing messages"); + + logger.baseLog(LogLevel.INFO, 'infoFoo'); + msg = logger.getMessages().pop(); + t.is(msg.level, "INFO", "baseLog converts level") + logger.baseLog(45, 'errorFoo'); + msg = logger.getMessages().pop(); + t.is(msg.level, "ERROR", "baseLog converts ad-hoc level") +}; diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Async.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Async.html new file mode 100644 index 000000000..7a321b955 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Async.html @@ -0,0 +1,408 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Async.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript"> +try { + + var increment = function (res) { + return res + 1; + } + + var throwStuff = function (res) { + throw new GenericError(res); + } + + var catchStuff = function (res) { + return res.message; + } + + var returnError = function (res) { + return new GenericError(res); + } + + var anythingOkCallback = function (msg) { + return function (res) { + ok(true, msg); + return res; + } + } + + var testEqCallback = function () { + /* + sort of emulate how deferreds work in Twisted + for "convenient" testing + */ + var args = []; + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + return function (res) { + var nargs = args.slice(); + nargs.unshift(res); + is.apply(this, nargs); + return res; + } + } + + var neverHappen = function (d) { + ok(false, "this should never happen"); + } + + /* + Test normal Deferred operation + */ + var d = new Deferred(); + d.addCallback(testEqCallback(1, "pre-deferred callback")); + d.callback(1); + d.addCallback(increment); + d.addCallback(testEqCallback(2, "post-deferred callback")); + d.addCallback(throwStuff); + d.addCallback(neverHappen); + d.addErrback(catchStuff); + d.addCallback(testEqCallback(2, "throw -> err, catch -> success")); + d.addCallback(returnError); + d.addCallback(neverHappen); + d.addErrback(catchStuff); + d.addCallback(testEqCallback(2, "return -> err, catch -> succcess")); + + /* + Test Deferred cancellation + */ + var cancelled = function (d) { + ok(true, "canceller called!"); + } + + var cancelledError = function (res) { + ok(res instanceof CancelledError, "CancelledError here"); + } + + d = new Deferred(cancelled); + d.addCallback(neverHappen); + d.addErrback(cancelledError); + d.cancel(); + + /* + Test succeed / fail + */ + + d = succeed(1).addCallback(testEqCallback(1, "succeed")); + + // default error + d = fail().addCallback(neverHappen); + d = d.addErrback(anythingOkCallback("default fail")); + + // default wrapped error + d = fail("web taco").addCallback(neverHappen).addErrback(catchStuff); + d = d.addCallback(testEqCallback("web taco", "wrapped fail")); + + // default unwrapped error + d = fail(new GenericError("ugh")).addCallback(neverHappen).addErrback(catchStuff); + d = d.addCallback(testEqCallback("ugh", "unwrapped fail")); + + /* + Test deferred dependencies + */ + + var deferredIncrement = function (res) { + var rval = succeed(res); + rval.addCallback(increment); + return rval; + } + + d = succeed(1).addCallback(deferredIncrement); + d = d.addCallback(testEqCallback(2, "dependent deferred succeed")); + + var deferredFailure = function (res) { + return fail(res); + } + + d = succeed("ugh").addCallback(deferredFailure).addErrback(catchStuff); + d = d.addCallback(testEqCallback("ugh", "dependent deferred fail")); + + /* + Test double-calling, double-failing, etc. + */ + try { + succeed(1).callback(2); + neverHappen(); + } catch (e) { + ok(e instanceof AlreadyCalledError, "double-call"); + } + try { + fail(1).errback(2); + neverHappen(); + } catch (e) { + ok(e instanceof AlreadyCalledError, "double-fail"); + } + try { + d = succeed(1); + d.cancel(); + d = d.callback(2); + ok(true, "swallowed one callback, no canceller"); + d.callback(3); + neverHappen(); + } catch (e) { + ok(e instanceof AlreadyCalledError, "swallow cancel"); + } + try { + d = new Deferred(cancelled); + d.cancel(); + d = d.callback(1); + neverHappen(); + } catch (e) { + ok(e instanceof AlreadyCalledError, "non-swallowed cancel"); + } + + /* Test incorrect Deferred usage */ + + d = new Deferred(); + try { + d.callback(new Deferred()); + neverHappen(); + } catch (e) { + ok (e instanceof Error, "deferred not allowed for callback"); + } + d = new Deferred(); + try { + d.errback(new Deferred()); + neverHappen(); + } catch (e) { + ok (e instanceof Error, "deferred not allowed for errback"); + } + + d = new Deferred(); + (new Deferred()).addCallback(function () { return d; }).callback(1); + try { + d.addCallback(function () {}); + neverHappen(); + } catch (e) { + ok (e instanceof Error, "chained deferred not allowed to be re-used"); + } + + /* + evalJSONRequest test + */ + var fakeReq = {"responseText":'[1,2,3,4,"asdf",{"a":["b", "c"]}]'}; + var obj = [1,2,3,4,"asdf",{"a":["b", "c"]}]; + isDeeply(obj, evalJSONRequest(fakeReq), "evalJSONRequest"); + + try { + MochiKit.Async.getXMLHttpRequest(); + ok(true, "getXMLHttpRequest"); + } catch (e) { + ok(false, "no love from getXMLHttpRequest"); + } + + var lock = new DeferredLock(); + var lst = []; + var pushNumber = function (x) { + return function (res) { lst.push(x); } + }; + lock.acquire().addCallback(pushNumber(1)); + is( compare(lst, [1]), 0, "lock acquired" ); + lock.acquire().addCallback(pushNumber(2)); + is( compare(lst, [1]), 0, "lock waiting for release" ); + lock.acquire().addCallback(pushNumber(3)); + is( compare(lst, [1]), 0, "lock waiting for release" ); + lock.release(); + is( compare(lst, [1, 2]), 0, "lock passed on" ); + lock.release(); + is( compare(lst, [1, 2, 3]), 0, "lock passed on" ); + lock.release(); + try { + lock.release(); + ok( false, "over-release didn't raise" ); + } catch (e) { + ok( true, "over-release raised" ); + } + lock.acquire().addCallback(pushNumber(1)); + is( compare(lst, [1, 2, 3, 1]), 0, "lock acquired" ); + lock.release(); + is( compare(lst, [1, 2, 3, 1]), 0, "lock released" ); + + var d = new Deferred(); + lst = []; + d.addCallback(operator.add, 2); + d.addBoth(operator.add, 4); + d.addCallback(bind(lst.push, lst)); + d.callback(1); + is( lst[0], 7, "auto-partial addCallback addBoth" ); + d.addCallback(function () { throw new Error(); }); + ebTest = function(a, b) { + map(bind(lst.push, lst), arguments); + }; + d.addErrback(ebTest, "foo"); + is( lst[1], "foo", "auto-partial errback" ); + is( lst.length, 3, "auto-partial errback" ); + + /* + Test DeferredList + */ + + var callList = [new Deferred(), new Deferred(), new Deferred()]; + callList[0].addCallback(increment); + callList[1].addCallback(increment); + callList[2].addCallback(increment); + var defList = new DeferredList(callList); + ok(defList instanceof Deferred, "DeferredList looks like a Deferred"); + + callList[0].callback(3); + callList[1].callback(5); + callList[2].callback(4); + + defList.addCallback(function (lst) { + is( arrayEqual(lst, [[true, 4], [true, 6], [true, 5]]), true, + "deferredlist result ok" ); + }); + + /* + Test fireOnOneCallback + */ + + var callList2 = [new Deferred(), new Deferred(), new Deferred()]; + callList2[0].addCallback(increment); + callList2[1].addCallback(increment); + callList2[2].addCallback(increment); + var defList2 = new DeferredList(callList2, true); + callList2[1].callback(5); + callList2[0].callback(3); + callList2[2].callback(4); + + defList2.addCallback(function (lst) { + is( arrayEqual(lst, [1, 6]), true, "deferredlist fireOnOneCallback ok" ); + }); + + /* + Test fireOnOneErrback + */ + + var callList3 = [new Deferred(), new Deferred(), new Deferred()]; + callList3[0].addCallback(increment); + callList3[1].addCallback(throwStuff); + callList3[2].addCallback(increment); + var defList3 = new DeferredList(callList3, false, true); + defList3.callback = neverHappen; + callList3[0].callback(3); + callList3[1].callback("foo"); + callList3[2].callback(4); + + defList3.addErrback(function (err) { + is( err.message, "foo", "deferredlist fireOnOneErrback ok" ); + }); + + /* + Test consumeErrors + */ + + var callList4 = [new Deferred(), new Deferred(), new Deferred()]; + callList4[0].addCallback(increment); + callList4[1].addCallback(throwStuff); + callList4[2].addCallback(increment); + var defList4 = new DeferredList(callList4, false, false, true); + defList4.addErrback(neverHappen); + callList4[1].addCallback(function (arg) { + is(arg, null, "deferredlist consumeErrors ok" ); + }); + callList4[0].callback(3); + callList4[1].callback("foo"); + callList4[2].callback(4); + + /* + Test gatherResults + */ + + var callList5 = [new Deferred(), new Deferred(), new Deferred()]; + callList5[0].addCallback(increment); + callList5[1].addCallback(increment); + callList5[2].addCallback(increment); + var gatherRet = gatherResults(callList5); + callList5[0].callback(3); + callList5[1].callback(5); + callList5[2].callback(4); + + gatherRet.addCallback(function (lst) { + is( arrayEqual(lst, [4, 6, 5]), true, + "gatherResults result ok" ); + }); + + /* + Test maybeDeferred + */ + + var maybeDef = maybeDeferred(increment, 4); + maybeDef.addCallback(testEqCallback(5, "maybeDeferred sync ok")); + + var maybeDef2 = deferredIncrement(8); + maybeDef2.addCallback(testEqCallback(9, "maybeDeferred async ok")); + + ok( true, "synchronous test suite finished!"); + + var t = (new Date().getTime()); + SimpleTest.waitForExplicitFinish(); + checkCallLater = function (originalTime) { + is(originalTime, t, "argument passed in OK"); + is(arguments.length, 1, "argument count right"); + }; + var lock = new DeferredLock(); + withLock = function (msg) { + var cb = partial.apply(null, extend(null, arguments, 1)); + var d = lock.acquire().addCallback(cb); + d.addErrback(ok, false, msg); + d.addCallback(function () { + ok(true, msg); + lock.release(); + }); + return d; + } + withLock("callLater", function () { + return callLater(0, checkCallLater, t); + }); + withLock("wait", function () { + return wait(0, t).addCallback(checkCallLater); + }); + withLock("loadJSONDoc", function () { + var d = loadJSONDoc("test_MochiKit-Async.json"); + d.addCallback(function (doc) { + is(doc.passed, true, "loadJSONDoc passed"); + }); + d.addErrback(function (doc) { + ok(false, "loadJSONDoc failed"); + }); + return d; + }); + lock.acquire().addCallback(function () { + ok(true, "async suite finished"); + SimpleTest.finish(); + }); + + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + SimpleTest.finish(); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Async.json b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Async.json new file mode 100644 index 000000000..037e18c81 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Async.json @@ -0,0 +1 @@ +{"passed": true} diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Base.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Base.html new file mode 100644 index 000000000..92e6b3de0 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Base.html @@ -0,0 +1,34 @@ +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript" src="test_Base.js"></script> +<script type="text/javascript"> +try { + tests.test_Base({ok:ok,is:is}); + ok( true, "test suite finished!"); +} catch (err) { + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Color.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Color.html new file mode 100644 index 000000000..9b45618c6 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Color.html @@ -0,0 +1,84 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="../MochiKit/Logging.js"></script> + <script type="text/javascript" src="../MochiKit/Color.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + <style type="text/css">.redtext {color: red}</style> +</head> +<body> +<div style="position:absolute; top: 0px; left:0px; width:0px; height:0px"> + <span style="color: red" id="c_direct"></span> + <span class="redtext" id="c_indirect"></span> +</div> +<pre id="test"> +<script type="text/javascript" src="test_Color.js"></script> +<script type="text/javascript"> +try { + + var t = {ok:ok, is:is}; + tests.test_Color({ok:ok, is:is}); + is( + Color.fromText(SPAN()).toHexString(), + "#000000", + "fromText no style" + ); + + is( + Color.fromText("c_direct").toHexString(), + Color.fromName("red").toHexString(), + "fromText direct style" + ); + + is( + Color.fromText("c_indirect").toHexString(), + Color.fromName("red").toHexString(), + "fromText indirect style" + ); + + is( + Color.fromComputedStyle("c_direct", "color").toHexString(), + Color.fromName("red").toHexString(), + "fromComputedStyle direct style" + ); + + is( + Color.fromComputedStyle("c_indirect", "color").toHexString(), + Color.fromName("red").toHexString(), + "fromComputedStyle indirect style" + ); + + is( + Color.fromBackground((SPAN(null, 'test'))).toHexString(), + Color.fromName("white").toHexString(), + "fromBackground with DOM" + ); + + + // Done! + + ok( true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DOM-Safari.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DOM-Safari.html new file mode 100644 index 000000000..6b274835e --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DOM-Safari.html @@ -0,0 +1,48 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/MockDOM.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript"> +try { + + for (var i = 0; i < 10000; i++) { + var n = document.createElement("DIV"); + n.appendChild(document.createTextNode("")); + var list = MochiKit.Iter.list(n.childNodes); + var n2 = document.createElement("DIV"); + appendChildNodes(n2, n.childNodes); + var n3 = document.createElement("DIV"); + replaceChildNodes(n3, n2.childNodes); + } + ok( true, "Safari didn't crash! #213" ); + ok( true, "test suite finished!"); + + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DOM.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DOM.html new file mode 100644 index 000000000..5fb07cdbf --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DOM.html @@ -0,0 +1,363 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/MockDOM.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<div style="display: none;"> + <form id="form_test"> + <select name="select"> + <option value="foo" selected="selected">foo</option> + <option value="bar">bar</option> + <option value="baz">baz</option> + </select> + <select name="selmultiple" multiple="multiple"> + <option value="bar" selected="selected">bar</option> + <option value="baz" selected="selected">baz</option> + <option value="foo">foo</option> + </select> + <input type="hidden" name="hidden" value="test" /> + <input type="radio" name="radio_off" value="1" /> + <input type="radio" name="radio_off" value="2" /> + <input type="radio" name="radio_off" value="3" /> + <input type="radio" name="radio_on" value="1" /> + <input type="radio" name="radio_on" value="2" checked="checked" /> + <input type="radio" name="radio_on" value="3" /> + </form> + <form id="form_test2"> + <select name="selempty"> + <option value="" selected="selected">foo</option> + </select> + <select name="selempty2"> + <option selected="selected">foo</option> + </select> + </form> + <div id="parentTwo" class="two"> + <div id="parentOne" class="one"> + <div id="parentZero" class="zero"> + <span id="child">child</span> + </div> + </div> + </div> +</div> + +<pre id="test"> +<script type="text/javascript"> +try { + + lst = []; + o = {"blah": function () { lst.push("original"); }}; + addToCallStack(o, "blah", function () { lst.push("new"); }, true); + addToCallStack(o, "blah", function () { lst.push("stuff"); }, true); + is( typeof(o.blah), 'function', 'addToCallStack has a function' ); + is( o.blah.callStack.length, 3, 'callStack length 3' ); + o.blah(); + is( lst.join(" "), "original new stuff", "callStack in correct order" ); + is( o.blah, null, "set to null" ); + lst = []; + o = {"blah": function () { lst.push("original"); }}; + addToCallStack(o, "blah", + function () { lst.push("new"); return false;}, false); + addToCallStack(o, "blah", function () { lst.push("stuff"); }, false); + o.blah(); + is( lst.join(" "), "original new", "callStack in correct order (abort)" ); + o.blah(); + is( lst.join(" "), "original new original new", "callStack in correct order (again)" ); + + + is( escapeHTML("<>\"&bar"), "<>"&bar", "escapeHTML" ); // for emacs highlighting: " + + var isDOM = function (value, expected, message) { + is( escapeHTML(toHTML(value)), escapeHTML(expected), message ); + }; + + var d = document.createElement('span'); + updateNodeAttributes(d, {"foo": "bar", "baz": "wibble"}); + isDOM( d, '<span baz="wibble" foo="bar"/>', "updateNodeAttributes" ); + var d = document.createElement('input'); + d.value = "foo"; + updateNodeAttributes(d, { value: "bar" }); + is( d.value, 'bar', "updateNodeAttributes updates value property" ); + is( d.getAttribute("value"), 'bar', "updateNodeAttributes updates value attribute" ); + + var d = document.createElement('span'); + var o = { elem: SPAN(null, "foo"), __dom__: function() { return this.elem; } }; + appendChildNodes(d, 'word up', [document.createElement('span')], o); + isDOM( d, '<span>word up<span/><span>foo</span></span>', 'appendChildNodes' ); + removeElement(o); + isDOM( d, '<span>word up<span/></span>', 'removeElement using DOM Coercion Rules' ); + + replaceChildNodes(d, 'Think Different'); + isDOM( d, '<span>Think Different</span>', 'replaceChildNodes' ); + + + insertSiblingNodesBefore(d.childNodes[0], 'word up', document.createElement('span')); + isDOM( d, '<span>word up<span/>Think Different</span>', 'insertSiblingNodesBefore' ); + + insertSiblingNodesAfter(d.childNodes[0], 'purple monkey', document.createElement('span')); + isDOM( d, '<span>word uppurple monkey<span/><span/>Think Different</span>', 'insertSiblingNodesAfter' ); + + d = createDOM("span"); + isDOM( d, "<span/>", "createDOM empty" ); + + + d = createDOM("span", {"foo": "bar", "baz": "wibble"}); + isDOM( d, '<span baz="wibble" foo="bar"/>', "createDOM attributes" ); + + d = createDOM("span", {"foo": "bar", "baz": "wibble", "spam": "egg"}, "one", "two", "three"); + is( getNodeAttribute(d, 'foo'), "bar", "createDOM attribute" ); + is( getNodeAttribute(d, 'baz'), "wibble", "createDOM attribute" ); + is( getNodeAttribute(d, 'lang'), null, "getNodeAttribute on IE added attribute" ); + is( getNodeAttribute("donotexist", 'foo'), null, "getNodeAttribute invalid node id" ); + removeNodeAttribute(d, "spam"); + is( scrapeText(d), "onetwothree", "createDOM contents" ); + + isDOM( d, '<span baz="wibble" foo="bar">onetwothree</span>', "createDOM contents" ); + + d = createDOM("span", null, function (f) { + return this.nodeName.toLowerCase() + "hi" + f.nodeName.toLowerCase();}); + isDOM( d, '<span>spanhispan</span>', 'createDOM function call' ); + + d = createDOM("span", null, {msg: "hi", dom: function (f) { + return f.nodeName.toLowerCase() + this.msg; }}); + isDOM( d, '<span>spanhi</span>', 'createDOM this.dom() call' ); + + d = createDOM("span", null, {msg: "hi", __dom__: function (f) { + return f.nodeName.toLowerCase() + this.msg; }}); + isDOM( d, '<span>spanhi</span>', 'createDOM this.__dom__() call' ); + + d = createDOM("span", null, range(4)); + isDOM( d, '<span>0123</span>', 'createDOM iterable' ); + + + var d = {"taco": "pork"}; + registerDOMConverter("taco", + function (o) { return !isUndefinedOrNull(o.taco); }, + function (o) { return "Goddamn, I like " + o.taco + " tacos"; } + ); + d = createDOM("span", null, d); + // not yet public API + domConverters.unregister("taco"); + + isDOM( d, "<span>Goddamn, I like pork tacos</span>", "createDOM with custom converter" ); + + is( + escapeHTML(toHTML(SPAN(null))), + escapeHTML(toHTML(createDOM("span", null))), + "createDOMFunc vs createDOM" + ); + + is( scrapeText(d), "Goddamn, I like pork tacos", "scrape OK" ); + is( scrapeText(d, true).join(""), "Goddamn, I like pork tacos", "scrape Array OK" ); + + var st = DIV(null, STRONG(null, "d"), "oor ", STRONG(null, "f", SPAN(null, "r"), "a"), "me"); + is( scrapeText(st), "door frame", "scrape in-order" ); + + + ok( !isUndefinedOrNull(getElement("test")), "getElement might work" ); + ok( !isUndefinedOrNull($("test")), "$ alias might work" ); + ok( getElement("donotexist") === null, "getElement invalid id" ); + + d = createDOM("span", null, "one", "two"); + swapDOM(d.childNodes[0], document.createTextNode("uno")); + isDOM( d, "<span>unotwo</span>", "swapDOM" ); + var o = { elem: SPAN(null, "foo"), __dom__: function() { return this.elem; } }; + swapDOM(d.childNodes[0], o); + isDOM( d, "<span><span>foo</span>two</span>", "swapDOM using DOM Coercion Rules" ); + + is( scrapeText(d, true).join(" "), "foo two", "multi-node scrapeText" ); + /* + + TODO: + addLoadEvent (async test?) + + */ + + d = createDOM("span", {"class": "foo"}); + setElementClass(d, "bar baz"); + ok( d.className == "bar baz", "setElementClass"); + toggleElementClass("bar", d); + ok( d.className == "baz", "toggleElementClass: " + d.className); + toggleElementClass("bar", d); + ok( hasElementClass(d, "baz", "bar"), + "toggleElementClass 2: " + d.className); + addElementClass(d, "bar"); + ok( hasElementClass(d, "baz", "bar"), + "toggleElementClass 3: " + d.className); + ok( addElementClass(d, "blah"), "addElementClass return"); + ok( hasElementClass(d, "baz", "bar", "blah"), "addElementClass action"); + ok( !hasElementClass(d, "not"), "hasElementClass single"); + ok( !hasElementClass(d, "baz", "not"), "hasElementClass multiple"); + ok( removeElementClass(d, "blah"), "removeElementClass" ); + ok( !removeElementClass(d, "blah"), "removeElementClass again" ); + ok( !hasElementClass(d, "blah"), "removeElementClass again (hasElement)" ); + removeElementClass(d, "baz"); + ok( !swapElementClass(d, "blah", "baz"), "false swapElementClass" ); + ok( !hasElementClass(d, "baz"), "false swapElementClass from" ); + ok( !hasElementClass(d, "blah"), "false swapElementClass to" ); + addElementClass(d, "blah"); + ok( swapElementClass(d, "blah", "baz"), "swapElementClass" ); + ok( hasElementClass(d, "baz"), "swapElementClass has toClass" ); + ok( !hasElementClass(d, "blah"), "swapElementClass !has fromClass" ); + ok( !swapElementClass(d, "blah", "baz"), "swapElementClass twice" ); + ok( hasElementClass(d, "baz"), "swapElementClass has toClass" ); + ok( !hasElementClass(d, "blah"), "swapElementClass !has fromClass" ); + ok( !hasElementClass("donotexist", "foo"), "hasElementClass invalid node id" ); + + TABLE; + TBODY; + TR; + var t = TABLE(null, + TBODY({"class": "foo bar", "id":"tbody0"}, + TR({"class": "foo", "id":"tr0"}), + TR({"class": "bar", "id":"tr1"}) + ) + ); + + var matchElements = getElementsByTagAndClassName; + is( + map(itemgetter("id"), matchElements(null, "foo", t)).join(" "), + "tbody0 tr0", + "getElementsByTagAndClassName found all tags with foo class" + ); + is( + map(itemgetter("id"), matchElements("tr", "foo", t)).join(" "), + "tr0", + "getElementsByTagAndClassName found all tr tags with foo class" + ); + is( + map(itemgetter("id"), matchElements("tr", null, t)).join(" "), + "tr0 tr1", + "getElementsByTagAndClassName found all tr tags" + ); + is( getElementsByTagAndClassName("td", null, t).length, 0, "getElementsByTagAndClassName no match found"); + is( getElementsByTagAndClassName("p", [], "donotexist").length, 0, "getElementsByTagAndClassName invalid parent id"); + + is( getFirstElementByTagAndClassName(null, "foo", t).id, "tbody0", "getFirstElementByTagAndClassName class name" ); + is( getFirstElementByTagAndClassName("tr", "foo", t).id, "tr0", "getFirstElementByTagAndClassName tag and class name" ); + is( getFirstElementByTagAndClassName("tr", null, t).id, "tr0", "getFirstElementByTagAndClassName tag name" ); + ok( getFirstElementByTagAndClassName("td", null, t) === null, "getFirstElementByTagAndClassName no matching tag" ); + ok( getFirstElementByTagAndClassName("tr", "donotexist", t) === null, "getFirstElementByTagAndClassName no matching class" ); + ok( getFirstElementByTagAndClassName('*', null, 'donotexist') === null, "getFirstElementByTagAndClassName invalid parent id" ); + + var oldDoc = document; + var doc = MochiKit.MockDOM.createDocument(); + is( currentDocument(), document, "currentDocument() correct" ); + withDocument(doc, function () { + ok( document != doc, "global doc unchanged" ); + is( currentDocument(), doc, "currentDocument() correct" ); + var h1 = H1(); + var span = SPAN(null, "foo", h1); + appendChildNodes(currentDocument().body, span); + }); + is( document, oldDoc, "doc restored" ); + is( doc.childNodes.length, 1, "doc has one child" ); + is( doc.body.childNodes.length, 1, "body has one child" ); + var sp = doc.body.childNodes[0]; + is( sp.nodeName, "SPAN", "only child is SPAN" ); + is( sp.childNodes.length, 2, "SPAN has two childNodes" ); + is( sp.childNodes[0].nodeValue, "foo", "first node is text" ); + is( sp.childNodes[1].nodeName, "H1", "second child is H1" ); + + is( currentDocument(), document, "currentDocument() correct" ); + try { + withDocument(doc, function () { + ok( document != doc, "global doc unchanged" ); + is( currentDocument(), doc, "currentDocument() correct" ); + throw new Error("foo"); + }); + ok( false, "didn't throw" ); + } catch (e) { + ok( true, "threw" ); + } + + var mockWindow = {"foo": "bar"}; + is (currentWindow(), window, "currentWindow ok"); + withWindow(mockWindow, function () { + is(currentWindow(), mockWindow, "withWindow ok"); + }); + is (currentWindow(), window, "currentWindow ok"); + + doc = MochiKit.MockDOM.createDocument(); + var frm; + withDocument(doc, function () { + frm = FORM({name: "ignore"}, + INPUT({name:"foo", value:"bar"}), + INPUT({name:"foo", value:"bar"}), + INPUT({name:"baz", value:"bar"}) + ); + }); + var kv = formContents(frm); + is( kv[0].join(","), "foo,foo,baz", "mock formContents names" ); + is( kv[1].join(","), "bar,bar,bar", "mock formContents values" ); + is( queryString(frm), "foo=bar&foo=bar&baz=bar", "mock queryString hook" ); + + var kv = formContents("form_test"); + is( kv[0].join(","), "select,selmultiple,selmultiple,hidden,radio_on", "formContents names" ); + is( kv[1].join(","), "foo,bar,baz,test,2", "formContents values" ); + is( queryString("form_test"), "select=foo&selmultiple=bar&selmultiple=baz&hidden=test&radio_on=2", "queryString hook" ); + kv = formContents("form_test2"); + is( kv[0].join(","), "selempty,selempty2", "formContents names empty option values" ); + is( kv[1].join(","), ",foo", "formContents empty option values" ); + is( queryString("form_test2"), "selempty=&selempty2=foo", "queryString empty option values" ); + + var d = DIV(null, SPAN(), " \n\t", SPAN(), "foo", SPAN(), " "); + is( d.childNodes.length, 6, "removeEmptyNodes test conditions correct" ); + removeEmptyTextNodes(d); + is( d.childNodes.length, 4, "removeEmptyNodes" ); + + is( getFirstParentByTagAndClassName('child', 'div', 'two'), getElement("parentTwo"), "getFirstParentByTagAndClassName found parent" ); + is( getFirstParentByTagAndClassName('child', 'div'), getElement("parentZero"), "getFirstParentByTagAndClassName found parent (any class)" ); + is( getFirstParentByTagAndClassName('child', '*', 'two'), getElement("parentTwo"), "getFirstParentByTagAndClassName found parent (any tag)" ); + is( getFirstParentByTagAndClassName('child', '*'), getElement("parentZero"), "getFirstParentByTagAndClassName found parent (any tag + any class)" ); + ok( getFirstParentByTagAndClassName('child', 'form') === null, "getFirstParentByTagAndClassName found null parent (no match)" ); + ok( getFirstParentByTagAndClassName('donotexist', '*') === null, "getFirstParentByTagAndClassName invalid elem id" ); + + ok( isChildNode('child', 'child'), "isChildNode of itself"); + ok( isChildNode('child', 'parentZero'), "isChildNode direct child"); + ok( isChildNode('child', 'parentTwo'), "isChildNode sub child"); + ok( !isChildNode('child', 'form_test'), "isChildNode wrong child"); + ok( !isChildNode('child', 'donotexist'), "isChildNode no parent"); + ok( !isChildNode('donotexist', 'child'), "isChildNode no parent"); + ok( isChildNode('child', document.body), "isChildNode of body"); + ok( isChildNode($('child').firstChild, 'parentTwo'), "isChildNode text node"); + ok( !isChildNode( SPAN(), document.body), "isChildNode child not in DOM"); + ok( !isChildNode( SPAN(), 'child'), "isChildNode child not in DOM"); + ok( !isChildNode( 'child', SPAN()), "isChildNode parent not in DOM"); + + // Test optional dependency on Iter + var Iter = MochiKit.Iter; + delete MochiKit["Iter"]; + d = DIV({"foo": "bar"}, SPAN({}, "one")); + is( getNodeAttribute(d, 'foo'), "bar", "createDOM attribute without Iter" ); + is( scrapeText(d), "one", "node contents without Iter" ); + MochiKit.Iter = Iter; + + ok( true, "test suite finished!"); + + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DateTime.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DateTime.html new file mode 100644 index 000000000..69d656ea1 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DateTime.html @@ -0,0 +1,39 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/DateTime.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript" src="test_DateTime.js"></script> +<script type="text/javascript"> +try { + + tests.test_DateTime({ok:ok, is:is}); + ok( true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DragAndDrop.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DragAndDrop.html new file mode 100644 index 000000000..5e9f9e6a1 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-DragAndDrop.html @@ -0,0 +1,60 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="../MochiKit/Color.js"></script> + <script type="text/javascript" src="../MochiKit/Signal.js"></script> + <!-- + Include MochiKit/Position.js to fix the 2 following errors: + "Error: uncaught exception: MochiKit.Visual depends on MochiKit.Position!" + "Error: uncaught exception: MochiKit.DragAndDrop depends on MochiKit.Position!" + --> + <script type="text/javascript" src="../MochiKit/Position.js"></script> + <script type="text/javascript" src="../MochiKit/Visual.js"></script> + <script type="text/javascript" src="../MochiKit/DragAndDrop.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + <style type="text/css"> + .drop-hover { + } + #drag1 { + visibility: hidden; + } + #drop1 { + visibility: hidden; + } + </style> +</head> +<body> +<div id='drag1'>drag1</div> +<div id='drop1'>drop1</div> +<pre id="test"> +<script type="text/javascript" src="test_DragAndDrop.js"></script> +<script type="text/javascript"> +try { + + // Counting the number of tests is really lame + tests.test_DragAndDrop({ok:ok, is:is}); + ok( true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Format.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Format.html new file mode 100644 index 000000000..198239b9d --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Format.html @@ -0,0 +1,39 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Format.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript" src="test_Format.js"></script> +<script type="text/javascript"> +try { + + tests.test_Format({ok:ok, is:is}); + ok( true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Iter.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Iter.html new file mode 100644 index 000000000..04854e88b --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Iter.html @@ -0,0 +1,38 @@ +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript" src="test_Iter.js"></script> +<script type="text/javascript"> +try { + + tests.test_Iter({ok:ok, is:is}); + ok( true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-JSAN.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-JSAN.html new file mode 100644 index 000000000..53a0e0ed0 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-JSAN.html @@ -0,0 +1,32 @@ +<html> +<head> + <script type="text/javascript" src="JSAN.js"></script> +</head> +<body> + +<pre id="test"> +<script type="text/javascript"> + // TODO: Make this a harness for the other tests + JSAN.use('Test.More'); + JSAN.addRepository('..'); + var lst = []; + plan({"tests": 1}); + var wc = {}; + wc['MochiKit'] = true; + for (var k in window) { wc[k] = true; } + for (var k in window) { wc[k] = true; } + JSAN.use('MochiKit.MochiKit', []); + for (var k in window) { + if (!(k in wc) && !(k.charAt(0) == '[')) { + lst.push(k); + } + } + lst.sort(); + pollution = lst.join(" "); + is(pollution, "compare reduce", "namespace pollution?"); + JSAN.use('MochiKit.MochiKit'); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Logging.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Logging.html new file mode 100644 index 000000000..d7eb9482b --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Logging.html @@ -0,0 +1,40 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Logging.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + +</head> +<body> + +<pre id="test"> +<script type="text/javascript" src="test_Logging.js"></script> +<script type="text/javascript"> +try { + + tests.test_Logging({ok:ok, is:is}); + ok( true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-MochiKit.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-MochiKit.html new file mode 100644 index 000000000..f75e7428b --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-MochiKit.html @@ -0,0 +1,18 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> +</head> +<body> + +<pre id="test"> +<script type="text/javascript"> + is( isUndefined(null), false, "null is not undefined" ); + is( isUndefined(""), false, "empty string is not undefined" ); + is( isUndefined(undefined), true, "undefined is undefined" ); + is( isUndefined({}.foo), true, "missing property is undefined" ); +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Selector.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Selector.html new file mode 100644 index 000000000..4e82b6907 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Selector.html @@ -0,0 +1,295 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/MockDOM.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="../MochiKit/Selector.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + <style type="text/css"> + p, #sequence { + display: none; + } + </style> +</head> +<body> + <p>Test originally from <a href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a>.</p> + + <p>Here are some links in a normal paragraph: <a href="http://www.google.com/" title="Google!">Google</a>, <a href="http://groups.google.com/">Google Groups</a>. This link has <code>class="blog"</code>: <a href="http://diveintomark.org/" class="blog" fakeattribute="bla">diveintomark</a></p> + <div id="foo"> + <p>Everything inside the red border is inside a div with <code>id="foo"</code>.</p> + <p>This is a normal link: <a href="http://www.yahoo.com/">Yahoo</a></p> + + <a style="display: none" href="http://www.example.com/outsidep">This a is not inside a p</a> + + <p>This link has <code>class="blog"</code>: <a href="http://simon.incutio.com/" class="blog">Simon Willison's Weblog</a></p> + <p>This <span><a href="http://www.example.com/insidespan">link</a></span> is inside a span, not directly child of p</p> + <p lang="en-us">Nonninn</p> + <p lang="is-IS">Sniðugt</p> + <p> + <input type="button" name="enabled" value="enabled" id="enabled"> + <input type="button" name="disabled" value="disabled" id="disabled" disabled="1" /> + <input type="checkbox" name="checked" value="checked" id="checked" checked="1" /> + </p> + </div> + + <div id="sequence"> + <a href="http://www.example.com/link1">Link 1</a> + <a href="http://www.example.com/link2">Link 2</a> + <a href="http://www.example.com/link3">Link 3</a> + <a href="http://www.example.com/link4">Link 4</a> + <p>Something else</p> + <a href="http://www.example.com/link5">Link 5</a> + <a href="http://www.example.com/link6">Link 6</a> + <a href="http://www.example.com/link7">Link 7</a> + <a href="http://www.example.com/link8">Link 8</a> + </div> + + <div id="multiclass" class="multiple classnames here"></div> +<pre id="test"> +<script type="text/javascript"> +try { + + var testExpected = function (res, exp, lbl) { + for (var i=0; i < res.length; i ++) { + is( res[i].href, exp[i], lbl + ' (' + i + ')'); + } + }; + + var expected = ['http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector', + 'http://www.google.com/', + 'http://groups.google.com/', + 'http://diveintomark.org/', + 'http://www.yahoo.com/', + 'http://www.example.com/outsidep', + 'http://simon.incutio.com/', + 'http://www.example.com/insidespan', + 'http://www.example.com/link1', + 'http://www.example.com/link2', + 'http://www.example.com/link3', + 'http://www.example.com/link4', + 'http://www.example.com/link5', + 'http://www.example.com/link6', + 'http://www.example.com/link7', + 'http://www.example.com/link8']; + var results = $$('a'); + testExpected(results, expected, "'a' selector"); + + expected = ['http://diveintomark.org/', 'http://simon.incutio.com/']; + results = $$('p a.blog'); + testExpected(results, expected, "'p a.blog' selector"); + + expected = ['http://www.yahoo.com/', + 'http://www.example.com/outsidep', + 'http://simon.incutio.com/', + 'http://www.example.com/insidespan', + 'http://www.example.com/link1', + 'http://www.example.com/link2', + 'http://www.example.com/link3', + 'http://www.example.com/link4', + 'http://www.example.com/link5', + 'http://www.example.com/link6', + 'http://www.example.com/link7', + 'http://www.example.com/link8']; + results = $$('div a'); + testExpected(results, expected, "'div a' selector"); + + expected = ['http://www.yahoo.com/', + 'http://www.example.com/outsidep', + 'http://simon.incutio.com/', + 'http://www.example.com/insidespan']; + results = $$('div#foo a'); + testExpected(results, expected, "'div#foo a' selector"); + + expected = ['http://simon.incutio.com/', + 'http://www.example.com/insidespan']; + results = $$('#foo a.blog'); + testExpected(results, expected, "'#foo a.blog' selector"); + + expected = ['http://diveintomark.org/', + 'http://simon.incutio.com/', + 'http://www.example.com/insidespan']; + results = $$('.blog'); + testExpected(results, expected, "'.blog' selector"); + + expected = ['http://www.google.com/', + 'http://www.yahoo.com/', + 'http://www.example.com/outsidep', + 'http://www.example.com/insidespan', + 'http://www.example.com/link1', + 'http://www.example.com/link2', + 'http://www.example.com/link3', + 'http://www.example.com/link4', + 'http://www.example.com/link5', + 'http://www.example.com/link6', + 'http://www.example.com/link7', + 'http://www.example.com/link8']; + results = $$('a[href^="http://www"]'); + testExpected(results, expected, "'a[href^=http://www]' selector"); + + expected = ['http://diveintomark.org/']; + results = $$('a[href$="org/"]'); + testExpected(results, expected, "'a[href$=org/]' selector"); + + expected = ['http://www.google.com/', + 'http://groups.google.com/']; + results = $$('a[href*="google"]'); + testExpected(results, expected, "'a[href*=google]' selector"); + + expected = ['http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector']; + results = $$('a[rel="bookmark"]'); + testExpected(results, expected, "'a[rel=bookmark]' selector"); + + expected = ['http://diveintomark.org/']; + results = $$('a[fakeattribute]'); + testExpected(results, expected, "'a[fakeattribute]' selector"); + + /* This doesn't work in IE due to silly DOM implementation + expected = ['http://www.google.com/']; + results = $$('a[title]'); + testExpected(results, expected, "'a[title]' selector"); + */ + + // Test attribute operators (also for elements not having the attribute) + results = $$('p[lang="en-us"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang=en-us]' selector"); + results = $$('p[lang!="is-IS"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang!=is-IS]' selector"); + results = $$('p[lang~="en-us"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang~=en-us]' selector"); + results = $$('p[lang^="en"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang^=en]' selector"); + results = $$('p[lang$="us"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang$=us]' selector"); + results = $$('p[lang*="-u"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang*=-u]' selector"); + results = $$('p[lang|="en"]'); + is( results[0].firstChild.nodeValue, 'Nonninn', "'p[lang|=en]' selector"); + + expected = ['http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector', + 'http://www.google.com/', + 'http://groups.google.com/', + 'http://diveintomark.org/', + 'http://www.yahoo.com/', + 'http://simon.incutio.com/', + 'http://www.example.com/insidespan']; + results = $$('p > a'); + testExpected(results, expected, "'p > a' selector"); + + expected = ['http://www.example.com/insidespan']; + results = $$('span > a'); + testExpected(results, expected, "'span > a' selector"); + + expected = ['http://groups.google.com/', + 'http://www.example.com/link2', + 'http://www.example.com/link3', + 'http://www.example.com/link4', + 'http://www.example.com/link6', + 'http://www.example.com/link7', + 'http://www.example.com/link8']; + results = $$('a + a'); + testExpected(results, expected, "'a + a' selector"); + + expected = ['http://www.example.com/outsidep', + 'http://www.example.com/link5', + 'http://www.example.com/link6', + 'http://www.example.com/link7', + 'http://www.example.com/link8']; + results = $$('p ~ a'); + testExpected(results, expected, "'p ~ a' selector"); + + expected = ['http://www.example.com/link1', + 'http://www.example.com/link3', + 'http://www.example.com/link6', + 'http://www.example.com/link8']; + results = $$('#sequence a:nth-child(odd)'); + testExpected(results, expected, "'#sequence a:nth-child(odd)' selector"); + + expected = ['http://www.example.com/link1', + 'http://www.example.com/link3', + 'http://www.example.com/link5', + 'http://www.example.com/link7']; + results = $$('#sequence a:nth-of-type(odd)'); + testExpected(results, expected, "'#sequence a:nth-of-type(odd)' selector"); + + expected = ['http://www.example.com/link1', + 'http://www.example.com/link4', + 'http://www.example.com/link7']; + results = $$('#sequence a:nth-of-type(3n+1)'); + testExpected(results, expected, "'#sequence a:nth-of-type(3n+1)' selector"); + + expected = ['http://www.example.com/link5']; + results = $$('#sequence a:nth-child(6)'); + testExpected(results, expected, "'#sequence a:nth-child(6)' selector"); + + expected = ['http://www.example.com/link5']; + results = $$('#sequence a:nth-of-type(5)'); + testExpected(results, expected, "'#sequence a:nth-of-type(5)' selector"); + + expected = [$('enabled'), $('checked')]; + results = $$('body :enabled'); + for (var i=0; i < results.length; i ++) { + is( results[i], expected[i], "'body :enabled" + ' (' + i + ')'); + } + + expected = [$('disabled')]; + results = $$('body :disabled'); + for (var i=0; i < results.length; i ++) { + is( results[i], expected[i], "'body :disabled" + ' (' + i + ')'); + } + + expected = [$('checked')]; + results = $$('body :checked'); + for (var i=0; i < results.length; i ++) { + is( results[i], expected[i], "'body :checked" + ' (' + i + ')'); + } + + expected = document.getElementsByTagName('p'); + results = $$('a[href$=outsidep] ~ *'); + for (var i=0; i < results.length; i ++) { + is( results[i], expected[i+4], "'a[href$=outsidep] ~ *' selector" + ' (' + i + ')'); + } + + expected = [document.documentElement]; + results = $$(':root'); + for (var i=0; i < results.length; i ++) { + is( results[i], expected[i], "':root' selector" + ' (' + i + ')'); + } + + expected = [$('multiclass')]; + results = $$('[class~=classnames]'); + for (var i=0; i < results.length; i ++) { + is( results[i], expected[i], "'~=' attribute test" + ' (' + i + ')'); + } + + var doc = MochiKit.MockDOM.createDocument(); + appendChildNodes(doc.body, A({"href": "http://www.example.com/insideAnotherDocument"}, "Inside a document")); + withDocument(doc, function(){ + is( $$(":root")[0], doc, ":root on a different document" ); + is( $$("a")[0], doc.body.firstChild, "a inside a different document" ); + }); + + ok( true, "test suite finished!"); + + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Signal.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Signal.html new file mode 100644 index 000000000..ba5d00267 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Signal.html @@ -0,0 +1,43 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="../MochiKit/Signal.js"></script> + <script type="text/javascript" src="../MochiKit/Logging.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + +</head> +<body> + +Please ignore this button: <input type="submit" id="submit" /><br /> + +<pre id="test"> +<script type="text/javascript" src="test_Signal.js"></script> +<script type="text/javascript"> +try { + + tests.test_Signal({ok:ok, is:is}); + ok(true, "test suite finished!"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok(false, s); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Style.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Style.html new file mode 100644 index 000000000..701db956f --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Style.html @@ -0,0 +1,231 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <script type="text/javascript" src="../MochiKit/MockDOM.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="../MochiKit/Color.js"></script> + <script type="text/javascript" src="../MochiKit/Logging.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + <style type="text/css"> + #hideTest { + display: none; + } + </style> +</head> +<body style="border: 0; margin: 0; padding: 0;"> + +<div id="styleTest" style="position: absolute; left: 400px; top: 100px; width: 100px; height: 80px; padding: 10px 20px 30px 40px; border-width: 1px 2px 3px 4px; border-style: solid; border-color: blue; background: red; opacity: 0.5; filter: alpha(opacity=50); font-size: 10px; overflow-x: visible; overflow-y: hidden;"><div id="innerDiv"></div>TEST<span id="styleSubTest">SUB</span><div id="floatTest" style="float: left;">Float</div></div> + +<div id="hideTest" style="width: 100px; height: 100px;"><div id="innerHideTest" style="width: 10px; height: 10px;"></div></div> + +<table id="testTable" border="0" cellspacing="0" cellpadding="0" + style="position:absolute;left: 400px; top:300px;line-height:20px;"><tr align="center"> +<td id="testCell1" style="width: 80px; height: 30px; border:2px solid blue">1</td> +<td id="testCell2" style="width: 80px; height: 30px; border:2px solid blue">2</td> +<td id="testCell3" style="width: 80px; height: 30px; border:2px solid blue">3</td> +</tr></table> + +<pre id="test"> +<script type="text/javascript"> + +try { + + // initial + var pos = getElementPosition('styleTest'); + is(pos.x, 400, 'initial x position'); + is(pos.y, 100, 'initial y position'); + + // Coordinates including border and padding + pos = getElementPosition('innerDiv'); + is(pos.x, 444, 'x position with offsetParent border'); + is(pos.y, 111, 'y position with offsetParent border'); + + // moved + var newPos = new MochiKit.Style.Coordinates(500, 200); + setElementPosition('styleTest', newPos); + pos = getElementPosition('styleTest'); + is(pos.x, 500, 'updated x position'); + is(pos.y, 200, 'updated y position'); + + // moved with relativeTo + anotherPos = new MochiKit.Style.Coordinates(100, 100); + pos = getElementPosition('styleTest', anotherPos); + is(pos.x, 400, 'updated x position (using relativeTo parameter)'); + is(pos.y, 100, 'updated y position (using relativeTo parameter)'); + + // Coordinates object + pos = getElementPosition({x: 123, y: 321}); + is(pos.x, 123, 'passthrough x position'); + is(pos.y, 321, 'passthrough y position'); + + // Coordinates object with relativeTo + pos = getElementPosition({x: 123, y: 321}, {x: 100, y: 50}); + is(pos.x, 23, 'passthrough x position (using relativeTo parameter)'); + is(pos.y, 271, 'passthrough y position (using relativeTo parameter)'); + + pos = getElementPosition('garbage'); + is(typeof(pos), 'undefined', + 'invalid element should return an undefined position'); + + // Only set one coordinate + setElementPosition('styleTest', {'x': 300}); + pos = getElementPosition('styleTest'); + is(pos.x, 300, 'updated only x position'); + is(pos.y, 200, 'not updated y position'); + + var mc = MochiKit.Color.Color; + var red = mc.fromString('rgb(255,0,0)'); + var color = null; + + color = mc.fromString(getStyle('styleTest', 'background-color')); + is(color.toHexString(), red.toHexString(), + 'test getStyle selector case'); + + color = mc.fromString(getStyle('styleTest', 'backgroundColor')); + is(color.toHexString(), red.toHexString(), + 'test getStyle camel case'); + + is(getStyle('styleSubTest', 'font-size'), '10px', + 'test computed getStyle selector case'); + + is(getStyle('styleSubTest', 'fontSize'), '10px', + 'test computed getStyle camel case'); + + is(eval(getStyle('styleTest', 'opacity')), 0.5, + 'test getStyle opacity'); + + is(getStyle('styleTest', 'opacity'), 0.5, 'test getOpacity'); + + setStyle('styleTest', {'opacity': 0.2}); + is(getStyle('styleTest', 'opacity'), 0.2, 'test setOpacity'); + + setStyle('styleTest', {'opacity': 0}); + is(getStyle('styleTest', 'opacity'), 0, 'test setOpacity'); + + setStyle('styleTest', {'opacity': 1}); + var t = getStyle('styleTest', 'opacity'); + ok(t > 0.999 && t <= 1, 'test setOpacity'); + + is(getStyle('floatTest', 'float'), "left", 'getStyle of float'); + is(getStyle('floatTest', 'cssFloat'), "left", 'getStyle of cssFloat'); + is(getStyle('floatTest', 'styleFloat'), "left", 'getStyle of styleFloat'); + is(getStyle('styleTest', 'float'), "none", 'getStyle of float when unset'); + + setStyle('floatTest', { "float": "right" }); + is(getStyle('floatTest', 'float'), "right", 'setStyle of CSS float'); + is(getStyle('floatTest', 'cssFloat'), "right", 'setStyle of CSS cssFloat'); + is(getStyle('floatTest', 'styleFloat'), "right", 'setStyle of CSS styleFloat'); + + var dims = getElementDimensions('styleTest'); + is(dims.w, 166, 'getElementDimensions w ok'); + is(dims.h, 124, 'getElementDimensions h ok'); + + dims = getElementDimensions('styleTest', true); + is(dims.w, 100, 'getElementDimensions content w ok'); + is(dims.h, 80, 'getElementDimensions content h ok'); + + setElementDimensions('styleTest', {'w': 200, 'h': 150}); + dims = getElementDimensions('styleTest', true); + is(dims.w, 200, 'setElementDimensions w ok'); + is(dims.h, 150, 'setElementDimensions h ok'); + + setElementDimensions('styleTest', {'w': 150}); + dims = getElementDimensions('styleTest', true); + is(dims.w, 150, 'setElementDimensions only w ok'); + is(dims.h, 150, 'setElementDimensions h not updated ok'); + + hideElement('styleTest'); + dims = getElementDimensions('styleTest', true); + is(dims.w, 150, 'getElementDimensions w ok when display none'); + is(dims.h, 150, 'getElementDimensions h ok when display none'); + + dims = getElementDimensions('hideTest', true); + is(dims.w, 100, 'getElementDimensions w ok when CSS display none'); + is(dims.h, 100, 'getElementDimensions h ok when CSS display none'); + + /* TODO: can we create a work-around for this case? + dims = getElementDimensions('innerHideTest', true); + is(dims.w, 10, 'getElementDimensions w ok when parent CSS display none'); + is(dims.h, 10, 'getElementDimensions h ok when parent CSS display none'); + */ + + var elem = DIV(); + appendChildNodes('styleTest', elem); + var before = elem.style.display; + getElementDimensions(elem); + var after = elem.style.display; + is(after, before, 'getElementDimensions modified element display'); + + dims = getViewportDimensions(); + is(dims.w > 0, true, 'test getViewportDimensions w'); + is(dims.h > 0, true, 'test getViewportDimensions h'); + + pos = getViewportPosition(); + is(pos.x, 0, 'test getViewportPosition x'); + is(pos.y, 0, 'test getViewportPosition y'); + + // The 3(+3) following |is(dims.w, 80, ...);| need a width of at least 652px to pass. + // Our SimpleTest/TestRunner.js runs tests inside an |iframe.width = "500";| only. + // Work around that. + // NB: This test already passes without this workaround when run alone. + setElementPosition('testTable', {'x': dims.w - (3 * (2 + 80 + 2))}); + pos = getElementPosition('testTable'); + is(dims.w - pos.x >= (3 * (2 + 80 + 2)), true, 'Is viewport width enough to display \'testTable\' at expected size?'); + + dims = getElementDimensions('testCell1', true); + is(dims.w, 80, 'default left table cell content w ok'); + is(dims.h, 30, 'default left table cell content h ok'); + dims = getElementDimensions('testCell2', true); + is(dims.w, 80, 'default middle table cell content w ok'); + is(dims.h, 30, 'default middle table cell content h ok'); + dims = getElementDimensions('testCell3', true); + is(dims.w, 80, 'default right table cell content w ok'); + is(dims.h, 30, 'default right table cell content h ok'); + + setStyle('testTable', {'borderCollapse': 'collapse'}); + dims = getElementDimensions('testCell1', true); + is(dims.w, 80, 'collapsed left table cell content w ok'); + is(dims.h, 30, 'collapsed left table cell content h ok'); + dims = getElementDimensions('testCell2', true); + is(dims.w, 80, 'collapsed middle table cell content w ok'); + is(dims.h, 30, 'collapsed middle table cell content h ok'); + dims = getElementDimensions('testCell3', true); + is(dims.w, 80, 'collapsed right table cell content w ok'); + is(dims.h, 30, 'collapsed right table cell content h ok'); + + hideElement('testTable'); + + var overflow = makeClipping('styleTest'); + is(getStyle('styleTest', 'overflow-x'), 'hidden', 'make clipping on overflow-x'); + is(getStyle('styleTest', 'overflow-y'), 'hidden', 'make clipping on overflow-y'); + + undoClipping('styleTest', overflow); + is(getStyle('styleTest', 'overflow-x'), 'visible', 'undo clipping on overflow-x'); + is(getStyle('styleTest', 'overflow-y'), 'hidden', 'undo clipping on overflow-y'); + + ok( true, "test suite finished!"); + + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Visual.html b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Visual.html new file mode 100644 index 000000000..4fdab615c --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_MochiKit-Visual.html @@ -0,0 +1,197 @@ +<html> +<head> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Iter.js"></script> + <script type="text/javascript" src="../MochiKit/DOM.js"></script> + <script type="text/javascript" src="../MochiKit/Async.js"></script> + <script type="text/javascript" src="../MochiKit/Style.js"></script> + <script type="text/javascript" src="../MochiKit/Color.js"></script> + <script type="text/javascript" src="../MochiKit/Signal.js"></script> + <script type="text/javascript" src="../MochiKit/Position.js"></script> + <script type="text/javascript" src="../MochiKit/Visual.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + + <link rel="stylesheet" type="text/css" href="SimpleTest/test.css"> + <style type="text/css"> + #elt1, #elt2, #ctn1 { + visibility: hidden; + font-size: 1em; + margin: 2px; + } + #elt3 { + display: none; + } + #ctn1 { + height: 2px; + } + </style> +</head> +<body> + +<div id='elt1'>elt1</div> +<div id='ctn1'><div id='elt2'></div></div> +<div id='elt3'>elt3</div> +<pre id="test"> +<script type="text/javascript"> +try { + var TestQueue = function () { + }; + + TestQueue.prototype = new MochiKit.Visual.ScopedQueue(); + + MochiKit.Base.update(TestQueue.prototype, { + startLoop: function (func, interval) { + this.started = true; + var timePos = new Date().getTime(); + while (this.started) { + timePos += interval; + MochiKit.Base.map(function (effect) { + effect.loop(timePos); + }, this.effects); + } + }, + stopLoop: function () { + this.started = false; + } + }); + + var gl = new TestQueue(); + MochiKit.Visual.Queues.instances['global'] = gl; + MochiKit.Visual.Queues.instances['elt1'] = gl; + MochiKit.Visual.Queues.instances['elt2'] = gl; + MochiKit.Visual.Queues.instances['elt3'] = gl; + MochiKit.Visual.Queues.instances['ctn1'] = gl; + MochiKit.Visual.Queue = gl; + + pulsate("elt1", {afterFinish: function () { + is(getElement('elt1').style.display != 'none', true, "pulsate ok"); + }}); + + pulsate("elt1", {pulses: 2, afterFinish: function () { + is(getElement('elt1').style.display != 'none', true, "pulsate with numbered pulses ok"); + }}); + + shake("elt1", {afterFinish: function () { + is(getElement('elt1').style.display != 'none', true, "shake ok"); + }}); + + fade("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "fade ok"); + }}); + + appear("elt1", {afterFinish: function () { + is(getElement('elt1').style.display != 'none', true, "appear ok"); + }}); + + toggle("elt1", "size", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "toggle size ok"); + }}); + + toggle("elt1", "size", {afterFinish: function () { + is(getElement('elt1').style.display != 'none', true, "toggle size reverse ok"); + }}); + + Morph("elt1", {"style": {"font-size": "2em"}, afterFinish: function () { + is(getStyle("elt1", "font-size"), "2em", "Morph OK"); + }}); + + Morph("elt1", {"style": {"font-size": "1em", "margin-left": "4px"}, afterFinish: function () { + is(getStyle("elt1", "font-size"), "1em", "Morph multiple (font) OK"); + is(getStyle("elt1", "margin-left"), "4px", "Morph multiple (margin) OK"); + }}); + + Morph("elt1", {"style": {"font-style": "italic"}, afterFinish: function () { + is(getStyle("elt1", "font-style"), "italic", "Morph generic property OK"); + }}); + + switchOff("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "switchOff ok"); + }}); + + grow("elt1", {afterFinish: function () { + is(getElement('elt1').style.display != 'none', true, "grow ok"); + }}); + + shrink("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "shrink ok"); + }}); + + showElement('elt1'); + dropOut("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "dropOut ok"); + }}); + + showElement('elt1'); + puff("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "puff ok"); + }}); + + showElement('elt1'); + fold("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "fold ok"); + }}); + + showElement('elt1'); + squish("elt1", {afterFinish: function () { + is(getElement('elt1').style.display, 'none', "squish ok"); + }}); + + slideUp("ctn1", {afterFinish: function () { + is(getElement('ctn1').style.display, 'none', "slideUp ok"); + }}); + + slideDown("ctn1", {afterFinish: function () { + is(getElement('ctn1').style.display != 'none', true, "slideDown ok"); + }}); + + blindDown("ctn1", {afterFinish: function () { + is(getElement('ctn1').style.display != 'none', true, "blindDown ok"); + }}); + + blindUp("ctn1", {afterFinish: function () { + is(getElement('ctn1').style.display, 'none', "blindUp ok"); + }}); + + multiple(["elt1", "ctn1"], appear, {afterFinish: function (effect) { + is(effect.element.style.display != 'none', true, "multiple ok"); + }}); + + toggle("elt3", "size", {afterFinish: function () { + is(getElement('elt3').style.display != 'none', true, "toggle with css ok"); + }}); + + toggle("elt3", "size", {afterFinish: function () { + is(getElement('elt3').style.display, 'none', "toggle with css ok"); + }}); + + var toTests = [roundElement, roundClass, tagifyText, Opacity, Move, Highlight, ScrollTo, Morph]; + for (var m in toTests) { + toTests[m]("elt1"); + ok(true, toTests[m].NAME + " doesn't need 'new' keyword"); + } + Scale("elt1", 1); + ok(true, "Scale doesn't need 'new' keyword"); + + ok(true, "visual suite finished"); + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + SimpleTest.finish(); + +} +</script> +</pre> +</body> +</html> + diff --git a/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Signal.js b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Signal.js new file mode 100644 index 000000000..cbee78575 --- /dev/null +++ b/testing/mochitest/tests/MochiKit-1.4.2/tests/test_Signal.js @@ -0,0 +1,481 @@ +if (typeof(dojo) != 'undefined') { dojo.require('MochiKit.Signal'); } +if (typeof(JSAN) != 'undefined') { JSAN.use('MochiKit.Signal'); } +if (typeof(tests) == 'undefined') { tests = {}; } + +tests.test_Signal = function (t) { + + var submit = MochiKit.DOM.getElement('submit'); + var ident = null; + var i = 0; + var aFunction = function() { + t.ok(this === submit, "aFunction should have 'this' as submit"); + i++; + if (typeof(this.someVar) != 'undefined') { + i += this.someVar; + } + }; + + var aObject = {}; + aObject.aMethod = function() { + t.ok(this === aObject, "aMethod should have 'this' as aObject"); + i++; + }; + + ident = connect('submit', 'onclick', aFunction); + MochiKit.DOM.getElement('submit').click(); + t.is(i, 1, 'HTML onclick event can be connected to a function'); + + disconnect(ident); + MochiKit.DOM.getElement('submit').click(); + t.is(i, 1, 'HTML onclick can be disconnected from a function'); + + var submit = MochiKit.DOM.getElement('submit'); + + ident = connect(submit, 'onclick', aFunction); + submit.click(); + t.is(i, 2, 'Checking that a DOM element can be connected to a function'); + + disconnect(ident); + submit.click(); + t.is(i, 2, '...and then disconnected'); + + if (MochiKit.DOM.getElement('submit').fireEvent || + (document.createEvent && + typeof(document.createEvent('MouseEvents').initMouseEvent) == 'function')) { + + /* + + Adapted from: + http://www.devdaily.com/java/jwarehouse/jforum/tests/selenium/javascript/htmlutils.js.shtml + License: Apache + Copyright: Copyright 2004 ThoughtWorks, Inc + + */ + var triggerMouseEvent = function(element, eventType, canBubble) { + element = MochiKit.DOM.getElement(element); + canBubble = (typeof(canBubble) == 'undefined') ? true : canBubble; + if (element.fireEvent) { + var newEvt = document.createEventObject(); + newEvt.clientX = 1; + newEvt.clientY = 1; + newEvt.button = 1; + newEvt.detail = 3; + element.fireEvent('on' + eventType, newEvt); + } else if (document.createEvent && (typeof(document.createEvent('MouseEvents').initMouseEvent) == 'function')) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent(eventType, canBubble, true, // event, bubbles, cancelable + document.defaultView, 3, // view, detail (either scroll or # of clicks) + 1, 0, 0, 0, // screenX, screenY, clientX, clientY + false, false, false, false, // ctrlKey, altKey, shiftKey, metaKey + 0, null); // buttonCode, relatedTarget + element.dispatchEvent(evt); + } + }; + + var eventTest = function(e) { + i++; + t.ok((typeof(e.event()) === 'object'), 'checking that event() is an object'); + t.ok((typeof(e.type()) === 'string'), 'checking that type() is a string'); + t.ok((e.target() === MochiKit.DOM.getElement('submit')), 'checking that target is "submit"'); + t.ok((typeof(e.modifier()) === 'object'), 'checking that modifier() is an object'); + t.ok(e.modifier().alt === false, 'checking that modifier().alt is defined, but false'); + t.ok(e.modifier().ctrl === false, 'checking that modifier().ctrl is defined, but false'); + t.ok(e.modifier().meta === false, 'checking that modifier().meta is defined, but false'); + t.ok(e.modifier().shift === false, 'checking that modifier().shift is defined, but false'); + t.ok((typeof(e.mouse()) === 'object'), 'checking that mouse() is an object'); + t.ok((typeof(e.mouse().button) === 'object'), 'checking that mouse().button is an object'); + t.ok(e.mouse().button.left === true, 'checking that mouse().button.left is true'); + t.ok(e.mouse().button.middle === false, 'checking that mouse().button.middle is false'); + t.ok(e.mouse().button.right === false, 'checking that mouse().button.right is false'); + t.ok((typeof(e.mouse().page) === 'object'), 'checking that mouse().page is an object'); + t.ok((typeof(e.mouse().page.x) === 'number'), 'checking that mouse().page.x is a number'); + t.ok((typeof(e.mouse().page.y) === 'number'), 'checking that mouse().page.y is a number'); + t.ok((typeof(e.mouse().client) === 'object'), 'checking that mouse().client is an object'); + t.ok((typeof(e.mouse().client.x) === 'number'), 'checking that mouse().client.x is a number'); + t.ok((typeof(e.mouse().client.y) === 'number'), 'checking that mouse().client.y is a number'); + + /* these should not be defined */ + t.ok((typeof(e.relatedTarget()) === 'undefined'), 'checking that relatedTarget() is undefined'); + t.ok((typeof(e.key()) === 'undefined'), 'checking that key() is undefined'); + t.ok((typeof(e.mouse().wheel) === 'undefined'), 'checking that mouse().wheel is undefined'); + }; + + + ident = connect('submit', 'onmousedown', eventTest); + triggerMouseEvent('submit', 'mousedown', false); + t.is(i, 3, 'Connecting an event to an HTML object and firing a synthetic event'); + + disconnect(ident); + triggerMouseEvent('submit', 'mousedown', false); + t.is(i, 3, 'Disconnecting an event to an HTML object and firing a synthetic event'); + + ident = connect('submit', 'onmousewheel', function(e) { + i++; + t.ok((typeof(e.mouse()) === 'object'), 'checking that mouse() is an object'); + t.ok((typeof(e.mouse().wheel) === 'object'), 'checking that mouse().wheel is an object'); + t.ok((typeof(e.mouse().wheel.x) === 'number'), 'checking that mouse().wheel.x is a number'); + t.ok((typeof(e.mouse().wheel.y) === 'number'), 'checking that mouse().wheel.y is a number'); + }); + var nativeSignal = 'mousewheel'; + if (MochiKit.Signal._browserLacksMouseWheelEvent()) { + nativeSignal = 'DOMMouseScroll'; + } + triggerMouseEvent('submit', nativeSignal, false); + t.is(i, 4, 'Connecting a mousewheel event to an HTML object and firing a synthetic event'); + disconnect(ident); + triggerMouseEvent('submit', nativeSignal, false); + t.is(i, 4, 'Disconnecting a mousewheel event to an HTML object and firing a synthetic event'); + } + + // non-DOM tests + + var hasNoSignals = {}; + + var hasSignals = {someVar: 1}; + + var i = 0; + + var aFunction = function() { + i++; + if (typeof(this.someVar) != 'undefined') { + i += this.someVar; + } + }; + + var bFunction = function(someArg, someOtherArg) { + i += someArg + someOtherArg; + }; + + + var aObject = {}; + aObject.aMethod = function() { + i++; + }; + + aObject.bMethod = function() { + i++; + }; + + var bObject = {}; + bObject.bMethod = function() { + i++; + }; + + + ident = connect(hasSignals, 'signalOne', aFunction); + signal(hasSignals, 'signalOne'); + t.is(i, 2, 'Connecting function'); + i = 0; + + disconnect(ident); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'New style disconnecting function'); + i = 0; + + + ident = connect(hasSignals, 'signalOne', bFunction); + signal(hasSignals, 'signalOne', 1, 2); + t.is(i, 3, 'Connecting function'); + i = 0; + + disconnect(ident); + signal(hasSignals, 'signalOne', 1, 2); + t.is(i, 0, 'New style disconnecting function'); + i = 0; + + + connect(hasSignals, 'signalOne', aFunction); + signal(hasSignals, 'signalOne'); + t.is(i, 2, 'Connecting function'); + i = 0; + + disconnect(hasSignals, 'signalOne', aFunction); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'Old style disconnecting function'); + i = 0; + + + ident = connect(hasSignals, 'signalOne', aObject, aObject.aMethod); + signal(hasSignals, 'signalOne'); + t.is(i, 1, 'Connecting obj-function'); + i = 0; + + disconnect(ident); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'New style disconnecting obj-function'); + i = 0; + + connect(hasSignals, 'signalOne', aObject, aObject.aMethod); + signal(hasSignals, 'signalOne'); + t.is(i, 1, 'Connecting obj-function'); + i = 0; + + disconnect(hasSignals, 'signalOne', aObject, aObject.aMethod); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'Disconnecting obj-function'); + i = 0; + + + ident = connect(hasSignals, 'signalTwo', aObject, 'aMethod'); + signal(hasSignals, 'signalTwo'); + t.is(i, 1, 'Connecting obj-string'); + i = 0; + + disconnect(ident); + signal(hasSignals, 'signalTwo'); + t.is(i, 0, 'New style disconnecting obj-string'); + i = 0; + + + connect(hasSignals, 'signalTwo', aObject, 'aMethod'); + signal(hasSignals, 'signalTwo'); + t.is(i, 1, 'Connecting obj-string'); + i = 0; + + disconnect(hasSignals, 'signalTwo', aObject, 'aMethod'); + signal(hasSignals, 'signalTwo'); + t.is(i, 0, 'Old style disconnecting obj-string'); + i = 0; + + + var shouldRaise = function() { return undefined.attr; }; + + try { + connect(hasSignals, 'signalOne', shouldRaise); + signal(hasSignals, 'signalOne'); + t.ok(false, 'An exception was not raised'); + } catch (e) { + t.ok(true, 'An exception was raised'); + } + disconnect(hasSignals, 'signalOne', shouldRaise); + t.is(i, 0, 'Exception raised, signal should not have fired'); + i = 0; + + + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + connect(hasSignals, 'signalOne', aObject, 'bMethod'); + signal(hasSignals, 'signalOne'); + t.is(i, 2, 'Connecting one signal to two slots in one object'); + i = 0; + + disconnect(hasSignals, 'signalOne', aObject, 'aMethod'); + disconnect(hasSignals, 'signalOne', aObject, 'bMethod'); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'Disconnecting one signal from two slots in one object'); + i = 0; + + + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + connect(hasSignals, 'signalOne', bObject, 'bMethod'); + signal(hasSignals, 'signalOne'); + t.is(i, 2, 'Connecting one signal to two slots in two objects'); + i = 0; + + disconnect(hasSignals, 'signalOne', aObject, 'aMethod'); + disconnect(hasSignals, 'signalOne', bObject, 'bMethod'); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'Disconnecting one signal from two slots in two objects'); + i = 0; + + + try { + connect(nothing, 'signalOne', aObject, 'aMethod'); + signal(nothing, 'signalOne'); + t.ok(false, 'An exception was not raised when connecting undefined'); + } catch (e) { + t.ok(true, 'An exception was raised when connecting undefined'); + } + + try { + disconnect(nothing, 'signalOne', aObject, 'aMethod'); + t.ok(false, 'An exception was not raised when disconnecting undefined'); + } catch (e) { + t.ok(true, 'An exception was raised when disconnecting undefined'); + } + + + try { + connect(hasSignals, 'signalOne', nothing); + signal(hasSignals, 'signalOne'); + t.ok(false, 'An exception was not raised when connecting an undefined function'); + } catch (e) { + t.ok(true, 'An exception was raised when connecting an undefined function'); + } + + try { + disconnect(hasSignals, 'signalOne', nothing); + t.ok(false, 'An exception was not raised when disconnecting an undefined function'); + } catch (e) { + t.ok(true, 'An exception was raised when disconnecting an undefined function'); + } + + + try { + connect(hasSignals, 'signalOne', aObject, aObject.nothing); + signal(hasSignals, 'signalOne'); + t.ok(false, 'An exception was not raised when connecting an undefined method'); + } catch (e) { + t.ok(true, 'An exception was raised when connecting an undefined method'); + } + + try { + connect(hasSignals, 'signalOne', aObject, 'nothing'); + signal(hasSignals, 'signalOne'); + t.ok(false, 'An exception was not raised when connecting an undefined method (as string)'); + } catch (e) { + t.ok(true, 'An exception was raised when connecting an undefined method (as string)'); + } + + t.is(i, 0, 'Signals should not have fired'); + + connect(hasSignals, 'signalOne', aFunction); + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + disconnectAll(hasSignals, 'signalOne'); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'disconnectAll works with single explicit signal'); + i = 0; + + connect(hasSignals, 'signalOne', aFunction); + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + connect(hasSignals, 'signalTwo', aFunction); + connect(hasSignals, 'signalTwo', aObject, 'aMethod'); + disconnectAll(hasSignals, 'signalOne'); + signal(hasSignals, 'signalOne'); + t.is(i, 0, 'disconnectAll works with single explicit signal'); + signal(hasSignals, 'signalTwo'); + t.is(i, 3, 'disconnectAll does not disconnect unrelated signals'); + i = 0; + + connect(hasSignals, 'signalOne', aFunction); + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + connect(hasSignals, 'signalTwo', aFunction); + connect(hasSignals, 'signalTwo', aObject, 'aMethod'); + disconnectAll(hasSignals, 'signalOne', 'signalTwo'); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(i, 0, 'disconnectAll works with two explicit signals'); + i = 0; + + connect(hasSignals, 'signalOne', aFunction); + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + connect(hasSignals, 'signalTwo', aFunction); + connect(hasSignals, 'signalTwo', aObject, 'aMethod'); + disconnectAll(hasSignals, ['signalOne', 'signalTwo']); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(i, 0, 'disconnectAll works with two explicit signals as a list'); + i = 0; + + connect(hasSignals, 'signalOne', aFunction); + connect(hasSignals, 'signalOne', aObject, 'aMethod'); + connect(hasSignals, 'signalTwo', aFunction); + connect(hasSignals, 'signalTwo', aObject, 'aMethod'); + disconnectAll(hasSignals); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(i, 0, 'disconnectAll works with implicit signals'); + i = 0; + + var toggle = function() { + disconnectAll(hasSignals, 'signalOne'); + connect(hasSignals, 'signalOne', aFunction); + i++; + }; + + connect(hasSignals, 'signalOne', aFunction); + connect(hasSignals, 'signalTwo', function() { i++; }); + connect(hasSignals, 'signalTwo', toggle); + connect(hasSignals, 'signalTwo', function() { i++; }); // #147 + connect(hasSignals, 'signalTwo', function() { i++; }); + signal(hasSignals, 'signalTwo'); + t.is(i, 4, 'disconnectAll fired in a signal loop works'); + i = 0; + disconnectAll('signalOne'); + disconnectAll('signalTwo'); + + var testfunc = function () { arguments.callee.count++; }; + testfunc.count = 0; + var testObj = { + methOne: function () { this.countOne++; }, countOne: 0, + methTwo: function () { this.countTwo++; }, countTwo: 0 + }; + connect(hasSignals, 'signalOne', testfunc); + connect(hasSignals, 'signalTwo', testfunc); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(testfunc.count, 2, 'disconnectAllTo func precondition'); + disconnectAllTo(testfunc); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(testfunc.count, 2, 'disconnectAllTo func'); + + connect(hasSignals, 'signalOne', testObj, 'methOne'); + connect(hasSignals, 'signalTwo', testObj, 'methTwo'); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(testObj.countOne, 1, 'disconnectAllTo obj precondition'); + t.is(testObj.countTwo, 1, 'disconnectAllTo obj precondition'); + disconnectAllTo(testObj); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(testObj.countOne, 1, 'disconnectAllTo obj'); + t.is(testObj.countTwo, 1, 'disconnectAllTo obj'); + + testObj.countOne = testObj.countTwo = 0; + connect(hasSignals, 'signalOne', testObj, 'methOne'); + connect(hasSignals, 'signalTwo', testObj, 'methTwo'); + disconnectAllTo(testObj, 'methOne'); + signal(hasSignals, 'signalOne'); + signal(hasSignals, 'signalTwo'); + t.is(testObj.countOne, 0, 'disconnectAllTo obj+str'); + t.is(testObj.countTwo, 1, 'disconnectAllTo obj+str'); + + has__Connect = { + count: 0, + __connect__: function (ident) { + this.count += arguments.length; + disconnect(ident); + } + }; + connect(has__Connect, 'signalOne', function() { + t.fail("__connect__ should have disconnected signal"); + }); + t.is(has__Connect.count, 3, '__connect__ is called when it exists'); + signal(has__Connect, 'signalOne'); + + has__Disconnect = { + count: 0, + __disconnect__: function (ident) { + this.count += arguments.length; + } + }; + connect(has__Disconnect, 'signalOne', aFunction); + connect(has__Disconnect, 'signalTwo', aFunction); + disconnectAll(has__Disconnect); + t.is(has__Disconnect.count, 8, '__disconnect__ is called when it exists'); + + var events = {}; + var test_ident = connect(events, "test", function() { + var fail_ident = connect(events, "fail", function () { + events.failed = true; + }); + disconnect(fail_ident); + signal(events, "fail"); + }); + signal(events, "test"); + t.is(events.failed, undefined, 'disconnected slots do not fire'); + + var sink = {f: function (ev) { this.ev = ev; }}; + var src = {}; + bindMethods(sink); + connect(src, 'signal', sink.f); + signal(src, 'signal', 'worked'); + t.is(sink.ev, 'worked', 'custom signal does not re-bind methods'); + + var lateObj = { fun: function() { this.value = 1; } }; + connect(src, 'signal', lateObj, "fun"); + signal(src, 'signal'); + lateObj.fun = function() { this.value = 2; }; + signal(src, 'signal'); + t.is(lateObj.value, 2, 'connect uses late function binding'); +}; diff --git a/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js new file mode 100644 index 000000000..0f1cc0608 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js @@ -0,0 +1,98 @@ +/* + * This code is used for handling synthesizeMouse in a content process. + * Generally it just delegates to EventUtils.js. + */ + +// Set up a dummy environment so that EventUtils works. We need to be careful to +// pass a window object into each EventUtils method we call rather than having +// it rely on the |window| global. +var EventUtils = {}; +EventUtils.window = {}; +EventUtils.parent = EventUtils.window; +EventUtils._EU_Ci = Components.interfaces; +EventUtils._EU_Cc = Components.classes; +// EventUtils' `sendChar` function relies on the navigator to synthetize events. +EventUtils.navigator = content.document.defaultView.navigator; +EventUtils.KeyboardEvent = content.document.defaultView.KeyboardEvent; + +Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + +addMessageListener("Test:SynthesizeMouse", (message) => { + let data = message.data; + let target = data.target; + if (typeof target == "string") { + target = content.document.querySelector(target); + } + else if (typeof data.targetFn == "string") { + let runnablestr = ` + (() => { + return (${data.targetFn}); + })();` + target = eval(runnablestr)(); + } + else { + target = message.objects.object; + } + + let left = data.x; + let top = data.y; + if (target) { + if (target.ownerDocument !== content.document) { + // Account for nodes found in iframes. + let cur = target; + do { + let frame = cur.ownerDocument.defaultView.frameElement; + let rect = frame.getBoundingClientRect(); + + left += rect.left; + top += rect.top; + + cur = frame; + } while (cur && cur.ownerDocument !== content.document); + + // node must be in this document tree. + if (!cur) { + sendAsyncMessage("Test:SynthesizeMouseDone", + { error: "target must be in the main document tree" }); + return; + } + } + + let rect = target.getBoundingClientRect(); + left += rect.left; + top += rect.top; + + if (data.event.centered) { + left += rect.width / 2; + top += rect.height / 2; + } + } + + let result; + if (data.event && data.event.wheel) { + EventUtils.synthesizeWheelAtPoint(left, top, data.event, content); + } else { + result = EventUtils.synthesizeMouseAtPoint(left, top, data.event, content); + } + sendAsyncMessage("Test:SynthesizeMouseDone", { defaultPrevented: result }); +}); + +addMessageListener("Test:SendChar", message => { + let result = EventUtils.sendChar(message.data.char, content); + sendAsyncMessage("Test:SendCharDone", { result, seq: message.data.seq }); +}); + +addMessageListener("Test:SynthesizeKey", message => { + EventUtils.synthesizeKey(message.data.key, message.data.event || {}, content); + sendAsyncMessage("Test:SynthesizeKeyDone", { seq: message.data.seq }); +}); + +addMessageListener("Test:SynthesizeComposition", message => { + let result = EventUtils.synthesizeComposition(message.data.event, content); + sendAsyncMessage("Test:SynthesizeCompositionDone", { result, seq: message.data.seq }); +}); + +addMessageListener("Test:SynthesizeCompositionChange", message => { + EventUtils.synthesizeCompositionChange(message.data.event, content); + sendAsyncMessage("Test:SynthesizeCompositionChangeDone", { seq: message.data.seq }); +}); diff --git a/testing/mochitest/tests/SimpleTest/ChromePowers.js b/testing/mochitest/tests/SimpleTest/ChromePowers.js new file mode 100644 index 000000000..97de57815 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/ChromePowers.js @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function ChromePowers(window) { + this.window = Components.utils.getWeakReference(window); + + // In the case of browser-chrome tests, we are running as a [ChromeWindow] + // and we have no window.QueryInterface available, content.window is what we need + if (typeof(window) == "ChromeWindow" && typeof(content.window) == "Window") { + this.DOMWindowUtils = bindDOMWindowUtils(content.window); + this.window = Components.utils.getWeakReference(content.window); + } else { + this.DOMWindowUtils = bindDOMWindowUtils(window); + } + + this.spObserver = new SpecialPowersObserverAPI(); + this.spObserver._sendReply = this._sendReply.bind(this); + this.listeners = new Map(); +} + +ChromePowers.prototype = new SpecialPowersAPI(); + +ChromePowers.prototype.toString = function() { return "[ChromePowers]"; }; +ChromePowers.prototype.sanityCheck = function() { return "foo"; }; + +// This gets filled in in the constructor. +ChromePowers.prototype.DOMWindowUtils = undefined; + +ChromePowers.prototype._sendReply = function(aOrigMsg, aType, aMsg) { + var msg = {'name':aType, 'json': aMsg, 'data': aMsg}; + if (!this.listeners.has(aType)) { + throw new Error(`No listener for ${aType}`); + } + this.listeners.get(aType)(msg); +}; + +ChromePowers.prototype._sendSyncMessage = function(aType, aMsg) { + var msg = {'name':aType, 'json': aMsg, 'data': aMsg}; + return [this._receiveMessage(msg)]; +}; + +ChromePowers.prototype._sendAsyncMessage = function(aType, aMsg) { + var msg = {'name':aType, 'json': aMsg, 'data': aMsg}; + this._receiveMessage(msg); +}; + +ChromePowers.prototype._addMessageListener = function(aType, aCallback) { + if (this.listeners.has(aType)) { + throw new Error(`unable to handle multiple listeners for ${aType}`); + } + this.listeners.set(aType, aCallback); +}; +ChromePowers.prototype._removeMessageListener = function(aType, aCallback) { + this.listeners.delete(aType); +}; + +ChromePowers.prototype.registerProcessCrashObservers = function() { + this._sendSyncMessage("SPProcessCrashService", { op: "register-observer" }); +}; + +ChromePowers.prototype.unregisterProcessCrashObservers = function() { + this._sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" }); +}; + +ChromePowers.prototype._receiveMessage = function(aMessage) { + switch (aMessage.name) { + case "SpecialPowers.Quit": + let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); + appStartup.quit(Ci.nsIAppStartup.eForceQuit); + break; + case "SPProcessCrashService": + if (aMessage.json.op == "register-observer" || aMessage.json.op == "unregister-observer") { + // Hack out register/unregister specifically for browser-chrome leaks + break; + } else if (aMessage.type == "crash-observed") { + for (let e of msg.dumpIDs) { + this._encounteredCrashDumpFiles.push(e.id + "." + e.extension); + } + } + default: + // All calls go here, because we need to handle SPProcessCrashService calls as well + return this.spObserver._receiveMessageAPI(aMessage); + } + return undefined; // Avoid warning. +}; + +ChromePowers.prototype.quit = function() { + // We come in here as SpecialPowers.quit, but SpecialPowers is really ChromePowers. + // For some reason this.<func> resolves to TestRunner, so using SpecialPowers + // allows us to use the ChromePowers object which we defined below. + SpecialPowers._sendSyncMessage("SpecialPowers.Quit", {}); +}; + +ChromePowers.prototype.focus = function(aWindow) { + // We come in here as SpecialPowers.focus, but SpecialPowers is really ChromePowers. + // For some reason this.<func> resolves to TestRunner, so using SpecialPowers + // allows us to use the ChromePowers object which we defined below. + if (aWindow) + aWindow.focus(); +}; + +ChromePowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) { + aCallback(); +}; + +// Expose everything but internal APIs (starting with underscores) to +// web content. We cannot use Object.keys to view SpecialPowers.prototype since +// we are using the functions from SpecialPowersAPI.prototype +ChromePowers.prototype.__exposedProps__ = {}; +for (var i in ChromePowers.prototype) { + if (i.charAt(0) != "_") + ChromePowers.prototype.__exposedProps__[i] = "r"; +} + +if ((window.parent !== null) && + (window.parent !== undefined) && + (window.parent.wrappedJSObject.SpecialPowers) && + !(window.wrappedJSObject.SpecialPowers)) { + window.wrappedJSObject.SpecialPowers = window.parent.SpecialPowers; +} else { + window.wrappedJSObject.SpecialPowers = new ChromePowers(window); +} + diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js new file mode 100644 index 000000000..17243625d --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -0,0 +1,2143 @@ +/** + * EventUtils provides some utility methods for creating and sending DOM events. + * Current methods: + * sendMouseEvent + * sendDragEvent + * sendChar + * sendString + * sendKey + * sendWheelAndPaint + * synthesizeMouse + * synthesizeMouseAtCenter + * synthesizePointer + * synthesizeWheel + * synthesizeWheelAtPoint + * synthesizeKey + * synthesizeNativeKey + * synthesizeMouseExpectEvent + * synthesizeKeyExpectEvent + * synthesizeNativeClick + * + * When adding methods to this file, please add a performance test for it. + */ + +// This file is used both in privileged and unprivileged contexts, so we have to +// be careful about our access to Components.interfaces. We also want to avoid +// naming collisions with anything that might be defined in the scope that imports +// this script. +window.__defineGetter__('_EU_Ci', function() { + // Even if the real |Components| doesn't exist, we might shim in a simple JS + // placebo for compat. An easy way to differentiate this from the real thing + // is whether the property is read-only or not. + var c = Object.getOwnPropertyDescriptor(window, 'Components'); + return c.value && !c.writable ? Components.interfaces : SpecialPowers.Ci; +}); + +window.__defineGetter__('_EU_Cc', function() { + var c = Object.getOwnPropertyDescriptor(window, 'Components'); + return c.value && !c.writable ? Components.classes : SpecialPowers.Cc; +}); + +window.__defineGetter__('_EU_Cu', function() { + var c = Object.getOwnPropertyDescriptor(window, 'Components'); + return c.value && !c.writable ? Components.utils : SpecialPowers.Cu; +}); + +window.__defineGetter__("_EU_OS", function() { + delete this._EU_OS; + try { + this._EU_OS = this._EU_Cu.import("resource://gre/modules/AppConstants.jsm", {}).platform; + } catch (ex) { + this._EU_OS = null; + } + return this._EU_OS; +}); + +function _EU_isMac(aWindow = window) { + if (window._EU_OS) { + return window._EU_OS == "macosx"; + } + if (aWindow) { + try { + return aWindow.navigator.platform.indexOf("Mac") > -1; + } catch (ex) {} + } + return navigator.platform.indexOf("Mac") > -1; +} + +function _EU_isWin(aWindow = window) { + if (window._EU_OS) { + return window._EU_OS == "win"; + } + if (aWindow) { + try { + return aWindow.navigator.platform.indexOf("Win") > -1; + } catch (ex) {} + } + return navigator.platform.indexOf("Win") > -1; +} + +/** + * Send a mouse event to the node aTarget (aTarget can be an id, or an + * actual node) . The "event" passed in to aEvent is just a JavaScript + * object with the properties set that the real mouse event object should + * have. This includes the type of the mouse event. + * E.g. to send an click event to the node with id 'node' you might do this: + * + * sendMouseEvent({type:'click'}, 'node'); + */ +function getElement(id) { + return ((typeof(id) == "string") ? + document.getElementById(id) : id); +}; + +this.$ = this.getElement; + +function computeButton(aEvent) { + if (typeof aEvent.button != 'undefined') { + return aEvent.button; + } + return aEvent.type == 'contextmenu' ? 2 : 0; +} + +function sendMouseEvent(aEvent, aTarget, aWindow) { + if (['click', 'contextmenu', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) { + throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'"); + } + + if (!aWindow) { + aWindow = window; + } + + if (typeof aTarget == "string") { + aTarget = aWindow.document.getElementById(aTarget); + } + + var event = aWindow.document.createEvent('MouseEvent'); + + var typeArg = aEvent.type; + var canBubbleArg = true; + var cancelableArg = true; + var viewArg = aWindow; + var detailArg = aEvent.detail || (aEvent.type == 'click' || + aEvent.type == 'mousedown' || + aEvent.type == 'mouseup' ? 1 : + aEvent.type == 'dblclick'? 2 : 0); + var screenXArg = aEvent.screenX || 0; + var screenYArg = aEvent.screenY || 0; + var clientXArg = aEvent.clientX || 0; + var clientYArg = aEvent.clientY || 0; + var ctrlKeyArg = aEvent.ctrlKey || false; + var altKeyArg = aEvent.altKey || false; + var shiftKeyArg = aEvent.shiftKey || false; + var metaKeyArg = aEvent.metaKey || false; + var buttonArg = computeButton(aEvent); + var relatedTargetArg = aEvent.relatedTarget || null; + + event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg, + screenXArg, screenYArg, clientXArg, clientYArg, + ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg, + buttonArg, relatedTargetArg); + + return SpecialPowers.dispatchEvent(aWindow, aTarget, event); +} + +/** + * Send a drag event to the node aTarget (aTarget can be an id, or an + * actual node) . The "event" passed in to aEvent is just a JavaScript + * object with the properties set that the real drag event object should + * have. This includes the type of the drag event. + */ +function sendDragEvent(aEvent, aTarget, aWindow = window) { + if (['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop'].indexOf(aEvent.type) == -1) { + throw new Error("sendDragEvent doesn't know about event type '" + aEvent.type + "'"); + } + + if (typeof aTarget == "string") { + aTarget = aWindow.document.getElementById(aTarget); + } + + var event = aWindow.document.createEvent('DragEvent'); + + var typeArg = aEvent.type; + var canBubbleArg = true; + var cancelableArg = true; + var viewArg = aWindow; + var detailArg = aEvent.detail || 0; + var screenXArg = aEvent.screenX || 0; + var screenYArg = aEvent.screenY || 0; + var clientXArg = aEvent.clientX || 0; + var clientYArg = aEvent.clientY || 0; + var ctrlKeyArg = aEvent.ctrlKey || false; + var altKeyArg = aEvent.altKey || false; + var shiftKeyArg = aEvent.shiftKey || false; + var metaKeyArg = aEvent.metaKey || false; + var buttonArg = computeButton(aEvent); + var relatedTargetArg = aEvent.relatedTarget || null; + var dataTransfer = aEvent.dataTransfer || null; + + event.initDragEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg, + screenXArg, screenYArg, clientXArg, clientYArg, + ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg, + buttonArg, relatedTargetArg, dataTransfer); + + var utils = _getDOMWindowUtils(aWindow); + return utils.dispatchDOMEventViaPresShell(aTarget, event, true); +} + +/** + * Send the char aChar to the focused element. This method handles casing of + * chars (sends the right charcode, and sends a shift key for uppercase chars). + * No other modifiers are handled at this point. + * + * For now this method only works for ASCII characters and emulates the shift + * key state on US keyboard layout. + */ +function sendChar(aChar, aWindow) { + var hasShift; + // Emulate US keyboard layout for the shiftKey state. + switch (aChar) { + case "!": + case "@": + case "#": + case "$": + case "%": + case "^": + case "&": + case "*": + case "(": + case ")": + case "_": + case "+": + case "{": + case "}": + case ":": + case "\"": + case "|": + case "<": + case ">": + case "?": + hasShift = true; + break; + default: + hasShift = (aChar == aChar.toUpperCase()); + break; + } + synthesizeKey(aChar, { shiftKey: hasShift }, aWindow); +} + +/** + * Send the string aStr to the focused element. + * + * For now this method only works for ASCII characters and emulates the shift + * key state on US keyboard layout. + */ +function sendString(aStr, aWindow) { + for (var i = 0; i < aStr.length; ++i) { + sendChar(aStr.charAt(i), aWindow); + } +} + +/** + * Send the non-character key aKey to the focused node. + * The name of the key should be the part that comes after "DOM_VK_" in the + * KeyEvent constant name for this key. + * No modifiers are handled at this point. + */ +function sendKey(aKey, aWindow) { + var keyName = "VK_" + aKey.toUpperCase(); + synthesizeKey(keyName, { shiftKey: false }, aWindow); +} + +/** + * Parse the key modifier flags from aEvent. Used to share code between + * synthesizeMouse and synthesizeKey. + */ +function _parseModifiers(aEvent, aWindow = window) +{ + var navigator = _getNavigator(aWindow); + var nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils; + var mval = 0; + if (aEvent.shiftKey) { + mval |= nsIDOMWindowUtils.MODIFIER_SHIFT; + } + if (aEvent.ctrlKey) { + mval |= nsIDOMWindowUtils.MODIFIER_CONTROL; + } + if (aEvent.altKey) { + mval |= nsIDOMWindowUtils.MODIFIER_ALT; + } + if (aEvent.metaKey) { + mval |= nsIDOMWindowUtils.MODIFIER_META; + } + if (aEvent.accelKey) { + mval |= _EU_isMac(aWindow) ? + nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL; + } + if (aEvent.altGrKey) { + mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH; + } + if (aEvent.capsLockKey) { + mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK; + } + if (aEvent.fnKey) { + mval |= nsIDOMWindowUtils.MODIFIER_FN; + } + if (aEvent.fnLockKey) { + mval |= nsIDOMWindowUtils.MODIFIER_FNLOCK; + } + if (aEvent.numLockKey) { + mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK; + } + if (aEvent.scrollLockKey) { + mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK; + } + if (aEvent.symbolKey) { + mval |= nsIDOMWindowUtils.MODIFIER_SYMBOL; + } + if (aEvent.symbolLockKey) { + mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK; + } + if (aEvent.osKey) { + mval |= nsIDOMWindowUtils.MODIFIER_OS; + } + + return mval; +} + +/** + * Synthesize a mouse event on a target. The actual client point is determined + * by taking the aTarget's client box and offseting it by aOffsetX and + * aOffsetY. This allows mouse clicks to be simulated by calling this method. + * + * aEvent is an object which may contain the properties: + * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type + * + * If the type is specified, an mouse event of that type is fired. Otherwise, + * a mousedown followed by a mouse up is performed. + * + * aWindow is optional, and defaults to the current window object. + * + * Returns whether the event had preventDefault() called on it. + */ +function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) +{ + var rect = aTarget.getBoundingClientRect(); + return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, + aEvent, aWindow); +} +function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) +{ + var rect = aTarget.getBoundingClientRect(); + synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, + aEvent, aWindow); +} +function synthesizePointer(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) +{ + var rect = aTarget.getBoundingClientRect(); + return synthesizePointerAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, + aEvent, aWindow); +} + +/* + * Synthesize a mouse event at a particular point in aWindow. + * + * aEvent is an object which may contain the properties: + * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type + * + * If the type is specified, an mouse event of that type is fired. Otherwise, + * a mousedown followed by a mouse up is performed. + * + * aWindow is optional, and defaults to the current window object. + */ +function synthesizeMouseAtPoint(left, top, aEvent, aWindow = window) +{ + var utils = _getDOMWindowUtils(aWindow); + var defaultPrevented = false; + + if (utils) { + var button = computeButton(aEvent); + var clickCount = aEvent.clickCount || 1; + var modifiers = _parseModifiers(aEvent, aWindow); + var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0; + var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0; + var isDOMEventSynthesized = + ("isSynthesized" in aEvent) ? aEvent.isSynthesized : true; + var isWidgetEventSynthesized = + ("isWidgetEventSynthesized" in aEvent) ? aEvent.isWidgetEventSynthesized : false; + var buttons = ("buttons" in aEvent) ? aEvent.buttons : + utils.MOUSE_BUTTONS_NOT_SPECIFIED; + if (("type" in aEvent) && aEvent.type) { + defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, + clickCount, modifiers, false, + pressure, inputSource, + isDOMEventSynthesized, + isWidgetEventSynthesized, + buttons); + } + else { + utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, + false, pressure, inputSource, isDOMEventSynthesized, + isWidgetEventSynthesized, buttons); + utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, + false, pressure, inputSource, isDOMEventSynthesized, + isWidgetEventSynthesized, buttons); + } + } + + return defaultPrevented; +} + +function synthesizeTouchAtPoint(left, top, aEvent, aWindow = window) +{ + var utils = _getDOMWindowUtils(aWindow); + + if (utils) { + var id = aEvent.id || 0; + var rx = aEvent.rx || 1; + var ry = aEvent.rx || 1; + var angle = aEvent.angle || 0; + var force = aEvent.force || 1; + var modifiers = _parseModifiers(aEvent, aWindow); + + if (("type" in aEvent) && aEvent.type) { + utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers); + } + else { + utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers); + utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers); + } + } +} + +function synthesizePointerAtPoint(left, top, aEvent, aWindow = window) +{ + var utils = _getDOMWindowUtils(aWindow); + var defaultPrevented = false; + + if (utils) { + var button = computeButton(aEvent); + var clickCount = aEvent.clickCount || 1; + var modifiers = _parseModifiers(aEvent, aWindow); + var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0; + var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0; + var synthesized = ("isSynthesized" in aEvent) ? aEvent.isSynthesized : true; + var isPrimary = ("isPrimary" in aEvent) ? aEvent.isPrimary : false; + + if (("type" in aEvent) && aEvent.type) { + defaultPrevented = utils.sendPointerEventToWindow(aEvent.type, left, top, button, + clickCount, modifiers, false, + pressure, inputSource, + synthesized, 0, 0, 0, 0, isPrimary); + } + else { + utils.sendPointerEventToWindow("pointerdown", left, top, button, clickCount, modifiers, false, pressure, inputSource); + utils.sendPointerEventToWindow("pointerup", left, top, button, clickCount, modifiers, false, pressure, inputSource); + } + } + + return defaultPrevented; +} + +// Call synthesizeMouse with coordinates at the center of aTarget. +function synthesizeMouseAtCenter(aTarget, aEvent, aWindow) +{ + var rect = aTarget.getBoundingClientRect(); + return synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent, + aWindow); +} +function synthesizeTouchAtCenter(aTarget, aEvent, aWindow) +{ + var rect = aTarget.getBoundingClientRect(); + synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent, + aWindow); +} + +/** + * Synthesize a wheel event without flush layout at a particular point in + * aWindow. + * + * aEvent is an object which may contain the properties: + * shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ, + * deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, + * isNoLineOrPageDelta, isCustomizedByPrefs, expectedOverflowDeltaX, + * expectedOverflowDeltaY + * + * deltaMode must be defined, others are ok even if undefined. + * + * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value. The + * value is just checked as 0 or positive or negative. + * + * aWindow is optional, and defaults to the current window object. + */ +function synthesizeWheelAtPoint(aLeft, aTop, aEvent, aWindow = window) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return; + } + + var modifiers = _parseModifiers(aEvent, aWindow); + var options = 0; + if (aEvent.isNoLineOrPageDelta) { + options |= utils.WHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_DEVICE; + } + if (aEvent.isMomentum) { + options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM; + } + if (aEvent.isCustomizedByPrefs) { + options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS; + } + if (typeof aEvent.expectedOverflowDeltaX !== "undefined") { + if (aEvent.expectedOverflowDeltaX === 0) { + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO; + } else if (aEvent.expectedOverflowDeltaX > 0) { + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE; + } else { + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE; + } + } + if (typeof aEvent.expectedOverflowDeltaY !== "undefined") { + if (aEvent.expectedOverflowDeltaY === 0) { + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO; + } else if (aEvent.expectedOverflowDeltaY > 0) { + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE; + } else { + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE; + } + } + var isNoLineOrPageDelta = aEvent.isNoLineOrPageDelta; + + // Avoid the JS warnings "reference to undefined property" + if (!aEvent.deltaX) { + aEvent.deltaX = 0; + } + if (!aEvent.deltaY) { + aEvent.deltaY = 0; + } + if (!aEvent.deltaZ) { + aEvent.deltaZ = 0; + } + + var lineOrPageDeltaX = + aEvent.lineOrPageDeltaX != null ? aEvent.lineOrPageDeltaX : + aEvent.deltaX > 0 ? Math.floor(aEvent.deltaX) : + Math.ceil(aEvent.deltaX); + var lineOrPageDeltaY = + aEvent.lineOrPageDeltaY != null ? aEvent.lineOrPageDeltaY : + aEvent.deltaY > 0 ? Math.floor(aEvent.deltaY) : + Math.ceil(aEvent.deltaY); + utils.sendWheelEvent(aLeft, aTop, + aEvent.deltaX, aEvent.deltaY, aEvent.deltaZ, + aEvent.deltaMode, modifiers, + lineOrPageDeltaX, lineOrPageDeltaY, options); +} + +/** + * Synthesize a wheel event on a target. The actual client point is determined + * by taking the aTarget's client box and offseting it by aOffsetX and + * aOffsetY. + * + * aEvent is an object which may contain the properties: + * shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ, + * deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, + * isNoLineOrPageDelta, isCustomizedByPrefs, expectedOverflowDeltaX, + * expectedOverflowDeltaY + * + * deltaMode must be defined, others are ok even if undefined. + * + * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value. The + * value is just checked as 0 or positive or negative. + * + * aWindow is optional, and defaults to the current window object. + */ +function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) +{ + var rect = aTarget.getBoundingClientRect(); + synthesizeWheelAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, + aEvent, aWindow); +} + +/** + * This is a wrapper around synthesizeWheel that waits for the wheel event + * to be dispatched and for the subsequent layout/paints to be flushed. + * + * This requires including paint_listener.js. Tests must call + * DOMWindowUtils.restoreNormalRefresh() before finishing, if they use this + * function. + * + * If no callback is provided, the caller is assumed to have its own method of + * determining scroll completion and the refresh driver is not automatically + * restored. + */ +function sendWheelAndPaint(aTarget, aOffsetX, aOffsetY, aEvent, aCallback, aWindow = window) { + var utils = _getDOMWindowUtils(aWindow); + if (!utils) + return; + + if (utils.isMozAfterPaintPending) { + // If a paint is pending, then APZ may be waiting for a scroll acknowledgement + // from the content thread. If we send a wheel event now, it could be ignored + // by APZ (or its scroll offset could be overridden). To avoid problems we + // just wait for the paint to complete. + aWindow.waitForAllPaintsFlushed(function() { + sendWheelAndPaint(aTarget, aOffsetX, aOffsetY, aEvent, aCallback, aWindow); + }); + return; + } + + var onwheel = function() { + SpecialPowers.removeSystemEventListener(window, "wheel", onwheel); + + // Wait one frame since the wheel event has not caused a refresh observer + // to be added yet. + setTimeout(function() { + utils.advanceTimeAndRefresh(1000); + + if (!aCallback) { + utils.advanceTimeAndRefresh(0); + return; + } + + var waitForPaints = function () { + SpecialPowers.Services.obs.removeObserver(waitForPaints, "apz-repaints-flushed", false); + aWindow.waitForAllPaintsFlushed(function() { + utils.restoreNormalRefresh(); + aCallback(); + }); + } + + SpecialPowers.Services.obs.addObserver(waitForPaints, "apz-repaints-flushed", false); + if (!utils.flushApzRepaints(aWindow)) { + waitForPaints(); + } + }, 0); + }; + + // Listen for the system wheel event, because it happens after all of + // the other wheel events, including legacy events. + SpecialPowers.addSystemEventListener(aWindow, "wheel", onwheel); + synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow); +} + +function synthesizeNativeMouseMove(aTarget, aOffsetX, aOffsetY, aCallback, aWindow = window) { + var utils = _getDOMWindowUtils(aWindow); + if (!utils) + return; + + var rect = aTarget.getBoundingClientRect(); + var x = aOffsetX + window.mozInnerScreenX + rect.left; + var y = aOffsetY + window.mozInnerScreenY + rect.top; + var scale = utils.screenPixelsPerCSSPixel; + + var observer = { + observe: (subject, topic, data) => { + if (aCallback && topic == "mouseevent") { + aCallback(data); + } + } + }; + utils.sendNativeMouseMove(x * scale, y * scale, null, observer); +} + +function _computeKeyCodeFromChar(aChar) +{ + if (aChar.length != 1) { + return 0; + } + var KeyEvent = _EU_Ci.nsIDOMKeyEvent; + if (aChar >= 'a' && aChar <= 'z') { + return KeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0); + } + if (aChar >= 'A' && aChar <= 'Z') { + return KeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0); + } + if (aChar >= '0' && aChar <= '9') { + return KeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0); + } + // returns US keyboard layout's keycode + switch (aChar) { + case '~': + case '`': + return KeyEvent.DOM_VK_BACK_QUOTE; + case '!': + return KeyEvent.DOM_VK_1; + case '@': + return KeyEvent.DOM_VK_2; + case '#': + return KeyEvent.DOM_VK_3; + case '$': + return KeyEvent.DOM_VK_4; + case '%': + return KeyEvent.DOM_VK_5; + case '^': + return KeyEvent.DOM_VK_6; + case '&': + return KeyEvent.DOM_VK_7; + case '*': + return KeyEvent.DOM_VK_8; + case '(': + return KeyEvent.DOM_VK_9; + case ')': + return KeyEvent.DOM_VK_0; + case '-': + case '_': + return KeyEvent.DOM_VK_SUBTRACT; + case '+': + case '=': + return KeyEvent.DOM_VK_EQUALS; + case '{': + case '[': + return KeyEvent.DOM_VK_OPEN_BRACKET; + case '}': + case ']': + return KeyEvent.DOM_VK_CLOSE_BRACKET; + case '|': + case '\\': + return KeyEvent.DOM_VK_BACK_SLASH; + case ':': + case ';': + return KeyEvent.DOM_VK_SEMICOLON; + case '\'': + case '"': + return KeyEvent.DOM_VK_QUOTE; + case '<': + case ',': + return KeyEvent.DOM_VK_COMMA; + case '>': + case '.': + return KeyEvent.DOM_VK_PERIOD; + case '?': + case '/': + return KeyEvent.DOM_VK_SLASH; + case '\n': + return KeyEvent.DOM_VK_RETURN; + case ' ': + return KeyEvent.DOM_VK_SPACE; + default: + return 0; + } +} + +/** + * Synthesize a key event. It is targeted at whatever would be targeted by an + * actual keypress by the user, typically the focused element. + * + * aKey should be: + * - key value (recommended). If you specify a non-printable key name, + * append "KEY_" prefix. Otherwise, specifying a printable key, the + * key value should be specified. + * - keyCode name starting with "VK_" (e.g., VK_RETURN). This is available + * only for compatibility with legacy API. Don't use this with new tests. + * + * aEvent is an object which may contain the properties: + * - code: If you emulates a physical keyboard's key event, this should be + * specified. + * - repeat: If you emulates auto-repeat, you should set the count of repeat. + * This method will automatically synthesize keydown (and keypress). + * - location: If you want to specify this, you can specify this explicitly. + * However, if you don't specify this value, it will be computed + * from code value. + * - type: Basically, you shouldn't specify this. Then, this function will + * synthesize keydown (, keypress) and keyup. + * If keydown is specified, this only fires keydown (and keypress if + * it should be fired). + * If keyup is specified, this only fires keyup. + * - altKey, altGraphKey, ctrlKey, capsLockKey, fnKey, fnLockKey, numLockKey, + * metaKey, osKey, scrollLockKey, shiftKey, symbolKey, symbolLockKey: + * Basically, you shouldn't use these attributes. nsITextInputProcessor + * manages modifier key state when you synthesize modifier key events. + * However, if some of these attributes are true, this function activates + * the modifiers only during dispatching the key events. + * Note that if some of these values are false, they are ignored (i.e., + * not inactivated with this function). + * - keyCode: Must be 0 - 255 (0xFF). If this is specified explicitly, + * .keyCode value is initialized with this value. + * + * aWindow is optional, and defaults to the current window object. + */ +function synthesizeKey(aKey, aEvent, aWindow = window) +{ + var TIP = _getTIP(aWindow); + if (!TIP) { + return; + } + var KeyboardEvent = _getKeyboardEvent(aWindow); + var modifiers = _emulateToActivateModifiers(TIP, aEvent, aWindow); + var keyEventDict = _createKeyboardEventDictionary(aKey, aEvent, aWindow); + var keyEvent = new KeyboardEvent("", keyEventDict.dictionary); + var dispatchKeydown = + !("type" in aEvent) || aEvent.type === "keydown" || !aEvent.type; + var dispatchKeyup = + !("type" in aEvent) || aEvent.type === "keyup" || !aEvent.type; + + try { + if (dispatchKeydown) { + TIP.keydown(keyEvent, keyEventDict.flags); + if ("repeat" in aEvent && aEvent.repeat > 1) { + keyEventDict.dictionary.repeat = true; + var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary); + for (var i = 1; i < aEvent.repeat; i++) { + TIP.keydown(repeatedKeyEvent, keyEventDict.flags); + } + } + } + if (dispatchKeyup) { + TIP.keyup(keyEvent, keyEventDict.flags); + } + } finally { + _emulateToInactivateModifiers(TIP, modifiers, aWindow); + } +} + +function _parseNativeModifiers(aModifiers, aWindow = window) +{ + var navigator = _getNavigator(aWindow); + var modifiers; + if (aModifiers.capsLockKey) { + modifiers |= 0x00000001; + } + if (aModifiers.numLockKey) { + modifiers |= 0x00000002; + } + if (aModifiers.shiftKey) { + modifiers |= 0x00000100; + } + if (aModifiers.shiftRightKey) { + modifiers |= 0x00000200; + } + if (aModifiers.ctrlKey) { + modifiers |= 0x00000400; + } + if (aModifiers.ctrlRightKey) { + modifiers |= 0x00000800; + } + if (aModifiers.altKey) { + modifiers |= 0x00001000; + } + if (aModifiers.altRightKey) { + modifiers |= 0x00002000; + } + if (aModifiers.metaKey) { + modifiers |= 0x00004000; + } + if (aModifiers.metaRightKey) { + modifiers |= 0x00008000; + } + if (aModifiers.helpKey) { + modifiers |= 0x00010000; + } + if (aModifiers.fnKey) { + modifiers |= 0x00100000; + } + if (aModifiers.numericKeyPadKey) { + modifiers |= 0x01000000; + } + + if (aModifiers.accelKey) { + modifiers |= _EU_isMac(aWindow) ? 0x00004000 : 0x00000400; + } + if (aModifiers.accelRightKey) { + modifiers |= _EU_isMac(aWindow) ? 0x00008000 : 0x00000800; + } + if (aModifiers.altGrKey) { + modifiers |= _EU_isWin(aWindow) ? 0x00002800 : 0x00001000; + } + return modifiers; +} + +// Mac: Any unused number is okay for adding new keyboard layout. +// When you add new keyboard layout here, you need to modify +// TISInputSourceWrapper::InitByLayoutID(). +// Win: These constants can be found by inspecting registry keys under +// HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts + +const KEYBOARD_LAYOUT_ARABIC = + { name: "Arabic", Mac: 6, Win: 0x00000401 }; +const KEYBOARD_LAYOUT_ARABIC_PC = + { name: "Arabic - PC", Mac: 7, Win: null }; +const KEYBOARD_LAYOUT_BRAZILIAN_ABNT = + { name: "Brazilian ABNT", Mac: null, Win: 0x00000416 }; +const KEYBOARD_LAYOUT_DVORAK_QWERTY = + { name: "Dvorak-QWERTY", Mac: 4, Win: null }; +const KEYBOARD_LAYOUT_EN_US = + { name: "US", Mac: 0, Win: 0x00000409 }; +const KEYBOARD_LAYOUT_FRENCH = + { name: "French", Mac: 8, Win: 0x0000040C }; +const KEYBOARD_LAYOUT_GREEK = + { name: "Greek", Mac: 1, Win: 0x00000408 }; +const KEYBOARD_LAYOUT_GERMAN = + { name: "German", Mac: 2, Win: 0x00000407 }; +const KEYBOARD_LAYOUT_HEBREW = + { name: "Hebrew", Mac: 9, Win: 0x0000040D }; +const KEYBOARD_LAYOUT_JAPANESE = + { name: "Japanese", Mac: null, Win: 0x00000411 }; +const KEYBOARD_LAYOUT_KHMER = + { name: "Khmer", Mac: null, Win: 0x00000453 }; // available on Win7 or later. +const KEYBOARD_LAYOUT_LITHUANIAN = + { name: "Lithuanian", Mac: 10, Win: 0x00010427 }; +const KEYBOARD_LAYOUT_NORWEGIAN = + { name: "Norwegian", Mac: 11, Win: 0x00000414 }; +const KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC = + { name: "Russian - Mnemonic", Mac: null, Win: 0x00020419 }; // available on Win8 or later. +const KEYBOARD_LAYOUT_SPANISH = + { name: "Spanish", Mac: 12, Win: 0x0000040A }; +const KEYBOARD_LAYOUT_SWEDISH = + { name: "Swedish", Mac: 3, Win: 0x0000041D }; +const KEYBOARD_LAYOUT_THAI = + { name: "Thai", Mac: 5, Win: 0x0002041E }; + +/** + * synthesizeNativeKey() dispatches native key event on active window. + * This is implemented only on Windows and Mac. Note that this function + * dispatches the key event asynchronously and returns immediately. If a + * callback function is provided, the callback will be called upon + * completion of the key dispatch. + * + * @param aKeyboardLayout One of KEYBOARD_LAYOUT_* defined above. + * @param aNativeKeyCode A native keycode value defined in + * NativeKeyCodes.js. + * @param aModifiers Modifier keys. If no modifire key is pressed, + * this must be {}. Otherwise, one or more items + * referred in _parseNativeModifiers() must be + * true. + * @param aChars Specify characters which should be generated + * by the key event. + * @param aUnmodifiedChars Specify characters of unmodified (except Shift) + * aChar value. + * @param aCallback If provided, this callback will be invoked + * once the native keys have been processed + * by Gecko. Will never be called if this + * function returns false. + * @return True if this function succeed dispatching + * native key event. Otherwise, false. + */ + +function synthesizeNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers, + aChars, aUnmodifiedChars, aCallback, aWindow = window) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return false; + } + var navigator = _getNavigator(aWindow); + var nativeKeyboardLayout = null; + if (_EU_isMac(aWindow)) { + nativeKeyboardLayout = aKeyboardLayout.Mac; + } else if (_EU_isWin(aWindow)) { + nativeKeyboardLayout = aKeyboardLayout.Win; + } + if (nativeKeyboardLayout === null) { + return false; + } + + var observer = { + observe: function(aSubject, aTopic, aData) { + if (aCallback && aTopic == "keyevent") { + aCallback(aData); + } + } + }; + utils.sendNativeKeyEvent(nativeKeyboardLayout, aNativeKeyCode, + _parseNativeModifiers(aModifiers, aWindow), + aChars, aUnmodifiedChars, observer); + return true; +} + +var _gSeenEvent = false; + +/** + * Indicate that an event with an original target of aExpectedTarget and + * a type of aExpectedEvent is expected to be fired, or not expected to + * be fired. + */ +function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName) +{ + if (!aExpectedTarget || !aExpectedEvent) + return null; + + _gSeenEvent = false; + + var type = (aExpectedEvent.charAt(0) == "!") ? + aExpectedEvent.substring(1) : aExpectedEvent; + var eventHandler = function(event) { + var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget && + event.type == type); + is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : "")); + _gSeenEvent = true; + }; + + aExpectedTarget.addEventListener(type, eventHandler, false); + return eventHandler; +} + +/** + * Check if the event was fired or not. The event handler aEventHandler + * will be removed. + */ +function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName) +{ + if (aEventHandler) { + var expectEvent = (aExpectedEvent.charAt(0) != "!"); + var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1); + aExpectedTarget.removeEventListener(type, aEventHandler, false); + var desc = type + " event"; + if (!expectEvent) + desc += " not"; + is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired"); + } + + _gSeenEvent = false; +} + +/** + * Similar to synthesizeMouse except that a test is performed to see if an + * event is fired at the right target as a result. + * + * aExpectedTarget - the expected originalTarget of the event. + * aExpectedEvent - the expected type of the event, such as 'select'. + * aTestName - the test name when outputing results + * + * To test that an event is not fired, use an expected type preceded by an + * exclamation mark, such as '!select'. This might be used to test that a + * click on a disabled element doesn't fire certain events for instance. + * + * aWindow is optional, and defaults to the current window object. + */ +function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent, + aExpectedTarget, aExpectedEvent, aTestName, + aWindow) +{ + var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); + synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow); + _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); +} + +/** + * Similar to synthesizeKey except that a test is performed to see if an + * event is fired at the right target as a result. + * + * aExpectedTarget - the expected originalTarget of the event. + * aExpectedEvent - the expected type of the event, such as 'select'. + * aTestName - the test name when outputing results + * + * To test that an event is not fired, use an expected type preceded by an + * exclamation mark, such as '!select'. + * + * aWindow is optional, and defaults to the current window object. + */ +function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent, + aTestName, aWindow) +{ + var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); + synthesizeKey(key, aEvent, aWindow); + _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); +} + +function disableNonTestMouseEvents(aDisable) +{ + var domutils = _getDOMWindowUtils(); + domutils.disableNonTestMouseEvents(aDisable); +} + +function _getDOMWindowUtils(aWindow = window) +{ + // Leave this here as something, somewhere, passes a falsy argument + // to this, causing the |window| default argument not to get picked up. + if (!aWindow) { + aWindow = window; + } + + // we need parent.SpecialPowers for: + // layout/base/tests/test_reftests_with_caret.html + // chrome: toolkit/content/tests/chrome/test_findbar.xul + // chrome: toolkit/content/tests/chrome/test_popup_anchor.xul + if ("SpecialPowers" in window && window.SpecialPowers != undefined) { + return SpecialPowers.getDOMWindowUtils(aWindow); + } + if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) { + return parent.SpecialPowers.getDOMWindowUtils(aWindow); + } + + // TODO: this is assuming we are in chrome space + return aWindow + .QueryInterface(_EU_Ci.nsIInterfaceRequestor) + .getInterface(_EU_Ci.nsIDOMWindowUtils); +} + +function _defineConstant(name, value) { + Object.defineProperty(this, name, { + value: value, + enumerable: true, + writable: false + }); +} + +const COMPOSITION_ATTR_RAW_CLAUSE = + _EU_Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE; +_defineConstant("COMPOSITION_ATTR_RAW_CLAUSE", COMPOSITION_ATTR_RAW_CLAUSE); +const COMPOSITION_ATTR_SELECTED_RAW_CLAUSE = + _EU_Ci.nsITextInputProcessor.ATTR_SELECTED_RAW_CLAUSE; +_defineConstant("COMPOSITION_ATTR_SELECTED_RAW_CLAUSE", COMPOSITION_ATTR_SELECTED_RAW_CLAUSE); +const COMPOSITION_ATTR_CONVERTED_CLAUSE = + _EU_Ci.nsITextInputProcessor.ATTR_CONVERTED_CLAUSE; +_defineConstant("COMPOSITION_ATTR_CONVERTED_CLAUSE", COMPOSITION_ATTR_CONVERTED_CLAUSE); +const COMPOSITION_ATTR_SELECTED_CLAUSE = + _EU_Ci.nsITextInputProcessor.ATTR_SELECTED_CLAUSE; +_defineConstant("COMPOSITION_ATTR_SELECTED_CLAUSE", COMPOSITION_ATTR_SELECTED_CLAUSE); + +var TIPMap = new WeakMap(); + +function _getTIP(aWindow, aCallback) +{ + if (!aWindow) { + aWindow = window; + } + var tip; + if (TIPMap.has(aWindow)) { + tip = TIPMap.get(aWindow); + } else { + tip = + _EU_Cc["@mozilla.org/text-input-processor;1"]. + createInstance(_EU_Ci.nsITextInputProcessor); + TIPMap.set(aWindow, tip); + } + if (!tip.beginInputTransactionForTests(aWindow, aCallback)) { + tip = null; + TIPMap.delete(aWindow); + } + return tip; +} + +function _getKeyboardEvent(aWindow = window) +{ + if (typeof KeyboardEvent != "undefined") { + try { + // See if the object can be instantiated; sometimes this yields + // 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'. + new KeyboardEvent("", {}); + return KeyboardEvent; + } catch (ex) {} + } + if (typeof content != "undefined" && ("KeyboardEvent" in content)) { + return content.KeyboardEvent; + } + return aWindow.KeyboardEvent; +} + +function _getNavigator(aWindow = window) +{ + if (typeof navigator != "undefined") { + return navigator; + } + return aWindow.navigator; +} + +function _guessKeyNameFromKeyCode(aKeyCode, aWindow = window) +{ + var KeyboardEvent = _getKeyboardEvent(aWindow); + switch (aKeyCode) { + case KeyboardEvent.DOM_VK_CANCEL: + return "Cancel"; + case KeyboardEvent.DOM_VK_HELP: + return "Help"; + case KeyboardEvent.DOM_VK_BACK_SPACE: + return "Backspace"; + case KeyboardEvent.DOM_VK_TAB: + return "Tab"; + case KeyboardEvent.DOM_VK_CLEAR: + return "Clear"; + case KeyboardEvent.DOM_VK_RETURN: + return "Enter"; + case KeyboardEvent.DOM_VK_SHIFT: + return "Shift"; + case KeyboardEvent.DOM_VK_CONTROL: + return "Control"; + case KeyboardEvent.DOM_VK_ALT: + return "Alt"; + case KeyboardEvent.DOM_VK_PAUSE: + return "Pause"; + case KeyboardEvent.DOM_VK_EISU: + return "Eisu"; + case KeyboardEvent.DOM_VK_ESCAPE: + return "Escape"; + case KeyboardEvent.DOM_VK_CONVERT: + return "Convert"; + case KeyboardEvent.DOM_VK_NONCONVERT: + return "NonConvert"; + case KeyboardEvent.DOM_VK_ACCEPT: + return "Accept"; + case KeyboardEvent.DOM_VK_MODECHANGE: + return "ModeChange"; + case KeyboardEvent.DOM_VK_PAGE_UP: + return "PageUp"; + case KeyboardEvent.DOM_VK_PAGE_DOWN: + return "PageDown"; + case KeyboardEvent.DOM_VK_END: + return "End"; + case KeyboardEvent.DOM_VK_HOME: + return "Home"; + case KeyboardEvent.DOM_VK_LEFT: + return "ArrowLeft"; + case KeyboardEvent.DOM_VK_UP: + return "ArrowUp"; + case KeyboardEvent.DOM_VK_RIGHT: + return "ArrowRight"; + case KeyboardEvent.DOM_VK_DOWN: + return "ArrowDown"; + case KeyboardEvent.DOM_VK_SELECT: + return "Select"; + case KeyboardEvent.DOM_VK_PRINT: + return "Print"; + case KeyboardEvent.DOM_VK_EXECUTE: + return "Execute"; + case KeyboardEvent.DOM_VK_PRINTSCREEN: + return "PrintScreen"; + case KeyboardEvent.DOM_VK_INSERT: + return "Insert"; + case KeyboardEvent.DOM_VK_DELETE: + return "Delete"; + case KeyboardEvent.DOM_VK_WIN: + return "OS"; + case KeyboardEvent.DOM_VK_CONTEXT_MENU: + return "ContextMenu"; + case KeyboardEvent.DOM_VK_SLEEP: + return "Standby"; + case KeyboardEvent.DOM_VK_F1: + return "F1"; + case KeyboardEvent.DOM_VK_F2: + return "F2"; + case KeyboardEvent.DOM_VK_F3: + return "F3"; + case KeyboardEvent.DOM_VK_F4: + return "F4"; + case KeyboardEvent.DOM_VK_F5: + return "F5"; + case KeyboardEvent.DOM_VK_F6: + return "F6"; + case KeyboardEvent.DOM_VK_F7: + return "F7"; + case KeyboardEvent.DOM_VK_F8: + return "F8"; + case KeyboardEvent.DOM_VK_F9: + return "F9"; + case KeyboardEvent.DOM_VK_F10: + return "F10"; + case KeyboardEvent.DOM_VK_F11: + return "F11"; + case KeyboardEvent.DOM_VK_F12: + return "F12"; + case KeyboardEvent.DOM_VK_F13: + return "F13"; + case KeyboardEvent.DOM_VK_F14: + return "F14"; + case KeyboardEvent.DOM_VK_F15: + return "F15"; + case KeyboardEvent.DOM_VK_F16: + return "F16"; + case KeyboardEvent.DOM_VK_F17: + return "F17"; + case KeyboardEvent.DOM_VK_F18: + return "F18"; + case KeyboardEvent.DOM_VK_F19: + return "F19"; + case KeyboardEvent.DOM_VK_F20: + return "F20"; + case KeyboardEvent.DOM_VK_F21: + return "F21"; + case KeyboardEvent.DOM_VK_F22: + return "F22"; + case KeyboardEvent.DOM_VK_F23: + return "F23"; + case KeyboardEvent.DOM_VK_F24: + return "F24"; + case KeyboardEvent.DOM_VK_NUM_LOCK: + return "NumLock"; + case KeyboardEvent.DOM_VK_SCROLL_LOCK: + return "ScrollLock"; + case KeyboardEvent.DOM_VK_VOLUME_MUTE: + return "AudioVolumeMute"; + case KeyboardEvent.DOM_VK_VOLUME_DOWN: + return "AudioVolumeDown"; + case KeyboardEvent.DOM_VK_VOLUME_UP: + return "AudioVolumeUp"; + case KeyboardEvent.DOM_VK_META: + return "Meta"; + case KeyboardEvent.DOM_VK_ALTGR: + return "AltGraph"; + case KeyboardEvent.DOM_VK_ATTN: + return "Attn"; + case KeyboardEvent.DOM_VK_CRSEL: + return "CrSel"; + case KeyboardEvent.DOM_VK_EXSEL: + return "ExSel"; + case KeyboardEvent.DOM_VK_EREOF: + return "EraseEof"; + case KeyboardEvent.DOM_VK_PLAY: + return "Play"; + default: + return "Unidentified"; + } +} + +function _createKeyboardEventDictionary(aKey, aKeyEvent, aWindow = window) { + var result = { dictionary: null, flags: 0 }; + var keyCodeIsDefined = "keyCode" in aKeyEvent; + var keyCode = + (keyCodeIsDefined && aKeyEvent.keyCode >= 0 && aKeyEvent.keyCode <= 255) ? + aKeyEvent.keyCode : 0; + var keyName = "Unidentified"; + if (aKey.indexOf("KEY_") == 0) { + keyName = aKey.substr("KEY_".length); + result.flags |= _EU_Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY; + } else if (aKey.indexOf("VK_") == 0) { + keyCode = _EU_Ci.nsIDOMKeyEvent["DOM_" + aKey]; + if (!keyCode) { + throw "Unknown key: " + aKey; + } + keyName = _guessKeyNameFromKeyCode(keyCode, aWindow); + result.flags |= _EU_Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY; + } else if (aKey != "") { + keyName = aKey; + if (!keyCodeIsDefined) { + keyCode = _computeKeyCodeFromChar(aKey.charAt(0)); + } + if (!keyCode) { + result.flags |= _EU_Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO; + } + result.flags |= _EU_Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY; + } + var locationIsDefined = "location" in aKeyEvent; + if (locationIsDefined && aKeyEvent.location === 0) { + result.flags |= _EU_Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD; + } + result.dictionary = { + key: keyName, + code: "code" in aKeyEvent ? aKeyEvent.code : "", + location: locationIsDefined ? aKeyEvent.location : 0, + repeat: "repeat" in aKeyEvent ? aKeyEvent.repeat === true : false, + keyCode: keyCode, + }; + return result; +} + +function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window) +{ + if (!aKeyEvent) { + return null; + } + var KeyboardEvent = _getKeyboardEvent(aWindow); + var navigator = _getNavigator(aWindow); + + var modifiers = { + normal: [ + { key: "Alt", attr: "altKey" }, + { key: "AltGraph", attr: "altGraphKey" }, + { key: "Control", attr: "ctrlKey" }, + { key: "Fn", attr: "fnKey" }, + { key: "Meta", attr: "metaKey" }, + { key: "OS", attr: "osKey" }, + { key: "Shift", attr: "shiftKey" }, + { key: "Symbol", attr: "symbolKey" }, + { key: _EU_isMac(aWindow) ? "Meta" : "Control", + attr: "accelKey" }, + ], + lockable: [ + { key: "CapsLock", attr: "capsLockKey" }, + { key: "FnLock", attr: "fnLockKey" }, + { key: "NumLock", attr: "numLockKey" }, + { key: "ScrollLock", attr: "scrollLockKey" }, + { key: "SymbolLock", attr: "symbolLockKey" }, + ] + } + + for (var i = 0; i < modifiers.normal.length; i++) { + if (!aKeyEvent[modifiers.normal[i].attr]) { + continue; + } + if (aTIP.getModifierState(modifiers.normal[i].key)) { + continue; // already activated. + } + var event = new KeyboardEvent("", { key: modifiers.normal[i].key }); + aTIP.keydown(event, + aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + modifiers.normal[i].activated = true; + } + for (var i = 0; i < modifiers.lockable.length; i++) { + if (!aKeyEvent[modifiers.lockable[i].attr]) { + continue; + } + if (aTIP.getModifierState(modifiers.lockable[i].key)) { + continue; // already activated. + } + var event = new KeyboardEvent("", { key: modifiers.lockable[i].key }); + aTIP.keydown(event, + aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + aTIP.keyup(event, + aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + modifiers.lockable[i].activated = true; + } + return modifiers; +} + +function _emulateToInactivateModifiers(aTIP, aModifiers, aWindow = window) +{ + if (!aModifiers) { + return; + } + var KeyboardEvent = _getKeyboardEvent(aWindow); + for (var i = 0; i < aModifiers.normal.length; i++) { + if (!aModifiers.normal[i].activated) { + continue; + } + var event = new KeyboardEvent("", { key: aModifiers.normal[i].key }); + aTIP.keyup(event, + aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + } + for (var i = 0; i < aModifiers.lockable.length; i++) { + if (!aModifiers.lockable[i].activated) { + continue; + } + if (!aTIP.getModifierState(aModifiers.lockable[i].key)) { + continue; // who already inactivated this? + } + var event = new KeyboardEvent("", { key: aModifiers.lockable[i].key }); + aTIP.keydown(event, + aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + aTIP.keyup(event, + aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + } +} + +/** + * Synthesize a composition event. + * + * @param aEvent The composition event information. This must + * have |type| member. The value must be + * "compositionstart", "compositionend", + * "compositioncommitasis" or "compositioncommit". + * And also this may have |data| and |locale| which + * would be used for the value of each property of + * the composition event. Note that the |data| is + * ignored if the event type is "compositionstart" + * or "compositioncommitasis". + * If |key| is specified, the key event may be + * dispatched. This can emulates changing + * composition state caused by key operation. + * Its key value should start with "KEY_" if the + * value is non-printable key name defined in D3E. + * @param aWindow Optional (If null, current |window| will be used) + * @param aCallback Optional (If non-null, use the callback for + * receiving notifications to IME) + */ +function synthesizeComposition(aEvent, aWindow = window, aCallback) +{ + var TIP = _getTIP(aWindow, aCallback); + if (!TIP) { + return false; + } + var KeyboardEvent = _getKeyboardEvent(aWindow); + var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow); + var ret = false; + var keyEventDict = + "key" in aEvent ? + _createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow) : + { dictionary: null, flags: 0 }; + var keyEvent = + "key" in aEvent ? + new KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "", + keyEventDict.dictionary) : + null; + try { + switch (aEvent.type) { + case "compositionstart": + ret = TIP.startComposition(keyEvent, keyEventDict.flags); + break; + case "compositioncommitasis": + ret = TIP.commitComposition(keyEvent, keyEventDict.flags); + break; + case "compositioncommit": + ret = TIP.commitCompositionWith(aEvent.data, keyEvent, + keyEventDict.flags); + break; + } + } finally { + _emulateToInactivateModifiers(TIP, modifiers, aWindow); + } +} +/** + * Synthesize a compositionchange event which causes a DOM text event and + * compositionupdate event if it's necessary. + * + * @param aEvent The compositionchange event's information, this has + * |composition| and |caret| members. |composition| has + * |string| and |clauses| members. |clauses| must be array + * object. Each object has |length| and |attr|. And |caret| + * has |start| and |length|. See the following tree image. + * + * aEvent + * +-- composition + * | +-- string + * | +-- clauses[] + * | +-- length + * | +-- attr + * +-- caret + * | +-- start + * | +-- length + * +-- key + * + * Set the composition string to |composition.string|. Set its + * clauses information to the |clauses| array. + * + * When it's composing, set the each clauses' length to the + * |composition.clauses[n].length|. The sum of the all length + * values must be same as the length of |composition.string|. + * Set nsICompositionStringSynthesizer.ATTR_* to the + * |composition.clauses[n].attr|. + * + * When it's not composing, set 0 to the + * |composition.clauses[0].length| and + * |composition.clauses[0].attr|. + * + * Set caret position to the |caret.start|. It's offset from + * the start of the composition string. Set caret length to + * |caret.length|. If it's larger than 0, it should be wide + * caret. However, current nsEditor doesn't support wide + * caret, therefore, you should always set 0 now. + * + * If |key| is specified, the key event may be dispatched. + * This can emulates changing composition state caused by key + * operation. Its key value should start with "KEY_" if the + * value is non-printable key name defined in D3E. + * + * @param aWindow Optional (If null, current |window| will be used) + * @param aCallback Optional (If non-null, use the callback for receiving + * notifications to IME) + */ +function synthesizeCompositionChange(aEvent, aWindow = window, aCallback) +{ + var TIP = _getTIP(aWindow, aCallback); + if (!TIP) { + return; + } + var KeyboardEvent = _getKeyboardEvent(aWindow); + + if (!aEvent.composition || !aEvent.composition.clauses || + !aEvent.composition.clauses[0]) { + return; + } + + TIP.setPendingCompositionString(aEvent.composition.string); + if (aEvent.composition.clauses[0].length) { + for (var i = 0; i < aEvent.composition.clauses.length; i++) { + switch (aEvent.composition.clauses[i].attr) { + case TIP.ATTR_RAW_CLAUSE: + case TIP.ATTR_SELECTED_RAW_CLAUSE: + case TIP.ATTR_CONVERTED_CLAUSE: + case TIP.ATTR_SELECTED_CLAUSE: + TIP.appendClauseToPendingComposition( + aEvent.composition.clauses[i].length, + aEvent.composition.clauses[i].attr); + break; + case 0: + // Ignore dummy clause for the argument. + break; + default: + throw new Error("invalid clause attribute specified"); + break; + } + } + } + + if (aEvent.caret) { + TIP.setCaretInPendingComposition(aEvent.caret.start); + } + + var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow); + try { + var keyEventDict = + "key" in aEvent ? + _createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow) : + { dictionary: null, flags: 0 }; + var keyEvent = + "key" in aEvent ? + new KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "", + keyEventDict.dictionary) : + null; + TIP.flushPendingComposition(keyEvent, keyEventDict.flags); + } finally { + _emulateToInactivateModifiers(TIP, modifiers, aWindow); + } +} + +// Must be synchronized with nsIDOMWindowUtils. +const QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK = 0x0000; +const QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK = 0x0001; + +const QUERY_CONTENT_FLAG_SELECTION_NORMAL = 0x0000; +const QUERY_CONTENT_FLAG_SELECTION_SPELLCHECK = 0x0002; +const QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT = 0x0004; +const QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT = 0x0008; +const QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT = 0x0010; +const QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT = 0x0020; +const QUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITY = 0x0040; +const QUERY_CONTENT_FLAG_SELECTION_FIND = 0x0080; +const QUERY_CONTENT_FLAG_SELECTION_URLSECONDARY = 0x0100; +const QUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUT = 0x0200; + +const QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT = 0x0400; + +const SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK = 0x0000; +const SELECTION_SET_FLAG_USE_XP_LINE_BREAK = 0x0001; +const SELECTION_SET_FLAG_REVERSE = 0x0002; + +/** + * Synthesize a query text content event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of getting text. If the length is too long, + * the extra length is ignored. + * @param aIsRelative Optional (If true, aOffset is relative to start of + * composition if there is, or start of selection.) + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryTextContent(aOffset, aLength, aIsRelative, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nullptr; + } + var flags = QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK; + if (aIsRelative === true) { + flags |= QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT; + } + return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT, + aOffset, aLength, 0, 0, flags); +} + +/** + * Synthesize a query selected text event. + * + * @param aSelectionType Optional, one of QUERY_CONTENT_FLAG_SELECTION_*. + * If null, QUERY_CONTENT_FLAG_SELECTION_NORMAL will + * be used. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQuerySelectedText(aSelectionType, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return null; + } + + var flags = QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK; + if (aSelectionType) { + flags |= aSelectionType; + } + + return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0, + flags); +} + +/** + * Synthesize a query caret rect event. + * + * @param aOffset The caret offset. 0 means left side of the first character + * in the selection root. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryCaretRect(aOffset, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return null; + } + return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, + aOffset, 0, 0, 0, + QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK); +} + +/** + * Synthesize a selection set event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of the text. If the length is too long, + * the extra length is ignored. + * @param aReverse If true, the selection is from |aOffset + aLength| to + * |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|. + * @param aWindow Optional (If null, current |window| will be used) + * @return True, if succeeded. Otherwise false. + */ +function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return false; + } + var flags = aReverse ? SELECTION_SET_FLAG_REVERSE : 0; + return utils.sendSelectionSetEvent(aOffset, aLength, flags); +} + +/* + * Synthesize a native mouse click event at a particular point in screen. + * This function should be used only for testing native event loop. + * Use synthesizeMouse instead for most case. + * + * This works only on OS X. Throws an error on other OS. Also throws an error + * when the library or any of function are not found, or something goes wrong + * in native functions. + */ +function synthesizeNativeOSXClick(x, y) +{ + var { ctypes } = _EU_Cu.import("resource://gre/modules/ctypes.jsm", {}); + + // Library + var CoreFoundation = ctypes.open("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"); + var CoreGraphics = ctypes.open("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics"); + + // Contants + var kCGEventLeftMouseDown = 1; + var kCGEventLeftMouseUp = 2; + var kCGEventSourceStateHIDSystemState = 1; + var kCGHIDEventTap = 0; + var kCGMouseButtonLeft = 0; + var kCGMouseEventClickState = 1; + + // Types + var CGEventField = ctypes.uint32_t; + var CGEventRef = ctypes.voidptr_t; + var CGEventSourceRef = ctypes.voidptr_t; + var CGEventSourceStateID = ctypes.uint32_t; + var CGEventTapLocation = ctypes.uint32_t; + var CGEventType = ctypes.uint32_t; + var CGFloat = ctypes.voidptr_t.size == 4 ? ctypes.float : ctypes.double; + var CGMouseButton = ctypes.uint32_t; + + var CGPoint = new ctypes.StructType( + "CGPoint", + [ { "x" : CGFloat }, + { "y" : CGFloat } ]); + + // Functions + var CGEventSourceCreate = CoreGraphics.declare( + "CGEventSourceCreate", + ctypes.default_abi, + CGEventSourceRef, CGEventSourceStateID); + var CGEventCreateMouseEvent = CoreGraphics.declare( + "CGEventCreateMouseEvent", + ctypes.default_abi, + CGEventRef, + CGEventSourceRef, CGEventType, CGPoint, CGMouseButton); + var CGEventSetIntegerValueField = CoreGraphics.declare( + "CGEventSetIntegerValueField", + ctypes.default_abi, + ctypes.void_t, + CGEventRef, CGEventField, ctypes.int64_t); + var CGEventPost = CoreGraphics.declare( + "CGEventPost", + ctypes.default_abi, + ctypes.void_t, + CGEventTapLocation, CGEventRef); + var CFRelease = CoreFoundation.declare( + "CFRelease", + ctypes.default_abi, + ctypes.void_t, + CGEventRef); + + var source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + if (!source) { + throw new Error("CGEventSourceCreate returns null"); + } + + var loc = new CGPoint({ x: x, y: y }); + var event = CGEventCreateMouseEvent(source, kCGEventLeftMouseDown, loc, + kCGMouseButtonLeft); + if (!event) { + throw new Error("CGEventCreateMouseEvent returns null"); + } + CGEventSetIntegerValueField(event, kCGMouseEventClickState, + new ctypes.Int64(1)); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + + event = CGEventCreateMouseEvent(source, kCGEventLeftMouseUp, loc, + kCGMouseButtonLeft); + if (!event) { + throw new Error("CGEventCreateMouseEvent returns null"); + } + CGEventSetIntegerValueField(event, kCGMouseEventClickState, + new ctypes.Int64(1)); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + + CFRelease(source); + + CoreFoundation.close(); + CoreGraphics.close(); +} + +/** + * Emulate a dragstart event. + * element - element to fire the dragstart event on + * expectedDragData - the data you expect the data transfer to contain afterwards + * This data is in the format: + * [ [ {type: value, data: value, test: function}, ... ], ... ] + * can be null + * aWindow - optional; defaults to the current window object. + * x - optional; initial x coordinate + * y - optional; initial y coordinate + * Returns null if data matches. + * Returns the event.dataTransfer if data does not match + * + * eqTest is an optional function if comparison can't be done with x == y; + * function (actualData, expectedData) {return boolean} + * @param actualData from dataTransfer + * @param expectedData from expectedDragData + * see bug 462172 for example of use + * + */ +function synthesizeDragStart(element, expectedDragData, aWindow, x, y) +{ + if (!aWindow) + aWindow = window; + x = x || 2; + y = y || 2; + const step = 9; + + var result = "trapDrag was not called"; + var trapDrag = function(event) { + try { + // We must wrap only in plain mochitests, not chrome + var c = Object.getOwnPropertyDescriptor(window, 'Components'); + var dataTransfer = c.value && !c.writable + ? event.dataTransfer : SpecialPowers.wrap(event.dataTransfer); + result = null; + if (!dataTransfer) + throw "no dataTransfer"; + if (expectedDragData == null || + dataTransfer.mozItemCount != expectedDragData.length) + throw dataTransfer; + for (var i = 0; i < dataTransfer.mozItemCount; i++) { + var dtTypes = dataTransfer.mozTypesAt(i); + if (dtTypes.length != expectedDragData[i].length) + throw dataTransfer; + for (var j = 0; j < dtTypes.length; j++) { + if (dtTypes[j] != expectedDragData[i][j].type) + throw dataTransfer; + var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i); + if (expectedDragData[i][j].eqTest) { + if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data)) + throw dataTransfer; + } + else if (expectedDragData[i][j].data != dtData) + throw dataTransfer; + } + } + } catch(ex) { + result = ex; + } + event.preventDefault(); + event.stopPropagation(); + } + aWindow.addEventListener("dragstart", trapDrag, false); + synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow); + x += step; y += step; + synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow); + x += step; y += step; + synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow); + aWindow.removeEventListener("dragstart", trapDrag, false); + synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow); + return result; +} + +/** + * Synthesize a query text rect event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of the text. If the length is too long, + * the extra length is ignored. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryTextRect(aOffset, aLength, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nullptr; + } + return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT, + aOffset, aLength, 0, 0, + QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK); +} + +/** + * Synthesize a query text rect array event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of the text. If the length is too long, + * the extra length is ignored. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryTextRectArray(aOffset, aLength, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nullptr; + } + return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT_ARRAY, + aOffset, aLength, 0, 0, + QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK); +} + +/** + * Synthesize a query editor rect event. + * + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryEditorRect(aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nullptr; + } + return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0, + QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK); +} + +/** + * Synthesize a character at point event. + * + * @param aX, aY The offset in the client area of the DOM window. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeCharAtPoint(aX, aY, aWindow) +{ + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nullptr; + } + return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT, + 0, 0, aX, aY, + QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK); +} + +/** + * INTERNAL USE ONLY + * Create an event object to pass to sendDragEvent. + * + * @param aType The string represents drag event type. + * @param aDestElement The element to fire the drag event, used to calculate + * screenX/Y and clientX/Y. + * @param aDestWindow Optional; Defaults to the current window object. + * @param aDataTransfer dataTransfer for current drag session. + * @param aDragEvent The object contains properties to override the event + * object + * @return An object to pass to sendDragEvent. + */ +function createDragEventObject(aType, aDestElement, aDestWindow, aDataTransfer, + aDragEvent) +{ + var destRect = aDestElement.getBoundingClientRect(); + var destClientX = destRect.left + destRect.width / 2; + var destClientY = destRect.top + destRect.height / 2; + var destScreenX = aDestWindow.mozInnerScreenX + destClientX; + var destScreenY = aDestWindow.mozInnerScreenY + destClientY; + if ("clientX" in aDragEvent && !("screenX" in aDragEvent)) { + aDragEvent.screenX = aDestWindow.mozInnerScreenX + aDragEvent.clientX; + } + if ("clientY" in aDragEvent && !("screenY" in aDragEvent)) { + aDragEvent.screenY = aDestWindow.mozInnerScreenY + aDragEvent.clientY; + } + return Object.assign({ type: aType, + screenX: destScreenX, screenY: destScreenY, + clientX: destClientX, clientY: destClientY, + dataTransfer: aDataTransfer }, aDragEvent); +} + +/** + * Emulate a event sequence of dragstart, dragenter, and dragover. + * + * @param aSrcElement The element to use to start the drag. + * @param aDestElement The element to fire the dragover, dragenter events + * @param aDragData The data to supply for the data transfer. + * This data is in the format: + * [ [ {type: value, data: value}, ...], ... ] + * Pass null to avoid modifying dataTransfer. + * @param aDropEffect The drop effect to set during the dragstart event, or + * 'move' if null. + * @param aWindow Optional; Defaults to the current window object. + * @param aDestWindow Optional; Defaults to aWindow. + * Used when aDestElement is in a different window than + * aSrcElement. + * @param aDragEvent Optional; Defaults to empty object. Overwrites an object + * passed to sendDragEvent. + * @return A two element array, where the first element is the + * value returned from sendDragEvent for + * dragover event, and the second element is the + * dataTransfer for the current drag session. + */ +function synthesizeDragOver(aSrcElement, aDestElement, aDragData, aDropEffect, aWindow, aDestWindow, aDragEvent={}) +{ + if (!aWindow) { + aWindow = window; + } + if (!aDestWindow) { + aDestWindow = aWindow; + } + + var dataTransfer; + var trapDrag = function(event) { + dataTransfer = event.dataTransfer; + if (aDragData) { + for (var i = 0; i < aDragData.length; i++) { + var item = aDragData[i]; + for (var j = 0; j < item.length; j++) { + dataTransfer.mozSetDataAt(item[j].type, item[j].data, i); + } + } + } + dataTransfer.dropEffect = aDropEffect || "move"; + event.preventDefault(); + }; + + // need to use real mouse action + aWindow.addEventListener("dragstart", trapDrag, true); + synthesizeMouseAtCenter(aSrcElement, { type: "mousedown" }, aWindow); + + var rect = aSrcElement.getBoundingClientRect(); + var x = rect.width / 2; + var y = rect.height / 2; + synthesizeMouse(aSrcElement, x, y, { type: "mousemove" }, aWindow); + synthesizeMouse(aSrcElement, x+10, y+10, { type: "mousemove" }, aWindow); + aWindow.removeEventListener("dragstart", trapDrag, true); + + var event = createDragEventObject("dragenter", aDestElement, aDestWindow, + dataTransfer, aDragEvent); + sendDragEvent(event, aDestElement, aDestWindow); + + event = createDragEventObject("dragover", aDestElement, aDestWindow, + dataTransfer, aDragEvent); + var result = sendDragEvent(event, aDestElement, aDestWindow); + + return [result, dataTransfer]; +} + +/** + * Emulate the drop event and mouseup event. + * This should be called after synthesizeDragOver. + * + * @param aResult The first element of the array returned from + * synthesizeDragOver. + * @param aDataTransfer The second element of the array returned from + * synthesizeDragOver. + * @param aDestElement The element to fire the drop event. + * @param aDestWindow Optional; Defaults to the current window object. + * @param aDragEvent Optional; Defaults to empty object. Overwrites an + * object passed to sendDragEvent. + * @return "none" if aResult is true, + * aDataTransfer.dropEffect otherwise. + */ +function synthesizeDropAfterDragOver(aResult, aDataTransfer, aDestElement, aDestWindow, aDragEvent={}) +{ + if (!aDestWindow) { + aDestWindow = window; + } + + var effect = aDataTransfer.dropEffect; + var event; + + if (aResult) { + effect = "none"; + } else if (effect != "none") { + event = createDragEventObject("drop", aDestElement, aDestWindow, + aDataTransfer, aDragEvent); + sendDragEvent(event, aDestElement, aDestWindow); + } + + synthesizeMouseAtCenter(aDestElement, { type: "mouseup" }, aDestWindow); + + return effect; +} + +/** + * Emulate a drag and drop by emulating a dragstart and firing events dragenter, + * dragover, and drop. + * + * @param aSrcElement The element to use to start the drag. + * @param aDestElement The element to fire the dragover, dragenter events + * @param aDragData The data to supply for the data transfer. + * This data is in the format: + * [ [ {type: value, data: value}, ...], ... ] + * Pass null to avoid modifying dataTransfer. + * @param aDropEffect The drop effect to set during the dragstart event, or + * 'move' if null. + * @param aWindow Optional; Defaults to the current window object. + * @param aDestWindow Optional; Defaults to aWindow. + * Used when aDestElement is in a different window than + * aSrcElement. + * @param aDragEvent Optional; Defaults to empty object. Overwrites an object + * passed to sendDragEvent. + * @return The drop effect that was desired. + */ +function synthesizeDrop(aSrcElement, aDestElement, aDragData, aDropEffect, aWindow, aDestWindow, aDragEvent={}) +{ + if (!aWindow) { + aWindow = window; + } + if (!aDestWindow) { + aDestWindow = aWindow; + } + + var ds = _EU_Cc["@mozilla.org/widget/dragservice;1"] + .getService(_EU_Ci.nsIDragService); + + ds.startDragSession(); + + try { + var [result, dataTransfer] = synthesizeDragOver(aSrcElement, aDestElement, + aDragData, aDropEffect, + aWindow, aDestWindow, + aDragEvent); + return synthesizeDropAfterDragOver(result, dataTransfer, aDestElement, + aDestWindow, aDragEvent); + } finally { + ds.endDragSession(true); + } +} + +var PluginUtils = +{ + withTestPlugin : function(callback) + { + var ph = _EU_Cc["@mozilla.org/plugin/host;1"] + .getService(_EU_Ci.nsIPluginHost); + var tags = ph.getPluginTags(); + + // Find the test plugin + for (var i = 0; i < tags.length; i++) { + if (tags[i].name == "Test Plug-in") { + callback(tags[i]); + return true; + } + } + todo(false, "Need a test plugin on this platform"); + return false; + } +}; diff --git a/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js b/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js new file mode 100644 index 000000000..921d1a83f --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js @@ -0,0 +1,139 @@ +var ExtensionTestUtils = {}; + +ExtensionTestUtils.loadExtension = function(ext) +{ + // Cleanup functions need to be registered differently depending on + // whether we're in browser chrome or plain mochitests. + var registerCleanup; + if (typeof registerCleanupFunction != "undefined") { + registerCleanup = registerCleanupFunction; + } else { + registerCleanup = SimpleTest.registerCleanupFunction.bind(SimpleTest); + } + + var testResolve; + var testDone = new Promise(resolve => { testResolve = resolve; }); + + var messageHandler = new Map(); + var messageAwaiter = new Map(); + + var messageQueue = new Set(); + + registerCleanup(() => { + if (messageQueue.size) { + let names = Array.from(messageQueue, ([msg]) => msg); + SimpleTest.is(JSON.stringify(names), "[]", "message queue is empty"); + } + if (messageAwaiter.size) { + let names = Array.from(messageAwaiter.keys()); + SimpleTest.is(JSON.stringify(names), "[]", "no tasks awaiting on messages"); + } + }); + + function checkMessages() { + for (let message of messageQueue) { + let [msg, ...args] = message; + + let listener = messageAwaiter.get(msg); + if (listener) { + messageQueue.delete(message); + messageAwaiter.delete(msg); + + listener.resolve(...args); + return; + } + } + } + + function checkDuplicateListeners(msg) { + if (messageHandler.has(msg) || messageAwaiter.has(msg)) { + throw new Error("only one message handler allowed"); + } + } + + function testHandler(kind, pass, msg, ...args) { + if (kind == "test-eq") { + let [expected, actual, stack] = args; + SimpleTest.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`, undefined, stack); + } else if (kind == "test-log") { + SimpleTest.info(msg); + } else if (kind == "test-result") { + SimpleTest.ok(pass, msg, undefined, args[0]); + } + } + + var handler = { + testResult(kind, pass, msg, ...args) { + if (kind == "test-done") { + SimpleTest.ok(pass, msg, undefined, args[0]); + return testResolve(msg); + } + testHandler(kind, pass, msg, ...args); + }, + + testMessage(msg, ...args) { + var handler = messageHandler.get(msg); + if (handler) { + handler(...args); + } else { + messageQueue.add([msg, ...args]); + checkMessages(); + } + + }, + }; + + // Mimic serialization of functions as done in `Extension.generateXPI` and + // `Extension.generateZipFile` because functions are dropped when `ext` object + // is sent to the main process via the message manager. + ext = Object.assign({}, ext); + if (ext.files) { + ext.files = Object.assign({}, ext.files); + for (let filename of Object.keys(ext.files)) { + let file = ext.files[filename]; + if (typeof file == "function") { + ext.files[filename] = `(${file})();` + } + } + } + if (typeof ext.background == "function") { + ext.background = `(${ext.background})();` + } + + var extension = SpecialPowers.loadExtension(ext, handler); + + registerCleanup(() => { + if (extension.state == "pending" || extension.state == "running") { + SimpleTest.ok(false, "Extension left running at test shutdown") + return extension.unload(); + } else if (extension.state == "unloading") { + SimpleTest.ok(false, "Extension not fully unloaded at test shutdown") + } + }); + + extension.awaitMessage = (msg) => { + return new Promise(resolve => { + checkDuplicateListeners(msg); + + messageAwaiter.set(msg, {resolve}); + checkMessages(); + }); + }; + + extension.onMessage = (msg, callback) => { + checkDuplicateListeners(msg); + messageHandler.set(msg, callback); + }; + + extension.awaitFinish = (msg) => { + return testDone.then(actual => { + if (msg) { + SimpleTest.is(actual, msg, "test result correct"); + } + return actual; + }); + }; + + SimpleTest.info(`Extension loaded`); + return extension; +} diff --git a/testing/mochitest/tests/SimpleTest/LICENSE_SpawnTask b/testing/mochitest/tests/SimpleTest/LICENSE_SpawnTask new file mode 100644 index 000000000..088c54c9d --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/LICENSE_SpawnTask @@ -0,0 +1,24 @@ +LICENSE for SpawnTask.js (the co library): + +(The MIT License) + +Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/testing/mochitest/tests/SimpleTest/LogController.js b/testing/mochitest/tests/SimpleTest/LogController.js new file mode 100644 index 000000000..52fe9eea8 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/LogController.js @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var LogController = {}; //create the logger object + +LogController.counter = 0; //current log message number +LogController.listeners = []; +LogController.logLevel = { + FATAL: 50, + ERROR: 40, + WARNING: 30, + INFO: 20, + DEBUG: 10 +}; + +/* set minimum logging level */ +LogController.logLevelAtLeast = function(minLevel) { + if (typeof(minLevel) == 'string') { + minLevel = LogController.logLevel[minLevel]; + } + return function (msg) { + var msgLevel = msg.level; + if (typeof(msgLevel) == 'string') { + msgLevel = LogController.logLevel[msgLevel]; + } + return msgLevel >= minLevel; + }; +}; + +/* creates the log message with the given level and info */ +LogController.createLogMessage = function(level, info) { + var msg = {}; + msg.num = LogController.counter; + msg.level = level; + msg.info = info; + msg.timestamp = new Date(); + return msg; +}; + +/* helper method to return a sub-array */ +LogController.extend = function (args, skip) { + var ret = []; + for (var i = skip; i<args.length; i++) { + ret.push(args[i]); + } + return ret; +}; + +/* logs message with given level. Currently used locally by log() and error() */ +LogController.logWithLevel = function(level, message/*, ...*/) { + var msg = LogController.createLogMessage( + level, + LogController.extend(arguments, 1) + ); + LogController.dispatchListeners(msg); + LogController.counter += 1; +}; + +/* log with level INFO */ +LogController.log = function(message/*, ...*/) { + LogController.logWithLevel('INFO', message); +}; + +/* log with level ERROR */ +LogController.error = function(message/*, ...*/) { + LogController.logWithLevel('ERROR', message); +}; + +/* send log message to listeners */ +LogController.dispatchListeners = function(msg) { + for (var k in LogController.listeners) { + var pair = LogController.listeners[k]; + if (pair.ident != k || (pair[0] && !pair[0](msg))) { + continue; + } + pair[1](msg); + } +}; + +/* add a listener to this log given an identifier, a filter (can be null) and the listener object */ +LogController.addListener = function(ident, filter, listener) { + if (typeof(filter) == 'string') { + filter = LogController.logLevelAtLeast(filter); + } else if (filter !== null && typeof(filter) !== 'function') { + throw new Error("Filter must be a string, a function, or null"); + } + var entry = [filter, listener]; + entry.ident = ident; + LogController.listeners[ident] = entry; +}; + +/* remove a listener from this log */ +LogController.removeListener = function(ident) { + delete LogController.listeners[ident]; +}; diff --git a/testing/mochitest/tests/SimpleTest/MemoryStats.js b/testing/mochitest/tests/SimpleTest/MemoryStats.js new file mode 100644 index 000000000..2af971184 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/MemoryStats.js @@ -0,0 +1,122 @@ +/* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var MemoryStats = {}; + +/** + * Statistics that we want to retrieve and display after every test is + * done. The keys of this table are intended to be identical to the + * relevant attributes of nsIMemoryReporterManager. However, since + * nsIMemoryReporterManager doesn't necessarily support all these + * statistics in all build configurations, we also use this table to + * tell us whether statistics are supported or not. + */ +var MEM_STAT_UNKNOWN = 0; +var MEM_STAT_UNSUPPORTED = 1; +var MEM_STAT_SUPPORTED = 2; + +MemoryStats._hasMemoryStatistics = {} +MemoryStats._hasMemoryStatistics.vsize = MEM_STAT_UNKNOWN; +MemoryStats._hasMemoryStatistics.vsizeMaxContiguous = MEM_STAT_UNKNOWN; +MemoryStats._hasMemoryStatistics.residentFast = MEM_STAT_UNKNOWN; +MemoryStats._hasMemoryStatistics.heapAllocated = MEM_STAT_UNKNOWN; + +MemoryStats._getService = function (className, interfaceName) { + var service; + try { + service = Cc[className].getService(Ci[interfaceName]); + } catch (e) { + service = SpecialPowers.Cc[className] + .getService(SpecialPowers.Ci[interfaceName]); + } + return service; +} + +MemoryStats._nsIFile = function (pathname) { + var f; + var contractID = "@mozilla.org/file/local;1"; + try { + f = Cc[contractID].createInstance(Ci.nsIFile); + } catch(e) { + f = SpecialPowers.Cc[contractID].createInstance(SpecialPowers.Ci.nsIFile); + } + f.initWithPath(pathname); + return f; +} + +MemoryStats.constructPathname = function (directory, basename) { + var d = MemoryStats._nsIFile(directory); + d.append(basename); + return d.path; +} + +MemoryStats.dump = function (testNumber, + testURL, + dumpOutputDirectory, + dumpAboutMemory, + dumpDMD) { + // Use dump because treeherder uses --quiet, which drops 'info' + // from the structured logger. + var info = function(message) { + dump(message + "\n"); + }; + + var mrm = MemoryStats._getService("@mozilla.org/memory-reporter-manager;1", + "nsIMemoryReporterManager"); + var statMessage = ""; + for (var stat in MemoryStats._hasMemoryStatistics) { + var supported = MemoryStats._hasMemoryStatistics[stat]; + var firstAccess = false; + if (supported == MEM_STAT_UNKNOWN) { + firstAccess = true; + try { + var value = mrm[stat]; + supported = MEM_STAT_SUPPORTED; + } catch (e) { + supported = MEM_STAT_UNSUPPORTED; + } + MemoryStats._hasMemoryStatistics[stat] = supported; + } + if (supported == MEM_STAT_SUPPORTED) { + var sizeInMB = Math.round(mrm[stat] / (1024 * 1024)); + statMessage += " | " + stat + " " + sizeInMB + "MB"; + } else if (firstAccess) { + info("MEMORY STAT " + stat + " not supported in this build configuration."); + } + } + if (statMessage.length > 0) { + info("MEMORY STAT" + statMessage); + } + + if (dumpAboutMemory) { + var basename = "about-memory-" + testNumber + ".json.gz"; + var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory, + basename); + info(testURL + " | MEMDUMP-START " + dumpfile); + var md = MemoryStats._getService("@mozilla.org/memory-info-dumper;1", + "nsIMemoryInfoDumper"); + md.dumpMemoryReportsToNamedFile(dumpfile, function () { + info("TEST-INFO | " + testURL + " | MEMDUMP-END"); + }, null, /* anonymize = */ false); + } + + // This is the old, deprecated function. + if (dumpDMD && typeof(DMDReportAndDump) != undefined) { + var basename = "dmd-" + testNumber + "-deprecated.txt"; + var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory, + basename); + info(testURL + " | DMD-DUMP-deprecated " + dumpfile); + DMDReportAndDump(dumpfile); + } + + if (dumpDMD && typeof(DMDAnalyzeReports) != undefined) { + var basename = "dmd-" + testNumber + ".txt"; + var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory, + basename); + info(testURL + " | DMD-DUMP " + dumpfile); + DMDAnalyzeReports(dumpfile); + } +}; diff --git a/testing/mochitest/tests/SimpleTest/MockObjects.js b/testing/mochitest/tests/SimpleTest/MockObjects.js new file mode 100644 index 000000000..d00f5127b --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/MockObjects.js @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Allows registering a mock XPCOM component, that temporarily replaces the + * original one when an object implementing a given ContractID is requested + * using createInstance. + * + * @param aContractID + * The ContractID of the component to replace, for example + * "@mozilla.org/filepicker;1". + * + * @param aReplacementCtor + * The constructor function for the JavaScript object that will be + * created every time createInstance is called. This object must + * implement QueryInterface and provide the XPCOM interfaces required by + * the specified ContractID (for example + * Components.interfaces.nsIFilePicker). + */ + +function MockObjectRegisterer(aContractID, aReplacementCtor) { + this._contractID = aContractID; + this._replacementCtor = aReplacementCtor; +} + +MockObjectRegisterer.prototype = { + /** + * Replaces the current factory with one that returns a new mock object. + * + * After register() has been called, it is mandatory to call unregister() to + * restore the original component. Usually, you should use a try-catch block + * to ensure that unregister() is called. + */ + register: function MOR_register() { + if (this._originalFactory) + throw new Exception("Invalid object state when calling register()"); + + // Define a factory that creates a new object using the given constructor. + var providedConstructor = this._replacementCtor; + this._mockFactory = { + createInstance: function MF_createInstance(aOuter, aIid) { + if (aOuter != null) + throw SpecialPowers.Cr.NS_ERROR_NO_AGGREGATION; + return new providedConstructor().QueryInterface(aIid); + } + }; + + var retVal = SpecialPowers.swapFactoryRegistration(this._cid, this._contractID, this._mockFactory, this._originalFactory); + if ('error' in retVal) { + throw new Exception("ERROR: " + retVal.error); + } else { + this._cid = retVal.cid; + this._originalFactory = retVal.originalFactory; + } + }, + + /** + * Restores the original factory. + */ + unregister: function MOR_unregister() { + if (!this._originalFactory) + throw new Exception("Invalid object state when calling unregister()"); + + // Free references to the mock factory. + SpecialPowers.swapFactoryRegistration(this._cid, this._contractID, this._mockFactory, this._originalFactory); + + // Allow registering a mock factory again later. + this._cid = null; + this._originalFactory = null; + this._mockFactory = null; + }, + + // --- Private methods and properties --- + + /** + * The factory of the component being replaced. + */ + _originalFactory: null, + + /** + * The CID under which the mock contractID was registered. + */ + _cid: null, + + /** + * The nsIFactory that was automatically generated by this object. + */ + _mockFactory: null +} diff --git a/testing/mochitest/tests/SimpleTest/NativeKeyCodes.js b/testing/mochitest/tests/SimpleTest/NativeKeyCodes.js new file mode 100644 index 000000000..8130f3e18 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/NativeKeyCodes.js @@ -0,0 +1,370 @@ +/** + * This file defines all virtual keycodes for synthesizeNativeKey() of + * EventUtils.js and nsIDOMWindowUtils.sendNativeKeyEvent(). + * These values are defined in each platform's SDK or documents. + */ + +// Windows +// Windows' native key code values may include scan code value which can be +// retrieved with |((code & 0xFFFF0000 >> 16)|. If the value is 0, it will +// be computed with active keyboard layout automatically. +// FYI: Don't define scan code here for printable keys, numeric keys and +// IME keys because they depend on active keyboard layout. +// XXX: Although, ABNT C1 key depends on keyboard layout in strictly speaking. +// However, computing its scan code from the virtual keycode, +// WIN_VK_ABNT_C1, doesn't work fine (computed as 0x0073, "IntlRo"). +// Therefore, we should specify it here explicitly (it should be 0x0056, +// "IntlBackslash"). Fortunately, the key always generates 0x0056 with +// any keyboard layouts as far as I've tested. So, this must be safe to +// test new regressions. + +const WIN_VK_LBUTTON = 0x00000001; +const WIN_VK_RBUTTON = 0x00000002; +const WIN_VK_CANCEL = 0xE0460003; +const WIN_VK_MBUTTON = 0x00000004; +const WIN_VK_XBUTTON1 = 0x00000005; +const WIN_VK_XBUTTON2 = 0x00000006; +const WIN_VK_BACK = 0x000E0008; +const WIN_VK_TAB = 0x000F0009; +const WIN_VK_CLEAR = 0x004C000C; +const WIN_VK_RETURN = 0x001C000D; +const WIN_VK_SHIFT = 0x002A0010; +const WIN_VK_CONTROL = 0x001D0011; +const WIN_VK_MENU = 0x00380012; +const WIN_VK_PAUSE = 0x00450013; +const WIN_VK_CAPITAL = 0x003A0014; +const WIN_VK_KANA = 0x00000015; +const WIN_VK_HANGUEL = 0x00000015; +const WIN_VK_HANGUL = 0x00000015; +const WIN_VK_JUNJA = 0x00000017; +const WIN_VK_FINAL = 0x00000018; +const WIN_VK_HANJA = 0x00000019; +const WIN_VK_KANJI = 0x00000019; +const WIN_VK_ESCAPE = 0x0001001B; +const WIN_VK_CONVERT = 0x0000001C; +const WIN_VK_NONCONVERT = 0x0000001D; +const WIN_VK_ACCEPT = 0x0000001E; +const WIN_VK_MODECHANGE = 0x0000001F; +const WIN_VK_SPACE = 0x00390020; +const WIN_VK_PRIOR = 0xE0490021; +const WIN_VK_NEXT = 0xE0510022; +const WIN_VK_END = 0xE04F0023; +const WIN_VK_HOME = 0xE0470024; +const WIN_VK_LEFT = 0xE04B0025; +const WIN_VK_UP = 0xE0480026; +const WIN_VK_RIGHT = 0xE04D0027; +const WIN_VK_DOWN = 0xE0500028; +const WIN_VK_SELECT = 0x00000029; +const WIN_VK_PRINT = 0x0000002A; +const WIN_VK_EXECUTE = 0x0000002B; +const WIN_VK_SNAPSHOT = 0xE037002C; +const WIN_VK_INSERT = 0xE052002D; +const WIN_VK_DELETE = 0xE053002E; +const WIN_VK_HELP = 0x0000002F; +const WIN_VK_0 = 0x00000030; +const WIN_VK_1 = 0x00000031; +const WIN_VK_2 = 0x00000032; +const WIN_VK_3 = 0x00000033; +const WIN_VK_4 = 0x00000034; +const WIN_VK_5 = 0x00000035; +const WIN_VK_6 = 0x00000036; +const WIN_VK_7 = 0x00000037; +const WIN_VK_8 = 0x00000038; +const WIN_VK_9 = 0x00000039; +const WIN_VK_A = 0x00000041; +const WIN_VK_B = 0x00000042; +const WIN_VK_C = 0x00000043; +const WIN_VK_D = 0x00000044; +const WIN_VK_E = 0x00000045; +const WIN_VK_F = 0x00000046; +const WIN_VK_G = 0x00000047; +const WIN_VK_H = 0x00000048; +const WIN_VK_I = 0x00000049; +const WIN_VK_J = 0x0000004A; +const WIN_VK_K = 0x0000004B; +const WIN_VK_L = 0x0000004C; +const WIN_VK_M = 0x0000004D; +const WIN_VK_N = 0x0000004E; +const WIN_VK_O = 0x0000004F; +const WIN_VK_P = 0x00000050; +const WIN_VK_Q = 0x00000051; +const WIN_VK_R = 0x00000052; +const WIN_VK_S = 0x00000053; +const WIN_VK_T = 0x00000054; +const WIN_VK_U = 0x00000055; +const WIN_VK_V = 0x00000056; +const WIN_VK_W = 0x00000057; +const WIN_VK_X = 0x00000058; +const WIN_VK_Y = 0x00000059; +const WIN_VK_Z = 0x0000005A; +const WIN_VK_LWIN = 0xE05B005B; +const WIN_VK_RWIN = 0xE05C005C; +const WIN_VK_APPS = 0xE05D005D; +const WIN_VK_SLEEP = 0x0000005F; +const WIN_VK_NUMPAD0 = 0x00520060; +const WIN_VK_NUMPAD1 = 0x004F0061; +const WIN_VK_NUMPAD2 = 0x00500062; +const WIN_VK_NUMPAD3 = 0x00510063; +const WIN_VK_NUMPAD4 = 0x004B0064; +const WIN_VK_NUMPAD5 = 0x004C0065; +const WIN_VK_NUMPAD6 = 0x004D0066; +const WIN_VK_NUMPAD7 = 0x00470067; +const WIN_VK_NUMPAD8 = 0x00480068; +const WIN_VK_NUMPAD9 = 0x00490069; +const WIN_VK_MULTIPLY = 0x0037006A; +const WIN_VK_ADD = 0x004E006B; +const WIN_VK_SEPARATOR = 0x0000006C; +const WIN_VK_OEM_NEC_SEPARATE = 0x0000006C; +const WIN_VK_SUBTRACT = 0x004A006D; +const WIN_VK_DECIMAL = 0x0053006E; +const WIN_VK_DIVIDE = 0xE035006F; +const WIN_VK_F1 = 0x003B0070; +const WIN_VK_F2 = 0x003C0071; +const WIN_VK_F3 = 0x003D0072; +const WIN_VK_F4 = 0x003E0073; +const WIN_VK_F5 = 0x003F0074; +const WIN_VK_F6 = 0x00400075; +const WIN_VK_F7 = 0x00410076; +const WIN_VK_F8 = 0x00420077; +const WIN_VK_F9 = 0x00430078; +const WIN_VK_F10 = 0x00440079; +const WIN_VK_F11 = 0x0057007A; +const WIN_VK_F12 = 0x0058007B; +const WIN_VK_F13 = 0x0064007C; +const WIN_VK_F14 = 0x0065007D; +const WIN_VK_F15 = 0x0066007E; +const WIN_VK_F16 = 0x0067007F; +const WIN_VK_F17 = 0x00680080; +const WIN_VK_F18 = 0x00690081; +const WIN_VK_F19 = 0x006A0082; +const WIN_VK_F20 = 0x006B0083; +const WIN_VK_F21 = 0x006C0084; +const WIN_VK_F22 = 0x006D0085; +const WIN_VK_F23 = 0x006E0086; +const WIN_VK_F24 = 0x00760087; +const WIN_VK_NUMLOCK = 0xE0450090; +const WIN_VK_SCROLL = 0x00460091; +const WIN_VK_OEM_FJ_JISHO = 0x00000092; +const WIN_VK_OEM_NEC_EQUAL = 0x00000092; +const WIN_VK_OEM_FJ_MASSHOU = 0x00000093; +const WIN_VK_OEM_FJ_TOUROKU = 0x00000094; +const WIN_VK_OEM_FJ_LOYA = 0x00000095; +const WIN_VK_OEM_FJ_ROYA = 0x00000096; +const WIN_VK_LSHIFT = 0x002A00A0; +const WIN_VK_RSHIFT = 0x003600A1; +const WIN_VK_LCONTROL = 0x001D00A2; +const WIN_VK_RCONTROL = 0xE01D00A3; +const WIN_VK_LMENU = 0x003800A4; +const WIN_VK_RMENU = 0xE03800A5; +const WIN_VK_BROWSER_BACK = 0xE06A00A6; +const WIN_VK_BROWSER_FORWARD = 0xE06900A7; +const WIN_VK_BROWSER_REFRESH = 0xE06700A8; +const WIN_VK_BROWSER_STOP = 0xE06800A9; +const WIN_VK_BROWSER_SEARCH = 0x000000AA; +const WIN_VK_BROWSER_FAVORITES = 0xE06600AB; +const WIN_VK_BROWSER_HOME = 0xE03200AC; +const WIN_VK_VOLUME_MUTE = 0xE02000AD; +const WIN_VK_VOLUME_DOWN = 0xE02E00AE; +const WIN_VK_VOLUME_UP = 0xE03000AF; +const WIN_VK_MEDIA_NEXT_TRACK = 0xE01900B0; +const WIN_VK_OEM_FJ_000 = 0x000000B0; +const WIN_VK_MEDIA_PREV_TRACK = 0xE01000B1; +const WIN_VK_OEM_FJ_EUQAL = 0x000000B1; +const WIN_VK_MEDIA_STOP = 0xE02400B2; +const WIN_VK_MEDIA_PLAY_PAUSE = 0xE02200B3; +const WIN_VK_OEM_FJ_00 = 0x000000B3; +const WIN_VK_LAUNCH_MAIL = 0xE06C00B4; +const WIN_VK_LAUNCH_MEDIA_SELECT = 0xE06D00B5; +const WIN_VK_LAUNCH_APP1 = 0xE06B00B6; +const WIN_VK_LAUNCH_APP2 = 0xE02100B7; +const WIN_VK_OEM_1 = 0x000000BA; +const WIN_VK_OEM_PLUS = 0x000000BB; +const WIN_VK_OEM_COMMA = 0x000000BC; +const WIN_VK_OEM_MINUS = 0x000000BD; +const WIN_VK_OEM_PERIOD = 0x000000BE; +const WIN_VK_OEM_2 = 0x000000BF; +const WIN_VK_OEM_3 = 0x000000C0; +const WIN_VK_ABNT_C1 = 0x005600C1; +const WIN_VK_ABNT_C2 = 0x000000C2; +const WIN_VK_OEM_4 = 0x000000DB; +const WIN_VK_OEM_5 = 0x000000DC; +const WIN_VK_OEM_6 = 0x000000DD; +const WIN_VK_OEM_7 = 0x000000DE; +const WIN_VK_OEM_8 = 0x000000DF; +const WIN_VK_OEM_NEC_DP1 = 0x000000E0; +const WIN_VK_OEM_AX = 0x000000E1; +const WIN_VK_OEM_NEC_DP2 = 0x000000E1; +const WIN_VK_OEM_102 = 0x000000E2; +const WIN_VK_OEM_NEC_DP3 = 0x000000E2; +const WIN_VK_ICO_HELP = 0x000000E3; +const WIN_VK_OEM_NEC_DP4 = 0x000000E3; +const WIN_VK_ICO_00 = 0x000000E4; +const WIN_VK_PROCESSKEY = 0x000000E5; +const WIN_VK_ICO_CLEAR = 0x000000E6; +const WIN_VK_PACKET = 0x000000E7; +const WIN_VK_ERICSSON_BASE = 0x000000E8; +const WIN_VK_OEM_RESET = 0x000000E9; +const WIN_VK_OEM_JUMP = 0x000000EA; +const WIN_VK_OEM_PA1 = 0x000000EB; +const WIN_VK_OEM_PA2 = 0x000000EC; +const WIN_VK_OEM_PA3 = 0x000000ED; +const WIN_VK_OEM_WSCTRL = 0x000000EE; +const WIN_VK_OEM_CUSEL = 0x000000EF; +const WIN_VK_OEM_ATTN = 0x000000F0; +const WIN_VK_OEM_FINISH = 0x000000F1; +const WIN_VK_OEM_COPY = 0x000000F2; +const WIN_VK_OEM_AUTO = 0x000000F3; +const WIN_VK_OEM_ENLW = 0x000000F4; +const WIN_VK_OEM_BACKTAB = 0x000000F5; +const WIN_VK_ATTN = 0x000000F6; +const WIN_VK_CRSEL = 0x000000F7; +const WIN_VK_EXSEL = 0x000000F8; +const WIN_VK_EREOF = 0x000000F9; +const WIN_VK_PLAY = 0x000000FA; +const WIN_VK_ZOOM = 0x000000FB; +const WIN_VK_NONAME = 0x000000FC; +const WIN_VK_PA1 = 0x000000FD; +const WIN_VK_OEM_CLEAR = 0x000000FE; + +const WIN_VK_NUMPAD_RETURN = 0xE01C000D; +const WIN_VK_NUMPAD_PRIOR = 0x00490021; +const WIN_VK_NUMPAD_NEXT = 0x00510022; +const WIN_VK_NUMPAD_END = 0x004F0023; +const WIN_VK_NUMPAD_HOME = 0x00470024; +const WIN_VK_NUMPAD_LEFT = 0x004B0025; +const WIN_VK_NUMPAD_UP = 0x00480026; +const WIN_VK_NUMPAD_RIGHT = 0x004D0027; +const WIN_VK_NUMPAD_DOWN = 0x00500028; +const WIN_VK_NUMPAD_INSERT = 0x0052002D; +const WIN_VK_NUMPAD_DELETE = 0x0053002E; + +// Mac + +const MAC_VK_ANSI_A = 0x00; +const MAC_VK_ANSI_S = 0x01; +const MAC_VK_ANSI_D = 0x02; +const MAC_VK_ANSI_F = 0x03; +const MAC_VK_ANSI_H = 0x04; +const MAC_VK_ANSI_G = 0x05; +const MAC_VK_ANSI_Z = 0x06; +const MAC_VK_ANSI_X = 0x07; +const MAC_VK_ANSI_C = 0x08; +const MAC_VK_ANSI_V = 0x09; +const MAC_VK_ISO_Section = 0x0A; +const MAC_VK_ANSI_B = 0x0B; +const MAC_VK_ANSI_Q = 0x0C; +const MAC_VK_ANSI_W = 0x0D; +const MAC_VK_ANSI_E = 0x0E; +const MAC_VK_ANSI_R = 0x0F; +const MAC_VK_ANSI_Y = 0x10; +const MAC_VK_ANSI_T = 0x11; +const MAC_VK_ANSI_1 = 0x12; +const MAC_VK_ANSI_2 = 0x13; +const MAC_VK_ANSI_3 = 0x14; +const MAC_VK_ANSI_4 = 0x15; +const MAC_VK_ANSI_6 = 0x16; +const MAC_VK_ANSI_5 = 0x17; +const MAC_VK_ANSI_Equal = 0x18; +const MAC_VK_ANSI_9 = 0x19; +const MAC_VK_ANSI_7 = 0x1A; +const MAC_VK_ANSI_Minus = 0x1B; +const MAC_VK_ANSI_8 = 0x1C; +const MAC_VK_ANSI_0 = 0x1D; +const MAC_VK_ANSI_RightBracket = 0x1E; +const MAC_VK_ANSI_O = 0x1F; +const MAC_VK_ANSI_U = 0x20; +const MAC_VK_ANSI_LeftBracket = 0x21; +const MAC_VK_ANSI_I = 0x22; +const MAC_VK_ANSI_P = 0x23; +const MAC_VK_Return = 0x24; +const MAC_VK_ANSI_L = 0x25; +const MAC_VK_ANSI_J = 0x26; +const MAC_VK_ANSI_Quote = 0x27; +const MAC_VK_ANSI_K = 0x28; +const MAC_VK_ANSI_Semicolon = 0x29; +const MAC_VK_ANSI_Backslash = 0x2A; +const MAC_VK_ANSI_Comma = 0x2B; +const MAC_VK_ANSI_Slash = 0x2C; +const MAC_VK_ANSI_N = 0x2D; +const MAC_VK_ANSI_M = 0x2E; +const MAC_VK_ANSI_Period = 0x2F; +const MAC_VK_Tab = 0x30; +const MAC_VK_Space = 0x31; +const MAC_VK_ANSI_Grave = 0x32; +const MAC_VK_Delete = 0x33; +const MAC_VK_PC_Backspace = 0x33; +const MAC_VK_Powerbook_KeypadEnter = 0x34; +const MAC_VK_Escape = 0x35; +const MAC_VK_RightCommand = 0x36; +const MAC_VK_Command = 0x37; +const MAC_VK_Shift = 0x38; +const MAC_VK_CapsLock = 0x39; +const MAC_VK_Option = 0x3A; +const MAC_VK_Control = 0x3B; +const MAC_VK_RightShift = 0x3C; +const MAC_VK_RightOption = 0x3D; +const MAC_VK_RightControl = 0x3E; +const MAC_VK_Function = 0x3F; +const MAC_VK_F17 = 0x40; +const MAC_VK_ANSI_KeypadDecimal = 0x41; +const MAC_VK_ANSI_KeypadMultiply = 0x43; +const MAC_VK_ANSI_KeypadPlus = 0x45; +const MAC_VK_ANSI_KeypadClear = 0x47; +const MAC_VK_VolumeUp = 0x48; +const MAC_VK_VolumeDown = 0x49; +const MAC_VK_Mute = 0x4A; +const MAC_VK_ANSI_KeypadDivide = 0x4B; +const MAC_VK_ANSI_KeypadEnter = 0x4C; +const MAC_VK_ANSI_KeypadMinus = 0x4E; +const MAC_VK_F18 = 0x4F; +const MAC_VK_F19 = 0x50; +const MAC_VK_ANSI_KeypadEquals = 0x51; +const MAC_VK_ANSI_Keypad0 = 0x52; +const MAC_VK_ANSI_Keypad1 = 0x53; +const MAC_VK_ANSI_Keypad2 = 0x54; +const MAC_VK_ANSI_Keypad3 = 0x55; +const MAC_VK_ANSI_Keypad4 = 0x56; +const MAC_VK_ANSI_Keypad5 = 0x57; +const MAC_VK_ANSI_Keypad6 = 0x58; +const MAC_VK_ANSI_Keypad7 = 0x59; +const MAC_VK_F20 = 0x5A; +const MAC_VK_ANSI_Keypad8 = 0x5B; +const MAC_VK_ANSI_Keypad9 = 0x5C; +const MAC_VK_JIS_Yen = 0x5D; +const MAC_VK_JIS_Underscore = 0x5E; +const MAC_VK_JIS_KeypadComma = 0x5F; +const MAC_VK_F5 = 0x60; +const MAC_VK_F6 = 0x61; +const MAC_VK_F7 = 0x62; +const MAC_VK_F3 = 0x63; +const MAC_VK_F8 = 0x64; +const MAC_VK_F9 = 0x65; +const MAC_VK_JIS_Eisu = 0x66; +const MAC_VK_F11 = 0x67; +const MAC_VK_JIS_Kana = 0x68; +const MAC_VK_F13 = 0x69; +const MAC_VK_PC_PrintScreen = 0x69; +const MAC_VK_F16 = 0x6A; +const MAC_VK_F14 = 0x6B; +const MAC_VK_PC_ScrollLock = 0x6B; +const MAC_VK_F10 = 0x6D; +const MAC_VK_PC_ContextMenu = 0x6E; +const MAC_VK_F12 = 0x6F; +const MAC_VK_F15 = 0x71; +const MAC_VK_PC_Pause = 0x71; +const MAC_VK_Help = 0x72; +const MAC_VK_PC_Insert = 0x72; +const MAC_VK_Home = 0x73; +const MAC_VK_PageUp = 0x74; +const MAC_VK_ForwardDelete = 0x75; +const MAC_VK_PC_Delete = 0x75; +const MAC_VK_F4 = 0x76; +const MAC_VK_End = 0x77; +const MAC_VK_F2 = 0x78; +const MAC_VK_PageDown = 0x79; +const MAC_VK_F1 = 0x7A; +const MAC_VK_LeftArrow = 0x7B; +const MAC_VK_RightArrow = 0x7C; +const MAC_VK_DownArrow = 0x7D; +const MAC_VK_UpArrow = 0x7E; + diff --git a/testing/mochitest/tests/SimpleTest/SimpleTest.js b/testing/mochitest/tests/SimpleTest/SimpleTest.js new file mode 100644 index 000000000..37713737c --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js @@ -0,0 +1,1639 @@ +/* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/** + * SimpleTest, a partial Test.Simple/Test.More API compatible test library. + * + * Why? + * + * Test.Simple doesn't work on IE < 6. + * TODO: + * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit + * itself against IE 5.5 + * + * NOTE: Pay attention to cross-browser compatibility in this file. For + * instance, do not use const or JS > 1.5 features which are not yet + * implemented everywhere. + * +**/ + +var SimpleTest = { }; +var parentRunner = null; + +// In normal test runs, the window that has a TestRunner in its parent is +// the primary window. In single test runs, if there is no parent and there +// is no opener then it is the primary window. +var isSingleTestRun = (parent == window && !opener) +try { + var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun; +} catch(e) { + dump("TEST-UNEXPECTED-FAIL, Exception caught: " + e.message + + ", at: " + e.fileName + " (" + e.lineNumber + + "), location: " + window.location.href + "\n"); +} +// Finds the TestRunner for this test run and the SpecialPowers object (in +// case it is not defined) from a parent/opener window. +// +// Finding the SpecialPowers object is needed when we have ChromePowers in +// harness.xul and we need SpecialPowers in the iframe, and also for tests +// like test_focus.xul where we open a window which opens another window which +// includes SimpleTest.js. +(function() { + function ancestor(w) { + return w.parent != w ? w.parent : w.opener; + } + + var w = ancestor(window); + while (w && (!parentRunner || !window.SpecialPowers)) { + if (!parentRunner) { + parentRunner = w.TestRunner; + if (!parentRunner && w.wrappedJSObject) { + parentRunner = w.wrappedJSObject.TestRunner; + } + } + if (!window.SpecialPowers) { + window.SpecialPowers = w.SpecialPowers; + } + w = ancestor(w); + } + + if (parentRunner) { + SimpleTest.harnessParameters = parentRunner.getParameterInfo(); + } +})(); + +/* Helper functions pulled out of various MochiKit modules */ +if (typeof(repr) == 'undefined') { + this.repr = function(o) { + if (typeof(o) == "undefined") { + return "undefined"; + } else if (o === null) { + return "null"; + } + try { + if (typeof(o.__repr__) == 'function') { + return o.__repr__(); + } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) { + return o.repr(); + } + } catch (e) { + } + try { + if (typeof(o.NAME) == 'string' && ( + o.toString == Function.prototype.toString || + o.toString == Object.prototype.toString + )) { + return o.NAME; + } + } catch (e) { + } + var ostring; + try { + if (o === 0) { + ostring = (1 / o > 0) ? "+0" : "-0"; + } else if (typeof o === "string") { + ostring = JSON.stringify(o); + } else if (Array.isArray(o)) { + ostring = "[" + o.map(val => repr(val)).join(", ") + "]"; + } else { + ostring = (o + ""); + } + } catch (e) { + return "[" + typeof(o) + "]"; + } + if (typeof(o) == "function") { + o = ostring.replace(/^\s+/, ""); + var idx = o.indexOf("{"); + if (idx != -1) { + o = o.substr(0, idx) + "{...}"; + } + } + return ostring; + }; +} + +/* This returns a function that applies the previously given parameters. + * This is used by SimpleTest.showReport + */ +if (typeof(partial) == 'undefined') { + this.partial = function(func) { + var args = []; + for (var i = 1; i < arguments.length; i++) { + args.push(arguments[i]); + } + return function() { + if (arguments.length > 0) { + for (var i = 1; i < arguments.length; i++) { + args.push(arguments[i]); + } + } + func(args); + }; + }; +} + +if (typeof(getElement) == 'undefined') { + this.getElement = function(id) { + return ((typeof(id) == "string") ? + document.getElementById(id) : id); + }; + this.$ = this.getElement; +} + +SimpleTest._newCallStack = function(path) { + var rval = function () { + var callStack = arguments.callee.callStack; + for (var i = 0; i < callStack.length; i++) { + if (callStack[i].apply(this, arguments) === false) { + break; + } + } + try { + this[path] = null; + } catch (e) { + // pass + } + }; + rval.callStack = []; + return rval; +}; + +if (typeof(addLoadEvent) == 'undefined') { + this.addLoadEvent = function(func) { + var existing = window["onload"]; + var regfunc = existing; + if (!(typeof(existing) == 'function' + && typeof(existing.callStack) == "object" + && existing.callStack !== null)) { + regfunc = SimpleTest._newCallStack("onload"); + if (typeof(existing) == 'function') { + regfunc.callStack.push(existing); + } + window["onload"] = regfunc; + } + regfunc.callStack.push(func); + }; +} + +function createEl(type, attrs, html) { + //use createElementNS so the xul/xhtml tests have no issues + var el; + if (!document.body) { + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); + } + else { + el = document.createElement(type); + } + if (attrs !== null && attrs !== undefined) { + for (var k in attrs) { + el.setAttribute(k, attrs[k]); + } + } + if (html !== null && html !== undefined) { + el.appendChild(document.createTextNode(html)); + } + return el; +} + +/* lots of tests use this as a helper to get css properties */ +if (typeof(computedStyle) == 'undefined') { + this.computedStyle = function(elem, cssProperty) { + elem = getElement(elem); + if (elem.currentStyle) { + return elem.currentStyle[cssProperty]; + } + if (typeof(document.defaultView) == 'undefined' || document === null) { + return undefined; + } + var style = document.defaultView.getComputedStyle(elem, null); + if (typeof(style) == 'undefined' || style === null) { + return undefined; + } + + var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1' + ).toLowerCase(); + + return style.getPropertyValue(selectorCase); + }; +} + +SimpleTest._tests = []; +SimpleTest._stopOnLoad = true; +SimpleTest._cleanupFunctions = []; +SimpleTest._timeoutFunctions = []; +SimpleTest.expected = 'pass'; +SimpleTest.num_failed = 0; +SimpleTest._inChaosMode = false; + +SimpleTest.setExpected = function () { + if (parent.TestRunner) { + SimpleTest.expected = parent.TestRunner.expected; + } +} +SimpleTest.setExpected(); + +/** + * Something like assert. +**/ +SimpleTest.ok = function (condition, name, diag, stack = null) { + + var test = {'result': !!condition, 'name': name, 'diag': diag}; + if (SimpleTest.expected == 'fail') { + if (!test.result) { + SimpleTest.num_failed++; + test.result = !test.result; + } + var successInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"}; + var failureInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"}; + } else { + var successInfo = {status:"PASS", expected:"PASS", message:"TEST-PASS"}; + var failureInfo = {status:"FAIL", expected:"PASS", message:"TEST-UNEXPECTED-FAIL"}; + } + + if (condition) { + stack = null; + } else if (!stack) { + stack = (new Error).stack.replace(/^(.*@)http:\/\/mochi.test:8888\/tests\//gm, ' $1').split('\n'); + stack.splice(0, 1); + stack = stack.join('\n'); + } + + SimpleTest._logResult(test, successInfo, failureInfo, stack); + SimpleTest._tests.push(test); +}; + +/** + * Roughly equivalent to ok(Object.is(a, b), name) +**/ +SimpleTest.is = function (a, b, name) { + // Be lazy and use Object.is til we want to test a browser without it. + var pass = Object.is(a, b); + var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + SimpleTest.ok(pass, name, diag); +}; + +SimpleTest.isfuzzy = function (a, b, epsilon, name) { + var pass = (a >= b - epsilon) && (a <= b + epsilon); + var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon) + SimpleTest.ok(pass, name, diag); +}; + +SimpleTest.isnot = function (a, b, name) { + var pass = !Object.is(a, b); + var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it"; + SimpleTest.ok(pass, name, diag); +}; + +/** + * Check that the function call throws an exception. + */ +SimpleTest.doesThrow = function(fn, name) { + var gotException = false; + try { + fn(); + } catch (ex) { gotException = true; } + ok(gotException, name); +}; + +// --------------- Test.Builder/Test.More todo() ----------------- + +SimpleTest.todo = function(condition, name, diag) { + var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true}; + var successInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"}; + var failureInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"}; + SimpleTest._logResult(test, successInfo, failureInfo); + SimpleTest._tests.push(test); +}; + +/* + * Returns the absolute URL to a test data file from where tests + * are served. i.e. the file doesn't necessarely exists where tests + * are executed. + * (For android, mochitest are executed on the device, while + * all mochitest html (and others) files are served from the test runner + * slave) + */ +SimpleTest.getTestFileURL = function(path) { + var lastSlashIdx = path.lastIndexOf("/") + 1; + var filename = path.substr(lastSlashIdx); + var location = window.location; + // Remove mochitest html file name from the path + var remotePath = location.pathname.replace(/\/[^\/]+?$/,""); + var url = location.origin + + remotePath + "/" + path; + return url; +}; + +SimpleTest._getCurrentTestURL = function() { + return parentRunner && parentRunner.currentTestURL || + typeof gTestPath == "string" && gTestPath || + "unknown test url"; +}; + +SimpleTest._forceLogMessageOutput = false; + +/** + * Force all test messages to be displayed. Only applies for the current test. + */ +SimpleTest.requestCompleteLog = function() { + if (!parentRunner || SimpleTest._forceLogMessageOutput) { + return; + } + + parentRunner.structuredLogger.deactivateBuffering(); + SimpleTest._forceLogMessageOutput = true; + + SimpleTest.registerCleanupFunction(function() { + parentRunner.structuredLogger.activateBuffering(); + SimpleTest._forceLogMessageOutput = false; + }); +}; + +SimpleTest._logResult = function (test, passInfo, failInfo, stack) { + var url = SimpleTest._getCurrentTestURL(); + var result = test.result ? passInfo : failInfo; + var diagnostic = test.diag || null; + // BUGFIX : coercing test.name to a string, because some a11y tests pass an xpconnect object + var subtest = test.name ? String(test.name) : null; + var isError = !test.result == !test.todo; + + if (parentRunner) { + if (!result.status || !result.expected) { + if (diagnostic) { + parentRunner.structuredLogger.info(diagnostic); + } + return; + } + + if (isError) { + parentRunner.addFailedTest(url); + } + + parentRunner.structuredLogger.testStatus(url, + subtest, + result.status, + result.expected, + diagnostic, + stack); + } else if (typeof dump === "function") { + var diagMessage = test.name + (test.diag ? " - " + test.diag : ""); + var debugMsg = [result.message, url, diagMessage].join(' | '); + dump(debugMsg + "\n"); + } else { + // Non-Mozilla browser? Just do nothing. + } +}; + +SimpleTest.info = function(name, message) { + var log = message ? name + ' | ' + message : name; + if (parentRunner) { + parentRunner.structuredLogger.info(log); + } else { + dump(log + '\n'); + } +}; + +/** + * Copies of is and isnot with the call to ok replaced by a call to todo. +**/ + +SimpleTest.todo_is = function (a, b, name) { + var pass = Object.is(a, b); + var diag = pass ? repr(a) + " should equal " + repr(b) + : "got " + repr(a) + ", expected " + repr(b); + SimpleTest.todo(pass, name, diag); +}; + +SimpleTest.todo_isnot = function (a, b, name) { + var pass = !Object.is(a, b); + var diag = pass ? repr(a) + " should not equal " + repr(b) + : "didn't expect " + repr(a) + ", but got it"; + SimpleTest.todo(pass, name, diag); +}; + + +/** + * Makes a test report, returns it as a DIV element. +**/ +SimpleTest.report = function () { + var passed = 0; + var failed = 0; + var todo = 0; + + var tallyAndCreateDiv = function (test) { + var cls, msg, div; + var diag = test.diag ? " - " + test.diag : ""; + if (test.todo && !test.result) { + todo++; + cls = "test_todo"; + msg = "todo | " + test.name + diag; + } else if (test.result && !test.todo) { + passed++; + cls = "test_ok"; + msg = "passed | " + test.name + diag; + } else { + failed++; + cls = "test_not_ok"; + msg = "failed | " + test.name + diag; + } + div = createEl('div', {'class': cls}, msg); + return div; + }; + var results = []; + for (var d=0; d<SimpleTest._tests.length; d++) { + results.push(tallyAndCreateDiv(SimpleTest._tests[d])); + } + + var summary_class = failed != 0 ? 'some_fail' : + passed == 0 ? 'todo_only' : 'all_pass'; + + var div1 = createEl('div', {'class': 'tests_report'}); + var div2 = createEl('div', {'class': 'tests_summary ' + summary_class}); + var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed); + var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed); + var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo); + div2.appendChild(div3); + div2.appendChild(div4); + div2.appendChild(div5); + div1.appendChild(div2); + for (var t=0; t<results.length; t++) { + //iterate in order + div1.appendChild(results[t]); + } + return div1; +}; + +/** + * Toggle element visibility +**/ +SimpleTest.toggle = function(el) { + if (computedStyle(el, 'display') == 'block') { + el.style.display = 'none'; + } else { + el.style.display = 'block'; + } +}; + +/** + * Toggle visibility for divs with a specific class. +**/ +SimpleTest.toggleByClass = function (cls, evt) { + var children = document.getElementsByTagName('div'); + var elements = []; + for (var i=0; i<children.length; i++) { + var child = children[i]; + var clsName = child.className; + if (!clsName) { + continue; + } + var classNames = clsName.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == cls) { + elements.push(child); + break; + } + } + } + for (var t=0; t<elements.length; t++) { + //TODO: again, for-in loop over elems seems to break this + SimpleTest.toggle(elements[t]); + } + if (evt) + evt.preventDefault(); +}; + +/** + * Shows the report in the browser +**/ +SimpleTest.showReport = function() { + var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks"); + var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks"); + var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks"); + togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok'); + toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok'); + toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo'); + var body = document.body; // Handles HTML documents + if (!body) { + // Do the XML thing. + body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", + "body")[0]; + } + var firstChild = body.childNodes[0]; + var addNode; + if (firstChild) { + addNode = function (el) { + body.insertBefore(el, firstChild); + }; + } else { + addNode = function (el) { + body.appendChild(el) + }; + } + addNode(togglePassed); + addNode(createEl('span', null, " ")); + addNode(toggleFailed); + addNode(createEl('span', null, " ")); + addNode(toggleTodo); + addNode(SimpleTest.report()); + // Add a separator from the test content. + addNode(createEl('hr')); +}; + +/** + * Tells SimpleTest to don't finish the test when the document is loaded, + * useful for asynchronous tests. + * + * When SimpleTest.waitForExplicitFinish is called, + * explicit SimpleTest.finish() is required. +**/ +SimpleTest.waitForExplicitFinish = function () { + SimpleTest._stopOnLoad = false; +}; + +/** + * Multiply the timeout the parent runner uses for this test by the + * given factor. + * + * For example, in a test that may take a long time to complete, using + * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to + * finish. + */ +SimpleTest.requestLongerTimeout = function (factor) { + if (parentRunner) { + parentRunner.requestLongerTimeout(factor); + } +} + +/** + * Note that the given range of assertions is to be expected. When + * this function is not called, 0 assertions are expected. When only + * one argument is given, that number of assertions are expected. + * + * A test where we expect to have assertions (which should largely be a + * transitional mechanism to get assertion counts down from our current + * situation) can call the SimpleTest.expectAssertions() function, with + * either one or two arguments: one argument gives an exact number + * expected, and two arguments give a range. For example, a test might do + * one of the following: + * + * // Currently triggers two assertions (bug NNNNNN). + * SimpleTest.expectAssertions(2); + * + * // Currently triggers one assertion on Mac (bug NNNNNN). + * if (navigator.platform.indexOf("Mac") == 0) { + * SimpleTest.expectAssertions(1); + * } + * + * // Currently triggers two assertions on all platforms (bug NNNNNN), + * // but intermittently triggers two additional assertions (bug NNNNNN) + * // on Windows. + * if (navigator.platform.indexOf("Win") == 0) { + * SimpleTest.expectAssertions(2, 4); + * } else { + * SimpleTest.expectAssertions(2); + * } + * + * // Intermittently triggers up to three assertions (bug NNNNNN). + * SimpleTest.expectAssertions(0, 3); + */ +SimpleTest.expectAssertions = function(min, max) { + if (parentRunner) { + parentRunner.expectAssertions(min, max); + } +} + +SimpleTest._flakyTimeoutIsOK = false; +SimpleTest._originalSetTimeout = window.setTimeout; +window.setTimeout = function SimpleTest_setTimeoutShim() { + // Don't break tests that are loaded without a parent runner. + if (parentRunner) { + // Right now, we only enable these checks for mochitest-plain. + switch (SimpleTest.harnessParameters.testRoot) { + case "browser": + case "chrome": + case "a11y": + break; + default: + if (!SimpleTest._alreadyFinished && arguments.length > 1 && arguments[1] > 0) { + if (SimpleTest._flakyTimeoutIsOK) { + SimpleTest.todo(false, "The author of the test has indicated that flaky timeouts are expected. Reason: " + SimpleTest._flakyTimeoutReason); + } else { + SimpleTest.ok(false, "Test attempted to use a flaky timeout value " + arguments[1]); + } + } + } + } + return SimpleTest._originalSetTimeout.apply(window, arguments); +} + +/** + * Request the framework to allow usage of setTimeout(func, timeout) + * where |timeout > 0|. This is required to note that the author of + * the test is aware of the inherent flakiness in the test caused by + * that, and asserts that there is no way around using the magic timeout + * value number for some reason. + * + * The reason parameter should be a string representation of the + * reason why using such flaky timeouts. + * + * Use of this function is STRONGLY discouraged. Think twice before + * using it. Such magic timeout values could result in intermittent + * failures in your test, and are almost never necessary! + */ +SimpleTest.requestFlakyTimeout = function (reason) { + SimpleTest.is(typeof(reason), "string", "A valid string reason is expected"); + SimpleTest.isnot(reason, "", "Reason cannot be empty"); + SimpleTest._flakyTimeoutIsOK = true; + SimpleTest._flakyTimeoutReason = reason; +} + +SimpleTest._pendingWaitForFocusCount = 0; + +/** + * Version of waitForFocus that returns a promise. The Promise will + * not resolve to the focused window, as it might be a CPOW (and Promises + * cannot be resolved with CPOWs). If you require the focused window, + * you should use waitForFocus instead. + */ +SimpleTest.promiseFocus = function *(targetWindow, expectBlankPage) +{ + return new Promise(function (resolve, reject) { + SimpleTest.waitForFocus(win => { + // Just resolve, without passing the window (see bug 1233497) + resolve(); + }, targetWindow, expectBlankPage); + }); +} + +/** + * If the page is not yet loaded, waits for the load event. In addition, if + * the page is not yet focused, focuses and waits for the window to be + * focused. Calls the callback when completed. If the current page is + * 'about:blank', then the page is assumed to not yet be loaded. Pass true for + * expectBlankPage to not make this assumption if you expect a blank page to + * be present. + * + * targetWindow should be specified if it is different than 'window'. The actual + * focused window may be a descendant of targetWindow. + * + * @param callback + * function called when load and focus are complete + * @param targetWindow + * optional window to be loaded and focused, defaults to 'window'. + * This may also be a <browser> element, in which case the window within + * that browser will be focused. + * @param expectBlankPage + * true if targetWindow.location is 'about:blank'. Defaults to false + */ +SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) { + // A separate method is used that is serialized and passed to the child + // process via loadFrameScript. Once the child window is focused, the + // child will send the WaitForFocus:ChildFocused notification to the parent. + // If a child frame in a child process must be focused, a + // WaitForFocus:FocusChild message is then sent to the child to focus that + // child. This message is used so that the child frame can be passed to it. + function waitForFocusInner(targetWindow, isChildProcess, expectBlankPage) + { + /* Indicates whether the desired targetWindow has loaded or focused. The + finished flag is set when the callback has been called and is used to + reject extraneous events from invoking the callback again. */ + var loaded = false, focused = false, finished = false; + + function info(msg) { + if (!isChildProcess) { + SimpleTest.info(msg); + } + } + + function focusedWindow() { + if (isChildProcess) { + return Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager).focusedWindow; + } + return SpecialPowers.focusedWindow(); + } + + function getHref(aWindow) { + return isChildProcess ? aWindow.location.href : + SpecialPowers.getPrivilegedProps(aWindow, 'location.href'); + } + + /* Event listener for the load or focus events. It will also be called with + event equal to null to check if the page is already focused and loaded. */ + function focusedOrLoaded(event) { + try { + if (event) { + if (event.type == "load") { + if (expectBlankPage != (event.target.location == "about:blank")) { + return; + } + + loaded = true; + } else if (event.type == "focus") { + focused = true; + } + + event.currentTarget.removeEventListener(event.type, focusedOrLoaded, true); + } + + if (loaded && focused && !finished) { + finished = true; + if (isChildProcess) { + sendAsyncMessage("WaitForFocus:ChildFocused", {}, null); + } else { + SimpleTest._pendingWaitForFocusCount--; + SimpleTest.executeSoon(function() { callback(targetWindow) }); + } + } + } catch (e) { + if (!isChildProcess) { + SimpleTest.ok(false, "Exception caught in focusedOrLoaded: " + e.message + + ", at: " + e.fileName + " (" + e.lineNumber + ")"); + } + } + } + + function waitForLoadAndFocusOnWindow(desiredWindow) { + /* If the current document is about:blank and we are not expecting a blank + page (or vice versa), and the document has not yet loaded, wait for the + page to load. A common situation is to wait for a newly opened window + to load its content, and we want to skip over any intermediate blank + pages that load. This issue is described in bug 554873. */ + loaded = expectBlankPage ? + getHref(desiredWindow) == "about:blank" : + getHref(desiredWindow) != "about:blank" && + desiredWindow.document.readyState == "complete"; + if (!loaded) { + info("must wait for load"); + desiredWindow.addEventListener("load", focusedOrLoaded, true); + } + + var childDesiredWindow = { }; + if (isChildProcess) { + var fm = Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager); + fm.getFocusedElementForWindow(desiredWindow, true, childDesiredWindow); + childDesiredWindow = childDesiredWindow.value; + } else { + childDesiredWindow = SpecialPowers.getFocusedElementForWindow(desiredWindow, true); + } + + /* If this is a child frame, ensure that the frame is focused. */ + focused = (focusedWindow() == childDesiredWindow); + if (!focused) { + info("must wait for focus"); + childDesiredWindow.addEventListener("focus", focusedOrLoaded, true); + if (isChildProcess) { + childDesiredWindow.focus(); + } + else { + SpecialPowers.focus(childDesiredWindow); + } + } + + focusedOrLoaded(null); + } + + if (isChildProcess) { + /* This message is used when an inner child frame must be focused. */ + addMessageListener("WaitForFocus:FocusChild", function focusChild(msg) { + removeMessageListener("WaitForFocus:FocusChild", focusChild); + finished = false; + waitForLoadAndFocusOnWindow(msg.objects.child); + }); + } + + waitForLoadAndFocusOnWindow(targetWindow); + } + + SimpleTest._pendingWaitForFocusCount++; + if (!targetWindow) { + targetWindow = window; + } + + expectBlankPage = !!expectBlankPage; + + // If this is a request to focus a remote child window, the request must + // be forwarded to the child process. + // XXXndeakin now sure what this issue with Components.utils is about, but + // browser tests require the former and plain tests require the latter. + var Cu = Components.utils || SpecialPowers.Cu; + var Ci = Components.interfaces || SpecialPowers.Ci; + + var browser = null; + if (typeof(XULElement) != "undefined" && + targetWindow instanceof XULElement && + targetWindow.localName == "browser") { + browser = targetWindow; + } + + var isWrapper = Cu.isCrossProcessWrapper(targetWindow); + if (isWrapper || (browser && browser.isRemoteBrowser)) { + var mustFocusSubframe = false; + if (isWrapper) { + // Look for a tabbrowser and see if targetWindow corresponds to one + // within that tabbrowser. If not, just return. + var tabBrowser = document.getElementsByTagName("tabbrowser")[0] || null; + browser = tabBrowser ? tabBrowser.getBrowserForContentWindow(targetWindow.top) : null; + if (!browser) { + SimpleTest.info("child process window cannot be focused"); + return; + } + + mustFocusSubframe = (targetWindow != targetWindow.top); + } + + // If a subframe in a child process needs to be focused, first focus the + // parent frame, then send a WaitForFocus:FocusChild message to the child + // containing the subframe to focus. + browser.messageManager.addMessageListener("WaitForFocus:ChildFocused", function waitTest(msg) { + if (mustFocusSubframe) { + mustFocusSubframe = false; + var mm = gBrowser.selectedBrowser.messageManager; + mm.sendAsyncMessage("WaitForFocus:FocusChild", {}, { child: targetWindow } ); + } + else { + browser.messageManager.removeMessageListener("WaitForFocus:ChildFocused", waitTest); + SimpleTest._pendingWaitForFocusCount--; + setTimeout(callback, 0, browser ? browser.contentWindowAsCPOW : targetWindow); + } + }); + + // Serialize the waitForFocusInner function and run it in the child process. + var frameScript = "data:,(" + waitForFocusInner.toString() + + ")(content, true, " + expectBlankPage + ");"; + browser.messageManager.loadFrameScript(frameScript, true); + browser.focus(); + } + else { + // Otherwise, this is an attempt to focus a single process or parent window, + // so pass false for isChildProcess. + if (browser) { + targetWindow = browser.contentWindow; + } + + waitForFocusInner(targetWindow, false, expectBlankPage); + } +}; + +SimpleTest.waitForClipboard_polls = 0; + +/* + * Polls the clipboard waiting for the expected value. A known value different than + * the expected value is put on the clipboard first (and also polled for) so we + * can be sure the value we get isn't just the expected value because it was already + * on the clipboard. This only uses the global clipboard and only for text/unicode + * values. + * + * @param aExpectedStringOrValidatorFn + * The string value that is expected to be on the clipboard or a + * validator function getting cripboard data and returning a bool. + * @param aSetupFn + * A function responsible for setting the clipboard to the expected value, + * called after the known value setting succeeds. + * @param aSuccessFn + * A function called when the expected value is found on the clipboard. + * @param aFailureFn + * A function called if the expected value isn't found on the clipboard + * within 5s. It can also be called if the known value can't be found. + * @param aFlavor [optional] The flavor to look for. Defaults to "text/unicode". + * @param aTimeout [optional] + * The timeout (in milliseconds) to wait for a clipboard change. + * Defaults to 5000. + * @param aExpectFailure [optional] + * If true, fail if the clipboard contents are modified within the timeout + * interval defined by aTimeout. When aExpectFailure is true, the argument + * aExpectedStringOrValidatorFn must be null, as it won't be used. + * Defaults to false. + */ +SimpleTest.__waitForClipboardMonotonicCounter = 0; +SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () { + return SimpleTest.__waitForClipboardMonotonicCounter++; +}); +SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn, + aSuccessFn, aFailureFn, aFlavor, aTimeout, aExpectFailure) { + var requestedFlavor = aFlavor || "text/unicode"; + + // The known value we put on the clipboard before running aSetupFn + var initialVal = SimpleTest._waitForClipboardMonotonicCounter + + "-waitForClipboard-known-value"; + + var inputValidatorFn; + if (aExpectFailure) { + // If we expect failure, the aExpectedStringOrValidatorFn should be null + if (aExpectedStringOrValidatorFn !== null) { + SimpleTest.ok(false, "When expecting failure, aExpectedStringOrValidatorFn must be null"); + } + + inputValidatorFn = function(aData) { + return aData != initialVal; + }; + } else { + // Build a default validator function for common string input. + inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string" + ? function(aData) { return aData == aExpectedStringOrValidatorFn; } + : aExpectedStringOrValidatorFn; + } + + var maxPolls = aTimeout ? aTimeout / 100 : 50; + + // reset for the next use + function reset() { + SimpleTest.waitForClipboard_polls = 0; + } + + var lastValue; + function wait(validatorFn, successFn, failureFn, flavor) { + if (SimpleTest.waitForClipboard_polls == 0) { + lastValue = undefined; + } + + if (++SimpleTest.waitForClipboard_polls > maxPolls) { + // Log the failure. + SimpleTest.ok(aExpectFailure, "Timed out while polling clipboard for pasted data"); + dump("Got this value: " + lastValue); + reset(); + failureFn(); + return; + } + + var data = SpecialPowers.getClipboardData(flavor); + + if (validatorFn(data)) { + // Don't show the success message when waiting for preExpectedVal + if (preExpectedVal) + preExpectedVal = null; + else + SimpleTest.ok(!aExpectFailure, "Clipboard has the given value"); + reset(); + successFn(); + } else { + lastValue = data; + SimpleTest._originalSetTimeout.apply(window, [function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100]); + } + } + + // First we wait for a known value different from the expected one. + var preExpectedVal = initialVal; + SpecialPowers.clipboardCopyString(preExpectedVal); + wait(function(aData) { return aData == preExpectedVal; }, + function() { + // Call the original setup fn + aSetupFn(); + wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor); + }, aFailureFn, "text/unicode"); +} + +/** + * Wait for a condition for a while (actually up to 3s here). + * + * @param aCond + * A function returns the result of the condition + * @param aCallback + * A function called after the condition is passed or timeout. + * @param aErrorMsg + * The message displayed when the condition failed to pass + * before timeout. + */ +SimpleTest.waitForCondition = function (aCond, aCallback, aErrorMsg) { + var tries = 0; + var interval = setInterval(() => { + if (tries >= 30) { + ok(false, aErrorMsg); + moveOn(); + return; + } + var conditionPassed; + try { + conditionPassed = aCond(); + } catch (e) { + ok(false, `${e}\n${e.stack}`); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + var moveOn = () => { clearInterval(interval); aCallback(); }; +}; +SimpleTest.promiseWaitForCondition = function (aCond, aErrorMsg) { + return new Promise(resolve => { + this.waitForCondition(aCond, resolve, aErrorMsg); + }); +}; + +/** + * Executes a function shortly after the call, but lets the caller continue + * working (or finish). + */ +SimpleTest.executeSoon = function(aFunc) { + if ("SpecialPowers" in window) { + return SpecialPowers.executeSoon(aFunc, window); + } + setTimeout(aFunc, 0); + return null; // Avoid warning. +}; + +SimpleTest.registerCleanupFunction = function(aFunc) { + SimpleTest._cleanupFunctions.push(aFunc); +}; + +SimpleTest.registerTimeoutFunction = function(aFunc) { + SimpleTest._timeoutFunctions.push(aFunc); +}; + +SimpleTest.testInChaosMode = function() { + if (SimpleTest._inChaosMode) { + // It's already enabled for this test, don't enter twice + return; + } + SpecialPowers.DOMWindowUtils.enterChaosMode(); + SimpleTest._inChaosMode = true; +}; + +SimpleTest.timeout = function() { + for (let func of SimpleTest._timeoutFunctions) { + func(); + } + SimpleTest._timeoutFunctions = []; +} + +/** + * Finishes the tests. This is automatically called, except when + * SimpleTest.waitForExplicitFinish() has been invoked. +**/ +SimpleTest.finish = function() { + if (SimpleTest._alreadyFinished) { + var err = "[SimpleTest.finish()] this test already called finish!"; + if (parentRunner) { + parentRunner.structuredLogger.error(err); + } else { + dump(err + '\n'); + } + } + + if (SimpleTest.expected == 'fail' && SimpleTest.num_failed <= 0) { + msg = 'We expected at least one failure'; + var test = {'result': false, 'name': 'fail-if condition in manifest', 'diag': msg}; + var successInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"}; + var failureInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"}; + + SimpleTest._logResult(test, successInfo, failureInfo); + SimpleTest._tests.push(test); + } + + SimpleTest._timeoutFunctions = []; + + SimpleTest.testsLength = SimpleTest._tests.length; + + SimpleTest._alreadyFinished = true; + + if (SimpleTest._inChaosMode) { + SpecialPowers.DOMWindowUtils.leaveChaosMode(); + SimpleTest._inChaosMode = false; + } + + var afterCleanup = function() { + SpecialPowers.removeFiles(); + + if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) { + SimpleTest.ok(false, "test left refresh driver under test control"); + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + } + if (SimpleTest._expectingUncaughtException) { + SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!"); + } + if (SimpleTest._pendingWaitForFocusCount != 0) { + SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0, + "[SimpleTest.finish()] waitForFocus() was called a " + + "different number of times from the number of " + + "callbacks run. Maybe the test terminated " + + "prematurely -- be sure to use " + + "SimpleTest.waitForExplicitFinish()."); + } + if (SimpleTest._tests.length == 0) { + SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. " + + "(You need to call ok(), is(), or similar " + + "functions at least once. Make sure you use " + + "SimpleTest.waitForExplicitFinish() if you need " + + "it.)"); + } + if (SimpleTest._expectingRegisteredServiceWorker) { + if (!SpecialPowers.isServiceWorkerRegistered()) { + SimpleTest.ok(false, "This test is expected to leave a service worker registered"); + } + } else { + if (SpecialPowers.isServiceWorkerRegistered()) { + SimpleTest.ok(false, "This test left a service worker registered without cleaning it up"); + } + } + + if (parentRunner) { + /* We're running in an iframe, and the parent has a TestRunner */ + parentRunner.testFinished(SimpleTest._tests); + } + + if (!parentRunner || parentRunner.showTestReport) { + SpecialPowers.flushPermissions(function () { + SpecialPowers.flushPrefEnv(function() { + SimpleTest.showReport(); + }); + }); + } + } + + var executeCleanupFunction = function() { + var func = SimpleTest._cleanupFunctions.pop(); + + if (!func) { + afterCleanup(); + return; + } + + var ret; + try { + ret = func(); + } catch (ex) { + SimpleTest.ok(false, "Cleanup function threw exception: " + ex); + } + + if (ret && ret.constructor.name == "Promise") { + ret.then(executeCleanupFunction, + (ex) => SimpleTest.ok(false, "Cleanup promise rejected: " + ex)); + } else { + executeCleanupFunction(); + } + }; + + executeCleanupFunction(); +}; + +/** + * Monitor console output from now until endMonitorConsole is called. + * + * Expect to receive all console messages described by the elements of + * |msgs|, an array, in the order listed in |msgs|; each element is an + * object which may have any number of the following properties: + * message, errorMessage, sourceName, sourceLine, category: + * string or regexp + * lineNumber, columnNumber: number + * isScriptError, isWarning, isException, isStrict: boolean + * Strings, numbers, and booleans must compare equal to the named + * property of the Nth console message. Regexps must match. Any + * fields present in the message but not in the pattern object are ignored. + * + * In addition to the above properties, elements in |msgs| may have a |forbid| + * boolean property. When |forbid| is true, a failure is logged each time a + * matching message is received. + * + * If |forbidUnexpectedMsgs| is true, then the messages received in the console + * must exactly match the non-forbidden messages in |msgs|; for each received + * message not described by the next element in |msgs|, a failure is logged. If + * false, then other non-forbidden messages are ignored, but all expected + * messages must still be received. + * + * After endMonitorConsole is called, |continuation| will be called + * asynchronously. (Normally, you will want to pass |SimpleTest.finish| here.) + * + * It is incorrect to use this function in a test which has not called + * SimpleTest.waitForExplicitFinish. + */ +SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) { + if (SimpleTest._stopOnLoad) { + ok(false, "Console monitoring requires use of waitForExplicitFinish."); + } + + function msgMatches(msg, pat) { + for (var k in pat) { + if (!(k in msg)) { + return false; + } + if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') { + if (!pat[k].test(msg[k])) { + return false; + } + } else if (msg[k] !== pat[k]) { + return false; + } + } + return true; + } + + var forbiddenMsgs = []; + var i = 0; + while (i < msgs.length) { + var pat = msgs[i]; + if ("forbid" in pat) { + var forbid = pat.forbid; + delete pat.forbid; + if (forbid) { + forbiddenMsgs.push(pat); + msgs.splice(i, 1); + continue; + } + } + i++; + } + + var counter = 0; + var assertionLabel = msgs.toSource(); + function listener(msg) { + if (msg.message === "SENTINEL" && !msg.isScriptError) { + is(counter, msgs.length, + "monitorConsole | number of messages " + assertionLabel); + SimpleTest.executeSoon(continuation); + return; + } + for (var pat of forbiddenMsgs) { + if (msgMatches(msg, pat)) { + ok(false, "monitorConsole | observed forbidden message " + + JSON.stringify(msg)); + return; + } + } + if (counter >= msgs.length) { + var str = "monitorConsole | extra message | " + JSON.stringify(msg); + if (forbidUnexpectedMsgs) { + ok(false, str); + } else { + info(str); + } + return; + } + var matches = msgMatches(msg, msgs[counter]); + if (forbidUnexpectedMsgs) { + ok(matches, "monitorConsole | [" + counter + "] must match " + + JSON.stringify(msg)); + } else { + info("monitorConsole | [" + counter + "] " + + (matches ? "matched " : "did not match ") + JSON.stringify(msg)); + } + if (matches) + counter++; + } + SpecialPowers.registerConsoleListener(listener); +}; + +/** + * Stop monitoring console output. + */ +SimpleTest.endMonitorConsole = function () { + SpecialPowers.postConsoleSentinel(); +}; + +/** + * Run |testfn| synchronously, and monitor its console output. + * + * |msgs| is handled as described above for monitorConsole. + * + * After |testfn| returns, console monitoring will stop, and + * |continuation| will be called asynchronously. + */ +SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) { + SimpleTest.monitorConsole(continuation, msgs); + testfn(); + SimpleTest.executeSoon(SimpleTest.endMonitorConsole); +}; + +/** + * Wrapper around |expectConsoleMessages| for the case where the test has + * only one |testfn| to run. + */ +SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) { + SimpleTest.waitForExplicitFinish(); + SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish); +}; + +/** + * Indicates to the test framework that the current test expects one or + * more crashes (from plugins or IPC documents), and that the minidumps from + * those crashes should be removed. + */ +SimpleTest.expectChildProcessCrash = function () { + if (parentRunner) { + parentRunner.expectChildProcessCrash(); + } +}; + +/** + * Indicates to the test framework that the next uncaught exception during + * the test is expected, and should not cause a test failure. + */ +SimpleTest.expectUncaughtException = function (aExpecting) { + SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting; +}; + +/** + * Returns whether the test has indicated that it expects an uncaught exception + * to occur. + */ +SimpleTest.isExpectingUncaughtException = function () { + return SimpleTest._expectingUncaughtException; +}; + +/** + * Indicates to the test framework that all of the uncaught exceptions + * during the test are known problems that should be fixed in the future, + * but which should not cause the test to fail currently. + */ +SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) { + SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring; +}; + +/** + * Returns whether the test has indicated that all uncaught exceptions should be + * ignored. + */ +SimpleTest.isIgnoringAllUncaughtExceptions = function () { + return SimpleTest._ignoringAllUncaughtExceptions; +}; + +/** + * Indicates to the test framework that this test is expected to leave a + * service worker registered when it finishes. + */ +SimpleTest.expectRegisteredServiceWorker = function () { + SimpleTest._expectingRegisteredServiceWorker = true; +}; + +/** + * Resets any state this SimpleTest object has. This is important for + * browser chrome mochitests, which reuse the same SimpleTest object + * across a run. + */ +SimpleTest.reset = function () { + SimpleTest._ignoringAllUncaughtExceptions = false; + SimpleTest._expectingUncaughtException = false; + SimpleTest._expectingRegisteredServiceWorker = false; + SimpleTest._bufferedMessages = []; +}; + +if (isPrimaryTestWindow) { + addLoadEvent(function() { + if (SimpleTest._stopOnLoad) { + SimpleTest.finish(); + } + }); +} + +// --------------- Test.Builder/Test.More isDeeply() ----------------- + + +SimpleTest.DNE = {dne: 'Does not exist'}; +SimpleTest.LF = "\r\n"; + + +SimpleTest._deepCheck = function (e1, e2, stack, seen) { + var ok = false; + if (Object.is(e1, e2)) { + // Handles identical primitives and references. + ok = true; + } else if (typeof e1 != "object" || typeof e2 != "object" || e1 === null || e2 === null) { + // If either argument is a primitive or function, don't consider the arguments the same. + ok = false; + } else if (e1 == SimpleTest.DNE || e2 == SimpleTest.DNE) { + ok = false; + } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) { + ok = SimpleTest._eqArray(e1, e2, stack, seen); + } else { + ok = SimpleTest._eqAssoc(e1, e2, stack, seen); + } + return ok; +}; + +SimpleTest._eqArray = function (a1, a2, stack, seen) { + // Return if they're the same object. + if (a1 == a2) return true; + + // JavaScript objects have no unique identifiers, so we have to store + // references to them all in an array, and then compare the references + // directly. It's slow, but probably won't be much of an issue in + // practice. Start by making a local copy of the array to as to avoid + // confusing a reference seen more than once (such as [a, a]) for a + // circular reference. + for (var j = 0; j < seen.length; j++) { + if (seen[j][0] == a1) { + return seen[j][1] == a2; + } + } + + // If we get here, we haven't seen a1 before, so store it with reference + // to a2. + seen.push([ a1, a2 ]); + + var ok = true; + // Only examines enumerable attributes. Only works for numeric arrays! + // Associative arrays return 0. So call _eqAssoc() for them, instead. + var max = Math.max(a1.length, a2.length); + if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen); + for (var i = 0; i < max; i++) { + var e1 = i < a1.length ? a1[i] : SimpleTest.DNE; + var e2 = i < a2.length ? a2[i] : SimpleTest.DNE; + stack.push({ type: 'Array', idx: i, vals: [e1, e2] }); + ok = SimpleTest._deepCheck(e1, e2, stack, seen); + if (ok) { + stack.pop(); + } else { + break; + } + } + return ok; +}; + +SimpleTest._eqAssoc = function (o1, o2, stack, seen) { + // Return if they're the same object. + if (o1 == o2) return true; + + // JavaScript objects have no unique identifiers, so we have to store + // references to them all in an array, and then compare the references + // directly. It's slow, but probably won't be much of an issue in + // practice. Start by making a local copy of the array to as to avoid + // confusing a reference seen more than once (such as [a, a]) for a + // circular reference. + seen = seen.slice(0); + for (var j = 0; j < seen.length; j++) { + if (seen[j][0] == o1) { + return seen[j][1] == o2; + } + } + + // If we get here, we haven't seen o1 before, so store it with reference + // to o2. + seen.push([ o1, o2 ]); + + // They should be of the same class. + + var ok = true; + // Only examines enumerable attributes. + var o1Size = 0; for (var i in o1) o1Size++; + var o2Size = 0; for (var i in o2) o2Size++; + var bigger = o1Size > o2Size ? o1 : o2; + for (var i in bigger) { + var e1 = i in o1 ? o1[i] : SimpleTest.DNE; + var e2 = i in o2 ? o2[i] : SimpleTest.DNE; + stack.push({ type: 'Object', idx: i, vals: [e1, e2] }); + ok = SimpleTest._deepCheck(e1, e2, stack, seen) + if (ok) { + stack.pop(); + } else { + break; + } + } + return ok; +}; + +SimpleTest._formatStack = function (stack) { + var variable = '$Foo'; + for (var i = 0; i < stack.length; i++) { + var entry = stack[i]; + var type = entry['type']; + var idx = entry['idx']; + if (idx != null) { + if (type == 'Array') { + // Numeric array index. + variable += '[' + idx + ']'; + } else { + // Associative array index. + idx = idx.replace("'", "\\'"); + variable += "['" + idx + "']"; + } + } + } + + var vals = stack[stack.length-1]['vals'].slice(0, 2); + var vars = [ + variable.replace('$Foo', 'got'), + variable.replace('$Foo', 'expected') + ]; + + var out = "Structures begin differing at:" + SimpleTest.LF; + for (var i = 0; i < vals.length; i++) { + var val = vals[i]; + if (val === SimpleTest.DNE) { + val = "Does not exist"; + } else { + val = repr(val); + } + out += vars[i] + ' = ' + val + SimpleTest.LF; + } + + return ' ' + out; +}; + + +SimpleTest.isDeeply = function (it, as, name) { + var stack = [{ vals: [it, as] }]; + var seen = []; + if ( SimpleTest._deepCheck(it, as, stack, seen)) { + SimpleTest.ok(true, name); + } else { + SimpleTest.ok(false, name, SimpleTest._formatStack(stack)); + } +}; + +SimpleTest.typeOf = function (object) { + var c = Object.prototype.toString.apply(object); + var name = c.substring(8, c.length - 1); + if (name != 'Object') return name; + // It may be a non-core class. Try to extract the class name from + // the constructor function. This may not work in all implementations. + if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) { + return RegExp.$1; + } + // No idea. :-( + return name; +}; + +SimpleTest.isa = function (object, clas) { + return SimpleTest.typeOf(object) == clas; +}; + +// Global symbols: +var ok = SimpleTest.ok; +var is = SimpleTest.is; +var isfuzzy = SimpleTest.isfuzzy; +var isnot = SimpleTest.isnot; +var todo = SimpleTest.todo; +var todo_is = SimpleTest.todo_is; +var todo_isnot = SimpleTest.todo_isnot; +var isDeeply = SimpleTest.isDeeply; +var info = SimpleTest.info; + +var gOldOnError = window.onerror; +window.onerror = function simpletestOnerror(errorMsg, url, lineNumber, + columnNumber, originalException) { + // Log the message. + // XXX Chrome mochitests sometimes trigger this window.onerror handler, + // but there are a number of uncaught JS exceptions from those tests. + // For now, for tests that self identify as having unintentional uncaught + // exceptions, just dump it so that the error is visible but doesn't cause + // a test failure. See bug 652494. + var isExpected = !!SimpleTest._expectingUncaughtException; + var message = (isExpected ? "expected " : "") + "uncaught exception"; + var error = errorMsg + " at "; + try { + error += originalException.stack; + } catch (e) { + // At least use the url+line+column we were given + error += url + ":" + lineNumber + ":" + columnNumber; + } + if (!SimpleTest._ignoringAllUncaughtExceptions) { + // Don't log if SimpleTest.finish() is already called, it would cause failures + if (!SimpleTest._alreadyFinished) + SimpleTest.ok(isExpected, message, error); + SimpleTest._expectingUncaughtException = false; + } else { + SimpleTest.todo(false, message + ": " + error); + } + // There is no Components.stack.caller to log. (See bug 511888.) + + // Call previous handler. + if (gOldOnError) { + try { + // Ignore return value: always run default handler. + gOldOnError(errorMsg, url, lineNumber); + } catch (e) { + // Log the error. + SimpleTest.info("Exception thrown by gOldOnError(): " + e); + // Log its stack. + if (e.stack) { + SimpleTest.info("JavaScript error stack:\n" + e.stack); + } + } + } + + if (!SimpleTest._stopOnLoad && !isExpected && !SimpleTest._alreadyFinished) { + // Need to finish() manually here, yet let the test actually end first. + SimpleTest.executeSoon(SimpleTest.finish); + } +}; + +// Lifted from dom/media/test/manifest.js +// Make sure to not touch navigator in here, since we want to push prefs that +// will affect the APIs it exposes, but the set of exposed APIs is determined +// when Navigator.prototype is created. So if we touch navigator before pushing +// the prefs, the APIs it exposes will not take those prefs into account. We +// work around this by using a navigator object from a different global for our +// UA string testing. +var gAndroidSdk = null; +function getAndroidSdk() { + if (gAndroidSdk === null) { + var iframe = document.documentElement.appendChild(document.createElement("iframe")); + iframe.style.display = "none"; + var nav = iframe.contentWindow.navigator; + if (nav.userAgent.indexOf("Mobile") == -1 && + nav.userAgent.indexOf("Tablet") == -1) { + gAndroidSdk = -1; + } else { + // See nsSystemInfo.cpp, the getProperty('version') returns different value + // on each platforms, so we need to distinguish the android platform. + var versionString = nav.userAgent.indexOf("Android") != -1 ? + 'version' : 'sdk_version'; + gAndroidSdk = SpecialPowers.Cc['@mozilla.org/system-info;1'] + .getService(SpecialPowers.Ci.nsIPropertyBag2) + .getProperty(versionString); + } + document.documentElement.removeChild(iframe); + } + return gAndroidSdk; +} diff --git a/testing/mochitest/tests/SimpleTest/SpawnTask.js b/testing/mochitest/tests/SimpleTest/SpawnTask.js new file mode 100644 index 000000000..7ac598f88 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/SpawnTask.js @@ -0,0 +1,296 @@ +// # SpawnTask.js +// Directly copied from the "co" library by TJ Holowaychuk. +// See https://github.com/tj/co/tree/4.6.0 +// For use with mochitest-plain and mochitest-chrome. + +// spawn_task(generatorFunction): +// Expose only the `co` function, which is very similar to Task.spawn in Task.jsm. +// We call this function spawn_task to make its purpose more plain, and to +// reduce the chance of name collisions. +var spawn_task = (function () { + +/** + * slice() reference. + */ + +var slice = Array.prototype.slice; + +/** + * Wrap the given generator `fn` into a + * function that returns a promise. + * This is a separate function so that + * every `co()` call doesn't create a new, + * unnecessary closure. + * + * @param {GeneratorFunction} fn + * @return {Function} + * @api public + */ + +co.wrap = function (fn) { + createPromise.__generatorFunction__ = fn; + return createPromise; + function createPromise() { + return co.call(this, fn.apply(this, arguments)); + } +}; + +/** + * Execute the generator function or a generator + * and return a promise. + * + * @param {Function} fn + * @return {Promise} + * @api public + */ + +function co(gen) { + var ctx = this; + var args = slice.call(arguments, 1) + + // we wrap everything in a promise to avoid promise chaining, + // which leads to memory leak errors. + // see https://github.com/tj/co/issues/180 + return new Promise(function(resolve, reject) { + if (typeof gen === 'function') gen = gen.apply(ctx, args); + if (!gen || typeof gen.next !== 'function') return resolve(gen); + + onFulfilled(); + + /** + * @param {Mixed} res + * @return {Promise} + * @api private + */ + + function onFulfilled(res) { + var ret; + try { + ret = gen.next(res); + } catch (e) { + return reject(e); + } + next(ret); + } + + /** + * @param {Error} err + * @return {Promise} + * @api private + */ + + function onRejected(err) { + var ret; + try { + ret = gen.throw(err); + } catch (e) { + return reject(e); + } + next(ret); + } + + /** + * Get the next value in the generator, + * return a promise. + * + * @param {Object} ret + * @return {Promise} + * @api private + */ + + function next(ret) { + if (ret.done) return resolve(ret.value); + var value = toPromise.call(ctx, ret.value); + if (value && isPromise(value)) return value.then(onFulfilled, onRejected); + return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + + 'but the following object was passed: "' + String(ret.value) + '"')); + } + }); +} + +/** + * Convert a `yield`ed value into a promise. + * + * @param {Mixed} obj + * @return {Promise} + * @api private + */ + +function toPromise(obj) { + if (!obj) return obj; + if (isPromise(obj)) return obj; + if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); + if ('function' == typeof obj) return thunkToPromise.call(this, obj); + if (Array.isArray(obj)) return arrayToPromise.call(this, obj); + if (isObject(obj)) return objectToPromise.call(this, obj); + return obj; +} + +/** + * Convert a thunk to a promise. + * + * @param {Function} + * @return {Promise} + * @api private + */ + +function thunkToPromise(fn) { + var ctx = this; + return new Promise(function (resolve, reject) { + fn.call(ctx, function (err, res) { + if (err) return reject(err); + if (arguments.length > 2) res = slice.call(arguments, 1); + resolve(res); + }); + }); +} + +/** + * Convert an array of "yieldables" to a promise. + * Uses `Promise.all()` internally. + * + * @param {Array} obj + * @return {Promise} + * @api private + */ + +function arrayToPromise(obj) { + return Promise.all(obj.map(toPromise, this)); +} + +/** + * Convert an object of "yieldables" to a promise. + * Uses `Promise.all()` internally. + * + * @param {Object} obj + * @return {Promise} + * @api private + */ + +function objectToPromise(obj){ + var results = new obj.constructor(); + var keys = Object.keys(obj); + var promises = []; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var promise = toPromise.call(this, obj[key]); + if (promise && isPromise(promise)) defer(promise, key); + else results[key] = obj[key]; + } + return Promise.all(promises).then(function () { + return results; + }); + + function defer(promise, key) { + // predefine the key in the result + results[key] = undefined; + promises.push(promise.then(function (res) { + results[key] = res; + })); + } +} + +/** + * Check if `obj` is a promise. + * + * @param {Object} obj + * @return {Boolean} + * @api private + */ + +function isPromise(obj) { + return 'function' == typeof obj.then; +} + +/** + * Check if `obj` is a generator. + * + * @param {Mixed} obj + * @return {Boolean} + * @api private + */ + +function isGenerator(obj) { + return 'function' == typeof obj.next && 'function' == typeof obj.throw; +} + +/** + * Check if `obj` is a generator function. + * + * @param {Mixed} obj + * @return {Boolean} + * @api private + */ +function isGeneratorFunction(obj) { + var constructor = obj.constructor; + if (!constructor) return false; + if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; + return isGenerator(constructor.prototype); +} + +/** + * Check for plain object. + * + * @param {Mixed} val + * @return {Boolean} + * @api private + */ + +function isObject(val) { + return Object == val.constructor; +} + +return co; +})(); + +// add_task(generatorFunction): +// Call `add_task(generatorFunction)` for each separate +// asynchronous task in a mochitest. Tasks are run consecutively. +// Before the first task, `SimpleTest.waitForExplicitFinish()` +// will be called automatically, and after the last task, +// `SimpleTest.finish()` will be called. +var add_task = (function () { + // The list of tasks to run. + var task_list = []; + // The "add_task" function + return function (generatorFunction) { + if (task_list.length === 0) { + // This is the first time add_task has been called. + // First, confirm that SimpleTest is available. + if (!SimpleTest) { + throw new Error("SimpleTest not available."); + } + // Don't stop tests until asynchronous tasks are finished. + SimpleTest.waitForExplicitFinish(); + // Because the client is using add_task for this set of tests, + // we need to spawn a "master task" that calls each task in succesion. + // Use setTimeout to ensure the master task runs after the client + // script finishes. + setTimeout(function () { + spawn_task(function* () { + // We stop the entire test file at the first exception because this + // may mean that the state of subsequent tests may be corrupt. + try { + for (var task of task_list) { + var name = task.name || ""; + info("SpawnTask.js | Entering test " + name); + yield task(); + info("SpawnTask.js | Leaving test " + name); + } + } catch (ex) { + try { + ok(false, "" + ex); + } catch (ex2) { + ok(false, "(The exception cannot be converted to string.)"); + } + } + // All tasks are finished. + SimpleTest.finish(); + }); + }); + } + // Add the task to the list of tasks to run after + // the main thread is finished. + task_list.push(generatorFunction); + }; +})(); diff --git a/testing/mochitest/tests/SimpleTest/TestRunner.js b/testing/mochitest/tests/SimpleTest/TestRunner.js new file mode 100644 index 000000000..aa0af2f20 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/TestRunner.js @@ -0,0 +1,754 @@ +/* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */ +/* + * e10s event dispatcher from content->chrome + * + * type = eventName (QuitApplication) + * data = json object {"filename":filename} <- for LoggerInit + */ + +"use strict"; + +function getElement(id) { + return ((typeof(id) == "string") ? + document.getElementById(id) : id); +} + +this.$ = this.getElement; + +function contentDispatchEvent(type, data, sync) { + if (typeof(data) == "undefined") { + data = {}; + } + + var event = new CustomEvent("contentEvent", { + bubbles: true, + detail: { + "sync": sync, + "type": type, + "data": JSON.stringify(data) + } + }); + document.dispatchEvent(event); +} + +function contentAsyncEvent(type, data) { + contentDispatchEvent(type, data, 0); +} + +/* Helper Function */ +function extend(obj, /* optional */ skip) { + // Extend an array with an array-like object starting + // from the skip index + if (!skip) { + skip = 0; + } + if (obj) { + var l = obj.length; + var ret = []; + for (var i = skip; i < l; i++) { + ret.push(obj[i]); + } + } + return ret; +} + +function flattenArguments(lst/* ...*/) { + var res = []; + var args = extend(arguments); + while (args.length) { + var o = args.shift(); + if (o && typeof(o) == "object" && typeof(o.length) == "number") { + for (var i = o.length - 1; i >= 0; i--) { + args.unshift(o[i]); + } + } else { + res.push(o); + } + } + return res; +} + +/** + * TestRunner: A test runner for SimpleTest + * TODO: + * + * * Avoid moving iframes: That causes reloads on mozilla and opera. + * + * +**/ +var TestRunner = {}; +TestRunner.logEnabled = false; +TestRunner._currentTest = 0; +TestRunner._lastTestFinished = -1; +TestRunner._loopIsRestarting = false; +TestRunner.currentTestURL = ""; +TestRunner.originalTestURL = ""; +TestRunner._urls = []; +TestRunner._lastAssertionCount = 0; +TestRunner._expectedMinAsserts = 0; +TestRunner._expectedMaxAsserts = 0; + +TestRunner.timeout = 5 * 60 * 1000; // 5 minutes. +TestRunner.maxTimeouts = 4; // halt testing after too many timeouts +TestRunner.runSlower = false; +TestRunner.dumpOutputDirectory = ""; +TestRunner.dumpAboutMemoryAfterTest = false; +TestRunner.dumpDMDAfterTest = false; +TestRunner.slowestTestTime = 0; +TestRunner.slowestTestURL = ""; +TestRunner.interactiveDebugger = false; + +TestRunner._expectingProcessCrash = false; +TestRunner._structuredFormatter = new StructuredFormatter(); + +/** + * Make sure the tests don't hang indefinitely. +**/ +TestRunner._numTimeouts = 0; +TestRunner._currentTestStartTime = new Date().valueOf(); +TestRunner._timeoutFactor = 1; + +TestRunner._checkForHangs = function() { + function reportError(win, msg) { + if ("SimpleTest" in win) { + win.SimpleTest.ok(false, msg); + } else if ("W3CTest" in win) { + win.W3CTest.logFailure(msg); + } + } + + function killTest(win) { + if ("SimpleTest" in win) { + win.SimpleTest.timeout(); + win.SimpleTest.finish(); + } else if ("W3CTest" in win) { + win.W3CTest.timeout(); + } + } + + if (TestRunner._currentTest < TestRunner._urls.length) { + var runtime = new Date().valueOf() - TestRunner._currentTestStartTime; + if (runtime >= TestRunner.timeout * TestRunner._timeoutFactor) { + var frameWindow = $('testframe').contentWindow.wrappedJSObject || + $('testframe').contentWindow; + // TODO : Do this in a way that reports that the test ended with a status "TIMEOUT" + reportError(frameWindow, "Test timed out."); + + // If we have too many timeouts, give up. We don't want to wait hours + // for results if some bug causes lots of tests to time out. + if (++TestRunner._numTimeouts >= TestRunner.maxTimeouts) { + TestRunner._haltTests = true; + + TestRunner.currentTestURL = "(SimpleTest/TestRunner.js)"; + reportError(frameWindow, TestRunner.maxTimeouts + " test timeouts, giving up."); + var skippedTests = TestRunner._urls.length - TestRunner._currentTest; + reportError(frameWindow, "Skipping " + skippedTests + " remaining tests."); + } + + // Add a little (1 second) delay to ensure automation.py has time to notice + // "Test timed out" log and process it (= take a screenshot). + setTimeout(function delayedKillTest() { killTest(frameWindow); }, 1000); + + if (TestRunner._haltTests) + return; + } + + setTimeout(TestRunner._checkForHangs, 30000); + } +} + +TestRunner.requestLongerTimeout = function(factor) { + TestRunner._timeoutFactor = factor; +} + +/** + * This is used to loop tests +**/ +TestRunner.repeat = 0; +TestRunner._currentLoop = 1; + +TestRunner.expectAssertions = function(min, max) { + if (typeof(max) == "undefined") { + max = min; + } + if (typeof(min) != "number" || typeof(max) != "number" || + min < 0 || max < min) { + throw "bad parameter to expectAssertions"; + } + TestRunner._expectedMinAsserts = min; + TestRunner._expectedMaxAsserts = max; +} + +/** + * This function is called after generating the summary. +**/ +TestRunner.onComplete = null; + +/** + * Adds a failed test case to a list so we can rerun only the failed tests + **/ +TestRunner._failedTests = {}; +TestRunner._failureFile = ""; + +TestRunner.addFailedTest = function(testName) { + if (TestRunner._failedTests[testName] == undefined) { + TestRunner._failedTests[testName] = ""; + } +}; + +TestRunner.setFailureFile = function(fileName) { + TestRunner._failureFile = fileName; +} + +TestRunner.generateFailureList = function () { + if (TestRunner._failureFile) { + var failures = new SpecialPowersLogger(TestRunner._failureFile); + failures.log(JSON.stringify(TestRunner._failedTests)); + failures.close(); + } +}; + +/** + * If logEnabled is true, this is the logger that will be used. + **/ + +// This delimiter is used to avoid interleaving Mochitest/Gecko logs. +var LOG_DELIMITER = String.fromCharCode(0xe175) + String.fromCharCode(0xee31) + String.fromCharCode(0x2c32) + String.fromCharCode(0xacbf); + +// A log callback for StructuredLog.jsm +TestRunner._dumpMessage = function(message) { + var str; + + // This is a directive to python to format these messages + // for compatibility with mozharness. This can be removed + // with the MochitestFormatter (see bug 1045525). + message.js_source = 'TestRunner.js' + if (TestRunner.interactiveDebugger && message.action in TestRunner._structuredFormatter) { + str = TestRunner._structuredFormatter[message.action](message); + } else { + str = LOG_DELIMITER + JSON.stringify(message) + LOG_DELIMITER; + } + // BUGFIX: browser-chrome tests don't use LogController + if (Object.keys(LogController.listeners).length !== 0) { + LogController.log(str); + } else { + dump('\n' + str + '\n'); + } + // Checking for error messages + if (message.expected || message.level === "ERROR") { + TestRunner.failureHandler(); + } +}; + +// From https://dxr.mozilla.org/mozilla-central/source/testing/modules/StructuredLog.jsm +TestRunner.structuredLogger = new StructuredLogger('mochitest', TestRunner._dumpMessage); +TestRunner.structuredLogger.deactivateBuffering = function() { + TestRunner.structuredLogger._logData("buffering_off"); +}; +TestRunner.structuredLogger.activateBuffering = function() { + TestRunner.structuredLogger._logData("buffering_on"); +}; + +TestRunner.log = function(msg) { + if (TestRunner.logEnabled) { + TestRunner.structuredLogger.info(msg); + } else { + dump(msg + "\n"); + } +}; + +TestRunner.error = function(msg) { + if (TestRunner.logEnabled) { + TestRunner.structuredLogger.error(msg); + } else { + dump(msg + "\n"); + TestRunner.failureHandler(); + } +}; + +TestRunner.failureHandler = function() { + if (TestRunner.runUntilFailure) { + TestRunner._haltTests = true; + } + + if (TestRunner.debugOnFailure) { + // You've hit this line because you requested to break into the + // debugger upon a testcase failure on your test run. + debugger; + } +}; + +/** + * Toggle element visibility +**/ +TestRunner._toggle = function(el) { + if (el.className == "noshow") { + el.className = ""; + el.style.cssText = ""; + } else { + el.className = "noshow"; + el.style.cssText = "width:0px; height:0px; border:0px;"; + } +}; + +/** + * Creates the iframe that contains a test +**/ +TestRunner._makeIframe = function (url, retry) { + var iframe = $('testframe'); + if (url != "about:blank" && + (("hasFocus" in document && !document.hasFocus()) || + ("activeElement" in document && document.activeElement != iframe))) { + + contentAsyncEvent("Focus"); + window.focus(); + SpecialPowers.focus(); + iframe.focus(); + if (retry < 3) { + window.setTimeout('TestRunner._makeIframe("'+url+'", '+(retry+1)+')', 1000); + return; + } + + TestRunner.structuredLogger.info("Error: Unable to restore focus, expect failures and timeouts."); + } + window.scrollTo(0, $('indicator').offsetTop); + iframe.src = url; + iframe.name = url; + iframe.width = "500"; + return iframe; +}; + +/** + * Returns the current test URL. + * We use this to tell whether the test has navigated to another test without + * being finished first. + */ +TestRunner.getLoadedTestURL = function () { + var prefix = ""; + // handle mochitest-chrome URIs + if ($('testframe').contentWindow.location.protocol == "chrome:") { + prefix = "chrome://mochitests"; + } + return prefix + $('testframe').contentWindow.location.pathname; +}; + +TestRunner.setParameterInfo = function (params) { + this._params = params; +}; + +TestRunner.getParameterInfo = function() { + return this._params; +}; + +/** + * TestRunner entry point. + * + * The arguments are the URLs of the test to be ran. + * +**/ +TestRunner.runTests = function (/*url...*/) { + TestRunner.structuredLogger.info("SimpleTest START"); + TestRunner.originalTestURL = $("current-test").innerHTML; + + SpecialPowers.registerProcessCrashObservers(); + + TestRunner._urls = flattenArguments(arguments); + + var singleTestRun = this._urls.length <= 1 && TestRunner.repeat <= 1; + TestRunner.showTestReport = singleTestRun; + var frame = $('testframe'); + frame.src = ""; + if (singleTestRun) { + // Can't use document.body because this runs in a XUL doc as well... + var body = document.getElementsByTagName("body")[0]; + body.setAttribute("singletest", "true"); + frame.removeAttribute("scrolling"); + } + TestRunner._checkForHangs(); + TestRunner.runNextTest(); +}; + +/** + * Used for running a set of tests in a loop for debugging purposes + * Takes an array of URLs +**/ +TestRunner.resetTests = function(listURLs) { + TestRunner._currentTest = 0; + // Reset our "Current-test" line - functionality depends on it + $("current-test").innerHTML = TestRunner.originalTestURL; + if (TestRunner.logEnabled) + TestRunner.structuredLogger.info("SimpleTest START Loop " + TestRunner._currentLoop); + + TestRunner._urls = listURLs; + $('testframe').src=""; + TestRunner._checkForHangs(); + TestRunner.runNextTest(); +} + +TestRunner.getNextUrl = function() { + var url = ""; + // sometimes we have a subtest/harness which doesn't use a manifest + if ((TestRunner._urls[TestRunner._currentTest] instanceof Object) && ('test' in TestRunner._urls[TestRunner._currentTest])) { + url = TestRunner._urls[TestRunner._currentTest]['test']['url']; + TestRunner.expected = TestRunner._urls[TestRunner._currentTest]['test']['expected']; + } else { + url = TestRunner._urls[TestRunner._currentTest]; + TestRunner.expected = 'pass'; + } + return url; +} + +/** + * Run the next test. If no test remains, calls onComplete(). + **/ +TestRunner._haltTests = false; +TestRunner.runNextTest = function() { + if (TestRunner._currentTest < TestRunner._urls.length && + !TestRunner._haltTests) + { + var url = TestRunner.getNextUrl(); + TestRunner.currentTestURL = url; + + $("current-test-path").innerHTML = url; + + TestRunner._currentTestStartTime = new Date().valueOf(); + TestRunner._timeoutFactor = 1; + TestRunner._expectedMinAsserts = 0; + TestRunner._expectedMaxAsserts = 0; + + TestRunner.structuredLogger.testStart(url); + + TestRunner._makeIframe(url, 0); + } else { + $("current-test").innerHTML = "<b>Finished</b>"; + // Only unload the last test to run if we're running more than one test. + if (TestRunner._urls.length > 1) { + TestRunner._makeIframe("about:blank", 0); + } + + var passCount = parseInt($("pass-count").innerHTML, 10); + var failCount = parseInt($("fail-count").innerHTML, 10); + var todoCount = parseInt($("todo-count").innerHTML, 10); + + if (passCount === 0 && + failCount === 0 && + todoCount === 0) + { + // No |$('testframe').contentWindow|, so manually update: ... + // ... the log, + TestRunner.structuredLogger.testEnd('SimpleTest/TestRunner.js', + "ERROR", + "OK", + "No checks actually run"); + // ... the count, + $("fail-count").innerHTML = 1; + // ... the indicator. + var indicator = $("indicator"); + indicator.innerHTML = "Status: Fail (No checks actually run)"; + indicator.style.backgroundColor = "red"; + } + + SpecialPowers.unregisterProcessCrashObservers(); + + let e10sMode = SpecialPowers.isMainProcess() ? "non-e10s" : "e10s"; + + TestRunner.structuredLogger.info("TEST-START | Shutdown"); + TestRunner.structuredLogger.info("Passed: " + passCount); + TestRunner.structuredLogger.info("Failed: " + failCount); + TestRunner.structuredLogger.info("Todo: " + todoCount); + TestRunner.structuredLogger.info("Mode: " + e10sMode); + TestRunner.structuredLogger.info("Slowest: " + TestRunner.slowestTestTime + 'ms - ' + TestRunner.slowestTestURL); + + // If we are looping, don't send this cause it closes the log file + if (TestRunner.repeat === 0) { + TestRunner.structuredLogger.info("SimpleTest FINISHED"); + } + + if (TestRunner.repeat === 0 && TestRunner.onComplete) { + TestRunner.onComplete(); + } + + if (TestRunner._currentLoop <= TestRunner.repeat && !TestRunner._haltTests) { + TestRunner._currentLoop++; + TestRunner.resetTests(TestRunner._urls); + TestRunner._loopIsRestarting = true; + } else { + // Loops are finished + if (TestRunner.logEnabled) { + TestRunner.structuredLogger.info("TEST-INFO | Ran " + TestRunner._currentLoop + " Loops"); + TestRunner.structuredLogger.info("SimpleTest FINISHED"); + } + + if (TestRunner.onComplete) + TestRunner.onComplete(); + } + TestRunner.generateFailureList(); + } +}; + +TestRunner.expectChildProcessCrash = function() { + TestRunner._expectingProcessCrash = true; +}; + +/** + * This stub is called by SimpleTest when a test is finished. +**/ +TestRunner.testFinished = function(tests) { + // Prevent a test from calling finish() multiple times before we + // have a chance to unload it. + if (TestRunner._currentTest == TestRunner._lastTestFinished && + !TestRunner._loopIsRestarting) { + TestRunner.structuredLogger.testEnd(TestRunner.currentTestURL, + "ERROR", + "OK", + "called finish() multiple times"); + TestRunner.updateUI([{ result: false }]); + return; + } + TestRunner._lastTestFinished = TestRunner._currentTest; + TestRunner._loopIsRestarting = false; + + // TODO : replace this by a function that returns the mem data as an object + // that's dumped later with the test_end message + MemoryStats.dump(TestRunner._currentTest, + TestRunner.currentTestURL, + TestRunner.dumpOutputDirectory, + TestRunner.dumpAboutMemoryAfterTest, + TestRunner.dumpDMDAfterTest); + + function cleanUpCrashDumpFiles() { + if (!SpecialPowers.removeExpectedCrashDumpFiles(TestRunner._expectingProcessCrash)) { + TestRunner.structuredLogger.testEnd(TestRunner.currentTestURL, + "ERROR", + "OK", + "This test did not leave any crash dumps behind, but we were expecting some!"); + tests.push({ result: false }); + } + var unexpectedCrashDumpFiles = + SpecialPowers.findUnexpectedCrashDumpFiles(); + TestRunner._expectingProcessCrash = false; + if (unexpectedCrashDumpFiles.length) { + TestRunner.structuredLogger.testEnd(TestRunner.currentTestURL, + "ERROR", + "OK", + "This test left crash dumps behind, but we " + + "weren't expecting it to!", + {unexpected_crashdump_files: unexpectedCrashDumpFiles}); + tests.push({ result: false }); + unexpectedCrashDumpFiles.sort().forEach(function(aFilename) { + TestRunner.structuredLogger.info("Found unexpected crash dump file " + + aFilename + "."); + }); + } + } + + function runNextTest() { + if (TestRunner.currentTestURL != TestRunner.getLoadedTestURL()) { + TestRunner.structuredLogger.testStatus(TestRunner.currentTestURL, + TestRunner.getLoadedTestURL(), + "FAIL", + "PASS", + "finished in a non-clean fashion, probably" + + " because it didn't call SimpleTest.finish()", + {loaded_test_url: TestRunner.getLoadedTestURL()}); + tests.push({ result: false }); + } + + var runtime = new Date().valueOf() - TestRunner._currentTestStartTime; + + TestRunner.structuredLogger.testEnd(TestRunner.currentTestURL, + "OK", + undefined, + "Finished in " + runtime + "ms", + {runtime: runtime} + ); + + if (TestRunner.slowestTestTime < runtime && TestRunner._timeoutFactor >= 1) { + TestRunner.slowestTestTime = runtime; + TestRunner.slowestTestURL = TestRunner.currentTestURL; + } + + TestRunner.updateUI(tests); + + // Don't show the interstitial if we just run one test with no repeats: + if (TestRunner._urls.length == 1 && TestRunner.repeat <= 1) { + TestRunner.testUnloaded(); + return; + } + + var interstitialURL; + if ($('testframe').contentWindow.location.protocol == "chrome:") { + interstitialURL = "tests/SimpleTest/iframe-between-tests.html"; + } else { + interstitialURL = "/tests/SimpleTest/iframe-between-tests.html"; + } + // check if there were test run after SimpleTest.finish, which should never happen + $('testframe').contentWindow.addEventListener('unload', function() { + var testwin = $('testframe').contentWindow; + if (testwin.SimpleTest && testwin.SimpleTest._tests.length != testwin.SimpleTest.testsLength) { + var wrongtestlength = testwin.SimpleTest._tests.length - testwin.SimpleTest.testsLength; + var wrongtestname = ''; + for (var i = 0; i < wrongtestlength; i++) { + wrongtestname = testwin.SimpleTest._tests[testwin.SimpleTest.testsLength + i].name; + TestRunner.structuredLogger.testStatus(TestRunner.currentTestURL, wrongtestname, 'FAIL', 'PASS', "Result logged after SimpleTest.finish()"); + } + TestRunner.updateUI([{ result: false }]); + } + } , false); + TestRunner._makeIframe(interstitialURL, 0); + } + + SpecialPowers.executeAfterFlushingMessageQueue(function() { + cleanUpCrashDumpFiles(); + SpecialPowers.flushPermissions(function () { SpecialPowers.flushPrefEnv(runNextTest); }); + }); +}; + +TestRunner.testUnloaded = function() { + // If we're in a debug build, check assertion counts. This code is + // similar to the code in Tester_nextTest in browser-test.js used + // for browser-chrome mochitests. + if (SpecialPowers.isDebugBuild) { + var newAssertionCount = SpecialPowers.assertionCount(); + var numAsserts = newAssertionCount - TestRunner._lastAssertionCount; + TestRunner._lastAssertionCount = newAssertionCount; + + var url = TestRunner.getNextUrl(); + var max = TestRunner._expectedMaxAsserts; + var min = TestRunner._expectedMinAsserts; + if (numAsserts > max) { + TestRunner.structuredLogger.testEnd(url, + "ERROR", + "OK", + "Assertion count " + numAsserts + " is greater than expected range " + + min + "-" + max + " assertions.", + {assertions: numAsserts, min_asserts: min, max_asserts: max}); + TestRunner.updateUI([{ result: false }]); + } else if (numAsserts < min) { + TestRunner.structuredLogger.testEnd(url, + "OK", + "ERROR", + "Assertion count " + numAsserts + " is less than expected range " + + min + "-" + max + " assertions.", + {assertions: numAsserts, min_asserts: min, max_asserts: max}); + TestRunner.updateUI([{ result: false }]); + } else if (numAsserts > 0) { + TestRunner.structuredLogger.testEnd(url, + "ERROR", + "ERROR", + "Assertion count " + numAsserts + " within expected range " + + min + "-" + max + " assertions.", + {assertions: numAsserts, min_asserts: min, max_asserts: max}); + } + } + TestRunner._currentTest++; + if (TestRunner.runSlower) { + setTimeout(TestRunner.runNextTest, 1000); + } else { + TestRunner.runNextTest(); + } +}; + +/** + * Get the results. + */ +TestRunner.countResults = function(tests) { + var nOK = 0; + var nNotOK = 0; + var nTodo = 0; + for (var i = 0; i < tests.length; ++i) { + var test = tests[i]; + if (test.todo && !test.result) { + nTodo++; + } else if (test.result && !test.todo) { + nOK++; + } else { + nNotOK++; + } + } + return {"OK": nOK, "notOK": nNotOK, "todo": nTodo}; +} + +/** + * Print out table of any error messages found during looped run + */ +TestRunner.displayLoopErrors = function(tableName, tests) { + if(TestRunner.countResults(tests).notOK >0){ + var table = $(tableName); + var curtest; + if (table.rows.length == 0) { + //if table headers are not yet generated, make them + var row = table.insertRow(table.rows.length); + var cell = row.insertCell(0); + var textNode = document.createTextNode("Test File Name:"); + cell.appendChild(textNode); + cell = row.insertCell(1); + textNode = document.createTextNode("Test:"); + cell.appendChild(textNode); + cell = row.insertCell(2); + textNode = document.createTextNode("Error message:"); + cell.appendChild(textNode); + } + + //find the broken test + for (var testnum in tests){ + curtest = tests[testnum]; + if( !((curtest.todo && !curtest.result) || (curtest.result && !curtest.todo)) ){ + //this is a failed test or the result of todo test. Display the related message + row = table.insertRow(table.rows.length); + cell = row.insertCell(0); + textNode = document.createTextNode(TestRunner.currentTestURL); + cell.appendChild(textNode); + cell = row.insertCell(1); + textNode = document.createTextNode(curtest.name); + cell.appendChild(textNode); + cell = row.insertCell(2); + textNode = document.createTextNode((curtest.diag ? curtest.diag : "" )); + cell.appendChild(textNode); + } + } + } +} + +TestRunner.updateUI = function(tests) { + var results = TestRunner.countResults(tests); + var passCount = parseInt($("pass-count").innerHTML) + results.OK; + var failCount = parseInt($("fail-count").innerHTML) + results.notOK; + var todoCount = parseInt($("todo-count").innerHTML) + results.todo; + $("pass-count").innerHTML = passCount; + $("fail-count").innerHTML = failCount; + $("todo-count").innerHTML = todoCount; + + // Set the top Green/Red bar + var indicator = $("indicator"); + if (failCount > 0) { + indicator.innerHTML = "Status: Fail"; + indicator.style.backgroundColor = "red"; + } else if (passCount > 0) { + indicator.innerHTML = "Status: Pass"; + indicator.style.backgroundColor = "#0d0"; + } else { + indicator.innerHTML = "Status: ToDo"; + indicator.style.backgroundColor = "orange"; + } + + // Set the table values + var trID = "tr-" + $('current-test-path').innerHTML; + var row = $(trID); + + // Only update the row if it actually exists (autoUI) + if (row != null) { + var tds = row.getElementsByTagName("td"); + tds[0].style.backgroundColor = "#0d0"; + tds[0].innerHTML = parseInt(tds[0].innerHTML) + parseInt(results.OK); + tds[1].style.backgroundColor = results.notOK > 0 ? "red" : "#0d0"; + tds[1].innerHTML = parseInt(tds[1].innerHTML) + parseInt(results.notOK); + tds[2].style.backgroundColor = results.todo > 0 ? "orange" : "#0d0"; + tds[2].innerHTML = parseInt(tds[2].innerHTML) + parseInt(results.todo); + } + + //if we ran in a loop, display any found errors + if (TestRunner.repeat > 0) { + TestRunner.displayLoopErrors('fail-table', tests); + } +} diff --git a/testing/mochitest/tests/SimpleTest/WindowSnapshot.js b/testing/mochitest/tests/SimpleTest/WindowSnapshot.js new file mode 100644 index 000000000..c4ced41dd --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/WindowSnapshot.js @@ -0,0 +1,92 @@ +var gWindowUtils; + +try { + gWindowUtils = SpecialPowers.getDOMWindowUtils(window); + if (gWindowUtils && !gWindowUtils.compareCanvases) + gWindowUtils = null; +} catch (e) { + gWindowUtils = null; +} + +function snapshotWindow(win, withCaret) { + return SpecialPowers.snapshotWindow(win, withCaret); +} + +function snapshotRect(win, rect) { + return SpecialPowers.snapshotRect(win, rect); +} + +// If the two snapshots don't compare as expected (true for equal, false for +// unequal), returns their serializations as data URIs. In all cases, returns +// whether the comparison was as expected. +function compareSnapshots(s1, s2, expectEqual, fuzz) { + if (s1.width != s2.width || s1.height != s2.height) { + ok(false, "Snapshot canvases are not the same size - comparing them makes no sense"); + return [false]; + } + var passed = false; + var numDifferentPixels; + var maxDifference = { value: undefined }; + if (gWindowUtils) { + var equal; + try { + numDifferentPixels = gWindowUtils.compareCanvases(s1, s2, maxDifference); + if (!fuzz) { + equal = (numDifferentPixels == 0); + } else { + equal = (numDifferentPixels <= fuzz.numDifferentPixels && + maxDifference.value <= fuzz.maxDifference); + } + passed = (equal == expectEqual); + } catch (e) { + ok(false, "Exception thrown from compareCanvases: " + e); + } + } + + var s1DataURI, s2DataURI; + if (!passed) { + s1DataURI = s1.toDataURL(); + s2DataURI = s2.toDataURL(); + + if (!gWindowUtils) { + passed = ((s1DataURI == s2DataURI) == expectEqual); + } + } + + return [passed, s1DataURI, s2DataURI, numDifferentPixels, maxDifference.value]; +} + +function assertSnapshots(s1, s2, expectEqual, fuzz, s1name, s2name) { + var [passed, s1DataURI, s2DataURI, numDifferentPixels, maxDifference] = + compareSnapshots(s1, s2, expectEqual, fuzz); + var sym = expectEqual ? "==" : "!="; + ok(passed, "reftest comparison: " + sym + " " + s1name + " " + s2name); + if (!passed) { + // The language / format in this message should match the failure messages + // displayed by reftest.js's "RecordResult()" method so that log output + // can be parsed by reftest-analyzer.xhtml + var report = "REFTEST TEST-UNEXPECTED-FAIL | " + s1name + + " | image comparison (" + sym + "), max difference: " + + maxDifference + ", number of differing pixels: " + + numDifferentPixels + "\n"; + if (expectEqual) { + report += "REFTEST IMAGE 1 (TEST): " + s1DataURI + "\n"; + report += "REFTEST IMAGE 2 (REFERENCE): " + s2DataURI + "\n"; + } else { + report += "REFTEST IMAGE: " + s1DataURI + "\n"; + } + dump(report); + } + return passed; +} + +function assertWindowPureColor(win, color) { + const snapshot = SpecialPowers.snapshotRect(win); + const canvas = document.createElement("canvas"); + canvas.width = snapshot.width; + canvas.height = snapshot.height; + const context = canvas.getContext("2d"); + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + assertSnapshots(snapshot, canvas, true, null, "snapshot", color); +} diff --git a/testing/mochitest/tests/SimpleTest/iframe-between-tests.html b/testing/mochitest/tests/SimpleTest/iframe-between-tests.html new file mode 100644 index 000000000..8de879f20 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/iframe-between-tests.html @@ -0,0 +1,17 @@ +<title>iframe for between tests</title> +<!-- + This page exists so that our accounting for assertions correctly + counts assertions that happen while leaving a page. We load this page + after a test finishes, check the assertion counts, and then go on to + load the next. +--> +<script> +window.addEventListener("load", function() { + var runner = (parent.TestRunner || parent.wrappedJSObject.TestRunner); + runner.testUnloaded(); + + if (SpecialPowers) { + SpecialPowers.DOMWindowUtils.runNextCollectorTimer(); + } +}); +</script> diff --git a/testing/mochitest/tests/SimpleTest/moz.build b/testing/mochitest/tests/SimpleTest/moz.build new file mode 100644 index 000000000..461a6f49b --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_HARNESS_FILES.testing.mochitest.tests.SimpleTest += [ + '/docshell/test/chrome/docshell_helpers.js', + '/testing/specialpowers/content/MozillaLogger.js', + 'EventUtils.js', + 'ExtensionTestUtils.js', + 'iframe-between-tests.html', + 'LogController.js', + 'MemoryStats.js', + 'MockObjects.js', + 'NativeKeyCodes.js', + 'paint_listener.js', + 'setup.js', + 'SimpleTest.js', + 'SpawnTask.js', + 'test.css', + 'TestRunner.js', + 'WindowSnapshot.js', +] diff --git a/testing/mochitest/tests/SimpleTest/paint_listener.js b/testing/mochitest/tests/SimpleTest/paint_listener.js new file mode 100644 index 000000000..304a0fd62 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/paint_listener.js @@ -0,0 +1,83 @@ +(function() { + var accumulatedRect = null; + var onpaint = new Array(); + var debug = false; + const FlushModes = { + FLUSH: 0, + NOFLUSH: 1 + }; + + function paintListener(event) { + if (event.target != window) + return; + var eventRect = + [ event.boundingClientRect.left, + event.boundingClientRect.top, + event.boundingClientRect.right, + event.boundingClientRect.bottom ]; + if (debug) { + dump("got MozAfterPaint: " + eventRect.join(",") + "\n"); + } + accumulatedRect = accumulatedRect + ? [ Math.min(accumulatedRect[0], eventRect[0]), + Math.min(accumulatedRect[1], eventRect[1]), + Math.max(accumulatedRect[2], eventRect[2]), + Math.max(accumulatedRect[3], eventRect[3]) ] + : eventRect; + while (onpaint.length > 0) { + window.setTimeout(onpaint.pop(), 0); + } + } + window.addEventListener("MozAfterPaint", paintListener, false); + + function waitForPaints(callback, subdoc, flushMode) { + // Wait until paint suppression has ended + var utils = SpecialPowers.getDOMWindowUtils(window); + if (utils.paintingSuppressed) { + if (debug) { + dump("waiting for paint suppression to end...\n"); + } + window.setTimeout(function() { + waitForPaints(callback, subdoc, flushMode); + }, 0); + return; + } + + // The call to getBoundingClientRect will flush pending layout + // notifications. Sometimes, however, this is undesirable since it can mask + // bugs where the code under test should be performing the flush. + if (flushMode === FlushModes.FLUSH) { + document.documentElement.getBoundingClientRect(); + if (subdoc) { + subdoc.documentElement.getBoundingClientRect(); + } + } + + if (utils.isMozAfterPaintPending) { + if (debug) { + dump("waiting for paint...\n"); + } + onpaint.push( + function() { waitForPaints(callback, subdoc, FlushModes.NOFLUSH); }); + if (utils.isTestControllingRefreshes) { + utils.advanceTimeAndRefresh(0); + } + return; + } + + if (debug) { + dump("done...\n"); + } + var result = accumulatedRect || [ 0, 0, 0, 0 ]; + accumulatedRect = null; + callback.apply(null, result); + } + + window.waitForAllPaintsFlushed = function(callback, subdoc) { + waitForPaints(callback, subdoc, FlushModes.FLUSH); + }; + + window.waitForAllPaints = function(callback) { + waitForPaints(callback, null, FlushModes.NOFLUSH); + }; +})(); diff --git a/testing/mochitest/tests/SimpleTest/setup.js b/testing/mochitest/tests/SimpleTest/setup.js new file mode 100644 index 000000000..e6689022b --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/setup.js @@ -0,0 +1,260 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +TestRunner.logEnabled = true; +TestRunner.logger = LogController; + +/* Helper function */ +function parseQueryString(encodedString, useArrays) { + // strip a leading '?' from the encoded string + var qstr = (encodedString.length > 0 && encodedString[0] == "?") + ? encodedString.substring(1) + : encodedString; + var pairs = qstr.replace(/\+/g, "%20").split(/(\&\;|\&\#38\;|\&|\&)/); + var o = {}; + var decode; + if (typeof(decodeURIComponent) != "undefined") { + decode = decodeURIComponent; + } else { + decode = unescape; + } + if (useArrays) { + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split("="); + if (pair.length !== 2) { + continue; + } + var name = decode(pair[0]); + var arr = o[name]; + if (!(arr instanceof Array)) { + arr = []; + o[name] = arr; + } + arr.push(decode(pair[1])); + } + } else { + for (i = 0; i < pairs.length; i++) { + pair = pairs[i].split("="); + if (pair.length !== 2) { + continue; + } + o[decode(pair[0])] = decode(pair[1]); + } + } + return o; +}; + +// Check the query string for arguments +var params = parseQueryString(location.search.substring(1), true); + +var config = {}; +if (window.readConfig) { + config = readConfig(); +} + +if (config.testRoot == "chrome" || config.testRoot == "a11y") { + for (var p in params) { + // Compare with arrays to find boolean equivalents, since that's what + // |parseQueryString| with useArrays returns. + if (params[p] == [1]) { + config[p] = true; + } else if (params[p] == [0]) { + config[p] = false; + } else { + config[p] = params[p]; + } + } + params = config; + params.baseurl = "chrome://mochitests/content"; +} else { + params.baseurl = ""; +} + +if (params.testRoot == "browser") { + params.testPrefix = "chrome://mochitests/content/browser/"; +} else if (params.testRoot == "chrome") { + params.testPrefix = "chrome://mochitests/content/chrome/"; +} else if (params.testRoot == "a11y") { + params.testPrefix = "chrome://mochitests/content/a11y/"; +} else { + params.testPrefix = "/tests/"; +} + +// set the per-test timeout if specified in the query string +if (params.timeout) { + TestRunner.timeout = parseInt(params.timeout) * 1000; +} + +// log levels for console and logfile +var fileLevel = params.fileLevel || null; +var consoleLevel = params.consoleLevel || null; + +// repeat tells us how many times to repeat the tests +if (params.repeat) { + TestRunner.repeat = params.repeat; +} + +if (params.runUntilFailure) { + TestRunner.runUntilFailure = true; +} + +// closeWhenDone tells us to close the browser when complete +if (params.closeWhenDone) { + TestRunner.onComplete = SpecialPowers.quit; +} + +if (params.failureFile) { + TestRunner.setFailureFile(params.failureFile); +} + +// Breaks execution and enters the JS debugger on a test failure +if (params.debugOnFailure) { + TestRunner.debugOnFailure = true; +} + +// logFile to write our results +if (params.logFile) { + var spl = new SpecialPowersLogger(params.logFile); + TestRunner.logger.addListener("mozLogger", fileLevel + "", spl.getLogCallback()); +} + +// A temporary hack for android 4.0 where Fennec utilizes the pandaboard so much it reboots +if (params.runSlower) { + TestRunner.runSlower = true; +} + +if (params.dumpOutputDirectory) { + TestRunner.dumpOutputDirectory = params.dumpOutputDirectory; +} + +if (params.dumpAboutMemoryAfterTest) { + TestRunner.dumpAboutMemoryAfterTest = true; +} + +if (params.dumpDMDAfterTest) { + TestRunner.dumpDMDAfterTest = true; +} + +if (params.interactiveDebugger) { + TestRunner.interactiveDebugger = true; +} + +if (params.maxTimeouts) { + TestRunner.maxTimeouts = params.maxTimeouts; +} + +// Log things to the console if appropriate. +TestRunner.logger.addListener("dumpListener", consoleLevel + "", function(msg) { + dump(msg.info.join(' ') + "\n"); +}); + +var gTestList = []; +var RunSet = {}; +RunSet.runall = function(e) { + // Filter tests to include|exclude tests based on data in params.filter. + // This allows for including or excluding tests from the gTestList + // TODO Only used by ipc tests, remove once those are implemented sanely + if (params.testManifest) { + getTestManifest("http://mochi.test:8888/" + params.testManifest, params, function(filter) { gTestList = filterTests(filter, gTestList, params.runOnly); RunSet.runtests(); }); + } else { + RunSet.runtests(); + } +} + +RunSet.runtests = function(e) { + // Which tests we're going to run + var my_tests = gTestList; + + if (params.startAt || params.endAt) { + my_tests = skipTests(my_tests, params.startAt, params.endAt); + } + + if (params.shuffle) { + for (var i = my_tests.length-1; i > 0; --i) { + var j = Math.floor(Math.random() * i); + var tmp = my_tests[j]; + my_tests[j] = my_tests[i]; + my_tests[i] = tmp; + } + } + TestRunner.setParameterInfo(params); + TestRunner.runTests(my_tests); +} + +RunSet.reloadAndRunAll = function(e) { + e.preventDefault(); + //window.location.hash = ""; + var addParam = ""; + if (params.autorun) { + window.location.search += ""; + window.location.href = window.location.href; + } else if (window.location.search) { + window.location.href += "&autorun=1"; + } else { + window.location.href += "?autorun=1"; + } +}; + +// UI Stuff +function toggleVisible(elem) { + toggleElementClass("invisible", elem); +} + +function makeVisible(elem) { + removeElementClass(elem, "invisible"); +} + +function makeInvisible(elem) { + addElementClass(elem, "invisible"); +} + +function isVisible(elem) { + // you may also want to check for + // getElement(elem).style.display == "none" + return !hasElementClass(elem, "invisible"); +}; + +function toggleNonTests (e) { + e.preventDefault(); + var elems = document.getElementsByClassName("non-test"); + for (var i="0"; i<elems.length; i++) { + toggleVisible(elems[i]); + } + if (isVisible(elems[0])) { + $("toggleNonTests").innerHTML = "Hide Non-Tests"; + } else { + $("toggleNonTests").innerHTML = "Show Non-Tests"; + } +} + +// hook up our buttons +function hookup() { + if (params.manifestFile) { + getTestManifest("http://mochi.test:8888/" + params.manifestFile, params, hookupTests); + } else { + hookupTests(gTestList); + } +} + +function hookupTests(testList) { + if (testList.length > 0) { + gTestList = testList; + } else { + gTestList = []; + for (var obj in testList) { + gTestList.push(testList[obj]); + } + } + + document.getElementById('runtests').onclick = RunSet.reloadAndRunAll; + document.getElementById('toggleNonTests').onclick = toggleNonTests; + // run automatically if autorun specified + if (params.autorun) { + RunSet.runall(); + } +} diff --git a/testing/mochitest/tests/SimpleTest/test.css b/testing/mochitest/tests/SimpleTest/test.css new file mode 100644 index 000000000..e6fe345b9 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/test.css @@ -0,0 +1,43 @@ +.test_ok { + color: #0d0; + display: none; +} + +.test_not_ok { + color: red; + display: block; +} + +.test_todo { + /* color: orange; */ + display: block; +} + +.test_ok, .test_not_ok, .test_todo { + border-bottom-width: 2px; + border-bottom-style: solid; + border-bottom-color: black; +} + +.all_pass { + background-color: #0d0; +} + +.some_fail { + background-color: red; +} + +.todo_only { + background-color: orange; +} + +.tests_report { + border-width: 2px; + border-style: solid; + width: 20em; + display: table; +} + +browser[remote="true"] { + -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser"); +} diff --git a/testing/mochitest/tests/browser/browser.ini b/testing/mochitest/tests/browser/browser.ini new file mode 100644 index 000000000..091ccb68e --- /dev/null +++ b/testing/mochitest/tests/browser/browser.ini @@ -0,0 +1,49 @@ +[DEFAULT] +support-files = + head.js + +[browser_browserLoaded_content_loaded.js] +[browser_add_task.js] +[browser_async.js] +[browser_BrowserTestUtils.js] +support-files = + dummy.html +[browser_head.js] +[browser_pass.js] +[browser_parameters.js] +[browser_popupNode.js] +[browser_popupNode_check.js] +[browser_privileges.js] +[browser_sanityException.js] +[browser_sanityException2.js] +[browser_waitForFocus.js] +skip-if = (os == "win" && e10s && debug) +[browser_getTestFile.js] +support-files = + test-dir/* + waitForFocusPage.html + +# Disabled because it would take too long, useful to check functionality though. +# browser_requestLongerTimeout.js +[browser_zz_fail_openwindow.js] +skip-if = true # this catches outside of the main loop to find an extra window +[browser_fail.js] +skip-if = true +[browser_fail_add_task.js] +skip-if = true # fail-if doesnt catch an exception outside the test +[browser_fail_async_throw.js] +skip-if = true # fail-if doesnt catch an exception outside the test +[browser_fail_fp.js] +fail-if = true +[browser_fail_pf.js] +fail-if = true +[browser_fail_throw.js] +skip-if = true # fail-if doesnt catch an exception outside the test + +# Disabled beacuse it takes too long (bug 1178959) +[browser_fail_timeout.js] +skip-if = true +# Disabled beacuse it takes too long (bug 1178959) +[browser_fail_unexpectedTimeout.js] +skip-if = true + diff --git a/testing/mochitest/tests/browser/browser_BrowserTestUtils.js b/testing/mochitest/tests/browser/browser_BrowserTestUtils.js new file mode 100644 index 000000000..0b3700263 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_BrowserTestUtils.js @@ -0,0 +1,70 @@ +function getLastEventDetails(browser) +{ + return ContentTask.spawn(browser, {}, function* () { + return content.document.getElementById('out').textContent; + }); +} + +add_task(function* () { + let onClickEvt = 'document.getElementById("out").textContent = event.target.localName + "," + event.clientX + "," + event.clientY;' + const url = "<body onclick='" + onClickEvt + "' style='margin: 0'>" + + "<button id='one' style='margin: 0; margin-left: 16px; margin-top: 14px; width: 30px; height: 40px;'>Test</button>" + + "<div onmousedown='event.preventDefault()' style='margin: 0; width: 80px; height: 60px;'>Other</div>" + + "<span id='out'></span></body>"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html," + url); + + let browser = tab.linkedBrowser; + yield BrowserTestUtils.synthesizeMouseAtCenter("#one", {}, browser); + let details = yield getLastEventDetails(browser); + + is(details, "button,31,34", "synthesizeMouseAtCenter"); + + yield BrowserTestUtils.synthesizeMouse("#one", 4, 9, {}, browser); + details = yield getLastEventDetails(browser); + is(details, "button,20,23", "synthesizeMouse"); + + yield BrowserTestUtils.synthesizeMouseAtPoint(15, 6, {}, browser); + details = yield getLastEventDetails(browser); + is(details, "body,15,6", "synthesizeMouseAtPoint on body"); + + yield BrowserTestUtils.synthesizeMouseAtPoint(20, 22, {}, browser); + details = yield getLastEventDetails(browser); + is(details, "button,20,22", "synthesizeMouseAtPoint on button"); + + yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", {}, browser); + details = yield getLastEventDetails(browser); + is(details, "div,40,84", "synthesizeMouseAtCenter with complex selector"); + + let cancelled = yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", { type: "mousedown" }, browser); + details = yield getLastEventDetails(browser); + is(details, "div,40,84", "synthesizeMouseAtCenter mousedown with complex selector"); + ok(cancelled, "synthesizeMouseAtCenter mousedown with complex selector not cancelled"); + + cancelled = yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", { type: "mouseup" }, browser); + details = yield getLastEventDetails(browser); + is(details, "div,40,84", "synthesizeMouseAtCenter mouseup with complex selector"); + ok(!cancelled, "synthesizeMouseAtCenter mouseup with complex selector cancelled"); + + gBrowser.removeTab(tab); +}); + +add_task(function* () { + yield BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "about-pages-are-cool", + getRootDirectory(gTestPath) + "dummy.html", 0); + let tab = yield BrowserTestUtils.openNewForegroundTab( + gBrowser, "about:about-pages-are-cool", true); + ok(tab, "Successfully created an about: page and loaded it."); + yield BrowserTestUtils.removeTab(tab); + try { + yield BrowserTestUtils.unregisterAboutPage("about-pages-are-cool"); + ok(true, "Successfully unregistered the about page."); + } catch (ex) { + ok(false, "Should not throw unregistering a known about: page"); + } + yield BrowserTestUtils.unregisterAboutPage("random-other-about-page").then(() => { + ok(false, "Should not have succeeded unregistering an unknown about: page."); + }, () => { + ok(true, "Should have returned a rejected promise trying to unregister an unknown about page"); + }); +}); diff --git a/testing/mochitest/tests/browser/browser_add_task.js b/testing/mochitest/tests/browser/browser_add_task.js new file mode 100644 index 000000000..5318b09d4 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_add_task.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var test1Complete = false; +var test2Complete = false; + +function executeWithTimeout() { + return new Promise(resolve => + executeSoon(function() { + ok(true, "we get here after a timeout"); + resolve(); + }) + ); +} + +add_task(function* asyncTest_no1() { + yield executeWithTimeout(); + test1Complete = true; +}); + +add_task(function* asyncTest_no2() { + yield executeWithTimeout(); + test2Complete = true; +}); + +add_task(function() { + ok(test1Complete, "We have been through test 1"); + ok(test2Complete, "We have been through test 2"); +}); diff --git a/testing/mochitest/tests/browser/browser_async.js b/testing/mochitest/tests/browser/browser_async.js new file mode 100644 index 000000000..51ba0700e --- /dev/null +++ b/testing/mochitest/tests/browser/browser_async.js @@ -0,0 +1,8 @@ +function test() { + waitForExplicitFinish(); + function done() { + ok(true, "timeout ran"); + finish(); + } + setTimeout(done, 10000); +} diff --git a/testing/mochitest/tests/browser/browser_browserLoaded_content_loaded.js b/testing/mochitest/tests/browser/browser_browserLoaded_content_loaded.js new file mode 100644 index 000000000..5de7794f5 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_browserLoaded_content_loaded.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +'use strict'; + +function isDOMLoaded(browser) { + return ContentTask.spawn(browser, null, function*() { + Assert.equal(content.document.readyState, "complete", + "Browser should be loaded."); + }); +} + +// It checks if calling BrowserTestUtils.browserLoaded() yields +// browser object. +add_task(function*() { + let tab = gBrowser.addTab('http://example.com'); + let browser = tab.linkedBrowser; + yield BrowserTestUtils.browserLoaded(browser); + yield isDOMLoaded(browser); + gBrowser.removeTab(tab); +}); + +// It checks that BrowserTestUtils.browserLoaded() works well with +// promise.all(). +add_task(function*() { + let tabURLs = [ + `http://example.org`, + `http://mochi.test:8888`, + `http://test:80`, + ]; + //Add tabs, get the respective browsers + let browsers = [ + for (u of tabURLs) gBrowser.addTab(u).linkedBrowser + ]; + //wait for promises to settle + yield Promise.all(( + for (b of browsers) BrowserTestUtils.browserLoaded(b) + )); + let expected = 'Expected all promised browsers to have loaded.'; + for (const browser of browsers) { + yield isDOMLoaded(browser); + } + //cleanup + browsers + .map(browser => gBrowser.getTabForBrowser(browser)) + .forEach(tab => gBrowser.removeTab(tab)); +}); diff --git a/testing/mochitest/tests/browser/browser_fail.js b/testing/mochitest/tests/browser/browser_fail.js new file mode 100644 index 000000000..3d91439ea --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail.js @@ -0,0 +1,8 @@ +function test() { + ok(false, "fail ok"); + is(true, false, "fail is"); + isnot(true, true, "fail isnot"); + todo(true, "fail todo"); + todo_is(true, true, "fail todo_is"); + todo_isnot(true, false, "fail todo_isnot"); +} diff --git a/testing/mochitest/tests/browser/browser_fail_add_task.js b/testing/mochitest/tests/browser/browser_fail_add_task.js new file mode 100644 index 000000000..9ef20f7c7 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_add_task.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test is designed to fail. +// It ensures that throwing an asynchronous error from add_task will +// fail the test. + +var passedTests = 0; + +function rejectWithTimeout(error = undefined) { + let deferred = Promise.defer(); + executeSoon(function() { + ok(true, "we get here after a timeout"); + deferred.reject(error); + }); + return deferred.promise; +} + +add_task(function failWithoutError() { + try { + yield rejectWithTimeout(); + } finally { + ++passedTests; + } +}); + +add_task(function failWithString() { + try { + yield rejectWithTimeout("Meaningless error"); + } finally { + ++passedTests; + } +}); + +add_task(function failWithoutInt() { + try { + yield rejectWithTimeout(42); + } finally { + ++passedTests; + } +}); + + +// This one should display a stack trace +add_task(function failWithError() { + try { + yield rejectWithTimeout(new Error("This is an error")); + } finally { + ++passedTests; + } +}); + +add_task(function done() { + is(passedTests, 4, "Passed all tests"); +}); diff --git a/testing/mochitest/tests/browser/browser_fail_async_throw.js b/testing/mochitest/tests/browser/browser_fail_async_throw.js new file mode 100644 index 000000000..201cb241e --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_async_throw.js @@ -0,0 +1,7 @@ +function test() { + function end() { + throw "thrown exception"; + } + waitForExplicitFinish(); + setTimeout(end, 1000); +} diff --git a/testing/mochitest/tests/browser/browser_fail_fp.js b/testing/mochitest/tests/browser/browser_fail_fp.js new file mode 100644 index 000000000..04cae37cd --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_fp.js @@ -0,0 +1,4 @@ +function test() { + ok(false, "first fail ok"); + ok(true, "then pass ok"); +} diff --git a/testing/mochitest/tests/browser/browser_fail_pf.js b/testing/mochitest/tests/browser/browser_fail_pf.js new file mode 100644 index 000000000..88ed1d949 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_pf.js @@ -0,0 +1,4 @@ +function test() { + ok(true, "first pass ok"); + ok(false, "then fail ok"); +} diff --git a/testing/mochitest/tests/browser/browser_fail_throw.js b/testing/mochitest/tests/browser/browser_fail_throw.js new file mode 100644 index 000000000..aee0238df --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_throw.js @@ -0,0 +1,3 @@ +function test() { + throw "thrown exception"; +} diff --git a/testing/mochitest/tests/browser/browser_fail_timeout.js b/testing/mochitest/tests/browser/browser_fail_timeout.js new file mode 100644 index 000000000..6b99693d9 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_timeout.js @@ -0,0 +1,8 @@ +function test() { + function end() { + ok(false, "should have timed out"); + finish(); + } + waitForExplicitFinish(); + setTimeout(end, 40000); +} diff --git a/testing/mochitest/tests/browser/browser_fail_unexpectedTimeout.js b/testing/mochitest/tests/browser/browser_fail_unexpectedTimeout.js new file mode 100644 index 000000000..2175eea27 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_fail_unexpectedTimeout.js @@ -0,0 +1,12 @@ +function test() { + function message() { + info("This should delay timeout"); + } + function end() { + ok(true, "Should have not timed out, but notified long running test"); + finish(); + } + waitForExplicitFinish(); + setTimeout(message, 20000); + setTimeout(end, 40000); +} diff --git a/testing/mochitest/tests/browser/browser_getTestFile.js b/testing/mochitest/tests/browser/browser_getTestFile.js new file mode 100644 index 000000000..99456b633 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_getTestFile.js @@ -0,0 +1,44 @@ +function test() { + let {Promise} = Components.utils.import("resource://gre/modules/Promise.jsm"); + Components.utils.import("resource://gre/modules/osfile.jsm"); + let decoder = new TextDecoder(); + + waitForExplicitFinish(); + + SimpleTest.doesThrow(function () { + getTestFilePath("/browser_getTestFile.js") + }, "getTestFilePath rejects absolute paths"); + + Promise.all([ + OS.File.exists(getTestFilePath("browser_getTestFile.js")) + .then(function (exists) { + ok(exists, "getTestFilePath consider the path as being relative"); + }), + + OS.File.exists(getTestFilePath("./browser_getTestFile.js")) + .then(function (exists) { + ok(exists, "getTestFilePath also accepts explicit relative path"); + }), + + OS.File.exists(getTestFilePath("./browser_getTestFileTypo.xul")) + .then(function (exists) { + ok(!exists, "getTestFilePath do not throw if the file doesn't exists"); + }), + + OS.File.read(getTestFilePath("test-dir/test-file")) + .then(function (array) { + is(decoder.decode(array), "foo\n", "getTestFilePath can reach sub-folder files 1/2"); + }), + + OS.File.read(getTestFilePath("./test-dir/test-file")) + .then(function (array) { + is(decoder.decode(array), "foo\n", "getTestFilePath can reach sub-folder files 2/2"); + }) + + ]).then(function () { + finish(); + }, function (error) { + ok(false, error); + finish(); + }); +} diff --git a/testing/mochitest/tests/browser/browser_head.js b/testing/mochitest/tests/browser/browser_head.js new file mode 100644 index 000000000..0e1f7dd25 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_head.js @@ -0,0 +1,12 @@ +var testVar; + +registerCleanupFunction(function() { + ok(true, "I'm a cleanup function in test file"); + is(this.testVar, "I'm a var in test file", "Test cleanup function scope is correct"); +}); + +function test() { + is(headVar, "I'm a var in head file", "Head variables are set"); + ok(headMethod(), "Head methods are imported"); + testVar = "I'm a var in test file"; +} diff --git a/testing/mochitest/tests/browser/browser_parameters.js b/testing/mochitest/tests/browser/browser_parameters.js new file mode 100644 index 000000000..32ba82d92 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_parameters.js @@ -0,0 +1,4 @@ +function test() { + ok(SimpleTest.harnessParameters, "Should have parameters"); +} + diff --git a/testing/mochitest/tests/browser/browser_pass.js b/testing/mochitest/tests/browser/browser_pass.js new file mode 100644 index 000000000..dbdfa1f17 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_pass.js @@ -0,0 +1,13 @@ +function test() { + SimpleTest.requestCompleteLog(); + ok(true, "pass ok"); + is(true, true, "pass is"); + isnot(false, true, "pass isnot"); + todo(false, "pass todo"); + todo_is(false, true, "pass todo_is"); + todo_isnot(true, true, "pass todo_isnot"); + info("info message"); + + var func = is; + func(true, 1, "pass indirect is"); +} diff --git a/testing/mochitest/tests/browser/browser_popupNode.js b/testing/mochitest/tests/browser/browser_popupNode.js new file mode 100644 index 000000000..c6042011c --- /dev/null +++ b/testing/mochitest/tests/browser/browser_popupNode.js @@ -0,0 +1,4 @@ +function test() { + document.popupNode = document; + isnot(document.popupNode, null, "document.popupNode has been correctly set"); +} diff --git a/testing/mochitest/tests/browser/browser_popupNode_check.js b/testing/mochitest/tests/browser/browser_popupNode_check.js new file mode 100644 index 000000000..fb85378d9 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_popupNode_check.js @@ -0,0 +1,3 @@ +function test() { + is(document.popupNode, null, "document.popupNode has been correctly cleared"); +} diff --git a/testing/mochitest/tests/browser/browser_privileges.js b/testing/mochitest/tests/browser/browser_privileges.js new file mode 100644 index 000000000..7b6b1978c --- /dev/null +++ b/testing/mochitest/tests/browser/browser_privileges.js @@ -0,0 +1,16 @@ +function test() { + // simple test to confirm we have chrome privileges + let hasPrivileges = true; + + // this will throw an exception if we are not running with privileges + try { + var prefs = Components.classes["@mozilla.org/preferences-service;1"]. + getService(Components.interfaces.nsIPrefBranch); + } + catch (e) { + hasPrivileges = false; + } + + // if we get here, we must have chrome privileges + ok(hasPrivileges, "running with chrome privileges"); +} diff --git a/testing/mochitest/tests/browser/browser_requestLongerTimeout.js b/testing/mochitest/tests/browser/browser_requestLongerTimeout.js new file mode 100644 index 000000000..cb53e13a2 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_requestLongerTimeout.js @@ -0,0 +1,9 @@ +function test() { + requestLongerTimeout(2); + function end() { + ok(true, "should not time out"); + finish(); + } + waitForExplicitFinish(); + setTimeout(end, 40000); +} diff --git a/testing/mochitest/tests/browser/browser_sanityException.js b/testing/mochitest/tests/browser/browser_sanityException.js new file mode 100644 index 000000000..2039946f9 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_sanityException.js @@ -0,0 +1,5 @@ +function test() { + ok(true, "ok called"); + expectUncaughtException(); + throw "this is a deliberately thrown exception"; +} diff --git a/testing/mochitest/tests/browser/browser_sanityException2.js b/testing/mochitest/tests/browser/browser_sanityException2.js new file mode 100644 index 000000000..0b9296041 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_sanityException2.js @@ -0,0 +1,11 @@ +function test() { + waitForExplicitFinish(); + ok(true, "ok called"); + executeSoon(function() { + expectUncaughtException(); + throw "this is a deliberately thrown exception"; + }); + executeSoon(function() { + finish(); + }); +} diff --git a/testing/mochitest/tests/browser/browser_waitForFocus.js b/testing/mochitest/tests/browser/browser_waitForFocus.js new file mode 100644 index 000000000..8f9e27586 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_waitForFocus.js @@ -0,0 +1,69 @@ + +const gBaseURL = "https://example.com/browser/testing/mochitest/tests/browser/"; + +function *promiseTabLoadEvent(tab, url) +{ + return new Promise(function (resolve, reject) { + function handleLoadEvent(event) { + if (event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + (url && event.target.location.href != url)) { + return; + } + + tab.linkedBrowser.removeEventListener("load", handleLoadEvent, true); + resolve(event); + } + + tab.linkedBrowser.addEventListener("load", handleLoadEvent, true, true); + if (url) + tab.linkedBrowser.loadURI(url); + }); +} + +// Load a new blank tab +add_task(function *() { + yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + gURLBar.focus(); + + let browser = gBrowser.selectedBrowser; + yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW, true); + + is(document.activeElement, browser, "Browser is focused when about:blank is loaded"); + + gBrowser.removeCurrentTab(); + gURLBar.focus(); +}); + +// Load a tab with a subframe inside it and wait until the subframe is focused +add_task(function *() { + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + yield promiseTabLoadEvent(tab, gBaseURL + "waitForFocusPage.html"); + + yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW); + + is(document.activeElement, browser, "Browser is focused when page is loaded"); + + yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW.frames[0]); + + is(browser.contentWindowAsCPOW.document.activeElement.localName, "iframe", "Child iframe is focused"); + + gBrowser.removeCurrentTab(); +}); + +// Pass a browser to promiseFocus +add_task(function *() { + yield BrowserTestUtils.openNewForegroundTab(gBrowser, gBaseURL + "waitForFocusPage.html"); + + gURLBar.focus(); + + yield SimpleTest.promiseFocus(gBrowser.selectedBrowser); + + is(document.activeElement, gBrowser.selectedBrowser, "Browser is focused when promiseFocus is passed a browser"); + + gBrowser.removeCurrentTab(); +}); diff --git a/testing/mochitest/tests/browser/browser_zz_fail_openwindow.js b/testing/mochitest/tests/browser/browser_zz_fail_openwindow.js new file mode 100644 index 000000000..e9fe71d14 --- /dev/null +++ b/testing/mochitest/tests/browser/browser_zz_fail_openwindow.js @@ -0,0 +1,12 @@ +function test() { + waitForExplicitFinish(); + function done() { + ok(true, "timeout ran"); + finish(); + } + + ok(OpenBrowserWindow(), "opened browser window"); + // and didn't close it! + + setTimeout(done, 10000); +} diff --git a/testing/mochitest/tests/browser/dummy.html b/testing/mochitest/tests/browser/dummy.html new file mode 100644 index 000000000..c49925c19 --- /dev/null +++ b/testing/mochitest/tests/browser/dummy.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <title>This is a dummy page</title> + <meta charset="utf-8"> + <body>This is a dummy page</body> +</html> diff --git a/testing/mochitest/tests/browser/head.js b/testing/mochitest/tests/browser/head.js new file mode 100644 index 000000000..279333791 --- /dev/null +++ b/testing/mochitest/tests/browser/head.js @@ -0,0 +1,12 @@ +var headVar = "I'm a var in head file";
+
+function headMethod() {
+ return true;
+};
+
+ok(true, "I'm a test in head file");
+
+registerCleanupFunction(function() {
+ ok(true, "I'm a cleanup function in head file");
+ is(this.headVar, "I'm a var in head file", "Head cleanup function scope is correct");
+});
diff --git a/testing/mochitest/tests/browser/test-dir/test-file b/testing/mochitest/tests/browser/test-dir/test-file new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/testing/mochitest/tests/browser/test-dir/test-file @@ -0,0 +1 @@ +foo diff --git a/testing/mochitest/tests/browser/waitForFocusPage.html b/testing/mochitest/tests/browser/waitForFocusPage.html new file mode 100644 index 000000000..286ad7849 --- /dev/null +++ b/testing/mochitest/tests/browser/waitForFocusPage.html @@ -0,0 +1,4 @@ +<body> + <input> + <iframe id="f" src="data:text/plain,Test" width=80 height=80></iframe> +</body> diff --git a/testing/mochitest/tests/moz.build b/testing/mochitest/tests/moz.build new file mode 100644 index 000000000..db43722db --- /dev/null +++ b/testing/mochitest/tests/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + 'SimpleTest' +] + +TESTING_JS_MODULES += [ + 'Harness_sanity/ImportTesting.jsm', +] + +MOCHITEST_MANIFESTS += ['Harness_sanity/mochitest.ini'] +BROWSER_CHROME_MANIFESTS += ['browser/browser.ini'] |