git.fiddlerwoaroof.com
Raw Blame History
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();
  });
});