git.fiddlerwoaroof.com
Browse code

feat(primitives): handle all primitives correctly

Edward authored on 10/01/2022 08:48:11
Showing 2 changed files
... ...
@@ -16,6 +16,20 @@ export const NoNextMethodError = SubTypeError("NoNextMethodError");
16 16
 export const NoApplicableMethodError = SubTypeError("NoApplicableMethodError");
17 17
 export const NoPrimaryMethodError = SubTypeError("NoPrimaryMethodError");
18 18
 
19
+export class UnhandledObjType extends Error {
20
+  /**
21
+   * @param {string} objType
22
+   */
23
+  constructor(objType, ...params) {
24
+    super(...params);
25
+    this.objType = objType;
26
+  }
27
+
28
+  toString() {
29
+    return `[${this.name}: unhandled objType: ${this.objType}]`;
30
+  }
31
+}
32
+
19 33
 const before_qualifier = Symbol.for("before");
20 34
 const after_qualifier = Symbol.for("after");
21 35
 const around_qualifier = Symbol.for("around");
... ...
@@ -267,16 +281,24 @@ export function matches_specializer(obj, specializer) {
267 281
   if (obj === null && obj === specializer) {
268 282
     result = true;
269 283
   } else if (specializer && specializer.prototype !== undefined) {
270
-    if (!result && objType === "object") {
271
-      result = Object.isPrototypeOf.call(specializer_proto, obj);
284
+    if (objType === "object") {
285
+      if (!result) {
286
+        result = Object.isPrototypeOf.call(specializer_proto, obj);
287
+      }
272 288
     } else if (objType === "number") {
273
-      result =
274
-        matches_specializer(Number.prototype, specializer) ||
275
-        matches_specializer(specializer_proto, Number);
289
+      result = matches_specializer(Number.prototype, specializer);
290
+    } else if (objType === "boolean") {
291
+      result = matches_specializer(Boolean.prototype, specializer);
276 292
     } else if (objType === "string") {
277
-      result =
278
-        matches_specializer(String.prototype, specializer) ||
279
-        matches_specializer(specializer_proto, String);
293
+      result = matches_specializer(String.prototype, specializer);
294
+    } else if (objType === "symbol") {
295
+      result = matches_specializer(Symbol.prototype, specializer);
296
+    } else if (objType === "undefined") {
297
+      result = obj === specializer;
298
+    } else if (objType === "bigint") {
299
+      result = matches_specializer(BigInt.prototype, specializer);
300
+    } else {
301
+      throw new UnhandledObjType(objType);
280 302
     }
281 303
   } else if (specializer instanceof Specializer) {
282 304
     result = specializer.matches(obj);
... ...
@@ -46,43 +46,81 @@ describe("matches_specializer", () => {
46 46
     ).toBeFalsy();
47 47
   });
48 48
 
49
+  describe("primitives", () => {
50
+    test("null behavior", () => {
51
+      expect(uut.matches_specializer(null, null)).toBeTruthy();
52
+      expect(uut.matches_specializer(null, Number)).toBeFalsy();
53
+      expect(uut.matches_specializer(null, String)).toBeFalsy();
54
+      expect(uut.matches_specializer(null, Object)).toBeFalsy();
55
+    });
56
+
57
+    test("undefined (the value) behavior", () => {
58
+      expect(uut.matches_specializer(undefined, undefined)).toBeTruthy();
59
+      expect(uut.matches_specializer(undefined, Number)).toBeFalsy();
60
+      expect(uut.matches_specializer(undefined, String)).toBeFalsy();
61
+      expect(uut.matches_specializer(undefined, Object)).toBeFalsy();
62
+    });
63
+
64
+    test.each([true, false])("booleans -> %s", bool => {
65
+      expect(bool).not.toBe(Object(bool));
66
+      expect(uut.matches_specializer(new Boolean(bool), Boolean)).toBeTruthy();
67
+      expect(uut.matches_specializer(bool, Boolean)).toBeTruthy();
68
+      expect(uut.matches_specializer(bool, Object)).toBeTruthy();
69
+    });
70
+
71
+    test("works for numbers", () => {
72
+      expect(1).not.toBe(Object(1));
73
+      expect(uut.matches_specializer(new Number(1), Number)).toBeTruthy();
74
+      expect(uut.matches_specializer(new Number(1), Object)).toBeTruthy();
75
+      expect(uut.matches_specializer(new Number(1), String)).toBeFalsy();
76
+
77
+      expect(uut.matches_specializer(1, Number)).toBeTruthy();
78
+      expect(uut.matches_specializer(1, Object)).toBeTruthy();
79
+      expect(uut.matches_specializer(1, String)).toBeFalsy();
80
+    });
81
+
82
+    test("handles strings", () => {
83
+      expect("asdf").not.toBe(Object("asdf"));
84
+
85
+      expect(
86
+        uut.matches_specializer(new String("foobar"), String)
87
+      ).toBeTruthy();
88
+      expect(
89
+        uut.matches_specializer(new String("foobar"), Object)
90
+      ).toBeTruthy();
91
+
92
+      expect(uut.matches_specializer("1", String)).toBeTruthy();
93
+      expect(uut.matches_specializer("1", Object)).toBeTruthy();
94
+      expect(uut.matches_specializer("1", Number)).toBeFalsy();
95
+    });
96
+
97
+    test("handles symbols", () => {
98
+      const symbolPrim = Symbol("primitive");
99
+      const boxedSymbol = Object(symbolPrim);
100
+      expect(symbolPrim).not.toBe(boxedSymbol);
101
+
102
+      expect(uut.matches_specializer(boxedSymbol, Symbol)).toBeTruthy();
103
+      expect(uut.matches_specializer(boxedSymbol, Object)).toBeTruthy();
104
+
105
+      expect(uut.matches_specializer(symbolPrim, Symbol)).toBeTruthy();
106
+      expect(uut.matches_specializer(symbolPrim, Object)).toBeTruthy();
107
+    });
108
+
109
+    test("handles BigInt", () => {
110
+      expect(4n).not.toBe(Object(4n));
111
+
112
+      expect(uut.matches_specializer(Object(4n), BigInt)).toBeTruthy();
113
+      expect(uut.matches_specializer(Object(4n), Object)).toBeTruthy();
114
+
115
+      expect(uut.matches_specializer(4n, BigInt)).toBeTruthy();
116
+      expect(uut.matches_specializer(4n, Object)).toBeTruthy();
117
+    });
118
+  });
119
+
49 120
   test("works with custom specializers", () => {
50 121
     const AEql = makeCustomSpecializer();
51 122
     expect(uut.matches_specializer("foo", new AEql("foo"))).toBeTruthy();
52 123
   });
53
-
54
-  test("null behavior", () => {
55
-    expect(uut.matches_specializer(null, null)).toBeTruthy();
56
-    expect(uut.matches_specializer(null, Number)).toBeFalsy();
57
-    expect(uut.matches_specializer(null, String)).toBeFalsy();
58
-    expect(uut.matches_specializer(null, Object)).toBeFalsy();
59
-  });
60
-
61
-  test("undefined (the value) behavior", () => {
62
-    expect(uut.matches_specializer(undefined, undefined)).toBeTruthy();
63
-    expect(uut.matches_specializer(undefined, Number)).toBeFalsy();
64
-    expect(uut.matches_specializer(undefined, String)).toBeFalsy();
65
-    expect(uut.matches_specializer(undefined, Object)).toBeFalsy();
66
-  });
67
-
68
-  test("works for numbers", () => {
69
-    expect(uut.matches_specializer(new Number(1), Number)).toBeTruthy();
70
-    expect(uut.matches_specializer(new Number(1), Object)).toBeTruthy();
71
-    expect(uut.matches_specializer(new Number(1), String)).toBeFalsy();
72
-
73
-    expect(uut.matches_specializer(1, Number)).toBeTruthy();
74
-    expect(uut.matches_specializer(1, Object)).toBeTruthy();
75
-    expect(uut.matches_specializer(1, String)).toBeFalsy();
76
-  });
77
-
78
-  test("handles strings", () => {
79
-    expect(uut.matches_specializer(new String("foobar"), String)).toBeTruthy();
80
-    expect(uut.matches_specializer(new String("foobar"), Object)).toBeTruthy();
81
-
82
-    expect(uut.matches_specializer("1", String)).toBeTruthy();
83
-    expect(uut.matches_specializer("1", Object)).toBeTruthy();
84
-    expect(uut.matches_specializer("1", Number)).toBeFalsy();
85
-  });
86 124
 });
87 125
 
88 126
 describe("defgeneric", () => {