UNPKG

15.8 kBtext/coffeescriptView Raw
1suite 'Classes', ->
2
3 suite 'Class Definition', ->
4
5 #test 'Overriding the static property new doesn't clobber Function::new', ->
6 #
7 # class OneClass
8 # @new: 'new'
9 # function: 'function'
10 # constructor: (name) -> @name = name
11 #
12 # class TwoClass extends OneClass
13 # delete TwoClass.new
14 #
15 # Function.prototype.new = -> new this arguments...
16 #
17 # ok (TwoClass.new('three')).name is 'three'
18 # ok (new OneClass).function is 'function'
19 # ok OneClass.new is 'new'
20 #
21 # delete Function.prototype.new
22
23 #test 'basic classes, again, but in the manual prototype style', ->
24 #
25 # Base = ->
26 # Base::func = (string) ->
27 # 'zero/' + string
28 # Base::['func-func'] = (string) ->
29 # "dynamic-#{string}"
30 #
31 # FirstChild = ->
32 # SecondChild = ->
33 # ThirdChild = ->
34 # @array = [1, 2, 3]
35 # this
36 #
37 # ThirdChild extends SecondChild extends FirstChild extends Base
38 #
39 # FirstChild::func = (string) ->
40 # super('one/') + string
41 #
42 # SecondChild::func = (string) ->
43 # super('two/') + string
44 #
45 # ThirdChild::func = (string) ->
46 # super('three/') + string
47 #
48 # result = (new ThirdChild).func 'four'
49 #
50 # ok result is 'zero/one/two/three/four'
51 #
52 # ok (new ThirdChild)['func-func']('thing') is 'dynamic-thing'
53
54 test 'static assignment via colon', ->
55 nonce = {}
56 class A then @b: nonce
57 eq nonce, A.b
58
59 test 'classes with JS-keyword properties', ->
60
61 class Class
62 class: 'class'
63 name: -> @class
64
65 instance = new Class
66 ok instance.class is 'class'
67 ok instance.name() is 'class'
68
69 test 'nothing classes', ->
70
71 c = class
72 ok c instanceof Function
73
74 test 'instance-bound methods and statically-bound methods', ->
75
76 class Dog
77 constructor: (name) ->
78 @name = name
79
80 bark: =>
81 "#{@name} woofs!"
82
83 @static = =>
84 new this('Dog')
85
86 spark = new Dog('Spark')
87 fido = new Dog('Fido')
88 fido.bark = spark.bark
89
90 ok fido.bark() is 'Spark woofs!'
91
92 obj = {func: Dog.static}
93
94 ok obj.func().name is 'Dog'
95
96 test 'anonymous classes', ->
97
98 obj = klass: class
99 method: -> 'value'
100
101 instance = new obj.klass
102 ok instance.method() is 'value'
103
104 #test 'Implicit objects as static properties', ->
105 #
106 # class Static
107 # @static =
108 # one: 1
109 # two: 2
110 #
111 # ok Static.static.one is 1
112 # ok Static.static.two is 2
113
114 #test 'classes with static-level implicit objects', ->
115 #
116 # class A
117 # @static = one: 1
118 # two: 2
119 #
120 # class B
121 # @static = one: 1,
122 # two: 2
123 #
124 # eq A.static.one, 1
125 # eq A.static.two, undefined
126 # eq (new A).two, 2
127 #
128 # eq B.static.one, 1
129 # eq B.static.two, 2
130 # eq (new B).two, undefined
131
132 test 'external constructors', ->
133
134 counter = 0
135 classMaker = ->
136 inner = ++counter
137 ->
138 @value = inner
139
140 class One
141 constructor: classMaker()
142
143 class Two
144 constructor: classMaker()
145
146 eq (new One).value, 1
147 eq (new Two).value, 2
148 eq (new One).value, 1
149 eq (new Two).value, 2
150
151 #test 'exectuable class bodies', ->
152 #
153 # class A
154 # if true
155 # b: 'b'
156 # else
157 # c: 'c'
158 #
159 # a = new A
160 #
161 # eq a.b, 'b'
162 # eq a.c, undefined
163
164 test 'mild metaprogramming', ->
165
166 class Base
167 @attr = (name) ->
168 @::[name] = (val) ->
169 if arguments.length > 0
170 @["_#{name}"] = val
171 else
172 @["_#{name}"]
173
174 class Robot extends Base
175 @attr 'power'
176 @attr 'speed'
177
178 robby = new Robot
179
180 ok robby.power() is undefined
181
182 robby.power 11
183 robby.speed Infinity
184
185 eq robby.power(), 11
186 eq robby.speed(), Infinity
187
188 test 'namespaced classes do not reserve their function name in outside scope', ->
189
190 one = {}
191 two = {}
192
193 class one.Klass
194 @label = "one"
195
196 class two.Klass
197 @label = "two"
198
199 eq typeof Klass, 'undefined'
200 eq one.Klass.label, 'one'
201 eq two.Klass.label, 'two'
202
203 test 'nested classes', ->
204
205 class Outer
206 constructor: ->
207 @label = 'outer'
208
209 class @Inner
210 constructor: ->
211 @label = 'inner'
212
213 eq (new Outer).label, 'outer'
214 eq (new Outer.Inner).label, 'inner'
215
216 test 'variables in constructor bodies are correctly scoped', ->
217
218 class A
219 x = 1
220 constructor: ->
221 x = 10
222 y = 20
223 y = 2
224 captured: ->
225 {x, y}
226
227 a = new A
228 eq a.captured().x, 10
229 eq a.captured().y, 2
230
231 test 'Issue #924: Static methods in nested classes', ->
232
233 class A
234 @B = class
235 @c = -> 5
236
237 eq A.B.c(), 5
238
239 test 'jashkenas/coffee-script#1182: a subclass should be able to set its constructor to an external function', ->
240 ctor = ->
241 @val = 1
242 class A
243 class B extends A
244 constructor: ctor
245 eq (new B).val, 1
246
247 test 'jashkenas/coffee-script#1182: external constructors continued', ->
248 ctor = ->
249 class A
250 class B extends A
251 method: ->
252 constructor: ctor
253 ok B::method
254
255 test 'jashkenas/coffee-script#1182: execution order needs to be considered as well', ->
256 counter = 0
257 makeFn = (n) -> eq n, ++counter; ->
258 class B extends (makeFn 1)
259 @B = makeFn 2
260 constructor: makeFn 3
261
262 test 'jashkenas/coffee-script#1182: external constructors with bound functions', ->
263 fn = ->
264 {one: 1}
265 this
266 class B
267 class A
268 constructor: fn
269 method: => this instanceof A
270 ok (new A).method.call(new B)
271
272 test 'jashkenas/coffee-script#1372: bound class methods with reserved names', ->
273 class C
274 delete: =>
275 ok C::delete
276
277 test 'jashkenas/coffee-script#1464: bound class methods should keep context', ->
278 nonce = {}
279 class C
280 constructor: (id) -> @id = id
281 @boundStatic = => new this(nonce)
282 eq nonce, C.boundStatic().id
283
284 test 'jashkenas/coffee-script#1009: classes with reserved words as determined names', ->
285 fn = ->
286 eq 'function', typeof (class @for)
287 ok not /\seval\s?\(/.test (class @eval).toString()
288 ok not /\sarguments\s\(/.test (class @arguments).toString()
289 fn.call {}
290
291 test 'jashkenas/coffee-script#1842: Regression with bound functions within bound class methods', ->
292
293 class Store
294 @bound = =>
295 do =>
296 eq this, Store
297
298 Store.bound()
299
300 # And a fancier case:
301
302 class Store
303
304 eq this, Store
305
306 @bound = =>
307 do =>
308 eq this, Store
309
310 @unbound = ->
311 eq this, Store
312
313 instance: =>
314 ok this instanceof Store
315
316 Store.bound()
317 Store.unbound()
318 (new Store).instance()
319
320 test 'jashkenas/coffee-script#1813: Passing class definitions as expressions', ->
321 ident = (x) -> x
322
323 result = ident class A then x = 1
324
325 eq result, A
326
327 result = ident class B extends A
328 x = 1
329
330 eq result, B
331
332 test 'jashkenas/coffee-script#1966: external constructors should produce their return value', ->
333 ctor = -> {}
334 class A then constructor: ctor
335 ok (new A) not instanceof A
336
337 #test 'jashkenas/coffee-script#1534: class then 'use strict'', ->
338 # # [14.1 Directive Prologues and the Use Strict Directive](http://es5.github.com/#x14.1)
339 # nonce = {}
340 # error = 'do -> ok this'
341 # strictTest = "do ->'use strict';#{error}"
342 # return unless (try CoffeeScript.run strictTest, bare: yes catch e then nonce) is nonce
343 #
344 # throws -> CoffeeScript.run "class then 'use strict';#{error}", bare: yes
345 # doesNotThrow -> CoffeeScript.run "class then #{error}", bare: yes
346 # doesNotThrow -> CoffeeScript.run "class then #{error};'use strict'", bare: yes
347 #
348 # # comments are ignored in the Directive Prologue
349 # comments = ["""
350 # class
351 # ### comment ###
352 # 'use strict'
353 # #{error}""",
354 # """
355 # class
356 # ### comment 1 ###
357 # ### comment 2 ###
358 # 'use strict'
359 # #{error}""",
360 # """
361 # class
362 # ### comment 1 ###
363 # ### comment 2 ###
364 # 'use strict'
365 # #{error}
366 # ### comment 3 ###"""
367 # ]
368 # throws (-> CoffeeScript.run comment, bare: yes) for comment in comments
369 #
370 # # [ES5 §14.1](http://es5.github.com/#x14.1) allows for other directives
371 # directives = ["""
372 # class
373 # 'directive 1'
374 # 'use strict'
375 # #{error}""",
376 # """
377 # class
378 # 'use strict'
379 # 'directive 2'
380 # #{error}""",
381 # """
382 # class
383 # ### comment 1 ###
384 # 'directive 1'
385 # 'use strict'
386 # #{error}""",
387 # """
388 # class
389 # ### comment 1 ###
390 # 'directive 1'
391 # ### comment 2 ###
392 # 'use strict'
393 # #{error}"""
394 # ]
395 # throws (-> CoffeeScript.run directive, bare: yes) for directive in directives
396
397 test 'jashkenas/coffee-script#2052: classes should work in strict mode', ->
398 do ->
399 'use strict'
400 class A
401
402
403 suite 'Class Instantiation', ->
404
405 test '"@" referring to the current instance, and not being coerced into a call', ->
406
407 class ClassName
408 amI: ->
409 @ instanceof ClassName
410
411 obj = new ClassName
412 ok obj.amI()
413
414 test 'a bound function in a bound function', ->
415
416 class Mini
417 num: 10
418 generate: =>
419 for i in [0, 0, 0]
420 => @num
421
422 m = new Mini
423 eq (func() for func in m.generate()).join(' '), '10 10 10'
424
425 #test 'contructor called with varargs', ->
426 #
427 # class Connection
428 # constructor: (one, two, three) ->
429 # [@one, @two, @three] = [one, two, three]
430 #
431 # out: ->
432 # "#{@one}-#{@two}-#{@three}"
433 #
434 # list = [3, 2, 1]
435 # conn = new Connection list...
436 # ok conn instanceof Connection
437 # ok conn.out() is '3-2-1'
438
439 test 'classes wrapped in decorators', ->
440
441 func = (klass) ->
442 klass::prop = 'value'
443 klass
444
445 func class Test
446 prop2: 'value2'
447
448 ok (new Test).prop is 'value'
449 ok (new Test).prop2 is 'value2'
450
451 #test 'ensure that constructors invoked with splats return a new object', ->
452 #
453 # args = [1, 2, 3]
454 # Type = (@args) ->
455 # type = new Type args
456 #
457 # ok type and type instanceof Type
458 # ok type.args and type.args instanceof Array
459 # ok v is args[i] for v, i in type.args
460 #
461 # Type1 = (@a, @b, @c) ->
462 # type1 = new Type1 args...
463 #
464 # ok type1 instanceof Type1
465 # eq type1.constructor, Type1
466 # ok type1.a is args[0] and type1.b is args[1] and type1.c is args[2]
467 #
468 # # Ensure that constructors invoked with splats cache the function.
469 # called = 0
470 # get = -> if called++ then false else class Type
471 # new get() args...
472
473 test '`new` shouldn\'t add extra parens', ->
474
475 ok new Date().constructor is Date
476
477 # TODO: this test belongs with the operator tests
478 #test '`new` works against bare function', ->
479 #
480 # eq Date, new ->
481 # eq this, new => this
482 # Date
483
484
485 suite 'Inheritance and Super', ->
486
487 #test 'classes with a four-level inheritance chain', ->
488 #
489 # class Base
490 # func: (string) ->
491 # "zero/#{string}"
492 #
493 # @static: (string) ->
494 # "static/#{string}"
495 #
496 # class FirstChild extends Base
497 # func: (string) ->
498 # super('one/') + string
499 #
500 # SecondChild = class extends FirstChild
501 # func: (string) ->
502 # super('two/') + string
503 #
504 # thirdCtor = ->
505 # @array = [1, 2, 3]
506 #
507 # class ThirdChild extends SecondChild
508 # constructor: -> thirdCtor.call this
509 #
510 # # Gratuitous comment for testing.
511 # func: (string) ->
512 # super('three/') + string
513 #
514 # result = (new ThirdChild).func 'four'
515 #
516 # ok result is 'zero/one/two/three/four'
517 # ok Base.static('word') is 'static/word'
518 #
519 # FirstChild::func = (string) ->
520 # super('one/').length + string
521 #
522 # result = (new ThirdChild).func 'four'
523 #
524 # ok result is '9two/three/four'
525 #
526 # ok (new ThirdChild).array.join(' ') is '1 2 3'
527
528 #test 'constructors with inheritance and super', ->
529 #
530 # identity = (f) -> f
531 #
532 # class TopClass
533 # constructor: (arg) ->
534 # @prop = 'top-' + arg
535 #
536 # class SuperClass extends TopClass
537 # constructor: (arg) ->
538 # identity super 'super-' + arg
539 #
540 # class SubClass extends SuperClass
541 # constructor: ->
542 # identity super 'sub'
543 #
544 # ok (new SubClass).prop is 'top-super-sub'
545
546 #test 'super with plain ol' functions as the original constructors', ->
547 #
548 # TopClass = (arg) ->
549 # @prop = 'top-' + arg
550 # this
551 #
552 # SuperClass = (arg) ->
553 # super 'super-' + arg
554 # this
555 #
556 # SubClass = ->
557 # super 'sub'
558 # this
559 #
560 # SuperClass extends TopClass
561 # SubClass extends SuperClass
562 #
563 # ok (new SubClass).prop is 'top-super-sub'
564
565 #test 'super() calls in constructors of classes that are defined as object properties', ->
566 #
567 # class Hive
568 # constructor: (name) -> @name = name
569 #
570 # class Hive.Bee extends Hive
571 # constructor: (name) -> super
572 #
573 # maya = new Hive.Bee 'Maya'
574 # ok maya.name is 'Maya'
575
576 #test 'calling super and passing along all arguments', ->
577 #
578 # class Parent
579 # method: (args...) -> @args = args
580 #
581 # class Child extends Parent
582 # method: -> super
583 #
584 # c = new Child
585 # c.method 1, 2, 3, 4
586 # ok c.args.join(' ') is '1 2 3 4'
587
588 #test '`class extends this`', ->
589 #
590 # class A
591 # func: -> 'A'
592 #
593 # B = null
594 # makeClass = ->
595 # B = class extends this
596 # func: -> super + ' B'
597 #
598 # makeClass.call A
599 #
600 # eq (new B()).func(), 'A B'
601
602 test 'jashkenas/coffee-script#1313: misplaced __extends', ->
603 nonce = {}
604 class A
605 class B extends A
606 prop: nonce
607 constructor: ->
608 eq nonce, B::prop
609
610 #test 'jashkenas/coffee-script#1380: `super` with reserved names', ->
611 # class C
612 # do: -> super
613 # ok C::do
614 #
615 # class B
616 # 0: -> super
617 # ok B::[0]
618
619 test 'jashkenas/coffee-script#1482: classes can extend expressions', ->
620 id = (x) -> x
621 nonce = {}
622 class A then nonce: nonce
623 class B extends id A
624 eq nonce, (new B).nonce
625
626 #test 'jashkenas/coffee-script#1598: super works for static methods too', ->
627 #
628 # class Parent
629 # method: ->
630 # 'NO'
631 # @method: ->
632 # 'yes'
633 #
634 # class Child extends Parent
635 # @method: ->
636 # 'pass? ' + super
637 #
638 # eq Child.method(), 'pass? yes'
639
640 test 'jashkenas/coffee-script#1876: Class @A extends A', ->
641 class A
642 class @A extends A
643
644 ok (new @A) instanceof A
645
646 test 'jashkenas/coffee-script#1980: regression with an inherited class with static function members', ->
647
648 class A
649
650 class B extends A
651 @static = => 'value'
652
653 eq B.static(), 'value'
654
655 test 'bound function literals as constructors should be treated as unbound', ->
656 nonce = {}
657 class A
658 constructor: =>
659 @nonce = nonce
660 a = new A
661 ok a instanceof A
662 eq nonce, a.nonce