Issues (204)

test/ClassBuilder/FunctionOverloadingTests.js (11 issues)

1
/* Javascript Object Inheritance Implementation                ______  ________
2
 * (c) 2016 <[email protected]>                             __ / / __ \/  _/  _/
3
 * Licensed under MIT.                                    / // / /_/ // /_/ /
4
 * ------------------------------------------------------ \___/\____/___/__*/
5
var JOII = require('../../dist/joii').JOII;
6
7
/**
8
* Tests function overloading
9
*/
10
test('ClassBuilder:FunctionOverloadingTests', function(assert) {
11
12
    try {
0 ignored issues
show
This function should enable strict mode with use strict.

Strict mode is a way to opt-in to a restricted variant of JavaScript. It eliminates some common pitfalls by being less lenient and raising more errors.

Besides, it is also used to fix certain mistakes which made it difficult for JavaScript runtimes to perform certain optimizations.

We generally recommend to only enable strict mode on the function scope and not in the global scope as that might break third-party scripts on your website.

Loading history...
13
        // ***************************
14
        // Abstract tests
15
        // ***************************
16
17
        // An abstract property must also have a functional one in the same class.
18
        assert.throws(function() {
19
            var a = JOII.ClassBuilder({}, {
20
                'abstract public function test(string)': function() { }
21
            }); new a();
22
        }, function(err) { return err === 'Missing abstract member implementation of test(string)'; }, 'Validate: Missing implementation of abstract properties.');
23
24
        // An abstract property must be implemented by a child class.
25
        assert.throws(function() {
26
            var a = JOII.ClassBuilder({}, { 'abstract public function test(string)': function() { } });
27
            var b = JOII.ClassBuilder({ 'extends': a }, {}); new b();
28
        }, function(err) { return err === 'Missing abstract member implementation of test(string)'; }, 'Validate: Missing implementation of abstract properties.');
29
30
        // A final property cannot be implemented by a child class.
31
        assert.throws(function() {
32
            var a = JOII.ClassBuilder({}, {
33
                'final public function test(string)': function() { },
34
                'public function test(string, num)': function() { }
35
            });
36
            var b = JOII.ClassBuilder({ 'extends': a }, { 'public function test(string)': function() { } }); new b();
37
        }, function(err) { return err === 'Final member "test(string)" cannot be overwritten.'; }, 'Validate: Missing implementation of abstract properties.');
38
39
        // A final property cannot be implemented by a child class.
40
        assert.throws(function() {
41
            var a = JOII.ClassBuilder({}, {
42
                'final public function test(string)': function() { },
43
                'final public function test(string, num)': function() { }
44
            });
45
            var b = JOII.ClassBuilder({ 'extends': a }, { 'final public function test(string)': function() { } }); new b();
46
        }, function(err) { return err === 'Final member "test(string)" cannot be overwritten.'; }, 'Validate: Missing implementation of abstract properties.');
47
48
        // This shouldn't throw an error
49
        var a = JOII.ClassBuilder({}, {
50
            'public function test(string)': function() { },
51
            'final public function test(string, num)': function() { }
52
        });
53
        var b = JOII.ClassBuilder({ 'extends': a }, { 'final public function test(string)': function() { } });
54
        new b();
55
56
        // This shouldn't throw an error
57
        var a = JOII.ClassBuilder({}, {
0 ignored issues
show
It seems like a was already defined.
Loading history...
58
            'abstract public function test(string)': function() { },
59
            'public function test(string, num)': function() { }
60
        });
61
        var b = JOII.ClassBuilder({ 'extends': a }, { 'final public function test(string)': function() { } });
0 ignored issues
show
It seems like b was already defined.
Loading history...
62
        new b();
63
64
        // This shouldn't throw an error
65
        var a = JOII.ClassBuilder({}, {
0 ignored issues
show
It seems like a was already defined.
Loading history...
66
            'abstract public function test(string)': function() { },
67
            'final public function test(string, num)': function() { }
68
        });
69
        var b = JOII.ClassBuilder({ 'extends': a }, { 'public function test(string)': function() { } });
0 ignored issues
show
It seems like b was already defined.
Loading history...
70
        new b();
71
72
73
        // An abstract property must be implemented by a child class.
74
        assert.throws(function() {
75
            var a = JOII.ClassBuilder({}, {
76
                'abstract public function test(number)': function() { },
77
                'abstract public function test(string)': function() { }
78
            });
79
            var b = JOII.ClassBuilder({ 'extends': a }, {
80
                'public function test(string)': function() { }
81
            }); new b();
82
        }, function(err) { return err === 'Missing abstract member implementation of test(number)'; }, 'Validate: Missing implementation of abstract properties.');
83
84
        // An abstract property must be implemented by a child class.
85
        assert.throws(function() {
86
            var a = JOII.ClassBuilder({}, {
87
                'abstract public function test(number)': function() { },
88
                'abstract public function test(string)': function() { }
89
            });
90
            var b = JOII.ClassBuilder({ 'extends': a }, {
91
                'public function test(number)': function() { }
92
            }); new b();
93
        }, function(err) { return err === 'Missing abstract member implementation of test(string)'; }, 'Validate: Missing implementation of abstract properties (different order).');
94
95
        // Visibility of an abstract property may not change.
96
        assert.throws(function() {
97
            var a = JOII.ClassBuilder({}, { 'abstract public function test(string)': function() { } });
98
            var b = JOII.ClassBuilder({ 'extends': a }, { 'protected function test(string)': function() { } }); new b();
99
        }, function(err) { return err === 'Member "test" must be public as defined in the parent class.'; }, 'Validate: Visibility change of abstract implementation.');
100
101
        // This should _not_ throw an exception.
102
        var a = JOII.ClassBuilder({}, {
0 ignored issues
show
It seems like a was already defined.
Loading history...
103
            'abstract public function test(number)': function() { },
104
            'abstract public function test(string)': function() { }
105
        });
106
        var b = JOII.ClassBuilder({ 'extends': a }, {
0 ignored issues
show
It seems like b was already defined.
Loading history...
107
            'public function test(number)': function() { },
108
            'public function test(string)': function() { }
109
        });
110
        new b();
111
112
        // Defining an abstract and functional property in the same class is valid. (beware of declaration order)
113
        var c = JOII.ClassBuilder({}, {
114
            'abstract public function test(string)': function() { },
115
            'public function test(string)': function() { }
116
        });
117
        new c();
118
119
        // Implement abstract property in the middle of the inheritance chain.
120
        var c1 = JOII.ClassBuilder({}, { 'abstract public function test(string)': function() { } });
121
        var c2 = JOII.ClassBuilder({ 'extends': c1 }, {});
122
        var c3 = JOII.ClassBuilder({ 'extends': c2 }, { 'public function test(string)': function() { } });
123
        var c4 = JOII.ClassBuilder({ 'extends': c3 }, {});
124
125
        // Should not throw exception for missing an abstract implementation.
126
        new c4();
127
128
129
        // ***************************
130
        // Call tests
131
        // ***************************
132
133
        var C1 = JOII.ClassBuilder({
134
            '__call(number)': function(p) { return 1 + p; },
135
            '__call(string)': function(p) { return p + " passed"; }
136
        });
137
        assert.strictEqual(C1(1), 2, '__call number overload function OK.');
138
        assert.strictEqual(C1("test"), "test passed", '__call string overload function OK.');
139
140
        // __call may not return "this", because it references to the static
141
        // definition of the class body.
142
        assert.throws(function() {
143
            var a = JOII.ClassBuilder({ 'public function foo': function() { }, '__call(number)': function() { return this; } }); a(1);
144
        }, function(err) { return err === '__call cannot return itself.'; }, '__call cannot return itself.');
145
146
        // Test the context of a static call.
147
        var C2 = JOII.ClassBuilder({
148
            a: 1,
149
            b: "test",
150
151
            '__call(number)': function(val) {
152
                this.a = val;
153
            },
154
            '__call(string)': function(val) {
155
                this.b = val;
156
            }
0 ignored issues
show
There seems to be a bad line break before ,.
Loading history...
157
            ,
0 ignored issues
show
Comma warnings can be turned off with 'laxcomma'.
Loading history...
158
            '__call()': function() {
159
                return this.a;
160
            }
161
        });
162
163
        var c2 = new C2();
0 ignored issues
show
It seems like c2 was already defined.
Loading history...
164
        assert.strictEqual(c2.getA(), 1, 'c2.getA() returns this.a (1)');
165
        assert.strictEqual(c2.getB(), "test", 'c2.getB() returns this.b ("test")');
166
        assert.strictEqual(C2(), 1, '__call returns this.a (1)');
167
168
169
        // Update the value, THEN create another instance and check again...
170
        C2(2);
171
        C2("test again");
172
        var c2a = new C2();
173
        assert.strictEqual(C2(), 2, '__call returns this.a (2)');
174
        assert.strictEqual(c2.getA(), 1, 'c2.getA() returns this.a (1)');
175
        assert.strictEqual(c2a.getA(), 1, 'c2a.getA() returns this.a (1)');
176
        assert.strictEqual(c2.getB(), "test", 'c2.getB() returns this.b ("test")');
177
        assert.strictEqual(c2a.getB(), "test", 'c2a.getB() returns this.b ("test")');
178
179
180
        // 3.1.0: Custom callable method names.
181
182
        var C3 = Class({
183
            '<>(number)': function(val) { return val + 1; },
184
            '<>(string)': function(val) { return val + " passed"; }
185
        });
186
        assert.strictEqual(C3(4), 5, 'New default call number overload method "<>" used.');
187
        assert.strictEqual(C3("test"), "test passed", 'New default call string overload method "<>" used.');
188
189
        JOII.Config.addCallable('execute');
190
        var C4 = Class({
191
            'execute(number)': function(val) { return val + 1; },
192
            'execute(string)': function(val) { return val + " passed"; }
193
        });
194
        assert.strictEqual(C4(4), 5, 'Custom call number overload method "execute" used.');
195
        assert.strictEqual(C4("test"), "test passed", 'Custom call string overload method "execute" used.');
196
        JOII.Config.removeCallable('execute');
197
198
199
200
        // ***************************
201
        // General overloading tests
202
        // ***************************
203
204
205
206
        var UniqueTestClass = JOII.ClassBuilder('UniqueTestClass', {
207
            x: 6,
208
            y: "something else"
209
        });
210
211
        var SuperClass = JOII.ClassBuilder('SuperClass', {
212
            a: 1,
213
            b: "test",
214
215
            'test(number)': function(val) {
216
                this.a = val;
217
            },
218
            'test(string)': function(val) {
219
                this.b = val;
220
            },
221
            'test(UniqueTestClass)': function(val) {
222
                this.a = val.getX();
223
                this.b = val.getY();
224
            },
225
            'test(object)': function(val) {
226
                if ('a' in val) {
227
                    this.a = val.a;
228
                }
229
                if ('b' in val) {
230
                    this.b = val.b;
231
                }
232
            }
233
        });
234
235
        var sc = new SuperClass();
236
237
        sc.test(2);
238
        assert.strictEqual(sc.getA(), 2, 'sc.getA() returns this.a (2)');
239
240
        sc.test("success");
241
        assert.strictEqual(sc.getB(), "success", 'sc.getB() returns this.b ("success")');
242
243
        sc.test({ a: 5, b: "passed" });
244
        assert.strictEqual(sc.getA(), 5, 'sc.getA() returns this.a (5)');
245
        assert.strictEqual(sc.getB(), "passed", 'sc.getB() returns this.b ("passed")');
246
247
248
        var testClass = new UniqueTestClass();
249
        sc.test(testClass);
250
        assert.strictEqual(sc.getA(), 6, 'sc.getA() returns this.a (6)');
251
        assert.strictEqual(sc.getB(), "something else", 'sc.getB() returns this.b ("something else")');
252
253
254
        var SubClass1 = JOII.ClassBuilder('SubClass1', { 'extends': SuperClass }, {
255
            'test(number)': function(val) {
256
                this.a = val * 2;
257
            },
258
            'test(number, string)': function(num, str) {
259
                this.a = num * 4;
260
                this.b = str + " passed";
261
            },
262
            'test(string, number)': function(str, num) {
263
                this.test(num);
264
                this.test(str);
265
            }
266
        });
267
268
269
        var subc = new SubClass1();
270
271
        // first, test the methods that were inherited and not overridden
272
        subc.test("success");
273
        assert.strictEqual(subc.getB(), "success", 'subc.getB() returns this.b ("success")');
274
275
        subc.test({ a: 5, b: "passed" });
276
        assert.strictEqual(subc.getA(), 5, 'subc.getA() returns this.a (5)');
277
        assert.strictEqual(subc.getB(), "passed", 'subc.getB() returns this.b ("passed")');
278
279
        // test override
280
        subc.test(5);
281
        assert.strictEqual(subc.getA(), 10, 'subc.getA() returns this.a (10)');
282
283
        // test added overload
284
        subc.test(10, "testing");
285
        assert.strictEqual(subc.getA(), 40, 'subc.getA() returns this.a (40)');
286
        assert.strictEqual(subc.getB(), "testing passed", 'subc.getB() returns this.b ("testing passed")');
287
288
        // test overload calling different overloads, from different levels of inheritance
289
        subc.test("asdf", 100);
290
        assert.strictEqual(subc.getA(), 200, 'subc.getA() returns this.a (200)');
291
        assert.strictEqual(subc.getB(), "asdf", 'subc.getB() returns this.b ("asdf")');
292
293
294
        var CustomClass = JOII.ClassBuilder('CustomClass', {
295
            'SubClass1 custom': null,
296
            'construct(number)': function(num) {
297
                this.custom = new SubClass1();
298
                this.custom.test(num);
299
            },
300
            'construct(string)': function(str) {
301
                this.custom = new SubClass1();
302
                this.custom.test(str);
303
            },
304
            'construct(number, string)': function(num, str) {
305
                this.custom = new SubClass1();
306
                this.custom.test(num, str);
307
            },
308
            'test(number)': function(num) {
309
                this.custom.test(num);
310
            },
311
            'test(string)': function(str) {
312
                this.custom.test(str);
313
            },
314
            'test(SubClass1)': function(val) {
315
                this.custom = val;
316
            }
317
        });
318
319
        var custom1 = new CustomClass(5);
320
        assert.strictEqual(custom1.getCustom().getA(), 10, 'custom1.getCustom().getA() returns this.custom.a (10)');
321
322
        var custom2 = new CustomClass("success");
323
        assert.strictEqual(custom2.getCustom().getB(), "success", 'custom1.getCustom().getB() returns this.custom.a ("success")');
324
325
326
        subc.test("asdf", 100);
327
        custom2.test(subc);
328
        assert.strictEqual(custom2.getCustom().getA(), 200, 'custom2.getCustom().getA() returns this.custom.a (200)');
329
        assert.strictEqual(custom2.getCustom().getB(), "asdf", 'custom2.getCustom().getB() returns this.custom.b ("asdf")');
330
331
332
333
        assert.throws(function() {
334
            custom2.test(1, 2, 3, 4);
335
        }, function(err) { return err === 'Couldn\'t find a function handler to match: test(number, number, number, number).'; }, 'Validate: No matching overload.');
336
337
        assert.throws(function() {
338
            var c5 = JOII.ClassBuilder({
339
                'construct( number )': function(num) { },
340
                'construct(number)': function(num) { }
341
            });
342
            new c5();
343
        }, function(err) { return err === 'Member "construct(number)" is defined twice.'; }, 'Validate: Duplicate function overload.');
344
345
        assert.throws(function() {
346
            var c6 = JOII.ClassBuilder({
347
                'public test(number)': function(num) { },
348
                'private test(number)': function(num) { }
349
            });
350
            new c6();
351
        }, function(err) { return err === 'Member test: inconsistent visibility.'; }, 'Validate: Consistent visibility of overloads.');
352
353
        assert.throws(function() {
354
            var c7 = JOII.ClassBuilder({
355
                'test(number)': function(num) { },
356
                'test(str)': 'invalid'
357
            });
358
            new c7();
359
        }, function(err) { return err === 'Member test specifies parameters, but it\'s value isn\'t a function.'; }, 'Validate: Function body.');
360
361
        assert.throws(function() {
362
            var c8 = JOII.ClassBuilder({
363
                'test(number)': function(num) { },
364
                'test': 'invalid'
365
            });
366
            new c8();
367
        }, function(err) { return err === 'Member test overloads an existing function, but it\'s value isn\'t a function.'; }, 'Validate: Consistent overloads.');
368
369
        assert.throws(function() {
370
            var c9 = JOII.ClassBuilder({
371
                'test': 'invalid',
372
                'test(number)': function(num) { }
373
            });
374
            new c9();
375
        }, function(err) { return err === 'Member test overloads an existing property, but the previous property isn\'t a function.'; }, 'Validate: Consistent overloads (reverse order).');
376
377
378
        // ***************************
379
        // Variadic overloading tests
380
        // ***************************
381
382
383
        var C10 = JOII.ClassBuilder({
384
            'variadic(number)': function(num) {
385
                return "variadic (number): " + num;
386
            },
387
            'variadic(number, string)': function(num, str) {
388
                return "variadic (number, string): " + str + ": " + num;
389
            },
390
            'variadic(number, ...)': function(num, args) {
391
                return "variadic (number, ...): " + num + ' = ' + args.join(', ');
392
            },
393
            'variadic(number, string, ...)': function(num, str, args) {
394
                return "variadic (number, string, ...): " + str + ": " + num + ' = ' + args.join(', ');
395
            }
396
        });
397
        var c10 = new C10();
398
399
        assert.strictEqual(c10.variadic(1), "variadic (number): 1", 'c10.variadic(1) returns variadic (number): 1');
400
        assert.strictEqual(c10.variadic(1, "Test"), "variadic (number, string): Test: 1", 'c10.variadic(1, "Test") returns variadic (number, string): Test: 1');
401
402
        assert.strictEqual(c10.variadic(1, 2), "variadic (number, ...): 1 = 2", 'c10.variadic(1, 2) returns "variadic (number, ...): 1 = 2"');
403
        assert.strictEqual(c10.variadic(1, 2, "asdf"), "variadic (number, ...): 1 = 2, asdf", 'c10.variadic(1, 2, "asdf") returns "variadic (number, ...): 1 = 2, asdf"');
404
        assert.strictEqual(c10.variadic(1, "asdf", 2, 3), "variadic (number, string, ...): asdf: 1 = 2, 3", 'c10.variadic(1, "asdf", 2, 3) returns "variadic (number, string, ...): asdf: 1 = 2, 3"');
405
406
407
        assert.throws(function() {
408
            var c11 = JOII.ClassBuilder({
409
                'variadic(string, ..., string)': function(num, str, args) { }
410
            });
411
            new c11();
412
        }, function(err) { return err === 'Member variadic: Variadic parameter (...) must be the last in the function parameter list.'; }, 'Validate: Variadic last function parameter.');
413
414
415
416
417
418
419
420
        var ValueClass = Class('ValueClass', {
421
            id: 6,
422
            value: "",
423
            'construct()': function() { },
424
            'construct(number, string)': function(id, val) {
425
                this.id = id;
426
                this.value = val;
427
            },
428
            'construct(ValueClass)': function(vc) {
429
                this.id = vc.id;
430
                this.value = vc.value;
431
            },
432
        });
433
434
        var LookupClass = Class('LookupClass', {
435
            'private object table': [],
436
437
            // lookup a value object by id
438
            'get(number)': function(num) {
439
                for (var i in this.table) {
440
                    var val = this.table[i];
441
                    if (val.getId() == num) {
442
                        return val;
443
                    }
444
                }
445
            },
446
447
            // overload the get function to lookup a value object by it's value
448
            'get(string)': function(str) {
449
                for (var i in this.table) {
450
                    var val = this.table[i];
451
                    if (val.getValue() == str) {
452
                        return val;
453
                    }
454
                }
455
            },
456
457
            // add a new value, by providing a ValueClass object
458
            'add(ValueClass)': function(vc) {
459
                this.table.push(vc);
460
            },
461
462
            // add a new value, by providing an numeric id and string value
463
            'add(number, string)': function(id, val) {
464
                var vc = new ValueClass();
465
                vc.setId(id);
466
                vc.setValue(val);
467
468
                // call the add function above with our new ValueClass object
469
                this.add(vc);
470
            },
471
472
            // add a new value, by providing an anonymous object containing the id and value
473
            'add(object)': function(obj) {
474
                if ('id' in obj && 'value' in obj) {
475
                    var vc = new ValueClass();
476
                    vc.setId(obj.id);
477
                    vc.setValue(obj.value);
478
479
                    // call the add function above with our new ValueClass object
480
                    this.add(vc);
481
                }
482
            },
483
484
            // add several new values at the same time
485
            'add(...)': function(args) {
486
                for (var i in args) {
487
                    var val = args[i];
488
                    this.add(val);
489
                }
490
            }
491
        });
492
493
494
495
496
497
        var table = new LookupClass();
498
499
        var value1 = new ValueClass(1, 'bob');
500
501
        // add an instance of ValueClass
502
        table.add(value1);
503
504
        // add with a number and string
505
        table.add(2, 'joe');
506
507
        // add an anonymous object
508
        table.add({
509
            id: 3,
510
            value: 'sam'
511
        });
512
513
        // create a few different objects
514
        var value2 = new ValueClass(4, 'george');
515
        var value3 = new ValueClass(7, 'bill');
516
517
        var obj1 = {
518
            id: 5,
519
            value: 'jenny'
520
        };
521
522
        var obj2 = {
523
            id: 6,
524
            value: 'laura'
525
        };
526
527
528
        // add several things at once
529
        table.add(value2, obj1, obj2, value3);
530
531
532
        assert.strictEqual(table.get(5).getValue(), "jenny", 'Verify: table.get(5).getValue() == "jenny"');
533
        assert.strictEqual(table.get(2).getValue(), "joe", 'Verify: table.get(2).getValue() == "joe"');
534
        assert.strictEqual(table.get("sam").getId(), 3, 'Verify: table.get("sam").getId() == 3');
535
        assert.strictEqual(table.get("laura").getId(), 6, 'Verify: table.get("laura").getId() == 6');
536
        assert.strictEqual(table.get(4).getValue(), "george", 'Verify: table.get(4).getValue() == "george"');
537
        
538
539
540
541
542
        var IMyInterface = Interface('IMyInterface', {
543
            // Interface body ...
544
        });
545
546
        var RandomClass = Class('RandomClass', { implements: IMyInterface }, {});
547
548
        var MyClass2 = Class('MyClass2', {
549
550
            // anything which implements IMyInterface will be passed here
551
            'test(IMyInterface)': function(val) {
552
                return 1;
553
            },
554
555
            // RandomClass implements IMyInterface, so the above method will always match it.
556
            // This method will NEVER be called
557
            'test(RandomClass)': function(val) {
558
                return 2;
559
            }
560
        });
561
562
        var mc = new MyClass2();
563
        var rc = new RandomClass();
564
565
        assert.strictEqual(mc.test(rc), 1, 'Verify: Overloads order of operations with parent matching');
566
567
        var MyClass3 = Class('MyClass3', {
568
569
            // ALL JOII classes ultimately inherit from object,
570
            // so this will override ALL other one parameter methods if placed above them
571
            // unless they're other native types (such as string)
572
            'test(object)': function(val) {
573
                return 3;
574
            },
575
576
            // RandomClass is an object, so the above method will always match it.
577
            // This method will NEVER be called
578
            'test(RandomClass)': function(val) {
579
                return 4;
580
            },
581
582
            // This method WILL be called, because it has an extra parameter to match against
583
            'test(RandomClass, string)': function(val, str) {
584
                return 5;
585
            }
586
        });
587
588
        var mc = new MyClass3();
0 ignored issues
show
It seems like mc was already defined.
Loading history...
589
590
        assert.strictEqual(mc.test(rc), 3, 'Verify: Overloads order of operations with parent matching');
591
        assert.strictEqual(mc.test(rc, "test"), 5, 'Verify: Overloads order of operations with parent matching');
592
593
    }
594
    catch (e) {
595
        QUnit.pushFailure(e);
596
    }
597
598
});
599