diff --git a/qunit/qunit-tests.ts b/qunit/qunit-tests.ts index 153626d41..9a4849379 100644 --- a/qunit/qunit-tests.ts +++ b/qunit/qunit-tests.ts @@ -20,6 +20,15 @@ test("a test", function () { equal(1, "1", "String '1' and number 1 have the same value"); }); +test("test Array 1", function () { + equal([1,2,3], ["test", "foo"], "String '1' and number 1 have the same value"); +}); + +test("test Array 2", function () { + QUnit.equal([1,2,3], ["test", "foo"], "String '1' and number 1 have the same value"); +}); + + QUnit.test("equal test", function () { QUnit.equal(0, 0, "Zero; equal succeeds"); QUnit.equal("", 0, "Empty, Zero; equal succeeds"); @@ -162,3 +171,1523 @@ QUnit.test("a test", function (assert) { assert.equal(result, 4, "square(2) equals 4"); }); + +test( "throws", function() { + + function CustomError( message ) { + this.message = message; + } + + CustomError.prototype.toString = function() { + return this.message; + }; + + throws( + function() { + throw "error" + }, + "throws with just a message, no expected" + ); + + throws( + function() { + throw new Error(); + }, + Error, + "raised error is an instance of CustomError" + ); + + throws( + function() { + throw new Error("some error description"); + }, + /description/, + "raised error message contains 'description'" + ); + + QUnit.throws( + function() { + throw "error" + }, + "throws with just a message, no expected" + ); + + QUnit.throws( + function() { + throw new Error(); + }, + Error, + "raised error is an instance of CustomError" + ); + + QUnit.throws( + function() { + throw new Error("some error description"); + }, + /description/, + "raised error message contains 'description'" + ); +}); + + +QUnit.module("equiv"); + + +test("Primitive types and constants", function () { + equal(QUnit.equiv(null, null), true, "null"); + equal(QUnit.equiv(null, {}), false, "null"); + equal(QUnit.equiv(null, undefined), false, "null"); + equal(QUnit.equiv(null, 0), false, "null"); + equal(QUnit.equiv(null, false), false, "null"); + equal(QUnit.equiv(null, ''), false, "null"); + equal(QUnit.equiv(null, []), false, "null"); + + equal(QUnit.equiv(undefined, undefined), true, "undefined"); + equal(QUnit.equiv(undefined, null), false, "undefined"); + equal(QUnit.equiv(undefined, 0), false, "undefined"); + equal(QUnit.equiv(undefined, false), false, "undefined"); + equal(QUnit.equiv(undefined, {}), false, "undefined"); + equal(QUnit.equiv(undefined, []), false, "undefined"); + equal(QUnit.equiv(undefined, ""), false, "undefined"); + + // Nan usually doest not equal to Nan using the '==' operator. + // Only isNaN() is able to do it. + equal(QUnit.equiv(0/0, 0/0), true, "NaN"); // NaN VS NaN + equal(QUnit.equiv(1/0, 2/0), true, "Infinity"); // Infinity VS Infinity + equal(QUnit.equiv(-1/0, 2/0), false, "-Infinity, Infinity"); // -Infinity VS Infinity + equal(QUnit.equiv(-1/0, -2/0), true, "-Infinity, -Infinity"); // -Infinity VS -Infinity + equal(QUnit.equiv(0/0, 1/0), false, "NaN, Infinity"); // Nan VS Infinity + equal(QUnit.equiv(1/0, 0/0), false, "NaN, Infinity"); // Nan VS Infinity + equal(QUnit.equiv(0/0, null), false, "NaN"); + equal(QUnit.equiv(0/0, undefined), false, "NaN"); + equal(QUnit.equiv(0/0, 0), false, "NaN"); + equal(QUnit.equiv(0/0, false), false, "NaN"); + equal(QUnit.equiv(0/0, function () {}), false, "NaN"); + equal(QUnit.equiv(1/0, null), false, "NaN, Infinity"); + equal(QUnit.equiv(1/0, undefined), false, "NaN, Infinity"); + equal(QUnit.equiv(1/0, 0), false, "NaN, Infinity"); + equal(QUnit.equiv(1/0, 1), false, "NaN, Infinity"); + equal(QUnit.equiv(1/0, false), false, "NaN, Infinity"); + equal(QUnit.equiv(1/0, true), false, "NaN, Infinity"); + equal(QUnit.equiv(1/0, function () {}), false, "NaN, Infinity"); + + equal(QUnit.equiv(0, 0), true, "number"); + equal(QUnit.equiv(0, 1), false, "number"); + equal(QUnit.equiv(1, 0), false, "number"); + equal(QUnit.equiv(1, 1), true, "number"); + equal(QUnit.equiv(1.1, 1.1), true, "number"); + equal(QUnit.equiv(0.0000005, 0.0000005), true, "number"); + equal(QUnit.equiv(0, ''), false, "number"); + equal(QUnit.equiv(0, '0'), false, "number"); + equal(QUnit.equiv(1, '1'), false, "number"); + equal(QUnit.equiv(0, false), false, "number"); + equal(QUnit.equiv(1, true), false, "number"); + + equal(QUnit.equiv(true, true), true, "boolean"); + equal(QUnit.equiv(true, false), false, "boolean"); + equal(QUnit.equiv(false, true), false, "boolean"); + equal(QUnit.equiv(false, 0), false, "boolean"); + equal(QUnit.equiv(false, null), false, "boolean"); + equal(QUnit.equiv(false, undefined), false, "boolean"); + equal(QUnit.equiv(true, 1), false, "boolean"); + equal(QUnit.equiv(true, null), false, "boolean"); + equal(QUnit.equiv(true, undefined), false, "boolean"); + + equal(QUnit.equiv('', ''), true, "string"); + equal(QUnit.equiv('a', 'a'), true, "string"); + equal(QUnit.equiv("foobar", "foobar"), true, "string"); + equal(QUnit.equiv("foobar", "foo"), false, "string"); + equal(QUnit.equiv('', 0), false, "string"); + equal(QUnit.equiv('', false), false, "string"); + equal(QUnit.equiv('', null), false, "string"); + equal(QUnit.equiv('', undefined), false, "string"); + + // Rename for lint validation. + // We know this is bad, we are asserting whether we can coop with bad code like this. + var SafeNumber = Number, SafeString = String, SafeBoolean = Boolean, SafeObject = Object; + + // primitives vs. objects + + equal(QUnit.equiv(0, new SafeNumber()), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeNumber(), 0), true, "primitives vs. objects"); + equal(QUnit.equiv(1, new SafeNumber(1)), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeNumber(1), 1), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeNumber(0), 1), false, "primitives vs. objects"); + equal(QUnit.equiv(0, new SafeNumber(1)), false, "primitives vs. objects"); + + equal(QUnit.equiv(new SafeString(), ""), true, "primitives vs. objects"); + equal(QUnit.equiv("", new SafeString()), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeString("My String"), "My String"), true, "primitives vs. objects"); + equal(QUnit.equiv("My String", new SafeString("My String")), true, "primitives vs. objects"); + equal(QUnit.equiv("Bad String", new SafeString("My String")), false, "primitives vs. objects"); + equal(QUnit.equiv(new SafeString("Bad String"), "My String"), false, "primitives vs. objects"); + + equal(QUnit.equiv(false, new SafeBoolean()), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeBoolean(), false), true, "primitives vs. objects"); + equal(QUnit.equiv(true, new SafeBoolean(true)), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeBoolean(true), true), true, "primitives vs. objects"); + equal(QUnit.equiv(true, new SafeBoolean(1)), true, "primitives vs. objects"); + equal(QUnit.equiv(false, new SafeBoolean(false)), true, "primitives vs. objects"); + equal(QUnit.equiv(new SafeBoolean(false), false), true, "primitives vs. objects"); + equal(QUnit.equiv(false, new SafeBoolean(0)), true, "primitives vs. objects"); + equal(QUnit.equiv(true, new SafeBoolean(false)), false, "primitives vs. objects"); + equal(QUnit.equiv(new SafeBoolean(false), true), false, "primitives vs. objects"); + + equal(QUnit.equiv(new SafeObject(), {}), true, "object literal vs. instantiation"); + equal(QUnit.equiv({}, new SafeObject()), true, "object literal vs. instantiation"); + equal(QUnit.equiv(new SafeObject(), {a:1}), false, "object literal vs. instantiation"); + equal(QUnit.equiv({a:1}, new SafeObject()), false, "object literal vs. instantiation"); + equal(QUnit.equiv({a:undefined}, new SafeObject()), false, "object literal vs. instantiation"); + equal(QUnit.equiv(new SafeObject(), {a:undefined}), false, "object literal vs. instantiation"); +}); + +test("Objects Basics.", function() { + equal(QUnit.equiv({}, {}), true); + equal(QUnit.equiv({}, null), false); + equal(QUnit.equiv({}, undefined), false); + equal(QUnit.equiv({}, 0), false); + equal(QUnit.equiv({}, false), false); + + // This test is a hard one, it is very important + // REASONS: + // 1) They are of the same type "object" + // 2) [] instanceof Object is true + // 3) Their properties are the same (doesn't exists) + equal(QUnit.equiv({}, []), false); + + equal(QUnit.equiv({a:1}, {a:1}), true); + equal(QUnit.equiv({a:1}, {a:"1"}), false); + equal(QUnit.equiv({a:[]}, {a:[]}), true); + equal(QUnit.equiv({a:{}}, {a:null}), false); + equal(QUnit.equiv({a:1}, {}), false); + equal(QUnit.equiv({}, {a:1}), false); + + // Hard ones + equal(QUnit.equiv({a:undefined}, {}), false); + equal(QUnit.equiv({}, {a:undefined}), false); + equal(QUnit.equiv( + { + a: [{ bar: undefined }] + }, + { + a: [{ bat: undefined }] + } + ), false); + + // Objects with no prototype, created via Object.create(null), are used e.g. as dictionaries. + // Being able to test equivalence against object literals is quite useful. + if (typeof Object.create === 'function') { + equal(QUnit.equiv(Object.create(null), {}), true, "empty object without prototype VS empty object"); + + var nonEmptyWithNoProto = Object.create(null); + nonEmptyWithNoProto.foo = "bar"; + + equal(QUnit.equiv(nonEmptyWithNoProto, { foo: "bar" }), true, "object without prototype VS object"); + } +}); + + +test("Arrays Basics.", function() { + + equal(QUnit.equiv([], []), true); + + // May be a hard one, can invoke a crash at execution. + // because their types are both "object" but null isn't + // like a true object, it doesn't have any property at all. + equal(QUnit.equiv([], null), false); + + equal(QUnit.equiv([], undefined), false); + equal(QUnit.equiv([], false), false); + equal(QUnit.equiv([], 0), false); + equal(QUnit.equiv([], ""), false); + + // May be a hard one, but less hard + // than {} with [] (note the order) + equal(QUnit.equiv([], {}), false); + + equal(QUnit.equiv([null],[]), false); + equal(QUnit.equiv([undefined],[]), false); + equal(QUnit.equiv([],[null]), false); + equal(QUnit.equiv([],[undefined]), false); + equal(QUnit.equiv([null],[undefined]), false); + equal(QUnit.equiv([[]],[[]]), true); + equal(QUnit.equiv([[],[],[]],[[],[],[]]), true); + equal(QUnit.equiv( + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), + true); + equal(QUnit.equiv( + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // shorter + false); + equal(QUnit.equiv( + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // deepest element not an array + false); +}); + +test("Prototypal inheritance", function() { + function Gizmo(id?) { + this.id = id; + } + + function Hoozit(id?) { + this.id = id; + } + Hoozit.prototype = new Gizmo(); + + var gizmo = new Gizmo("ok"); + var hoozit = new Hoozit("ok"); + + // Try this test many times after test on instances that hold function + // to make sure that our code does not mess with last object constructor memoization. + equal(QUnit.equiv(function () {}, function () {}), false); + + // Hoozit inherit from Gizmo + // hoozit instanceof Hoozit; // true + // hoozit instanceof Gizmo; // true + equal(QUnit.equiv(hoozit, gizmo), true); + + Gizmo.prototype.bar = true; // not a function just in case we skip them + + // Hoozit inherit from Gizmo + // They are equivalent + equal(QUnit.equiv(hoozit, gizmo), true); + + // Make sure this is still true !important + // The reason for this is that I forgot to reset the last + // caller to where it were called from. + equal(QUnit.equiv(function () {}, function () {}), false); + + // Make sure this is still true !important + equal(QUnit.equiv(hoozit, gizmo), true); + + Hoozit.prototype.foo = true; // not a function just in case we skip them + + // Gizmo does not inherit from Hoozit + // gizmo instanceof Gizmo; // true + // gizmo instanceof Hoozit; // false + // They are not equivalent + equal(QUnit.equiv(hoozit, gizmo), false); + + // Make sure this is still true !important + equal(QUnit.equiv(function () {}, function () {}), false); +}); + + +test("Instances", function() { + function A() {} + var a1 = new A(); + var a2 = new A(); + + function B() { + this.fn = function () {}; + } + var b1 = new B(); + var b2 = new B(); + + equal(QUnit.equiv(a1, a2), true, "Same property, same constructor"); + + // b1.fn and b2.fn are functions but they are different references + // But we decided to skip function for instances. + equal(QUnit.equiv(b1, b2), true, "Same property, same constructor"); + equal(QUnit.equiv(a1, b1), false, "Same properties but different constructor"); // failed + + function Car(year) { + var privateVar = 0; + this.year = year; + this.isOld = function() { + return year > 10; + }; + } + + function Human(year) { + var privateVar = 1; + this.year = year; + this.isOld = function() { + return year > 80; + }; + } + + var car = new Car(30); + var carSame = new Car(30); + var carDiff = new Car(10); + var human = new Human(30); + + var diff = { + year: 30 + }; + + var same = { + year: 30, + isOld: function () {} + }; + + equal(QUnit.equiv(car, car), true); + equal(QUnit.equiv(car, carDiff), false); + equal(QUnit.equiv(car, carSame), true); + equal(QUnit.equiv(car, human), false); +}); + + +test("Complex Instances Nesting (with function value in literals and/or in nested instances)", function() { + function A(fn) { + this.a = {}; + this.fn = fn; + this.b = {a: []}; + this.o = {}; + this.fn1 = fn; + } + function B(fn) { + this.fn = fn; + this.fn1 = function () {}; + this.a = new A(function () {}); + } + + function fnOutside() { + } + + function C(fn) { + function fnInside() { + } + this.x = 10; + this.fn = fn; + this.fn1 = function () {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside // ok make reference to a function in all instances scope + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function () {}); + this.b2 = new B({ + x: { + b2: new B(function() {}) + } + }); + } + + function D(fn) { + function fnInside() { + } + this.x = 10; + this.fn = fn; + this.fn1 = function () {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside, // ok make reference to a function in all instances scope + + // This function won't be ingored. + // It isn't visible for all C insances + // and it is not in a property of an instance. (in an Object instances e.g. the object literal) + c: fnInside + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function () {}); + this.b2 = new B({ + x: { + b2: new B(function() {}) + } + }); + } + + function E(fn) { + function fnInside() { + } + this.x = 10; + this.fn = fn; + this.fn1 = function () {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside // ok make reference to a function in all instances scope + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function () {}); + this.b2 = new B({ + x: { + b1: new B({a: function() {}}), + b2: new B(function() {}) + } + }); + } + + + var a1 = new A(function () {}); + var a2 = new A(function () {}); + equal(QUnit.equiv(a1, a2), true); + + equal(QUnit.equiv(a1, a2), true); // different instances + + var b1 = new B(function () {}); + var b2 = new B(function () {}); + equal(QUnit.equiv(b1, b2), true); + + var c1 = new C(function () {}); + var c2 = new C(function () {}); + equal(QUnit.equiv(c1, c2), true); + + var d1 = new D(function () {}); + var d2 = new D(function () {}); + equal(QUnit.equiv(d1, d2), false); + + var e1 = new E(function () {}); + var e2 = new E(function () {}); + equal(QUnit.equiv(e1, e2), false); + +}); + + +test('object with references to self wont loop', function() { + var circularA = { + abc:null + }, circularB = { + abc:null + }; + circularA.abc = circularA; + circularB.abc = circularB; + equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambigous test)"); + + circularA.def = 1; + circularB.def = 1; + equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambigous test)"); + + circularA.def = 1; + circularB.def = 0; + equal(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object (unambigous test)"); +}); + +test('array with references to self wont loop', function() { + var circularA = [], + circularB = []; + circularA.push(circularA); + circularB.push(circularB); + equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambigous test)"); + + circularA.push( 'abc' ); + circularB.push( 'abc' ); + equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambigous test)"); + + circularA.push( 'hello' ); + circularB.push( 'goodbye' ); + equal(QUnit.equiv(circularA, circularB), false, "Should not repeat test on array (unambigous test)"); +}); + +test('mixed object/array with references to self wont loop', function() { + var circularA = [{abc:null}], + circularB = [{abc:null}]; + circularA[0].abc = circularA; + circularB[0].abc = circularB; + + circularA.push(circularA); + circularB.push(circularB); + equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambigous test)"); + + circularA[0].def = 1; + circularB[0].def = 1; + equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambigous test)"); + + circularA[0].def = 1; + circularB[0].def = 0; + equal(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object/array (unambigous test)"); +}); + +test("Test that must be done at the end because they extend some primitive's prototype", function() { + // Try that a function looks like our regular expression. + // This tests if we check that a and b are really both instance of RegExp + //Function.prototype.global = true; + //Function.prototype.multiline = true; + //Function.prototype.ignoreCase = false; + //Function.prototype.source = "my regex"; + var re = /my regex/gm; + equal(QUnit.equiv(re, function () {}), false, "A function that looks that a regex isn't a regex"); + // This test will ensures it works in both ways, and ALSO especially that we can make differences + // between RegExp and Function constructor because typeof on a RegExpt instance is "function" + equal(QUnit.equiv(function () {}, re), false, "Same conversely, but ensures that function and regexp are distinct because their constructor are different"); +}); + +QUnit.start(); + +test("just a test", function() { + expect(1); + ok(true); +}); + +// ************** BUG ? ****************** +// TODO disable reordering for this suite! + +var begin = 0, + moduleStart = 0, + moduleDone = 0, + testStart = 0, + testDone = 0, + log = 0, + moduleContext, + moduleDoneContext, + testContext, + testDoneContext, + logContext; +/* +QUnit.begin(function() { + //begin++; +}); +QUnit.done(function() { +}); +QUnit.moduleStart(function(context) { + //moduleStart++; + //moduleContext = context; +}); +QUnit.moduleDone(function(context) { + //moduleDone++; + //moduleDoneContext = context; +}); +QUnit.testStart(function(context) { + //testStart++; + //testContext = context; +}); +QUnit.testDone(function(context) { + //testDone++; + //testDoneContext = context; +}); +QUnit.log(function(context) { + //log++; + //logContext = context; +}); +*/ +QUnit.module("logs1"); + +QUnit.test("test1", 15, function() { + equal( begin, 1, "QUnit.begin calls" ); + equal( moduleStart, 1, "QUnit.moduleStart calls" ); + equal( testStart, 1, "QUnit.testStart calls" ); + equal( testDone, 0, "QUnit.testDone calls" ); + equal( moduleDone, 0, "QUnit.moduleDone calls" ); + deepEqual( logContext, { + name: "test1", + module: "logs1", + result: true, + message: "QUnit.moduleDone calls", + actual: 0, + expected: 0 + }, "log context after equal(actual, expected, message)" ); + + equal( "foo", "foo" ); + deepEqual(logContext, { + name: "test1", + module: "logs1", + result: true, + message: undefined, + actual: "foo", + expected: "foo" + }, "log context after equal(actual, expected)" ); + + ok( true, "ok(true, message)" ); + deepEqual( logContext, { + module: "logs1", + name: "test1", + result: true, + message: "ok(true, message)" + }, "log context after ok(true, message)" ); + + strictEqual( testDoneContext, undefined, "testDone context" ); + deepEqual( testContext, { + module: "logs1", + name: "test1" + }, "test context" ); + strictEqual( moduleDoneContext, undefined, "moduleDone context" ); + deepEqual( moduleContext, { + name: "logs1" + }, "module context" ); + + equal( log, 14, "QUnit.log calls" ); +}); +QUnit.test("test2", 11, function() { + equal( begin, 1, "QUnit.begin calls" ); + equal( moduleStart, 1, "QUnit.moduleStart calls" ); + equal( testStart, 2, "QUnit.testStart calls" ); + equal( testDone, 1, "QUnit.testDone calls" ); + equal( moduleDone, 0, "QUnit.moduleDone calls" ); + + ok( typeof testDoneContext.duration === "number" , "testDone context: duration" ); + delete testDoneContext.duration; + deepEqual( testDoneContext, { + module: "logs1", + name: "test1", + failed: 0, + passed: 15, + total: 15 + }, "testDone context" ); + deepEqual( testContext, { + module: "logs1", + name: "test2" + }, "test context" ); + strictEqual( moduleDoneContext, undefined, "moduleDone context" ); + deepEqual( moduleContext, { + name: "logs1" + }, "module context" ); + + equal( log, 25, "QUnit.log calls" ); +}); + +QUnit.module("logs2"); + +QUnit.test( "test1", 9, function() { + equal( begin, 1, "QUnit.begin calls" ); + equal( moduleStart, 2, "QUnit.moduleStart calls" ); + equal( testStart, 3, "QUnit.testStart calls" ); + equal( testDone, 2, "QUnit.testDone calls" ); + equal( moduleDone, 1, "QUnit.moduleDone calls" ); + + deepEqual( testContext, { + module: "logs2", + name: "test1" + }, "test context" ); + deepEqual( moduleDoneContext, { + name: "logs1", + failed: 0, + passed: 26, + total: 26 + }, "moduleDone context" ); + deepEqual( moduleContext, { + name: "logs2" + }, "module context" ); + + equal( log, 34, "QUnit.log calls" ); +}); +QUnit.test( "test2", 8, function() { + equal( begin, 1, "QUnit.begin calls" ); + equal( moduleStart, 2, "QUnit.moduleStart calls" ); + equal( testStart, 4, "QUnit.testStart calls" ); + equal( testDone, 3, "QUnit.testDone calls" ); + equal( moduleDone, 1, "QUnit.moduleDone calls" ); + + deepEqual( testContext, { + module: "logs2", + name: "test2" + }, "test context" ); + deepEqual( moduleContext, { + name: "logs2" + }, "module context" ); + + equal( log, 42, "QUnit.log calls" ); +}); + +var testAutorun = true; + +QUnit.done(function() { + + if (!testAutorun) { + return; + } + + testAutorun = false; + + QUnit.module("autorun"); + + QUnit.test("reset", 0, function() {}); + + //moduleStart = moduleDone = 0; + + test("first", function() { + equal(moduleStart, 1, "test started"); + equal(moduleDone, 0, "test in progress"); + }); + + test("second", function() { + equal(moduleStart, 2, "test started"); + equal(moduleDone, 1, "test in progress"); + }); +}); + +QUnit.log(function(details: any) { + if (!details.result) { + var output = "FAILED: " + (details.message ? details.message + ", " : ""); + if (details.actual) { + output += "expected: " + details.expected + ", actual: " + details.actual; + } + if (details.source) { + output += ", " + details.source; + } + } +}); + +QUnit.test("fail twice with stacktrace", function(assert) { + /*jshint expr:true */ + assert.equal(true, false); + assert.equal(true, false, "gotta fail"); +}); + +test("module without setup/teardown (default)", function() { + expect(1); + ok(true); +}); + +QUnit.test("expect in test", 3, function() { + ok(true); + ok(true); + ok(true); +}); + +QUnit.test("expect in test", 5, function() { + ok(true); +}); + +test("expect query and multiple issue", function() { + expect(2); + ok(true); + var expected = expect(null); + equal(expected, 2); + expect(expected + 1); + ok(true); +}); + +QUnit.module("assertion helpers"); + +QUnit.test( "QUnit.assert compatibility", 5, function( assert ) { + assert.ok( true, "Calling method on `assert` argument to test() callback" ); + + // Should also work, although discouraged and not documented + QUnit.assert.ok( true, "Calling method on QUnit.assert object" ); + + // Test compatibility aliases + QUnit.ok( true, "Calling aliased method in QUnit root object" ); + ok( true, "Calling aliased function in global namespace" ); + + // Regression fix for #341 + // The assert-context way of testing discouraged global variables, + // it doesn't make sense of it itself to be a global variable. + // Only allows for mistakes (e.g. forgetting to list 'assert' as parameter) + //assert.notStrictEqual( /*window.assert*/null, QUnit.assert, "Assert does not get exposed as a global variable" ); +}); + +QUnit.module("setup test", { + setup: function() { + ok(true); + } +}); + +test("module with setup", function() { + expect(2); + ok(true); +}); + +QUnit.test("module with setup, expect in test call", 2, function() { + ok(true); +}); + +QUnit.module("", { + setup: function() { + }, + teardown: function() { + // We can't use ok(false) inside script tags since some browsers + // don't evaluate script tags inserted through innerHTML after domready. + // Counting them before/after doesn't cover everything either as qunit-modulefilter + // is created before any test is ran. So use ids instead. + if (document.getElementById('qunit-unescaped-module')) { + // This can either be from in #qunit-modulefilter or #qunit-testresult + ok(false, 'Unscaped module name'); + } + if (document.getElementById('qunit-unescaped-test')) { + ok(false, 'Unscaped test name'); + } + if (document.getElementById('qunit-unescaped-assertion')) { + ok(false, 'Unscaped test name'); + } + } +}); + +QUnit.test("", 1, function() { + ok(true, ""); +}); + +var state; + +QUnit.module("setup/teardown test", { + setup: function() { + state = true; + ok(true); + // Assert that we can introduce and delete globals in setup/teardown + // without noglobals sounding any alarm. + + // Using an implied global variable instead of explicit window property + // because there is no way to delete a window.property in IE6-8 + // `delete x` only works for `x = 1, and `delete window.x` throws exception. + // No one-code fits all solution possible afaic. Resort to @cc. + + /*@cc_on + @if (@_jscript_version < 9) + x = 1; + @else @*/ + //window.x = 1; + /*@end + @*/ + }, + teardown: function() { + ok(true); + + /*@cc_on + @if (@_jscript_version < 9) + delete x; + @else @*/ + //delete window.x; + /*@end + @*/ + } +}); + +test("module with setup/teardown", function() { + expect(3); + ok(true); +}); + +QUnit.module("setup/teardown test 2"); + +test("module without setup/teardown", function() { + expect(1); + ok(true); +}); + +var orgDate; + +QUnit.module("Date test", { + setup: function() { + orgDate = Date; + var Date = function () { + ok( false, 'QUnit should internally be independant from Date-related manipulation and testing' ); + return new orgDate(); + }; + }, + teardown: function() { + var date = orgDate; + } +}); + +test("sample test for Date test", function () { + expect(1); + ok(true); +}); + +if (typeof setTimeout !== 'undefined') { +state = 'fail'; + +QUnit.module("teardown and stop", { + teardown: function() { + equal(state, "done", "Test teardown."); + } +}); + +test("teardown must be called after test ended", function() { + expect(1); + stop(); + setTimeout(function() { + state = "done"; + start(); + }, 13); +}); + +test("parameter passed to stop increments semaphore n times", function() { + expect(1); + stop(3); + setTimeout(function() { + state = "not enough starts"; + start(); + start(); + }, 13); + setTimeout(function() { + state = "done"; + start(); + }, 15); +}); + +test("parameter passed to start decrements semaphore n times", function() { + expect(1); + stop(); + stop(); + stop(); + setTimeout(function() { + state = "done"; + start(3); + }, 18); +}); + +QUnit.module("async setup test", { + setup: function() { + stop(); + setTimeout(function() { + ok(true); + start(); + }, 500); + } +}); + +asyncTest("module with async setup", function() { + expect(2); + ok(true); + start(); +}); + +QUnit.module("async teardown test", { + teardown: function() { + stop(); + setTimeout(function() { + ok(true); + start(); + }, 500); + } +}); + +asyncTest("module with async teardown", function() { + expect(2); + ok(true); + start(); +}); + +QUnit.module("asyncTest"); + +asyncTest("asyncTest", function() { + expect(2); + ok(true); + setTimeout(function() { + state = "done"; + ok(true); + start(); + }, 13); +}); + +asyncTest("asyncTest", 2, function() { + ok(true); + setTimeout(function() { + state = "done"; + ok(true); + start(); + }, 13); +}); + +QUnit.test("sync", 2, function() { + stop(); + setTimeout(function() { + ok(true); + start(); + }, 13); + stop(); + setTimeout(function() { + ok(true); + start(); + }, 125); +}); + +QUnit.test("test synchronous calls to stop", 2, function() { + stop(); + setTimeout(function() { + ok(true, 'first'); + start(); + stop(); + setTimeout(function() { + ok(true, 'second'); + start(); + }, 150); + }, 150); +}); +} + +QUnit.module("save scope", { + setup: function() { + this.foo = "bar"; + }, + teardown: function() { + deepEqual(this.foo, "bar"); + } +}); +test("scope check", function() { + expect(2); + deepEqual(this.foo, "bar"); +}); + +QUnit.module("simple testEnvironment setup", { + foo: "bar", + // example of meta-data + bugid: "#5311" +}); +test("scope check", function() { + deepEqual(this.foo, "bar"); +}); +test("modify testEnvironment",function() { + expect(0); + this.foo="hamster"; +}); +test("testEnvironment reset for next test",function() { + deepEqual(this.foo, "bar"); +}); + +QUnit.module("testEnvironment with object", { + options:{ + recipe:"soup", + ingredients:["hamster","onions"] + } +}); +test("scope check", function() { + deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions"]}) ; +}); +test("modify testEnvironment",function() { + expect(0); + // since we do a shallow copy, the testEnvironment can be modified + this.options.ingredients.push("carrots"); +}); +test("testEnvironment reset for next test",function() { + deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions","carrots"]}, "Is this a bug or a feature? Could do a deep copy") ; +}); + + +QUnit.module("testEnvironment tests"); + +function makeurl() { + var testEnv = QUnit.current_testEnvironment; + var url = testEnv.url || 'http://example.com/search'; + var q = testEnv.q || 'a search test'; + return url + '?q='+encodeURIComponent(q); +} + +test("makeurl working",function() { + equal( QUnit.current_testEnvironment, this, 'The current testEnvironment is global'); + equal( makeurl(), 'http://example.com/search?q=a%20search%20test', 'makeurl returns a default url if nothing specified in the testEnvironment'); +}); + +QUnit.module("testEnvironment with makeurl settings", { + url: 'http://google.com/', + q: 'another_search_test' +}); +test("makeurl working with settings from testEnvironment", function() { + equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to from the url'); +}); + +QUnit.module("jsDump"); +test("jsDump output", function() { + equal( QUnit.jsDump.parse([1, 2]), "[\n 1,\n 2\n]" ); + equal( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"left\": 0,\n \"top\": 5\n}" ); + if (typeof document !== 'undefined' && document.getElementById("qunit-header")) { + equal( QUnit.jsDump.parse(document.getElementById("qunit-header")), "

" ); + equal( QUnit.jsDump.parse(document.getElementsByTagName("h1")), "[\n

\n]" ); + } +}); + +QUnit.module("assertions"); + +QUnit.test("propEqual", 5, function( assert ) { + var objectCreate = Object.create || function ( origin ) { + function O() {} + O.prototype = origin; + var r = new O(); + return r; + }; + + function Foo( x, y, z ) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.doA = function () {}; + Foo.prototype.doB = function () {}; + Foo.prototype.bar = 'prototype'; + + function Bar() { + } + Bar.prototype = objectCreate( Foo.prototype ); + Bar.prototype.constructor = Bar; + + assert.propEqual( + new Foo( 1, '2', [] ), + { + x: 1, + y: '2', + z: [] + } + ); + + assert.notPropEqual( + new Foo( '1', 2, 3 ), + { + x: 1, + y: '2', + z: 3 + }, + 'Primitive values are strictly compared' + ); + + assert.notPropEqual( + new Foo( 1, '2', [] ), + { + x: 1, + y: '2', + z: {} + }, + 'Array type is preserved' + ); + + assert.notPropEqual( + new Foo( 1, '2', {} ), + { + x: 1, + y: '2', + z: [] + }, + 'Empty array is not the same as empty object' + ); + + assert.propEqual( + new Foo( 1, '2', new Foo( [ 3 ], new Bar(), null ) ), + { + x: 1, + y: '2', + z: { + x: [ 3 ], + y: {}, + z: null + } + }, + 'Complex nesting of different types, inheritance and constructors' + ); +}); + +QUnit.test("raises", 9, function() { + function CustomError( message ) { + this.message = message; + } + + CustomError.prototype.toString = function() { + return this.message; + }; + + throws( + function() { + throw "my error"; + } + ); + + throws( + function() { + throw "my error"; + }, + "simple string throw, no 'expected' value given" + ); + + // This test is for IE 7 and prior which does not properly + // implement Error.prototype.toString + throws( + function() { + throw new Error("error message"); + }, + /error message/, + "use regexp against instance of Error" + ); + + throws( + function() { + throw new Error(); + }, + Error, + 'thrown error is an instance of CustomError' + ); + + throws( + function() { + throw new Error("some error description"); + }, + /description/, + "use a regex to match against the stringified error" + ); + + throws( + function() { + throw new Error("some error description"); + }, + function( err ) { + if ( (err instanceof Error) && /description/.test(err) ) { + return true; + } + }, + "custom validation function" + ); + + throws( + function() { + /*jshint evil:true */ + ( window.execScript || function( data ) { + window["eval"].call( window, data ); + })( "throw 'error';" ); + }, + 'globally-executed errors caught' + ); + + this.CustomError = {}; + + throws( + function() { + throw new this.CustomError("some error description"); + }, + /description/, + "throw error from property of 'this' context" + ); + + raises( + function() { + throw "error"; + }, + "simple throw, asserting with deprecated raises() function" + ); + +}); + +if (typeof document !== "undefined") { + +QUnit.module("fixture"); +test("setup", function() { + expect(0); + document.getElementById("qunit-fixture").innerHTML = "foobar"; +}); + +test("basics", function() { + equal( document.getElementById("qunit-fixture").innerHTML, "test markup", "automatically reset" ); +}); + +test("running test name displayed", function() { + expect(2); + + var displaying = document.getElementById("qunit-testresult"); + + ok( /running test name displayed/.test(displaying.innerHTML), "Expect test name to be found in displayed text" ); + ok( /fixture/.test(displaying.innerHTML), "Expect module name to be found in displayed text" ); +}); + +(function() { + var delayNextSetup, + sleep = function( n ) { + stop(); + setTimeout( function() { start(); }, n ); + }; + + QUnit.module("timing", { + setup: function() { + if ( delayNextSetup ) { + delayNextSetup = false; + sleep( 250 ); + } + } + }); + + QUnit.test("setup", 0, function() { + delayNextSetup = true; + }); + + var getPreviousTests: (...args: any[]) => any; + + QUnit.test("basics", 2, function() { + var previous = getPreviousTests(/^setup$/, /^timing$/)[0], + runtime = previous.lastChild.previousSibling; + ok( /(^| )runtime( |$)/.test( runtime.className ), "Runtime element exists" ); + ok( /^\d+ ms$/.test( runtime.innerHTML ), "Runtime reported in ms" ); + }); + + QUnit.test("values", 2, function() { + var basics = getPreviousTests(/^setup$/, /^timing$/)[0], + slow = getPreviousTests(/^basics$/, /^timing$/)[0]; + ok( parseInt( basics.lastChild.previousSibling.innerHTML, 10 ) < 50, "Fast runtime for trivial test" ); + ok( parseInt( slow.lastChild.previousSibling.innerHTML, 10 ) > 250, "Runtime includes setup" ); + }); +})(); + +} + +QUnit.module("custom assertions"); +(function() { + function mod2(value, expected, message) { + var actual = value % 2; + QUnit.push(actual == expected, actual, expected, message); + } + test("mod2", function() { + mod2(2, 0, "2 % 2 == 0"); + mod2(3, 1, "3 % 2 == 1"); + }); +})(); + + +QUnit.module("recursions"); + +function Wrap(x?) { + this.wrap = x; + if (x === undefined) { + this.first = true; + } +} + +function chainwrap(depth?, first?, prev?) { + depth = depth || 0; + var last = prev || new Wrap(); + first = first || last; + + if (depth == 1) { + first.wrap = last; + } + if (depth > 1) { + last = chainwrap(depth-1, first, new Wrap(last)); + } + + return last; +} + +test("check jsDump recursion", function() { + expect(4); + + var noref = chainwrap(0); + var nodump = QUnit.jsDump.parse(noref); + equal(nodump, '{\n "first": true,\n "wrap": undefined\n}'); + + var selfref = chainwrap(1); + var selfdump = QUnit.jsDump.parse(selfref); + equal(selfdump, '{\n "first": true,\n "wrap": recursion(-1)\n}'); + + var parentref = chainwrap(2); + var parentdump = QUnit.jsDump.parse(parentref); + equal(parentdump, '{\n "wrap": {\n "first": true,\n "wrap": recursion(-2)\n }\n}'); + + var circref = chainwrap(10); + var circdump = QUnit.jsDump.parse(circref); + ok(new RegExp("recursion\\(-10\\)").test(circdump), "(" +circdump + ") should show -10 recursion level"); +}); + +test("check (deep-)equal recursion", function() { + var noRecursion = chainwrap(0); + equal(noRecursion, noRecursion, "I should be equal to me."); + deepEqual(noRecursion, noRecursion, "... and so in depth."); + + var selfref = chainwrap(1); + equal(selfref, selfref, "Even so if I nest myself."); + deepEqual(selfref, selfref, "... into the depth."); + + var circref = chainwrap(10); + equal(circref, circref, "Or hide that through some levels of indirection."); + deepEqual(circref, circref, "... and checked on all levels!"); +}); + + +test('Circular reference with arrays', function() { + + // pure array self-ref + var arr = []; + arr.push(arr); + + var arrdump = QUnit.jsDump.parse(arr); + + equal(arrdump, '[\n recursion(-1)\n]'); + equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref'); + + + // mix obj-arr circular ref + var obj: any = {}; + var childarr = [obj]; + obj.childarr = childarr; + + var objdump = QUnit.jsDump.parse(obj); + var childarrdump = QUnit.jsDump.parse(childarr); + + equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}'); + equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]'); + + equal(obj.childarr, childarr, 'no endless stack when trying to dump array/object mix with circular ref'); + equal(childarr[0], obj, 'no endless stack when trying to dump array/object mix with circular ref'); + +}); + + +test('Circular reference - test reported by soniciq in #105', function() { + var MyObject = function() {}; + MyObject.prototype.parent = function(obj) { + if (obj === undefined) { return this._parent; } + this._parent = obj; + }; + MyObject.prototype.children = function(obj) { + if (obj === undefined) { return this._children; } + this._children = obj; + }; + + var a = new MyObject(), + b = new MyObject(); + + var barr = [b]; + a.children(barr); + b.parent(a); + + equal(a.children(), barr); + deepEqual(a.children(), [b]); +}); + +(function() { + var reset = QUnit.reset; + QUnit.module("reset"); + test("reset runs assertions", function() { + expect(0); + QUnit.reset = function() { + ok( false, "reset should not modify test status" ); + reset.apply( this, arguments ); + }; + }); + test("reset runs assertions, cleanup", function() { + expect(0); + QUnit.reset = reset; + }); +})(); + +function testAfterDone() { + var testName = "ensure has correct number of assertions"; + + function secondAfterDoneTest() { + QUnit.config.done = []; + // Because when this does happen, the assertion count parameter doesn't actually + // work we use this test to check the assertion count. + QUnit.module("check previous test's assertion counts"); + test('count previous two test\'s assertions', function () { + //var tests = getPreviousTests(/^ensure has correct number of assertions/, /^Synchronous test after load of page$/); + var tests: any = {}; + + equal(tests[0].firstChild.lastChild.getElementsByTagName("b")[1].innerHTML, "99"); + equal(tests[1].firstChild.lastChild.getElementsByTagName("b")[1].innerHTML, "99"); + }); + } + QUnit.config.done = []; + QUnit.done(secondAfterDoneTest); + + QUnit.module("Synchronous test after load of page"); + + asyncTest('Async test', function() { + start(); + for (var i = 1; i < 100; i++) { + ok(i); + } + }); + + QUnit.test(testName, 99, function() { + for (var i = 1; i < 100; i++) { + ok(i); + } + }); + + // We need two of these types of tests in order to ensure that assertions + // don't move between tests. + QUnit.test(testName + ' 2', 99, function() { + for (var i = 1; i < 100; i++) { + ok(i); + } + }); +} \ No newline at end of file diff --git a/qunit/qunit.d.ts b/qunit/qunit.d.ts index 099ac905d..a57d945f5 100644 --- a/qunit/qunit.d.ts +++ b/qunit/qunit.d.ts @@ -48,24 +48,31 @@ interface Config { reorder: bool; requireExpects: bool; urlConfig: Array; + done: any; } interface LifecycleObject { - setup: () => any; - teardown: () => any; + setup?: () => any; + teardown?: () => any; } interface QUnitAssert { /* ASSERT */ - deepEqual(actual: Object, expected: Object, message: string); - equal(actual: Object, expected: Object, message: string); - notDeepEqual(actual: Object, expected: Object, message: string); - notEqual(actual: Object, expected: Object, message: string); - notStrictEqual(actual: Object, expected: Object, message: string); - ok(state: any, message: string); - strictEqual(actual: Object, expected: Object, message: string); - throws(block: () => any, expected: Object, message: string); - throws(block: () => any, message: string); + assert: any; + current_testEnvironment: any; + jsDump: any; + + deepEqual(actual: any, expected: any, message?: string); + equal(actual: any, expected: any, message?: string); + notDeepEqual(actual: any, expected: any, message?: string); + notEqual(actual: any, expected: any, message?: string); + notPropEqual(actual: any, expected: any, message?: string); + propEqual(actual: any, expected: any, message?: string); + notStrictEqual(actual: any, expected: any, message?: string); + ok(state: any, message?: string); + strictEqual(actual: any, expected: any, message?: string); + throws(block: () => any, expected: any, message?: string); + throws(block: () => any, message?: string); } interface QUnitStatic extends QUnitAssert{ @@ -90,21 +97,33 @@ interface QUnitStatic extends QUnitAssert{ asyncTest(name: string, test: () => any); expect(amount: number); module(name: string, lifecycle?: LifecycleObject); - test(title: string, expected: number, test: (assert: any) => any); - test(title: string, test: (assert: any) => any); + test(title: string, expected: number, test: (assert: QUnitAssert) => any); + test(title: string, test: (assert: QUnitAssert) => any); + + // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L1568 + equiv(a: any, b: any); + + // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L661 + raises: any; + + // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L897 + push(result, actual, expected, message): any; + + // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L839 + reset(): any; } /* ASSERT */ -declare var deepEqual: (actual: Object, expected: Object, message: string) => any; -declare var equal: (actual: Object, expected: Object, message: string) => any; -declare var notDeepEqual: (actual: Object, expected: Object, message: string) => any; -declare var notEqual: (actual: Object, expected: Object, message: string) => any; -declare var notStrictEqual: (actual: Object, expected: Object, message: string) => any; -declare var ok: (state: any, message: string) => any; -declare var strictEqual: (actual: Object, expected: Object, message: string) => any; -// ** I Can't make overload here! :( +declare var deepEqual: (actual: any, expected: any, message?: string) => any; +declare var equal: (actual: any, expected: any, message?: string) => any; +declare var notDeepEqual: (actual: any, expected: any, message?: string) => any; +declare var notEqual: (actual: any, expected: any, message?: string) => any; +declare var notStrictEqual: (actual: any, expected: any, message?: string) => any; +declare var ok: (state: any, message?: string) => any; +declare var strictEqual: (actual: any, expected: any, message?: string) => any; +// ** I Can't make overload here! :(s //declare var throws: (block: () => void, expected: Object, message: string) => any; -declare var throws: (block: () => void, message: string) => any; +declare var throws: (block: () => void, expected?: any, message?: string) => any; /* ASYNC CONTROL */ declare var start: (decrement?: number) => any; @@ -120,13 +139,21 @@ declare var testDone: (callback: (details: TestDoneCallbackObject) => any) => an declare var testStart: (callback: (details: TestStartCallbackObject) => any) => any; /* TEST */ -declare var asyncTest: (name: string, /*expected?: number, */test: () => any) => any; +declare var asyncTest: (name: string, expected?: any, test?: () => any) => any; declare var expect: (amount: number) => any; // ** conflict with TypeScript module keyword. Must be used on QUnit namespace //declare var module: (name: string, lifecycle?: LifecycleObject) => any; // ** I can't make an overload here! :( -//declare var test: (title: string, expected: number, test: (assert?: any) => any) => any; -declare var test: (title: string, test: (assert?: any) => any) => any; +//declare var test: (title: string, expected?: any, test?: (assert?: any) => any) => any; +declare var test: (title: string, test: (assert?: QUnitAssert) => any) => any; +declare var notPropEqual: (actual: any, expected: any, message?: string) => any; +declare var propEqual: (actual: any, expected: any, message?: string) => any; + +// https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L1568 +declare var equiv: (a: any, b: any) => any; + +// https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L661 +declare var raises: any; /* QUNIT */ declare var QUnit: QUnitStatic; \ No newline at end of file