Completed
Pull Request — master (#26)
by
unknown
50s
created

test/ClassBuilder/FunctionOverloadingTests.js (10 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
    catch (e) {
417
        QUnit.pushFailure(e);
418
    }
419
420
});
421