X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/6f1ff895670d04034ef09faea4779923a85097fb..7c3a2eda590637af9463e5a59ab798f802d274a9:/value.test.js?ds=inline diff --git a/value.test.js b/value.test.js new file mode 100644 index 0000000..6729b85 --- /dev/null +++ b/value.test.js @@ -0,0 +1,315 @@ +// ♓🌟 Piscēs ∷ value.test.js +// ==================================================================== +// +// Copyright © 2022 Lady [@ Lady’s Computer]. +// +// 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 . + +import { + assert, + assertStrictEquals, + assertThrows, + describe, + it, +} from "./dev-deps.js"; +import { + NULL, + ordinaryToPrimitive, + sameValue, + sameValueZero, + toPrimitive, + type, + UNDEFINED, +} from "./value.js"; + +describe("NULL", () => { + it("[[Get]] is null", () => { + assertStrictEquals(NULL, null); + }); +}); + +describe("UNDEFINED", () => { + it("[[Get]] is undefined", () => { + assertStrictEquals(UNDEFINED, void {}); + }); +}); + +describe("ordinaryToPrimitive", () => { + it("[[Call]] prefers `valueOf` by default", () => { + const obj = { + toString() { + return "failure"; + }, + valueOf() { + return "success"; + }, + }; + assertStrictEquals(ordinaryToPrimitive(obj), "success"); + assertStrictEquals(ordinaryToPrimitive(obj, "default"), "success"); + }); + + it('[[Call]] prefers `valueOf` for a "number" hint', () => { + const obj = { + toString() { + return "failure"; + }, + valueOf() { + return "success"; + }, + }; + assertStrictEquals(ordinaryToPrimitive(obj, "number"), "success"); + }); + + it('[[Call]] prefers `toString` for a "string" hint', () => { + const obj = { + toString() { + return "success"; + }, + valueOf() { + return "failure"; + }, + }; + assertStrictEquals(ordinaryToPrimitive(obj, "string"), "success"); + }); + + it("[[Call]] falls back to the other method if the first isn’t callable", () => { + const obj = { + toString() { + return "success"; + }, + valueOf: "failure", + }; + assertStrictEquals(ordinaryToPrimitive(obj), "success"); + }); + + it("[[Call]] falls back to the other method if the first returns an object", () => { + const obj = { + toString() { + return "success"; + }, + valueOf() { + return new String("failure"); + }, + }; + assertStrictEquals(ordinaryToPrimitive(obj), "success"); + }); + + it("[[Call]] throws an error if neither method is callable", () => { + const obj = { + toString: "failure", + valueOf: "failure", + }; + assertThrows(() => ordinaryToPrimitive(obj)); + }); + + it("[[Call]] throws an error if neither method returns an object", () => { + const obj = { + toString() { + return new String("failure"); + }, + valueOf() { + return new String("failure"); + }, + }; + assertThrows(() => ordinaryToPrimitive(obj)); + }); +}); + +describe("sameValue", () => { + it("[[Call]] returns false for null 🆚 undefined", () => { + assert(!sameValue(null, void {})); + }); + + it("[[Call]] returns false for null 🆚 an object", () => { + assert(!sameValue(null, {})); + }); + + it("[[Call]] returns true for null 🆚 null", () => { + assert(sameValue(null, null)); + }); + + it("[[Call]] returns false for two different objects", () => { + assert(!sameValue({}, {})); + }); + + it("[[Call]] returns true for the same object", () => { + const obj = {}; + assert(sameValue(obj, obj)); + }); + + it("[[Call]] returns false for ±0", () => { + assert(!sameValue(0, -0)); + }); + + it("[[Call]] returns true for -0", () => { + assert(sameValue(-0, -0)); + }); + + it("[[Call]] returns true for nan", () => { + assert(sameValue(0 / 0, 0 / 0)); + }); + + it("[[Call]] returns false for a primitive and its wrapped object", () => { + assert(!sameValue(false, new Boolean(false))); + }); +}); + +describe("sameValueZero", () => { + it("[[Call]] returns false for null 🆚 undefined", () => { + assert(!sameValueZero(null, void {})); + }); + + it("[[Call]] returns false for null 🆚 an object", () => { + assert(!sameValueZero(null, {})); + }); + + it("[[Call]] returns true for null 🆚 null", () => { + assert(sameValueZero(null, null)); + }); + + it("[[Call]] returns false for two different objects", () => { + assert(!sameValueZero({}, {})); + }); + + it("[[Call]] returns true for the same object", () => { + const obj = {}; + assert(sameValueZero(obj, obj)); + }); + + it("[[Call]] returns true for ±0", () => { + assert(sameValueZero(0, -0)); + }); + + it("[[Call]] returns true for -0", () => { + assert(sameValueZero(-0, -0)); + }); + + it("[[Call]] returns true for nan", () => { + assert(sameValueZero(0 / 0, 0 / 0)); + }); + + it("[[Call]] returns false for a primitive and its wrapped object", () => { + assert(!sameValueZero(false, new Boolean(false))); + }); +}); + +describe("toPrimitive", () => { + it("[[Call]] returns the argument when passed a primitive", () => { + const value = Symbol(); + assertStrictEquals(toPrimitive(value), value); + }); + + it("[[Call]] works with nullish values", () => { + assertStrictEquals(toPrimitive(null), null); + assertStrictEquals(toPrimitive(), void {}); + }); + + it("[[Call]] calls ordinaryToPrimitive by default", () => { + const value = Object.assign( + Object.create(null), + { + valueOf() { + return "success"; + }, + }, + ); + assertStrictEquals(toPrimitive(value), "success"); + }); + + it("[[Call]] accepts a hint", () => { + const value = Object.assign( + Object.create(null), + { + toString() { + return "success"; + }, + valueOf() { + return "failure"; + }, + }, + ); + assertStrictEquals(toPrimitive(value, "string"), "success"); + }); + + it("[[Call]] uses the exotic toPrimitive method if available", () => { + const value = Object.assign( + Object.create(null), + { + [Symbol.toPrimitive]() { + return "success"; + }, + }, + ); + assertStrictEquals(toPrimitive(value), "success"); + }); + + it("[[Call]] passes the hint to the exotic toPrimitive", () => { + const value = Object.assign( + Object.create(null), + { + [Symbol.toPrimitive](hint) { + return hint === "string" ? "success" : "failure"; + }, + }, + ); + assertStrictEquals(toPrimitive(value, "string"), "success"); + }); + + it('[[Call]] passes a "default" hint by default', () => { + const value = Object.assign( + Object.create(null), + { + [Symbol.toPrimitive](hint) { + return hint === "default" ? "success" : "failure"; + }, + }, + ); + assertStrictEquals(toPrimitive(value, "default"), "success"); + }); + + it("[[Call]] throws for an invalid hint", () => { + const value1 = Object.assign( + Object.create(null), + { + [Symbol.toPrimitive]() { + return "success"; + }, + }, + ); + const value2 = Object.assign( + Object.create(null), + { + valueOf() { + return true; + }, + }, + ); + assertThrows(() => toPrimitive(value1, "badhint")); + assertThrows(() => toPrimitive(value2, "badhint")); + assertThrows(() => toPrimitive(true, "badhint")); + }); +}); + +describe("type", () => { + it('[[Call]] returns "null" for null', () => { + assertStrictEquals(type(null), "null"); + }); + + it('[[Call]] returns "undefined" for undefined', () => { + assertStrictEquals(type(void {}), "undefined"); + }); + + it('[[Call]] returns "object" for non‐callable objects', () => { + assertStrictEquals(type(Object.create(null)), "object"); + }); + + it('[[Call]] returns "object" for callable objects', () => { + assertStrictEquals(type(() => {}), "object"); + }); + + it('[[Call]] returns "object" for constructable objects', () => { + assertStrictEquals(type(class {}), "object"); + }); +});