haroldiedema /
joii
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 62 | new b(); |
||
| 63 | |||
| 64 | // This shouldn't throw an error |
||
| 65 | var a = JOII.ClassBuilder({}, { |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 157 | , |
||
|
0 ignored issues
–
show
|
|||
| 158 | '__call()': function() { |
||
| 159 | return this.a; |
||
| 160 | } |
||
| 161 | }); |
||
| 162 | |||
| 163 | var c2 = new C2(); |
||
|
0 ignored issues
–
show
|
|||
| 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 |
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.