git.fiddlerwoaroof.com
src/genfuns.test.js
cc733b08
 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();
   });
 
42c8c155
   describe("primitives", () => {
     test("null behavior", () => {
       expect(uut.matches_specializer(null, null)).toBeTruthy();
       expect(uut.matches_specializer(null, Number)).toBeFalsy();
       expect(uut.matches_specializer(null, String)).toBeFalsy();
32f91b0a
       expect(uut.matches_specializer(null, Object)).toBeTruthy();
42c8c155
     });
b852ec93
 
42c8c155
     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();
32f91b0a
       expect(uut.matches_specializer(undefined, Object)).toBeTruthy();
42c8c155
     });
cc733b08
 
42c8c155
     test.each([true, false])("booleans -> %s", bool => {
       expect(bool).not.toBe(Object(bool));
       expect(uut.matches_specializer(new Boolean(bool), Boolean)).toBeTruthy();
       expect(uut.matches_specializer(bool, Boolean)).toBeTruthy();
       expect(uut.matches_specializer(bool, Object)).toBeTruthy();
     });
cc733b08
 
42c8c155
     test("works for numbers", () => {
       expect(1).not.toBe(Object(1));
       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();
cc733b08
 
42c8c155
       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("asdf").not.toBe(Object("asdf"));
 
       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();
     });
cc733b08
 
42c8c155
     test("handles symbols", () => {
       const symbolPrim = Symbol("primitive");
       const boxedSymbol = Object(symbolPrim);
       expect(symbolPrim).not.toBe(boxedSymbol);
cc733b08
 
42c8c155
       expect(uut.matches_specializer(boxedSymbol, Symbol)).toBeTruthy();
       expect(uut.matches_specializer(boxedSymbol, Object)).toBeTruthy();
 
       expect(uut.matches_specializer(symbolPrim, Symbol)).toBeTruthy();
       expect(uut.matches_specializer(symbolPrim, Object)).toBeTruthy();
     });
 
     test("handles BigInt", () => {
1c32c2b0
       expect(BigInt(4)).not.toBe(Object(BigInt(4)));
42c8c155
 
1c32c2b0
       expect(uut.matches_specializer(Object(BigInt(4)), BigInt)).toBeTruthy();
       expect(uut.matches_specializer(Object(BigInt(4)), Object)).toBeTruthy();
42c8c155
 
1c32c2b0
       expect(uut.matches_specializer(BigInt(4), BigInt)).toBeTruthy();
       expect(uut.matches_specializer(BigInt(4), Object)).toBeTruthy();
42c8c155
     });
   });
 
   test("works with custom specializers", () => {
     const AEql = makeCustomSpecializer();
     expect(uut.matches_specializer("foo", new AEql("foo"))).toBeTruthy();
cc733b08
   });
94944213
 });
 
cc733b08
 describe("defgeneric", () => {
   test("methods get called appropriately", () => {
     expect(
       uut
         .defgeneric("testing1", "a", "b")
         .primary([Object, Object], (_, __) => 1)
         .fn(1, 2)
     ).toEqual(1);
 
b852ec93
     expect(() => {
cc733b08
       uut
         .defgeneric("foobar", "a")
         .primary([String], function (a) {})
         .fn({});
b852ec93
     }).toThrow(uut.NoApplicableMethodError);
cc733b08
 
     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);
   });
 
b852ec93
   test("works with custom specializers", () => {
     const AEql = makeCustomSpecializer();
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([new AEql("foo")], function (a) {
           return 3;
         })
         .fn("foo")
     ).toEqual(3);
 
     expect(new AEql("foo").super_of(String)).toBeFalsy();
     expect(new AEql("foo").super_of(Object)).toBeFalsy();
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([Object], function (a) {
           return 1;
         })
         .primary([String], function (a) {
           return 2;
         })
         .primary([new AEql("foo")], function (a) {
           return 3;
         })
         .fn("foobar")
     ).toEqual(2);
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([Object], function (a) {
           return 1;
         })
         .primary([String], function (a) {
           return 2;
         })
         .primary([new AEql("foo")], function (a) {
           return 3;
         })
         .fn("foo")
     ).toEqual(3);
   });
 
cc733b08
   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", () => {
b852ec93
     expect(() => {
cc733b08
       uut
         .defgeneric("foobar", "a")
         .primary([Object], function (a) {
           this.call_next_method();
         })
         .primary([String], function (a) {
           return 1;
         })
         .fn({});
b852ec93
     }).toThrow(uut.NoNextMethodError);
cc733b08
 
     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);
     }
   });
32f91b0a
 
   test("bugfix: behavior of null in method", () => {
     const expectedResult = Symbol(4);
     expect(() => {
       uut
         .defgeneric("foobar", "a", "b")
         .primary([Object, Object], () => expectedResult)
         .fn(null, null);
     }).not.toThrow();
     expect(
       uut
         .defgeneric("foobar", "a", "b")
         .primary([Object, Object], () => expectedResult)
         .fn(null, null)
     ).toBe(expectedResult);
   });
406b5587
 });
 
cc733b08
 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");
   });
406b5587
 });
94944213
 
b852ec93
 describe("Eql", () => {
   test("basic stuff works", () => {
     expect(uut.matches_specializer("foo", uut.Eql("foo"))).toBeTruthy();
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([new uut.Eql("foo")], function (a) {
           return 3;
         })
         .fn("foo")
     ).toEqual(3);
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([new uut.Eql(5)], function (a) {
           return 3;
         })
         .fn(5)
     ).toEqual(3);
   });
   test("inheritance works", () => {
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([Symbol], function () {
           return 2;
         })
         .primary([uut.Eql(Symbol.iterator)], function () {
           return 3;
         })
         .fn(Symbol.iterator)
     ).toEqual(3);
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([Boolean], function (a) {
           return 2;
         })
         .primary([new uut.Eql(false)], function (a) {
           return 3;
         })
         .fn(false)
     ).toEqual(3);
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([Boolean], function (a) {
           return 2;
         })
         .primary([new uut.Eql(true)], function (a) {
           return 3;
         })
         .fn(true)
     ).toEqual(3);
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([String], function (a) {
           return 2;
         })
         .primary([new uut.Eql("5")], function (a) {
           return 3;
         })
         .fn("5")
     ).toEqual(3);
 
     expect(
       uut
         .defgeneric("foobar", "a")
         .primary([Number], function (a) {
           return 2;
         })
         .primary([new uut.Eql(5)], function (a) {
           return 3;
         })
         .fn(5)
     ).toEqual(3);
   });
 });
 
406b5587
 describe("Shape", () => {
cc733b08
   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();
   });
636eaffe
 });
b852ec93
 
 function makeCustomSpecializer() {
   function AEql(val) {
     this.val = val;
   }
   AEql.prototype = Object.assign(new uut.Specializer(), {
     toString() {
       return `AEql(${this.val})`;
     },
     matches(other) {
       return this.val === other;
     },
     super_of(other) {
       return other === String ? false : other !== Object;
     },
   });
   return AEql;
 }