import * as uut from "./genfuns"; import { fail } from "assert"; describe("matches_specializer", () => { test("works in expected cases", () => { function AThing() {} const an_instance = new AThing(); expect(uut.matches_specializer(an_instance, AThing)).toBeTruthy(); expect(uut.matches_specializer(an_instance, String)).toBeFalsy(); expect(uut.matches_specializer(an_instance, Object)).toBeTruthy(); expect(uut.matches_specializer([], Array)).toBeTruthy(); expect(uut.matches_specializer([], Object)).toBeTruthy(); expect(uut.matches_specializer([], Number)).toBeFalsy(); function Foo() {} Foo.prototype = Object.create(null); const inst = new Foo(); expect(uut.matches_specializer(inst, Foo)).toBeTruthy(); expect(uut.matches_specializer(inst, Object)).toBeFalsy(); expect(uut.matches_specializer({ a: 1 }, uut.Shape("a"))).toBeTruthy(); expect( uut.matches_specializer({ a: 1, b: 2 }, uut.Shape("a")) ).toBeTruthy(); expect(uut.matches_specializer({ b: 2 }, uut.Shape("a"))).toBeFalsy(); expect( uut.matches_specializer({ a: 1, b: 2, c: 3 }, uut.Shape("a", "b", "c")) ).toBeTruthy(); expect( uut.matches_specializer( { a: 1, b: 2, c: 3, d: 4 }, uut.Shape("a", "b", "c") ) ).toBeTruthy(); expect( uut.matches_specializer({ a: 1, c: 3 }, uut.Shape("a", "b", "c")) ).toBeFalsy(); expect( uut.matches_specializer({ c: 3 }, uut.Shape("a", "b", "c")) ).toBeFalsy(); expect( uut.matches_specializer({ d: 3 }, uut.Shape("a", "b", "c")) ).toBeFalsy(); }); test("null behavior", () => { expect(uut.matches_specializer(null, null)).toBeTruthy(); expect(uut.matches_specializer(null, Number)).toBeFalsy(); expect(uut.matches_specializer(null, String)).toBeFalsy(); expect(uut.matches_specializer(null, Object)).toBeFalsy(); }); test("undefined (the value) behavior", () => { expect(uut.matches_specializer(undefined, undefined)).toBeTruthy(); expect(uut.matches_specializer(undefined, Number)).toBeFalsy(); expect(uut.matches_specializer(undefined, String)).toBeFalsy(); expect(uut.matches_specializer(undefined, Object)).toBeFalsy(); }); test("works for numbers", () => { expect(uut.matches_specializer(new Number(1), Number)).toBeTruthy(); expect(uut.matches_specializer(new Number(1), Object)).toBeTruthy(); expect(uut.matches_specializer(new Number(1), String)).toBeFalsy(); expect(uut.matches_specializer(1, Number)).toBeTruthy(); expect(uut.matches_specializer(1, Object)).toBeTruthy(); expect(uut.matches_specializer(1, String)).toBeFalsy(); }); test("handles strings", () => { expect(uut.matches_specializer(new String("foobar"), String)).toBeTruthy(); expect(uut.matches_specializer(new String("foobar"), Object)).toBeTruthy(); expect(uut.matches_specializer("1", String)).toBeTruthy(); expect(uut.matches_specializer("1", Object)).toBeTruthy(); expect(uut.matches_specializer("1", Number)).toBeFalsy(); }); }); describe("defgeneric", () => { test("methods get called appropriately", () => { expect( uut .defgeneric("testing1", "a", "b") .primary([Object, Object], (_, __) => 1) .fn(1, 2) ).toEqual(1); try { uut .defgeneric("foobar", "a") .primary([String], function (a) {}) .fn({}); fail(); } catch (err) { expect(err).toBeInstanceOf(uut.NoApplicableMethodError); } expect( uut .defgeneric("testing1", "a", "b") .primary([Number, Number], (_, __) => 1) .fn(1, 2) ).toEqual(1); expect( uut .defgeneric("testing1", "a", "b") .primary([Number, Number], (_, __) => 2) .primary([String, String], (_, __) => 1) .fn("1", "2") ).toEqual(1); let firstCounts = 0; expect( uut .defgeneric("testing1", "a", "b") .primary([Number, Number], (_, __) => (firstCounts += 1)) .primary([String, String], (_, __) => (firstCounts += 1)) .fn("1", "2") ).toEqual(1); expect(firstCounts).toEqual(1); let secondCounts = 0; expect( uut .defgeneric("testing1", "a", "b") .primary([Object, Object], (_, __) => (secondCounts += 1)) .primary([String, String], (_, __) => (secondCounts += 1)) .fn("1", "2") ).toEqual(1); expect(secondCounts).toEqual(1); let thirdCounts = 0; expect( uut .defgeneric("testing1", "a", "b") .before([Object, Object], (_, __) => (thirdCounts += 1)) .primary([String, String], (_, __) => "hi") .after([Object, String], (_, __) => (thirdCounts += 1)) .fn("1", "2") ).toEqual("hi"); expect(thirdCounts).toEqual(2); expect( uut .defgeneric("foobar", "a") .primary([Object], function (a) { return 1; }) .primary([String], function (a) { return 2; }) .fn("foobar") ).toEqual(2); }); test("next-method-p works", () => { expect.assertions(3); uut .defgeneric("foobar", "a") .primary([Object], function (a) { expect(this.next_method_p).toBe(false); }) .fn({}); uut .defgeneric("foobar", "a") .primary([Object], function (a) { expect(this.next_method_p).toBe(false); }) .primary([String], function (a) { expect(this.next_method_p).toBe(true); }) .fn("foobar"); uut .defgeneric("foobar", "a") .primary([Object], function (a) { expect(this.next_method_p).toBe(false); }) .primary([String], function (a) { expect(this.next_method_p).toBe(true); }) .fn(1); }); test("call-next-method works", () => { try { uut .defgeneric("foobar", "a") .primary([Object], function (a) { this.call_next_method(); }) .primary([String], function (a) { return 1; }) .fn({}); fail(); } catch (err) { expect(err).toBeInstanceOf(uut.NoNextMethodError); } expect( uut .defgeneric("foobar", "a") .primary([Object], function (a) { return 1; }) .primary([String], function (a) { return this.call_next_method(); }) .fn("foobar") ).toEqual(1); expect( uut .defgeneric("foobar", "a", "b") .primary([String, String], function (a, b) { return `1${this.call_next_method()}`; }) .primary([Object, String], function (a, b) { return `3${this.call_next_method()}`; }) .primary([String, Object], function (a, b) { return `2${this.call_next_method()}`; }) .primary([Object, Object], function (a, b) { return `4`; }) .fn("a", "b") ).toEqual("1234"); try { uut .defgeneric("foobar", "a") .primary([Object], function (a) { this.call_next_method(); }) .fn({}); fail(); } catch (err) { expect(err).toBeInstanceOf(uut.NoNextMethodError); } }); }); describe("custom specializers", () => { test("Shape works", () => { expect( uut .defgeneric("foobar", "a") .primary([uut.Shape("a", "b")], ({ a, b }) => a + b) .primary([Object], _ => null) .fn({ a: 1, b: 2 }) ).toEqual(3); expect( uut .defgeneric("foobar", "a") .primary([uut.Shape("a", "b")], ({ a, b }) => a + b) .primary([Object], _ => null) .fn({ a: 1, b: 2, c: 3 }) ).toEqual(3); expect( uut .defgeneric("foobar", "a") .primary([uut.Shape("a", "b")], ({ a, b }) => a + b) .primary([Object], _ => null) .fn({ a: 1 }) ).toEqual(null); expect( uut .defgeneric("foobar", "a") .primary([uut.Shape(["a", 1], "b")], ({ a, b }) => a + b) .primary([Object], _ => null) .fn({ a: 1, b: 3 }) ).toEqual(4); expect( uut .defgeneric("foobar", "a") .primary([uut.Shape(["a", null], "b")], ({ a, b }) => b) .primary([Object], _ => null) .fn({ a: null, b: 3 }) ).toEqual(3); expect( uut .defgeneric("foobar", "a") .primary([uut.Shape(["a", undefined], "b")], ({ a, b }) => b) .primary([Object], _ => null) .fn({ b: 5 }) ).toEqual(null); //undefined is not a permissible default: treated as if the key is missing expect( uut .defgeneric("foobar", "a") .primary([uut.Shape(["a", 1], "b")], ({ a, b }) => a + b) .primary([Object], _ => null) .fn({ a: 2, b: 3 }) ).toEqual(null); }); test("Shape, prototype precedence", () => { expect( uut .defgeneric("foobar4", "a") .primary([uut.Shape("a")], ({ a }) => a) .primary([uut.Shape("a", "b")], ({ a, b }) => { return a + b; }) .primary([Object], _ => null) .fn({ a: 1, b: 3 }) ).toEqual(4); expect( uut .defgeneric("foobar", "a") .primary([uut.Shape("a", "b")], ({ a, b }) => a + b) .primary([uut.Shape("b")], ({ b }) => b) .primary([Object], _ => null) .fn({ a: 1, b: 2 }) ).toEqual(3); const Foo = function () {}; Foo.prototype = { a: true, b: null }; expect( uut .defgeneric("foobar", "a") .primary([uut.Shape("a")], function ({ a }) { return `a${this.call_next_method()}`; }) .primary([uut.Shape("a", "b", "c")], function ({ a, b, c }) { return `c${this.call_next_method()}`; }) .primary([uut.Shape("a", "b")], function ({ a, b }) { return `b${this.call_next_method()}`; }) .primary([Object], _ => "d") .fn(Object.assign(new Foo(), { c: 3 })) ).toEqual("cbad"); }); }); describe("Shape", () => { test("super_of", () => { expect(uut.Shape().super_of(uut.Shape("a", "b", "c"))).toBeTruthy(); expect(uut.Shape("a").super_of(uut.Shape("a", "b"))).toBeTruthy(); expect(uut.Shape("a", "b").super_of(uut.Shape("a", "b", "c"))).toBeTruthy(); expect(uut.Shape("a", "b").super_of(uut.Shape("a", "b", 3))).toBeTruthy(); expect(uut.Shape("a", "b").super_of(uut.Shape("a", "b"))).toBeFalsy(); expect(uut.Shape("a", "b").super_of(uut.Shape("a"))).toBeFalsy(); }); });