Browse code
Improve test coverage, use Exception objects instead of strings
Showing 3 changed files
... | ... |
@@ -1,8 +1,16 @@ |
1 |
+export function NoNextMethodError() {} |
|
2 |
+NoNextMethodError.prototype = Object.create(Error); |
|
3 |
+ |
|
4 |
+export function NoApplicableMethodError() {} |
|
5 |
+NoNextMethodError.prototype = Object.create(Error); |
|
6 |
+ |
|
7 |
+export function NoPrimaryMethodError() {} |
|
8 |
+NoPrimaryMethodError.prototype = Object.create(NoApplicableMethodError); |
|
9 |
+ |
|
1 | 10 |
const before_qualifier = Symbol.for('before'); |
2 | 11 |
const after_qualifier = Symbol.for('after'); |
3 | 12 |
const around_qualifier = Symbol.for('around'); |
4 | 13 |
|
5 |
- |
|
6 | 14 |
let genfun_prototype = { |
7 | 15 |
name: "(placeholder)", |
8 | 16 |
lambda_list: [], |
... | ... |
@@ -61,7 +69,7 @@ let method_prototype = { |
61 | 69 |
generic_function: {}, |
62 | 70 |
}; |
63 | 71 |
|
64 |
-function StandardMethod( |
|
72 |
+export function StandardMethod( |
|
65 | 73 |
lambda_list, qualifiers, specializers, body |
66 | 74 |
) { |
67 | 75 |
if (! (this instanceof StandardMethod) ) { |
... | ... |
@@ -97,7 +105,7 @@ function apply_generic_function(gf, args) { |
97 | 105 |
let applicable_methods = |
98 | 106 |
compute_applicable_methods_using_classes(gf, required_portion(args)); |
99 | 107 |
if (applicable_methods.length === 0) { |
100 |
- throw new Error(`no applicable methods for gf ${gf.name} with args ${JSON.stringify(args)}`); |
|
108 |
+ throw new NoApplicableMethodError(`no applicable methods for gf ${gf.name} with args ${JSON.stringify(args)}`); |
|
101 | 109 |
} else { |
102 | 110 |
return apply_methods(gf, args, applicable_methods); |
103 | 111 |
} |
... | ... |
@@ -107,30 +115,83 @@ function method_more_specific_p(m1, m2, required_classes) { |
107 | 115 |
const m1specializers = m1.specializers; |
108 | 116 |
const m2specializers = m2.specializers; |
109 | 117 |
|
118 |
+ let result = null; |
|
110 | 119 |
for (let [spec1, spec2] of m1specializers.map((el, idx) => [el, m2specializers[idx]])) { |
111 | 120 |
if (spec1 !== spec2) { |
112 |
- return sub_specializer_p(spec1, spec2); |
|
121 |
+ result = sub_specializer_p(spec1, spec2); |
|
122 |
+ break; |
|
113 | 123 |
} |
114 | 124 |
} |
125 |
+ |
|
126 |
+ return result; |
|
115 | 127 |
} |
116 | 128 |
|
117 |
-function sub_specializer_p(c1, c2) { |
|
118 |
- return c1.isPrototypeOf(c2); |
|
129 |
+export function sub_specializer_p(c1, c2) { |
|
130 |
+ let result = false; |
|
131 |
+ if (c1 instanceof Specializer) { |
|
132 |
+ result = c1.super_of(c2); |
|
133 |
+ } else if (c1.prototype !== undefined && c2.prototype !== undefined) { |
|
134 |
+ result = Object.isPrototypeOf.call(c1.prototype, c2.prototype); |
|
135 |
+ } |
|
136 |
+ return result; |
|
119 | 137 |
} |
120 | 138 |
|
121 | 139 |
const idS = Symbol.for('id'); |
122 | 140 |
Object.prototype[idS] = function () { return this }; |
123 | 141 |
|
124 |
-export function matchesSpecializer(obj, specializer) { |
|
125 |
- let result = obj === specializer.prototype; |
|
126 |
- let objType = typeof obj; |
|
142 |
+export function Specializer() {} |
|
143 |
+Specializer.prototype = { |
|
144 |
+ matches(obj) { return false; }, |
|
145 |
+ super_of(obj) { return false; }, |
|
146 |
+} |
|
147 |
+ |
|
148 |
+function isSuperset(superset, subset) { |
|
149 |
+ return Array.from(subset).every(superset.has.bind(superset)); |
|
150 |
+} |
|
151 |
+ |
|
152 |
+export function Shape(...keys) { |
|
153 |
+ if (! (this instanceof Shape) ) { |
|
154 |
+ return new Shape(...keys); |
|
155 |
+ } |
|
156 |
+ this.keys = new Set(keys); |
|
157 |
+} |
|
158 |
+Shape.prototype = Object.assign(new Specializer(), { |
|
159 |
+ matches(obj) { |
|
160 |
+ return Array.from(this.keys).every(key => obj[key] !== undefined); |
|
161 |
+ }, |
|
162 |
+ super_of(spec) { |
|
163 |
+ // this is the super of spec |
|
164 |
+ // if this.keys is a subset of spec.keys |
|
165 |
+ // and if this.keys != spec.keys |
|
127 | 166 |
|
128 |
- if (!result && objType === 'object') { |
|
129 |
- result = Object.isPrototypeOf.call(specializer.prototype, obj); |
|
130 |
- } else if (objType === 'number') { |
|
131 |
- result = matchesSpecializer(Number.prototype, specializer) || matchesSpecializer(specializer.prototype, Number); |
|
132 |
- } else if (objType === 'string') { |
|
133 |
- result = matchesSpecializer(String.prototype, specializer) || matchesSpecializer(specializer.prototype, String); |
|
167 |
+ if (!(spec instanceof Shape)) { |
|
168 |
+ return false; |
|
169 |
+ } |
|
170 |
+ |
|
171 |
+ let this_keys_subset_of_spec_keys = isSuperset(spec.keys, this.keys); |
|
172 |
+ let not_eq = this.keys.size !== spec.keys.size; |
|
173 |
+ |
|
174 |
+ return this_keys_subset_of_spec_keys && not_eq; |
|
175 |
+ } |
|
176 |
+}); |
|
177 |
+ |
|
178 |
+export function matches_specializer(obj, specializer) { |
|
179 |
+ let objType = typeof obj; |
|
180 |
+ let specializer_proto = specializer && specializer.prototype |
|
181 |
+ let result = obj === specializer_proto; |
|
182 |
+ |
|
183 |
+ if (obj === null && obj === specializer) { |
|
184 |
+ result = true; |
|
185 |
+ } else if (specializer && specializer.prototype !== undefined) { |
|
186 |
+ if (!result && objType === 'object') { |
|
187 |
+ result = Object.isPrototypeOf.call(specializer_proto, obj); |
|
188 |
+ } else if (objType === 'number') { |
|
189 |
+ result = matches_specializer(Number.prototype, specializer) || matches_specializer(specializer_proto, Number); |
|
190 |
+ } else if (objType === 'string') { |
|
191 |
+ result = matches_specializer(String.prototype, specializer) || matches_specializer(specializer_proto, String); |
|
192 |
+ } |
|
193 |
+ } else if (specializer instanceof Specializer) { |
|
194 |
+ result = specializer.matches(obj); |
|
134 | 195 |
} |
135 | 196 |
|
136 | 197 |
return result; |
... | ... |
@@ -139,17 +200,20 @@ export function matchesSpecializer(obj, specializer) { |
139 | 200 |
|
140 | 201 |
function compute_applicable_methods_using_classes(gf, required_classes) { |
141 | 202 |
const applicable_methods = gf.methods.filter( |
142 |
- method => method.specializers.every((specializer, idx) => matchesSpecializer(required_classes[idx], specializer)) |
|
203 |
+ method => method.specializers.every((specializer, idx) => matches_specializer(required_classes[idx], specializer)) |
|
204 |
+ |
|
143 | 205 |
); |
144 | 206 |
|
145 | 207 |
applicable_methods.sort((a,b) => { |
208 |
+ let result = 0; |
|
146 | 209 |
if (method_more_specific_p(a,b)) { |
147 |
- return 1; |
|
210 |
+ result = 1; |
|
148 | 211 |
} |
149 | 212 |
if (method_more_specific_p(b,a)) { |
150 |
- return -1; |
|
213 |
+ result = -1; |
|
151 | 214 |
} |
152 |
- return 0; |
|
215 |
+ |
|
216 |
+ return result; |
|
153 | 217 |
}) |
154 | 218 |
|
155 | 219 |
return applicable_methods; |
... | ... |
@@ -173,6 +237,19 @@ function arr_eq(a1, a2) { |
173 | 237 |
} |
174 | 238 |
} |
175 | 239 |
|
240 |
+function set_eq(a1, a2) { |
|
241 |
+ if (a1.length !== a2.length) { |
|
242 |
+ return false; |
|
243 |
+ } else { |
|
244 |
+ let result = true; |
|
245 |
+ for (let elem of a1) { |
|
246 |
+ result = result && a2.has(elem); |
|
247 |
+ if (!result) break; |
|
248 |
+ } |
|
249 |
+ return result; |
|
250 |
+ } |
|
251 |
+} |
|
252 |
+ |
|
176 | 253 |
const primary_method_p = |
177 | 254 |
method => method instanceof WrappedMethod || method.qualifiers.length === 0; |
178 | 255 |
const before_method_p = |
... | ... |
@@ -196,7 +273,7 @@ function apply_methods(gf, args, applicable_methods) { |
196 | 273 |
const main_call = Object.defineProperty( |
197 | 274 |
function() { |
198 | 275 |
if (primaries.length === 0) { |
199 |
- throw new Error(`No primary method for ${gf.name}`); |
|
276 |
+ throw new NoPrimaryMethodError(`No primary method for ${gf.name}`); |
|
200 | 277 |
} |
201 | 278 |
|
202 | 279 |
for (let before of befores) { |
... | ... |
@@ -227,15 +304,16 @@ function apply_method(method, args, next_methods) { |
227 | 304 |
const method_context = { |
228 | 305 |
call_next_method(...cnm_args) { |
229 | 306 |
if (next_methods.length === 0) { |
230 |
- throw new Error(`no next method for genfun ${method.generic_function.name}`); |
|
307 |
+ throw new NoNextMethodError(`no next method for genfun ${method.generic_function.name}`); |
|
231 | 308 |
} |
232 | 309 |
|
233 | 310 |
return method instanceof WrappedMethod |
234 | 311 |
? method.continuation() |
235 | 312 |
: apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods); |
236 | 313 |
}, |
314 |
+ |
|
237 | 315 |
get next_method_p() { |
238 |
- return next_methods.length === 0 |
|
316 |
+ return next_methods.length !== 0 |
|
239 | 317 |
} |
240 | 318 |
}; |
241 | 319 |
|
... | ... |
@@ -1,49 +1,68 @@ |
1 |
-import * as uut from './genfuns.js'; |
|
1 |
+import * as uut from './genfuns'; |
|
2 |
+import * as e from './genfuns'; |
|
2 | 3 |
|
3 |
-describe('matchesSpecializer', () => { |
|
4 |
- function AThing() {}; |
|
5 |
- const an_instance = new AThing(); |
|
6 |
- |
|
4 |
+describe('matches_specializer', () => { |
|
7 | 5 |
test('works in expected cases', () => { |
8 |
- expect(uut.matchesSpecializer(an_instance, AThing)).toBeTruthy(); |
|
9 |
- expect(uut.matchesSpecializer(an_instance, String)).toBeFalsy(); |
|
10 |
- expect(uut.matchesSpecializer(an_instance, Object)).toBeTruthy(); |
|
6 |
+ function AThing() { } |
|
7 |
+ const an_instance = new AThing(); |
|
11 | 8 |
|
12 |
- expect(uut.matchesSpecializer(new String("foobar"), String)).toBeTruthy(); |
|
13 |
- expect(uut.matchesSpecializer(new String("foobar"), Object)).toBeTruthy(); |
|
9 |
+ expect(uut.matches_specializer(an_instance, AThing)).toBeTruthy(); |
|
10 |
+ expect(uut.matches_specializer(an_instance, String)).toBeFalsy(); |
|
11 |
+ expect(uut.matches_specializer(an_instance, Object)).toBeTruthy(); |
|
14 | 12 |
|
15 |
- expect(uut.matchesSpecializer(new Number(1), Number)).toBeTruthy(); |
|
16 |
- expect(uut.matchesSpecializer(new Number(1), Object)).toBeTruthy(); |
|
17 |
- expect(uut.matchesSpecializer(new Number(1), String)).toBeFalsy(); |
|
18 |
- |
|
19 |
- expect(uut.matchesSpecializer([], Array)).toBeTruthy(); |
|
20 |
- expect(uut.matchesSpecializer([], Object)).toBeTruthy(); |
|
21 |
- expect(uut.matchesSpecializer([], Number)).toBeFalsy(); |
|
13 |
+ expect(uut.matches_specializer([], Array)).toBeTruthy(); |
|
14 |
+ expect(uut.matches_specializer([], Object)).toBeTruthy(); |
|
15 |
+ expect(uut.matches_specializer([], Number)).toBeFalsy(); |
|
22 | 16 |
|
23 | 17 |
function Foo() {} |
24 | 18 |
Foo.prototype = Object.create(null); |
25 | 19 |
const inst = new Foo(); |
26 |
- expect(uut.matchesSpecializer(inst, Foo)).toBeTruthy(); |
|
27 |
- expect(uut.matchesSpecializer(inst, Object)).toBeFalsy(); |
|
20 |
+ expect(uut.matches_specializer(inst, Foo)).toBeTruthy(); |
|
21 |
+ expect(uut.matches_specializer(inst, Object)).toBeFalsy(); |
|
22 |
+ |
|
23 |
+ expect(uut.matches_specializer({a:1}, uut.Shape('a'))).toBeTruthy(); |
|
24 |
+ expect(uut.matches_specializer({a:1, b:2}, uut.Shape('a'))).toBeTruthy(); |
|
25 |
+ expect(uut.matches_specializer({b:2}, uut.Shape('a'))).toBeFalsy(); |
|
26 |
+ |
|
27 |
+ expect(uut.matches_specializer({a:1, b:2, c:3}, uut.Shape('a', 'b', 'c'))).toBeTruthy(); |
|
28 |
+ expect(uut.matches_specializer({a:1, b:2, c:3, d:4}, uut.Shape('a', 'b', 'c'))).toBeTruthy(); |
|
29 |
+ expect(uut.matches_specializer({a:1, c:3}, uut.Shape('a', 'b', 'c'))).toBeFalsy(); |
|
30 |
+ expect(uut.matches_specializer({c:3}, uut.Shape('a', 'b', 'c'))).toBeFalsy(); |
|
31 |
+ expect(uut.matches_specializer({d:3}, uut.Shape('a', 'b', 'c'))).toBeFalsy(); |
|
28 | 32 |
}); |
29 | 33 |
|
30 |
- test('works in for primitives', () => { |
|
31 |
- expect(uut.matchesSpecializer(1, Number)).toBeTruthy(); |
|
32 |
- expect(uut.matchesSpecializer(1, Object)).toBeTruthy(); |
|
33 |
- expect(uut.matchesSpecializer(1, String)).toBeFalsy(); |
|
34 |
+ test('null behavior', () => { |
|
35 |
+ expect(uut.matches_specializer(null, null)).toBeTruthy(); |
|
36 |
+ expect(uut.matches_specializer(null, Number)).toBeFalsy(); |
|
37 |
+ expect(uut.matches_specializer(null, String)).toBeFalsy(); |
|
38 |
+ expect(uut.matches_specializer(null, Object)).toBeFalsy(); |
|
39 |
+ }); |
|
34 | 40 |
|
35 |
- expect(uut.matchesSpecializer("1", String)).toBeTruthy(); |
|
36 |
- expect(uut.matchesSpecializer("1", Object)).toBeTruthy(); |
|
37 |
- expect(uut.matchesSpecializer("1", Number)).toBeFalsy(); |
|
41 |
+ test('undefined (the value) behavior', () => { |
|
42 |
+ expect(uut.matches_specializer(undefined, undefined)).toBeTruthy(); |
|
43 |
+ expect(uut.matches_specializer(undefined, Number)).toBeFalsy(); |
|
44 |
+ expect(uut.matches_specializer(undefined, String)).toBeFalsy(); |
|
45 |
+ expect(uut.matches_specializer(undefined, Object)).toBeFalsy(); |
|
46 |
+ }); |
|
38 | 47 |
|
39 |
- expect(uut.matchesSpecializer(null, Number)).toBeFalsy(); |
|
40 |
- expect(uut.matchesSpecializer(null, String)).toBeFalsy(); |
|
41 |
- expect(uut.matchesSpecializer(null, Object)).toBeFalsy(); |
|
48 |
+ test('works for numbers', () => { |
|
49 |
+ expect(uut.matches_specializer(new Number(1), Number)).toBeTruthy(); |
|
50 |
+ expect(uut.matches_specializer(new Number(1), Object)).toBeTruthy(); |
|
51 |
+ expect(uut.matches_specializer(new Number(1), String)).toBeFalsy(); |
|
42 | 52 |
|
43 |
- expect(uut.matchesSpecializer(undefined, Number)).toBeFalsy(); |
|
44 |
- expect(uut.matchesSpecializer(undefined, String)).toBeFalsy(); |
|
45 |
- expect(uut.matchesSpecializer(undefined, Object)).toBeFalsy(); |
|
53 |
+ expect(uut.matches_specializer(1, Number)).toBeTruthy(); |
|
54 |
+ expect(uut.matches_specializer(1, Object)).toBeTruthy(); |
|
55 |
+ expect(uut.matches_specializer(1, String)).toBeFalsy(); |
|
46 | 56 |
}); |
57 |
+ |
|
58 |
+ test('handles strings', () => { |
|
59 |
+ expect(uut.matches_specializer(new String("foobar"), String)).toBeTruthy(); |
|
60 |
+ expect(uut.matches_specializer(new String("foobar"), Object)).toBeTruthy(); |
|
61 |
+ |
|
62 |
+ expect(uut.matches_specializer("1", String)).toBeTruthy(); |
|
63 |
+ expect(uut.matches_specializer("1", Object)).toBeTruthy(); |
|
64 |
+ expect(uut.matches_specializer("1", Number)).toBeFalsy(); |
|
65 |
+ }) |
|
47 | 66 |
}); |
48 | 67 |
|
49 | 68 |
describe('defgeneric', () => { |
... | ... |
@@ -54,6 +73,12 @@ describe('defgeneric', () => { |
54 | 73 |
.fn(1,2) |
55 | 74 |
).toEqual(1); |
56 | 75 |
|
76 |
+ expect(() => { |
|
77 |
+ uut.defgeneric("foobar", "a") |
|
78 |
+ .primary([String], function (a) {}) |
|
79 |
+ .fn({}) |
|
80 |
+ }).toThrow(e.NoApplicableMethodError); |
|
81 |
+ |
|
57 | 82 |
expect( |
58 | 83 |
uut.defgeneric("testing1", "a", "b") |
59 | 84 |
.primary([Number, Number], (_, __) => 1) |
... | ... |
@@ -95,6 +120,149 @@ describe('defgeneric', () => { |
95 | 120 |
).toEqual('hi'); |
96 | 121 |
expect(thirdCounts).toEqual(2); |
97 | 122 |
|
123 |
+ expect( |
|
124 |
+ uut.defgeneric("foobar", "a") |
|
125 |
+ .primary([Object], function (a) { |
|
126 |
+ return 1 |
|
127 |
+ }) |
|
128 |
+ .primary([String], function (a) { |
|
129 |
+ return 2 |
|
130 |
+ }) |
|
131 |
+ .fn("foobar")) |
|
132 |
+ .toEqual(2); |
|
133 |
+ }); |
|
134 |
+ |
|
135 |
+ test('next-method-p works', () => { |
|
136 |
+ expect.assertions(3); |
|
137 |
+ |
|
138 |
+ uut.defgeneric("foobar", "a") |
|
139 |
+ .primary([Object], function (a) { |
|
140 |
+ expect(this.next_method_p).toBe(false); |
|
141 |
+ }) |
|
142 |
+ .fn({}); |
|
143 |
+ |
|
144 |
+ uut.defgeneric("foobar", "a") |
|
145 |
+ .primary([Object], function (a) { |
|
146 |
+ expect(this.next_method_p).toBe(false); |
|
147 |
+ }) |
|
148 |
+ .primary([String], function (a) { |
|
149 |
+ expect(this.next_method_p).toBe(true); |
|
150 |
+ }) |
|
151 |
+ .fn("foobar"); |
|
152 |
+ |
|
153 |
+ uut.defgeneric("foobar", "a") |
|
154 |
+ .primary([Object], function (a) { |
|
155 |
+ expect(this.next_method_p).toBe(false); |
|
156 |
+ }) |
|
157 |
+ .primary([String], function (a) { |
|
158 |
+ expect(this.next_method_p).toBe(true); |
|
159 |
+ }) |
|
160 |
+ .fn(1); |
|
161 |
+ |
|
162 |
+ }); |
|
163 |
+ |
|
164 |
+ test('call-next-method works', () => { |
|
165 |
+ expect(() => { |
|
166 |
+ uut.defgeneric("foobar", "a") |
|
167 |
+ .primary([Object], function (a) { |
|
168 |
+ this.call_next_method(); |
|
169 |
+ }) |
|
170 |
+ .fn({}); |
|
171 |
+ }).toThrow(e.NoNextMethodError); |
|
172 |
+ |
|
173 |
+ expect(() => { |
|
174 |
+ uut.defgeneric("foobar", "a") |
|
175 |
+ .primary([Object], function (a) { |
|
176 |
+ this.call_next_method(); |
|
177 |
+ }) |
|
178 |
+ .primary([String], function (a) { |
|
179 |
+ return 1; |
|
180 |
+ }) |
|
181 |
+ .fn({}); |
|
182 |
+ }); |
|
183 |
+ |
|
184 |
+ expect( |
|
185 |
+ uut.defgeneric("foobar", "a") |
|
186 |
+ .primary([Object], function (a) { |
|
187 |
+ return 1; |
|
188 |
+ }) |
|
189 |
+ .primary([String], function (a) { |
|
190 |
+ return this.call_next_method(); |
|
191 |
+ }) |
|
192 |
+ .fn("foobar") |
|
193 |
+ ).toEqual(1); |
|
194 |
+ |
|
195 |
+ expect( |
|
196 |
+ uut.defgeneric("foobar", "a", "b") |
|
197 |
+ .primary ([String, String], function (a,b) { |
|
198 |
+ return `1${this.call_next_method()}`; |
|
199 |
+ }) |
|
200 |
+ .primary ([Object, String], function (a,b) { |
|
201 |
+ return `3${this.call_next_method()}`; |
|
202 |
+ }) |
|
203 |
+ .primary ([String, Object], function (a,b) { |
|
204 |
+ return `2${this.call_next_method()}`; |
|
205 |
+ }) |
|
206 |
+ .primary ([Object, Object], function (a,b) { |
|
207 |
+ return `4`; |
|
208 |
+ }).fn("a", "b") |
|
209 |
+ ).toEqual("1234"); |
|
210 |
+ }); |
|
211 |
+}); |
|
212 |
+ |
|
213 |
+describe('custom specializers', () => { |
|
214 |
+ test('Shape works', () => { |
|
215 |
+ expect(uut.defgeneric("foobar", "a") |
|
216 |
+ .primary([uut.Shape('a', 'b')], ({a,b}) => a+b) |
|
217 |
+ .primary([Object], _ => null) |
|
218 |
+ .fn({a:1,b:2})) |
|
219 |
+ .toEqual(3); |
|
220 |
+ |
|
221 |
+ expect(uut.defgeneric("foobar", "a") |
|
222 |
+ .primary([uut.Shape('a', 'b')], ({a,b}) => a+b) |
|
223 |
+ .primary([Object], _ => null) |
|
224 |
+ .fn({a:1,b:2,c:3})) |
|
225 |
+ .toEqual(3); |
|
226 |
+ |
|
227 |
+ expect(uut.defgeneric("foobar", "a") |
|
228 |
+ .primary([uut.Shape('a', 'b')], ({a,b}) => a+b) |
|
229 |
+ .primary([Object], _ => null) |
|
230 |
+ .fn({a:1})) |
|
231 |
+ .toEqual(null); |
|
232 |
+ }); |
|
233 |
+ |
|
234 |
+ test('Shape, prototype precedence', () => { |
|
235 |
+ expect(uut.defgeneric("foobar4", "a") |
|
236 |
+ .primary([uut.Shape('a')], ({a}) => a) |
|
237 |
+ .primary([uut.Shape('a', 'b')], ({a,b}) => {return a+b}) |
|
238 |
+ .primary([Object], _ => null).fn({a:1,b:3})) |
|
239 |
+ .toEqual(4); |
|
240 |
+ |
|
241 |
+ expect(uut.defgeneric("foobar", "a") |
|
242 |
+ .primary([uut.Shape('a', 'b')], ({a,b}) => a+b) |
|
243 |
+ .primary([uut.Shape('b')], ({b}) => b) |
|
244 |
+ .primary([Object], _ => null) |
|
245 |
+ .fn({a:1,b:2})) |
|
246 |
+ .toEqual(3); |
|
247 |
+ |
|
248 |
+ const Foo = function () {} |
|
249 |
+ Foo.prototype = {a: true, b: null}; |
|
250 |
+ expect(uut.defgeneric("foobar", "a") |
|
251 |
+ .primary([uut.Shape('a')], function ({a}) { return 'a'; }) |
|
252 |
+ .primary([uut.Shape('a', 'b', 'c')], function ({a,b,c}) { return `c${this.call_next_method()}`; }) |
|
253 |
+ .primary([uut.Shape('a', 'b')], function ({a,b}) { return `b${this.call_next_method()}`; }) |
|
254 |
+ .primary([Object], _ => null) |
|
255 |
+ .fn(Object.assign(new Foo(), {c: 3}))) |
|
256 |
+ .toEqual('cba'); |
|
257 |
+ }); |
|
258 |
+}); |
|
98 | 259 |
|
260 |
+describe("Shape", () => { |
|
261 |
+ test('super_of', () => { |
|
262 |
+ expect(uut.Shape().super_of(uut.Shape("a", "b", "c"))).toBeTruthy(); |
|
263 |
+ expect(uut.Shape('a').super_of(uut.Shape('a', 'b'))).toBeTruthy(); |
|
264 |
+ expect(uut.Shape("a", "b").super_of(uut.Shape("a", "b", "c"))).toBeTruthy(); |
|
265 |
+ expect(uut.Shape("a", "b").super_of(uut.Shape("a", "b"))).toBeFalsy(); |
|
266 |
+ expect(uut.Shape("a", "b").super_of(uut.Shape("a"))).toBeFalsy(); |
|
99 | 267 |
}); |
100 | 268 |
}); |