diff --git a/Libraries/LibJS/Tests/test-common-tests.js b/Libraries/LibJS/Tests/test-common-tests.js new file mode 100644 index 0000000000..80089515a3 --- /dev/null +++ b/Libraries/LibJS/Tests/test-common-tests.js @@ -0,0 +1,224 @@ +test("toBe", () => { + expect(null).toBe(null); + expect(undefined).toBe(undefined); + expect(null).not.toBe(undefined); + + expect(1).toBe(1); + expect(1).not.toBe(2); + + expect("1").toBe("1"); + expect("1").not.toBe("2"); + + expect(true).toBe(true); + expect(true).not.toBe(false); + + expect({}).not.toBe({}); + expect([]).not.toBe([]); + + function foo() {}; + expect(foo).toBe(foo); + expect(function(){}).not.toBe(function(){}); + + let s = Symbol("foo"); + expect(s).toBe(s); + expect(Symbol("foo")).not.toBe(Symbol("foo")); + + expect(1n).toBe(1n); + expect(1n).not.toBe(1); +}); + +test("toHaveLength", () => { + expect([]).toHaveLength(0); + expect([]).not.toHaveLength(1); + expect([1]).toHaveLength(1); + expect({ length: 1 }).toHaveLength(1); + + expect(() => { + expect(1).toHaveLength(); + }).toThrow(ExpectationError); +}); + +test("toHaveProperty", () => { + expect([]).toHaveProperty("length"); + expect([]).toHaveProperty("length", 0); + expect([1]).not.toHaveProperty("length", 0); + expect({ foo: "bar" }).toHaveProperty("foo") + expect({ foo: "bar" }).toHaveProperty("foo", "bar"); + + expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"]); + expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"], "baz"); + expect({ foo: { bar: "baz" } }).toHaveProperty("foo.bar"); + expect({ foo: { bar: "baz" } }).toHaveProperty("foo.bar", "baz"); + + expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"]); + expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"], "baz"); + expect({ foo: { bar: "baz" } }).not.toHaveProperty(["foo", "baz"]); + expect({ foo: { bar: "baz" } }).not.toHaveProperty(["foo", "baz"], "qux"); + expect({ foo: { bar: "baz" } }).not.toHaveProperty("foo.baz"); + expect({ foo: { bar: "baz" } }).not.toHaveProperty("foo.baz", "qux"); +}); + +test("toBeDefined", () => { + expect(1).toBeDefined(); + expect(true).toBeDefined(); + expect(false).toBeDefined(); + expect({}).toBeDefined(); + expect([]).toBeDefined(); + expect("a").toBeDefined(); + expect(null).toBeDefined(); + expect(undefined).not.toBeDefined(); +}); + +test("toBeInstanceOf", () => { + expect(new Error).toBeInstanceOf(Error); + expect(Error).not.toBeInstanceOf(Error); + + class Parent {}; + class Child extends Parent {}; + + expect(new Child).toBeInstanceOf(Child); + expect(new Child).toBeInstanceOf(Parent); + expect(new Parent).toBeInstanceOf(Parent); + expect(new Parent).not.toBeInstanceOf(Child); +}); + +test("toBeNull", () => { + expect(null).toBeNull(); + expect(undefined).not.toBeNull(); + expect(5).not.toBeNull(); +}); + +test("toBeUndefined", () => { + expect(undefined).toBeUndefined(); + expect(null).not.toBeUndefined(); + expect().toBeUndefined(); + expect(5).not.toBeUndefined(); +}); + +test("toBeNaN", () => { + expect(NaN).toBeNaN(); + expect(5).not.toBeNaN(); +}); + +test("toContain", () => { + expect([1, 2, 3]).toContain(1); + expect([1, 2, 3]).toContain(2); + expect([1, 2, 3]).toContain(3); + expect([{ foo: 1 }]).not.toContain({ foo: 1 }); +}); + +test("toContainEqual", () => { + expect([1, 2, 3]).toContainEqual(1); + expect([1, 2, 3]).toContainEqual(2); + expect([1, 2, 3]).toContainEqual(3); + expect([{ foo: 1 }]).toContainEqual({ foo: 1 }); +}); + +test("toEqual", () => { + expect(undefined).toEqual(undefined); + expect(null).toEqual(null); + expect(undefined).not.toEqual(null); + expect(null).not.toEqual(undefined); + expect(NaN).toEqual(NaN); + + expect(1).toEqual(1); + expect("abcd").toEqual("abcd"); + + let s = Symbol(); + expect(s).toEqual(s); + expect(Symbol()).not.toEqual(Symbol()); + expect(Symbol.for("foo")).toEqual(Symbol.for("foo")); + + expect({ foo: 1, bar: { baz: [1, 2, 3 ] } }) + .toEqual({ foo: 1, bar: { baz: [1, 2, 3 ] } }); + expect([1, 2, { foo: 1 }, [3, [4, 5]]]) + .toEqual([1, 2, { foo: 1 }, [3, [4, 5]]]); + + function foo() {} + expect(foo).toEqual(foo); + expect(function(){}).not.toEqual(function(){}); +}); + +test("toThrow", () => { + expect(() => {}).not.toThrow(); + expect(() => {}).not.toThrow("foo"); + expect(() => {}).not.toThrow(TypeError); + expect(() => {}).not.toThrow(new TypeError("foo")); + + let thrower = () => { + throw new TypeError("foo bar"); + }; + + expect(thrower).toThrow(); + expect(thrower).toThrow(TypeError); + expect(thrower).toThrow("o ba"); + expect(thrower).toThrow("foo bar"); + expect(thrower).not.toThrow("baz"); + expect(thrower).not.toThrow(ReferenceError); + expect(thrower).toThrow(new TypeError("foo bar")); + expect(thrower).not.toThrow(new TypeError("o ba")); + expect(thrower).toThrow(new ReferenceError("foo bar")); + expect(thrower).toThrow({ message: "foo bar" }); +}); + +test("pass", () => { + expect().pass(); + expect({}).pass(); +}); + +test("fail", () => { + // FIXME: Doesn't really make sense; this is a great candidate + // for expect.assertions() + try { + expect().fail(); + } catch (e) { + expect(e.name).toBe("ExpectationError"); + } +}); + +test("toThrowWithMessage", () => { + let incorrectUsages = [ + [1, undefined, undefined], + [() => {}, undefined, undefined], + [() => {}, function(){}, undefined], + [() => {}, undefined, "test"] + ]; + + incorrectUsages.forEach(arr => { + expect(() => { + expect(arr[0]).toThrowWithMessage(arr[1], arr[2]); + }).toThrow(); + }); + + let thrower = () => { + throw new TypeError("foo bar"); + }; + + expect(thrower).toThrowWithMessage(TypeError, "foo bar"); + expect(thrower).toThrowWithMessage(TypeError, "foo"); + expect(thrower).toThrowWithMessage(TypeError, "o ba"); + expect(thrower).not.toThrowWithMessage(ReferenceError, "foo bar"); + expect(thrower).not.toThrowWithMessage(TypeError, "foo baz"); +}); + +// FIXME: Will have to change when this matcher changes to use the +// "eval" function +test("toEval", () => { + expect("let a = 1").toEval(); + expect("a < 1").not.toEval(); + expect("&&*^%#%@").not.toEval(); + expect("function foo() { return 1; }; return foo();").toEval(); +}); + +// FIXME: Will have to change when this matcher changes to use the +// "eval" function +test("toEvalTo", () => { + expect("let a = 1").toEvalTo(); + expect("let a = 1").toEvalTo(undefined); + expect("return 10").toEvalTo(10); + expect("return 10").not.toEvalTo(5); + + expect(() => { + expect("*^&%%").not.toEvalTo(); + }).toThrow(); +}); diff --git a/Libraries/LibJS/Tests/test-common.js b/Libraries/LibJS/Tests/test-common.js index d109988bca..a6348327d8 100644 --- a/Libraries/LibJS/Tests/test-common.js +++ b/Libraries/LibJS/Tests/test-common.js @@ -16,6 +16,13 @@ console.log = (...args) => { __UserOutput__.push(args.join(" ")); }; +class ExpectationError extends Error { + constructor(message, fileName, lineNumber) { + super(message, fileName, lineNumber); + this.name = "ExpectationError"; + } +} + // Use an IIFE to avoid polluting the global namespace as much as possible (() => { @@ -48,13 +55,6 @@ const deepObjectEquals = (a, b) => { return true; } -class ExpectationError extends Error { - constructor(message, fileName, lineNumber) { - super(message, fileName, lineNumber); - this.name = "ExpectationError"; - } -} - class Expector { constructor(target, inverted) { this.target = target; @@ -72,6 +72,8 @@ class Expector { } toHaveLength(length) { + this.__expect(typeof this.target.length === "number"); + this.__doMatcher(() => { this.__expect(Object.is(this.target.length, length)); }); @@ -80,31 +82,26 @@ class Expector { toHaveProperty(property, value) { this.__doMatcher(() => { let object = this.target; - + if (typeof property === "string" && property.includes(".")) { let propertyArray = []; - - while (true) { + + while (property.includes(".")) { let index = property.indexOf("."); - if (index === -1) { - propertyArray.push(property); - break; - } - propertyArray.push(property.substring(0, index)); - property = property.substring(index, property.length); + if (index + 1 >= property.length) + break; + property = property.substring(index + 1, property.length); } - + + propertyArray.push(property); + property = propertyArray; } - + if (Array.isArray(property)) { for (let key of property) { - if (object === undefined || object === null) { - if (this.inverted) - return; - throw new ExpectationError(); - } + this.__expect(object !== undefined && object !== null); object = object[key]; } } else { @@ -117,51 +114,12 @@ class Expector { }); } - toBeCloseTo(number, numDigits) { - if (numDigits === undefined) - numDigits = 2; - - this.__doMatcher(() => { - this.__expect(Math.abs(number - this.target) < (10 ** -numDigits / numDigits)); - }); - } - toBeDefined() { this.__doMatcher(() => { this.__expect(this.target !== undefined); }); } - toBeFalsey() { - this.__doMatcher(() => { - this.__expect(!this.target); - }); - } - - toBeGreaterThan(number) { - this.__doMatcher(() => { - this.__expect(this.target > number); - }); - } - - toBeGreaterThanOrEqual(number) { - this.__doMatcher(() => { - this.__expect(this.target >= number); - }); - } - - toBeLessThan(number) { - this.__doMatcher(() => { - this.__expect(this.target < number); - }); - } - - toBeLessThanOrEqual(number) { - this.__doMatcher(() => { - this.__expect(this.target <= number); - }); - } - toBeInstanceOf(class_) { this.__doMatcher(() => { this.__expect(this.target instanceof class_); @@ -174,12 +132,6 @@ class Expector { }); } - toBeTruthy() { - this.__doMatcher(() => { - this.__expect(!!this.target); - }); - } - toBeUndefined() { this.__doMatcher(() => { this.__expect(this.target === undefined); @@ -199,7 +151,7 @@ class Expector { if (item === element) return; } - + throw new ExpectationError(); }); } @@ -211,7 +163,7 @@ class Expector { if (deepEquals(item, element)) return; } - + throw new ExpectationError(); }); } @@ -224,19 +176,26 @@ class Expector { toThrow(value) { this.__expect(typeof this.target === "function"); - this.__expect(typeof value === "string" || typeof value === "function" || value === undefined); + this.__expect(typeof value === "string" + || typeof value === "function" + || typeof value === "object" + || value === undefined); this.__doMatcher(() => { + let threw = true; try { this.target(); - this.__expect(false); + threw = false; } catch (e) { if (typeof value === "string") { this.__expect(e.message.includes(value)); } else if (typeof value === "function") { this.__expect(e instanceof value); + } else if (typeof value === "object") { + this.__expect(e.message === value.message); } } + this.__expect(threw); }); } @@ -281,13 +240,14 @@ class Expector { throw new ExpectationError(); } } else { + let threw; try { new Function(this.target)(); - throw new ExpectationError(); + threw = false; } catch (e) { - if (e.name !== "SyntaxError") - throw new ExpectationError(); + threw = true; } + this.__expect(threw); } } diff --git a/Userland/test-js.cpp b/Userland/test-js.cpp index 108a1f10cf..73e190e931 100644 --- a/Userland/test-js.cpp +++ b/Userland/test-js.cpp @@ -58,6 +58,7 @@ Vector tests_to_run = { "object-method-shorthand.js", "object-spread.js", "tagged-template-literals.js", + "test-common-tests.js", "switch-basic.js", "update-expression-on-member-expression.js", }; @@ -131,7 +132,7 @@ FileResults run_test(const String& path, const String& test_root) // FIXME: Should be printed to stdout in a nice format auto& arr = interpreter->get_variable("__UserOutput__", interpreter->global_object()).as_array(); for (auto& entry : arr.indexed_properties()) { - dbg() << "OUTPUT: " << entry.value_and_attributes(&interpreter->global_object()).value.to_string(*interpreter); + dbg() << "OUTPUT: " << entry.value_and_attributes(&interpreter->global_object()).value.to_string_without_side_effects(); } // FIXME: This is _so_ scuffed