450 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
| 
 | |
| Copyright 2008-2015 Clipperz Srl
 | |
| 
 | |
| This file is part of Clipperz, the online password manager.
 | |
| For further information about its features and functionalities please
 | |
| refer to http://www.clipperz.com.
 | |
| 
 | |
| * Clipperz is free software: you can redistribute it and/or modify it
 | |
|   under the terms of the GNU Affero General Public License as published
 | |
|   by the Free Software Foundation, either version 3 of the License, or 
 | |
|   (at your option) any later version.
 | |
| 
 | |
| * Clipperz is distributed in the hope that it will be useful, but 
 | |
|   WITHOUT ANY WARRANTY; without even the implied warranty of 
 | |
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | |
|   See the GNU Affero General Public License for more details.
 | |
| 
 | |
| * You should have received a copy of the GNU Affero General Public
 | |
|   License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | |
| 
 | |
| */
 | |
| 
 | |
| /**
 | |
|  * 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
 | |
|  *
 | |
| **/
 | |
| 
 | |
| if (typeof(SimpleTest) == "undefined") {
 | |
|     var SimpleTest = {};
 | |
| }
 | |
| 
 | |
| // Check to see if the TestRunner is present and has logging
 | |
| if (typeof(parent) != "undefined" && parent.TestRunner) {
 | |
|     SimpleTest._logEnabled = parent.TestRunner.logEnabled;
 | |
| }
 | |
| 
 | |
| SimpleTest._tests = [];
 | |
| SimpleTest._stopOnLoad = true;
 | |
| SimpleTest._scopeCopy = {};
 | |
| 
 | |
| /**
 | |
|  * Saves a copy of the specified scope variables.
 | |
|  */
 | |
