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(); }); });