1 | suite '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
|