| SimpleTest.saveScope = function (scope) {
 | |
|     SimpleTest._scopeCopy = {};
 | |
|     for (var k in scope) {
 | |
|         SimpleTest._scopeCopy[k] = scope[k];
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Verifies the specified scope against the stored copy and reports
 | |
|  * any differences as test failures.
 | |
|  */
 | |
| SimpleTest.verifyScope = function (scope) {
 | |
|     var filter = ['test', '_firebug','_FirebugConsole','XMLHttpRequest','Audio',
 | |
|                   'XSLTProcessor','Option','Image','scrollMaxX','scrollMaxY',
 | |
|                   'clipboardData'];
 | |
|     for (var k in scope) {
 | |
|         if (MochiKit.Base.findValue(filter, k) < 0) {
 | |
|             var v = scope[k];
 | |
|             var old = SimpleTest._scopeCopy[k];
 | |
|             if (v !== old && typeof(old) === "undefined") {
 | |
|                 SimpleTest.ok(false, "scope modified, variable " + k + " was added");
 | |
|             } else if (v !== old) {
 | |
|                 SimpleTest.ok(false, "scope modified, variable " + k + " changed from: " + old + ", to: " + v);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     for (var k in SimpleTest._scopeCopy) {
 | |
|         if (!(k in scope)) {
 | |
|             SimpleTest.ok(false, "scope modified, variable " + k + " has been removed");
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Something like assert.
 | |
|  */
 | |
| SimpleTest.ok = function (condition, name, diag) {
 | |
|     var test = {'result': !!condition, 'name': name, 'diag': diag || ""};
 | |
|     if (SimpleTest._logEnabled) {
 | |
|         var msg = test.result ? "PASS" : "FAIL";
 | |
|         msg += " | " + test.name;
 | |
|         if (test.result) {
 | |
|             parent.TestRunner.logger.log(msg);
 | |
|         } else {
 | |
|             msg += " | " + test.diag;
 | |
|             parent.TestRunner.logger.error(msg);
 | |
|         }
 | |
|     }
 | |
|     SimpleTest._tests.push(test);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Roughly equivalent to ok(a==b, name)
 | |
|  */
 | |
| SimpleTest.is = function (a, b, name) {
 | |
|     var repr = MochiKit.Base.repr;
 | |
| //  SimpleTest.ok(a == b, name, "got " + repr(a) + ", expected " + repr(b));
 | |
|     SimpleTest.ok(a == b, name, "got " + repr(b) + ", expected " + repr(a));
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Roughly equivalent to ok(compare(a,b)==0, name)
 | |
|  */
 | |
| SimpleTest.eq = function (a, b, name) {
 | |
|     var base = MochiKit.Base;
 | |
|     var repr = base.repr;
 | |
|     try {
 | |
| //      SimpleTest.ok(base.compare(a, b) == 0, name, "got " + repr(a) + ", expected " + repr(b));
 | |
|         SimpleTest.ok(base.compare(a, b) == 0, name, "got " + repr(b) + ", expected " + repr(a));
 | |
|     } catch (e) {
 | |
|         SimpleTest.ok(false, name, "exception in compare: " + repr(e));
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Makes a test report, returns it as a DIV element.
 | |
| **/
 | |
| SimpleTest.report = function () {
 | |
|     var DIV = MochiKit.DOM.DIV;
 | |
|     var passed = 0;
 | |
|     var failed = 0;
 | |
|     var results = MochiKit.Base.map(
 | |
|         function (test) {
 | |
|             var cls, msg;
 | |
|             if (test.result) {
 | |
|                 passed++;
 | |
|                 cls = "test_ok";
 | |
|                 msg = "ok - " + test.name;
 | |
|             } else {
 | |
|                 failed++;
 | |
|                 cls = "test_not_ok";
 | |
|                 msg = "not ok - " + test.name;
 | |
|                 if (test.diag != null && test.diag != "") {
 | |
|                     msg += ": " + test.diag;
 | |
|                 }
 | |
|             }
 | |
|             return DIV({"class": cls}, msg);
 | |
|         },
 | |
|         SimpleTest._tests
 | |
|     );
 | |
|     var summary_class = ((failed == 0) ? 'all_pass' : 'some_fail');
 | |
|     return DIV({'class': 'tests_report'},
 | |
|         DIV({'class': 'tests_summary ' + summary_class},
 | |
|             DIV({'class': 'tests_passed'}, "Passed: " + passed),
 | |
|             DIV({'class': 'tests_failed'}, "Failed: " + failed)),
 | |
|         results
 | |
|     );
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Toggle element visibility
 | |
| **/
 | |
| SimpleTest.toggle = function(el) {
 | |
|     if (MochiKit.Style.getStyle(el, 'display') == 'block') {
 | |
|         el.style.display = 'none';
 | |
|     } else {
 | |
|         el.style.display = 'block';
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Toggle visibility for divs with a specific class.
 | |
| **/
 | |
| SimpleTest.toggleByClass = function (cls) {
 | |
|     var elems = MochiKit.DOM.getElementsByTagAndClassName('div', cls);
 | |
|     MochiKit.Base.map(SimpleTest.toggle, elems);
 | |
| 	return false;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Shows the report in the browser
 | |
| **/
 | |
| 
 | |
| SimpleTest.showReport = function() {
 | |
|     var base = MochiKit.Base;
 | |
|     var dom = MochiKit.DOM;
 | |
|     var togglePassed = dom.A({'href': '#'}, "Toggle passed tests");
 | |
|     var toggleFailed = dom.A({'href': '#'}, "Toggle failed tests");
 | |
|     togglePassed.onclick = base.partial(SimpleTest.toggleByClass, 'test_ok');
 | |
|     toggleFailed.onclick = base.partial(SimpleTest.toggleByClass, 'test_not_ok');
 | |
|     var body = document.getElementsByTagName("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(dom.SPAN(null, " "));
 | |
|     addNode(toggleFailed);
 | |
|     addNode(SimpleTest.report());
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Talks to the TestRunner if being ran on a iframe and the parent has a 
 | |
|  * TestRunner object.
 | |
| **/
 | |
| SimpleTest.talkToRunner = function () {
 | |
|     if (typeof(parent) != "undefined" && parent.TestRunner) {
 | |
|         parent.TestRunner.testFinished(document);
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Finishes the tests. This is automatically called, except when 
 | |
|  * SimpleTest.waitForExplicitFinish() has been invoked.
 | |
| **/
 | |
| SimpleTest.finish = function () {
 | |
|     SimpleTest.showReport();
 | |
|     SimpleTest.talkToRunner();
 | |
| };
 | |
| 
 | |
| 
 | |
| MochiKit.DOM.addLoadEvent(function() {
 | |
|     if (SimpleTest._stopOnLoad) {
 | |
|         SimpleTest.finish();
 | |
|     }
 | |
| });
 | |
| 
 | |
| //  --------------- Test.Builder/Test.More isDeeply() -----------------
 | |
| 
 | |
| 
 | |
| SimpleTest.DNE = {dne: 'Does not exist'};
 | |
| SimpleTest.LF = "\r\n";
 | |
| SimpleTest._isRef = function (object) {
 | |
|     var type = typeof(object);
 | |
|     return type == 'object' || type == 'function';
 | |
| };
 | |
| 
 | |
| 
 | |
| SimpleTest._deepCheck = function (e1, e2, stack, seen) {
 | |
|     var ok = false;
 | |
|     // Either they're both references or both not.
 | |
|     var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2));
 | |
|     if (e1 == null && e2 == null) {
 | |
|         ok = true;
 | |
|     } else if (e1 != null ^ e2 != null) {
 | |
|         ok = false;
 | |
|     } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) {
 | |
|         ok = false;
 | |
|     } else if (sameRef && e1 == e2) {
 | |
|         // Handles primitives and any variables that reference the same
 | |
|         // object, including functions.
 | |
|         ok = true;
 | |
|     } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
 | |
|         ok = SimpleTest._eqArray(e1, e2, stack, seen);
 | |
|     } else if (typeof e1 == "object" && typeof e2 == "object") {
 | |
|         ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
 | |
|     } else {
 | |
|         // If we get here, they're not the same (function references must
 | |
|         // always simply rererence the same function).
 | |
|         stack.push({ vals: [e1, e2] });
 | |
|         ok = false;
 | |
|     }
 | |
|     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 = a1.length > a2.length ? 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 - 1 ? SimpleTest.DNE : a1[i];
 | |
|         var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i];
 | |
|         stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
 | |
|         if (ok = SimpleTest._deepCheck(e1, e2, stack, seen)) {
 | |
|             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 = o1[i] == undefined ? SimpleTest.DNE : o1[i];
 | |
|         var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i];
 | |
|         stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
 | |
|         if (ok = SimpleTest._deepCheck(e1, e2, stack, seen)) {
 | |
|             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 (/^\d+$/.test(idx)) {
 | |
|                 // 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 == null) {
 | |
|             val = 'undefined';
 | |
|         } else {
 | |
|              val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     out += vars[0] + ' = ' + vals[0] + SimpleTest.LF;
 | |
|     out += vars[1] + ' = ' + vals[1] + SimpleTest.LF;
 | |
|     
 | |
|     return '    ' + out;
 | |
| };
 | |
| 
 | |
| 
 | |
| SimpleTest.isDeeply = function (it, as, name) {
 | |
|     var ok;
 | |
|     // ^ is the XOR operator.
 | |
|     if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) {
 | |
|         // One's a reference, one isn't.
 | |
|         ok = false;
 | |
|     } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) {
 | |
|         // Neither is an object.
 | |
|         ok = SimpleTest.is(it, as, name);
 | |
|     } else {
 | |
|         // We have two objects. Do a deep comparison.
 | |
|         var stack = [], seen = [];
 | |
|         if ( SimpleTest._deepCheck(it, as, stack, seen)) {
 | |
|             ok = SimpleTest.ok(true, name);
 | |
|         } else {
 | |
|             ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
 | |
|         }
 | |
|     }
 | |
|     return ok;
 | |
| };
 | |
| 
 | |
| 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 isDeeply = SimpleTest.isDeeply;
 | 
