/test/classes.coffee
http://github.com/jashkenas/coffee-script · CoffeeScript · 1948 lines · 1446 code · 407 blank · 95 comment · 19 complexity · 25665dc5f64f2605be840e485297332d MD5 · raw file
- # Classes
- # -------
- # * Class Definition
- # * Class Instantiation
- # * Inheritance and Super
- # * ES2015+ Class Interoperability
- test "classes with a four-level inheritance chain", ->
- class Base
- func: (string) ->
- "zero/#{string}"
- @static: (string) ->
- "static/#{string}"
- class FirstChild extends Base
- func: (string) ->
- super('one/') + string
- SecondChild = class extends FirstChild
- func: (string) ->
- super('two/') + string
- thirdCtor = ->
- @array = [1, 2, 3]
- class ThirdChild extends SecondChild
- constructor: ->
- super()
- thirdCtor.call this
- # Gratuitous comment for testing.
- func: (string) ->
- super('three/') + string
- result = (new ThirdChild).func 'four'
- ok result is 'zero/one/two/three/four'
- ok Base.static('word') is 'static/word'
- ok (new ThirdChild).array.join(' ') is '1 2 3'
- test "constructors with inheritance and super", ->
- identity = (f) -> f
- class TopClass
- constructor: (arg) ->
- @prop = 'top-' + arg
- class SuperClass extends TopClass
- constructor: (arg) ->
- identity super 'super-' + arg
- class SubClass extends SuperClass
- constructor: ->
- identity super 'sub'
- ok (new SubClass).prop is 'top-super-sub'
- test "'super' with accessors", ->
- class Base
- m: -> 4
- n: -> 5
- o: -> 6
- name = 'o'
- class A extends Base
- m: -> super()
- n: -> super.n()
- "#{name}": -> super()
- p: -> super[name]()
- a = new A
- eq 4, a.m()
- eq 5, a.n()
- eq 6, a.o()
- eq 6, a.p()
- test "soaked 'super' invocation", ->
- class Base
- method: -> 2
- class A extends Base
- method: -> super?()
- noMethod: -> super?()
- a = new A
- eq 2, a.method()
- eq undefined, a.noMethod()
- name = 'noMethod'
- class B extends Base
- "#{'method'}": -> super?()
- "#{'noMethod'}": -> super?() ? super['method']()
- b = new B
- eq 2, b.method()
- eq 2, b.noMethod()
- test "'@' referring to the current instance, and not being coerced into a call", ->
- class ClassName
- amI: ->
- @ instanceof ClassName
- obj = new ClassName
- ok obj.amI()
- test "super() calls in constructors of classes that are defined as object properties", ->
- class Hive
- constructor: (name) -> @name = name
- class Hive.Bee extends Hive
- constructor: (name) -> super name
- maya = new Hive.Bee 'Maya'
- ok maya.name is 'Maya'
- test "classes with JS-keyword properties", ->
- class Class
- class: 'class'
- name: -> @class
- instance = new Class
- ok instance.class is 'class'
- ok instance.name() is 'class'
- test "Classes with methods that are pre-bound to the instance, or statically, to the class", ->
- class Dog
- constructor: (name) ->
- @name = name
- bark: =>
- "#{@name} woofs!"
- @static = =>
- new this('Dog')
- spark = new Dog('Spark')
- fido = new Dog('Fido')
- fido.bark = spark.bark
- ok fido.bark() is 'Spark woofs!'
- obj = func: Dog.static
- ok obj.func().name is 'Dog'
- test "a bound function in a bound function", ->
- class Mini
- num: 10
- generate: =>
- for i in [1..3]
- =>
- @num
- m = new Mini
- eq (func() for func in m.generate()).join(' '), '10 10 10'
- test "contructor called with varargs", ->
- class Connection
- constructor: (one, two, three) ->
- [@one, @two, @three] = [one, two, three]
- out: ->
- "#{@one}-#{@two}-#{@three}"
- list = [3, 2, 1]
- conn = new Connection list...
- ok conn instanceof Connection
- ok conn.out() is '3-2-1'
- test "calling super and passing along all arguments", ->
- class Parent
- method: (args...) -> @args = args
- class Child extends Parent
- method: -> super arguments...
- c = new Child
- c.method 1, 2, 3, 4
- ok c.args.join(' ') is '1 2 3 4'
- test "classes wrapped in decorators", ->
- func = (klass) ->
- klass::prop = 'value'
- klass
- func class Test
- prop2: 'value2'
- ok (new Test).prop is 'value'
- ok (new Test).prop2 is 'value2'
- test "anonymous classes", ->
- obj =
- klass: class
- method: -> 'value'
- instance = new obj.klass
- ok instance.method() is 'value'
- test "Implicit objects as static properties", ->
- class Static
- @static =
- one: 1
- two: 2
- ok Static.static.one is 1
- ok Static.static.two is 2
- test "nothing classes", ->
- c = class
- ok c instanceof Function
- test "classes with static-level implicit objects", ->
- class A
- @static = one: 1
- two: 2
- class B
- @static = one: 1,
- two: 2
- eq A.static.one, 1
- eq A.static.two, undefined
- eq (new A).two, 2
- eq B.static.one, 1
- eq B.static.two, 2
- eq (new B).two, undefined
- test "classes with value'd constructors", ->
- counter = 0
- classMaker = ->
- inner = ++counter
- ->
- @value = inner
- @
- class One
- constructor: classMaker()
- class Two
- constructor: classMaker()
- eq (new One).value, 1
- eq (new Two).value, 2
- eq (new One).value, 1
- eq (new Two).value, 2
- test "executable class bodies", ->
- class A
- if true
- b: 'b'
- else
- c: 'c'
- a = new A
- eq a.b, 'b'
- eq a.c, undefined
- test "#2502: parenthesizing inner object values", ->
- class A
- category: (type: 'string')
- sections: (type: 'number', default: 0)
- eq (new A).category.type, 'string'
- eq (new A).sections.default, 0
- test "conditional prototype property assignment", ->
- debug = false
- class Person
- if debug
- age: -> 10
- else
- age: -> 20
- eq (new Person).age(), 20
- test "mild metaprogramming", ->
- class Base
- @attr: (name) ->
- @::[name] = (val) ->
- if arguments.length > 0
- @["_#{name}"] = val
- else
- @["_#{name}"]
- class Robot extends Base
- @attr 'power'
- @attr 'speed'
- robby = new Robot
- ok robby.power() is undefined
- robby.power 11
- robby.speed Infinity
- eq robby.power(), 11
- eq robby.speed(), Infinity
- test "namespaced classes do not reserve their function name in outside scope", ->
- one = {}
- two = {}
- class one.Klass
- @label = "one"
- class two.Klass
- @label = "two"
- eq typeof Klass, 'undefined'
- eq one.Klass.label, 'one'
- eq two.Klass.label, 'two'
- test "nested classes", ->
- class Outer
- constructor: ->
- @label = 'outer'
- class @Inner
- constructor: ->
- @label = 'inner'
- eq (new Outer).label, 'outer'
- eq (new Outer.Inner).label, 'inner'
- test "variables in constructor bodies are correctly scoped", ->
- class A
- x = 1
- constructor: ->
- x = 10
- y = 20
- y = 2
- captured: ->
- {x, y}
- a = new A
- eq a.captured().x, 10
- eq a.captured().y, 2
- test "Issue #924: Static methods in nested classes", ->
- class A
- @B: class
- @c = -> 5
- eq A.B.c(), 5
- test "`class extends this`", ->
- class A
- func: -> 'A'
- B = null
- makeClass = ->
- B = class extends this
- func: -> super() + ' B'
- makeClass.call A
- eq (new B()).func(), 'A B'
- test "ensure that constructors invoked with splats return a new object", ->
- args = [1, 2, 3]
- Type = (@args) ->
- type = new Type args
- ok type and type instanceof Type
- ok type.args and type.args instanceof Array
- ok v is args[i] for v, i in type.args
- Type1 = (@a, @b, @c) ->
- type1 = new Type1 args...
- ok type1 instanceof Type1
- eq type1.constructor, Type1
- ok type1.a is args[0] and type1.b is args[1] and type1.c is args[2]
- # Ensure that constructors invoked with splats cache the function.
- called = 0
- get = -> if called++ then false else class Type
- new (get()) args...
- test "`new` shouldn't add extra parens", ->
- ok new Date().constructor is Date
- test "`new` works against bare function", ->
- eq Date, new ->
- Date
- test "`new` works against statement", ->
- class A
- (new try A) instanceof A
- test "#1182: a subclass should be able to set its constructor to an external function", ->
- ctor = ->
- @val = 1
- return
- class A
- class B extends A
- constructor: ctor
- eq (new B).val, 1
- test "#1182: external constructors continued", ->
- ctor = ->
- class A
- class B extends A
- method: ->
- constructor: ctor
- ok B::method
- test "#1313: misplaced __extends", ->
- nonce = {}
- class A
- class B extends A
- prop: nonce
- constructor: -> super()
- eq nonce, B::prop
- test "#1182: execution order needs to be considered as well", ->
- counter = 0
- makeFn = (n) -> eq n, ++counter; ->
- class B extends (makeFn 1)
- @B: makeFn 2
- constructor: makeFn 3
- test "#1182: external constructors with bound functions", ->
- fn = ->
- {one: 1}
- this
- class B
- class A
- constructor: fn
- method: => this instanceof A
- ok (new A).method.call(new B)
- test "#1372: bound class methods with reserved names", ->
- class C
- delete: =>
- ok C::delete
- test "#1380: `super` with reserved names", ->
- class C
- do: -> super()
- ok C::do
- class B
- 0: -> super()
- ok B::[0]
- test "#1464: bound class methods should keep context", ->
- nonce = {}
- nonce2 = {}
- class C
- constructor: (@id) ->
- @boundStaticColon: => new this(nonce)
- @boundStaticEqual= => new this(nonce2)
- eq nonce, C.boundStaticColon().id
- eq nonce2, C.boundStaticEqual().id
- test "#1009: classes with reserved words as determined names", -> (->
- eq 'function', typeof (class @for)
- ok not /\beval\b/.test (class @eval).toString()
- ok not /\barguments\b/.test (class @arguments).toString()
- ).call {}
- test "#1482: classes can extend expressions", ->
- id = (x) -> x
- nonce = {}
- class A then nonce: nonce
- class B extends id A
- eq nonce, (new B).nonce
- test "#1598: super works for static methods too", ->
- class Parent
- method: ->
- 'NO'
- @method: ->
- 'yes'
- class Child extends Parent
- @method: ->
- 'pass? ' + super()
- eq Child.method(), 'pass? yes'
- test "#1842: Regression with bound functions within bound class methods", ->
- class Store
- @bound: =>
- do =>
- eq this, Store
- Store.bound()
- # And a fancier case:
- class Store
- eq this, Store
- @bound: =>
- do =>
- eq this, Store
- @unbound: ->
- eq this, Store
- instance: =>
- ok this instanceof Store
- Store.bound()
- Store.unbound()
- (new Store).instance()
- test "#1876: Class @A extends A", ->
- class A
- class @A extends A
- ok (new @A) instanceof A
- test "#1813: Passing class definitions as expressions", ->
- ident = (x) -> x
- result = ident class A then x = 1
- eq result, A
- result = ident class B extends A
- x = 1
- eq result, B
- test "#1966: external constructors should produce their return value", ->
- ctor = -> {}
- class A then constructor: ctor
- ok (new A) not instanceof A
- test "#1980: regression with an inherited class with static function members", ->
- class A
- class B extends A
- @static: => 'value'
- eq B.static(), 'value'
- test "#1534: class then 'use strict'", ->
- # [14.1 Directive Prologues and the Use Strict Directive](http://es5.github.com/#x14.1)
- nonce = {}
- error = 'do -> ok this'
- strictTest = "do ->'use strict';#{error}"
- return unless (try CoffeeScript.run strictTest, bare: yes catch e then nonce) is nonce
- throws -> CoffeeScript.run "class then 'use strict';#{error}", bare: yes
- doesNotThrow -> CoffeeScript.run "class then #{error}", bare: yes
- doesNotThrow -> CoffeeScript.run "class then #{error};'use strict'", bare: yes
- # comments are ignored in the Directive Prologue
- comments = ["""
- class
- ### comment ###
- 'use strict'
- #{error}""",
- """
- class
- ### comment 1 ###
- ### comment 2 ###
- 'use strict'
- #{error}""",
- """
- class
- ### comment 1 ###
- ### comment 2 ###
- 'use strict'
- #{error}
- ### comment 3 ###"""
- ]
- throws (-> CoffeeScript.run comment, bare: yes) for comment in comments
- # [ES5 §14.1](http://es5.github.com/#x14.1) allows for other directives
- directives = ["""
- class
- 'directive 1'
- 'use strict'
- #{error}""",
- """
- class
- 'use strict'
- 'directive 2'
- #{error}""",
- """
- class
- ### comment 1 ###
- 'directive 1'
- 'use strict'
- #{error}""",
- """
- class
- ### comment 1 ###
- 'directive 1'
- ### comment 2 ###
- 'use strict'
- #{error}"""
- ]
- throws (-> CoffeeScript.run directive, bare: yes) for directive in directives
- test "#2052: classes should work in strict mode", ->
- try
- do ->
- 'use strict'
- class A
- catch e
- ok no
- test "directives in class with extends ", ->
- strictTest = """
- class extends Object
- ### comment ###
- 'use strict'
- do -> eq this, undefined
- """
- CoffeeScript.run strictTest, bare: yes
- test "#2630: class bodies can't reference arguments", ->
- throwsCompileError 'class Test then arguments'
- # #4320: Don't be too eager when checking, though.
- class Test
- arguments: 5
- eq 5, Test::arguments
- test "#2319: fn class n extends o.p [INDENT] x = 123", ->
- first = ->
- base = onebase: ->
- first class OneKeeper extends base.onebase
- one = 1
- one: -> one
- eq new OneKeeper().one(), 1
- test "#2599: other typed constructors should be inherited", ->
- class Base
- constructor: -> return {}
- class Derived extends Base
- ok (new Derived) not instanceof Derived
- ok (new Derived) not instanceof Base
- ok (new Base) not instanceof Base
- test "extending native objects works with and without defining a constructor", ->
- class MyArray extends Array
- method: -> 'yes!'
- myArray = new MyArray
- ok myArray instanceof MyArray
- ok 'yes!', myArray.method()
- class OverrideArray extends Array
- constructor: -> super()
- method: -> 'yes!'
- overrideArray = new OverrideArray
- ok overrideArray instanceof OverrideArray
- eq 'yes!', overrideArray.method()
- test "#2782: non-alphanumeric-named bound functions", ->
- class A
- 'b:c': =>
- 'd'
- eq (new A)['b:c'](), 'd'
- test "#2781: overriding bound functions", ->
- class A
- a: ->
- @b()
- b: =>
- 1
- class B extends A
- b: =>
- 2
- b = (new A).b
- eq b(), 1
- b = (new B).b
- eq b(), 2
- test "#2791: bound function with destructured argument", ->
- class Foo
- method: ({a}) => 'Bar'
- eq (new Foo).method({a: 'Bar'}), 'Bar'
- test "#2796: ditto, ditto, ditto", ->
- answer = null
- outsideMethod = (func) ->
- func.call message: 'wrong!'
- class Base
- constructor: ->
- @message = 'right!'
- outsideMethod @echo
- echo: =>
- answer = @message
- new Base
- eq answer, 'right!'
- test "#3063: Class bodies cannot contain pure statements", ->
- throwsCompileError """
- class extends S
- return if S.f
- @f: => this
- """
- test "#2949: super in static method with reserved name", ->
- class Foo
- @static: -> 'baz'
- class Bar extends Foo
- @static: -> super()
- eq Bar.static(), 'baz'
- test "#3232: super in static methods (not object-assigned)", ->
- class Foo
- @baz = -> true
- @qux = -> true
- class Bar extends Foo
- @baz = -> super()
- Bar.qux = -> super()
- ok Bar.baz()
- ok Bar.qux()
- test "#1392 calling `super` in methods defined on namespaced classes", ->
- class Base
- m: -> 5
- n: -> 4
- namespace =
- A: ->
- B: ->
- class namespace.A extends Base
- m: -> super()
- eq 5, (new namespace.A).m()
- namespace.B::m = namespace.A::m
- namespace.A::m = null
- eq 5, (new namespace.B).m()
- class C
- @a: class extends Base
- m: -> super()
- eq 5, (new C.a).m()
- test "#4436 immediately instantiated named class", ->
- ok new class Foo
- test "dynamic method names", ->
- class A
- "#{name = 'm'}": -> 1
- eq 1, new A().m()
- class B extends A
- "#{name = 'm'}": -> super()
- eq 1, new B().m()
- getName = -> 'm'
- class C
- "#{name = getName()}": -> 1
- eq 1, new C().m()
- test "dynamic method names and super", ->
- class Base
- @m: -> 6
- m: -> 5
- m2: -> 4.5
- n: -> 4
- name = -> count++; 'n'
- count = 0
- m = 'm'
- class A extends Base
- "#{m}": -> super()
- "#{name()}": -> super()
- m = 'n'
- eq 5, (new A).m()
- eq 4, (new A).n()
- eq 1, count
- m = 'm'
- m2 = 'm2'
- count = 0
- class B extends Base
- @[name()] = -> super()
- "#{m}": -> super()
- "#{m2}": -> super()
- b = new B
- m = m2 = 'n'
- eq 6, B.m()
- eq 5, b.m()
- eq 4.5, b.m2()
- eq 1, count
- class C extends B
- m: -> super()
- eq 5, (new C).m()
- # ES2015+ class interoperability
- # Based on https://github.com/balupton/es6-javascript-class-interop
- # Helper functions to generate true ES classes to extend:
- getBasicClass = ->
- ```
- class BasicClass {
- constructor (greeting) {
- this.greeting = greeting || 'hi'
- }
- }
- ```
- BasicClass
- getExtendedClass = (BaseClass) ->
- ```
- class ExtendedClass extends BaseClass {
- constructor (greeting, name) {
- super(greeting || 'hello')
- this.name = name
- }
- }
- ```
- ExtendedClass
- test "can instantiate a basic ES class", ->
- BasicClass = getBasicClass()
- i = new BasicClass 'howdy!'
- eq i.greeting, 'howdy!'
- test "can instantiate an extended ES class", ->
- BasicClass = getBasicClass()
- ExtendedClass = getExtendedClass BasicClass
- i = new ExtendedClass 'yo', 'buddy'
- eq i.greeting, 'yo'
- eq i.name, 'buddy'
- test "can extend a basic ES class", ->
- BasicClass = getBasicClass()
- class ExtendedClass extends BasicClass
- constructor: (@name) ->
- super()
- i = new ExtendedClass 'dude'
- eq i.name, 'dude'
- test "can extend an extended ES class", ->
- BasicClass = getBasicClass()
- ExtendedClass = getExtendedClass BasicClass
- class ExtendedExtendedClass extends ExtendedClass
- constructor: (@value) ->
- super()
- getDoubledValue: ->
- @value * 2
- i = new ExtendedExtendedClass 7
- eq i.getDoubledValue(), 14
- test "CoffeeScript class can be extended in ES", ->
- class CoffeeClass
- constructor: (@favoriteDrink = 'latte', @size = 'grande') ->
- getDrinkOrder: ->
- "#{@size} #{@favoriteDrink}"
- ```
- class ECMAScriptClass extends CoffeeClass {
- constructor (favoriteDrink) {
- super(favoriteDrink);
- this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons';
- }
- }
- ```
- e = new ECMAScriptClass 'coffee'
- eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons'
- test "extended CoffeeScript class can be extended in ES", ->
- class CoffeeClass
- constructor: (@favoriteDrink = 'latte') ->
- class CoffeeClassWithDrinkOrder extends CoffeeClass
- constructor: (@favoriteDrink, @size = 'grande') ->
- super()
- getDrinkOrder: ->
- "#{@size} #{@favoriteDrink}"
- ```
- class ECMAScriptClass extends CoffeeClassWithDrinkOrder {
- constructor (favoriteDrink) {
- super(favoriteDrink);
- this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons';
- }
- }
- ```
- e = new ECMAScriptClass 'coffee'
- eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons'
- test "`this` access after `super` in extended classes", ->
- class Base
- class Test extends Base
- constructor: (param, @param) ->
- eq param, nonce
- result = { super: super(), @param, @method }
- eq result.super, this
- eq result.param, @param
- eq result.method, @method
- ok result.method isnt Test::method
- method: =>
- nonce = {}
- new Test nonce, {}
- test "`@`-params and bound methods with multiple `super` paths (blocks)", ->
- nonce = {}
- class Base
- constructor: (@name) ->
- class Test extends Base
- constructor: (param, @param) ->
- if param
- super 'param'
- eq @name, 'param'
- else
- super 'not param'
- eq @name, 'not param'
- eq @param, nonce
- ok @method isnt Test::method
- method: =>
- new Test true, nonce
- new Test false, nonce
- test "`@`-params and bound methods with multiple `super` paths (expressions)", ->
- nonce = {}
- class Base
- constructor: (@name) ->
- class Test extends Base
- constructor: (param, @param) ->
- # Contrived example: force each path into an expression with inline assertions
- if param
- result = (
- eq (super 'param'), @;
- eq @name, 'param';
- eq @param, nonce;
- ok @method isnt Test::method
- )
- else
- result = (
- eq (super 'not param'), @;
- eq @name, 'not param';
- eq @param, nonce;
- ok @method isnt Test::method
- )
- method: =>
- new Test true, nonce
- new Test false, nonce
- test "constructor super in arrow functions", ->
- class Test extends (class)
- constructor: (@param) ->
- do => super()
- eq @param, nonce
- new Test nonce = {}
- # TODO Some of these tests use CoffeeScript.compile and CoffeeScript.run when they could use
- # regular test mechanics.
- # TODO Some of these tests might be better placed in `test/error_messages.coffee`.
- # TODO Some of these tests are duplicates.
- # Ensure that we always throw if we experience more than one super()
- # call in a constructor. This ends up being a runtime error.
- # Should be caught at compile time.
- test "multiple super calls", ->
- throwsA = """
- class A
- constructor: (@drink) ->
- make: -> "Making a #{@drink}"
- class MultiSuper extends A
- constructor: (drink) ->
- super(drink)
- super(drink)
- @newDrink = drink
- new MultiSuper('Late').make()
- """
- throws -> CoffeeScript.run throwsA, bare: yes
- # Basic test to ensure we can pass @params in a constuctor and
- # inheritance works correctly
- test "@ params", ->
- class A
- constructor: (@drink, @shots, @flavor) ->
- make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)"
- a = new A('Machiato', 2, 'chocolate')
- eq a.make(), "Making a chocolate Machiato with 2 shot(s)"
- class B extends A
- b = new B('Machiato', 2, 'chocolate')
- eq b.make(), "Making a chocolate Machiato with 2 shot(s)"
- # Ensure we can accept @params with default parameters in a constructor
- test "@ params with defaults in a constructor", ->
- class A
- # Multiple @ params with defaults
- constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') ->
- make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)"
- a = new A()
- eq a.make(), "Making a caramel Americano with 1 shot(s)"
- # Ensure we can handle default constructors with class params
- test "@ params with class params", ->
- class Beverage
- drink: 'Americano'
- shots: '1'
- flavor: 'caramel'
- class A
- # Class creation as a default param with `this`
- constructor: (@drink = new Beverage()) ->
- a = new A()
- eq a.drink.drink, 'Americano'
- beverage = new Beverage
- class B
- # class costruction with a default external param
- constructor: (@drink = beverage) ->
- b = new B()
- eq b.drink.drink, 'Americano'
- class C
- # Default constructor with anonymous empty class
- constructor: (@meta = class) ->
- c = new C()
- ok c.meta instanceof Function
- test "@ params without super, including errors", ->
- classA = """
- class A
- constructor: (@drink) ->
- make: -> "Making a #{@drink}"
- a = new A('Machiato')
- """
- throwsB = """
- class B extends A
- #implied super
- constructor: (@drink) ->
- b = new B('Machiato')
- """
- throwsCompileError classA + throwsB, bare: yes
- test "@ params super race condition", ->
- classA = """
- class A
- constructor: (@drink) ->
- make: -> "Making a #{@drink}"
- """
- throwsB = """
- class B extends A
- constructor: (@params) ->
- b = new B('Machiato')
- """
- throwsCompileError classA + throwsB, bare: yes
- # Race condition with @ and super
- throwsC = """
- class C extends A
- constructor: (@params) ->
- super(@params)
- c = new C('Machiato')
- """
- throwsCompileError classA + throwsC, bare: yes
- test "@ with super call", ->
- class D
- make: -> "Making a #{@drink}"
- class E extends D
- constructor: (@drink) ->
- super()
- e = new E('Machiato')
- eq e.make(), "Making a Machiato"
- test "@ with splats and super call", ->
- class A
- make: -> "Making a #{@drink}"
- class B extends A
- constructor: (@drink...) ->
- super()
- B = new B('Machiato')
- eq B.make(), "Making a Machiato"
- test "super and external constructors", ->
- # external constructor with @ param is allowed
- ctorA = (@drink) ->
- class A
- constructor: ctorA
- make: -> "Making a #{@drink}"
- a = new A('Machiato')
- eq a.make(), "Making a Machiato"
- # External constructor with super
- throwsC = """
- class B
- constructor: (@drink) ->
- make: -> "Making a #{@drink}"
- ctorC = (drink) ->
- super(drink)
- class C extends B
- constructor: ctorC
- c = new C('Machiato')
- """
- throwsCompileError throwsC, bare: yes
- test "bound functions without super", ->
- # Bound function with @
- # Throw on compile, since bound
- # constructors are illegal
- throwsA = """
- class A
- constructor: (drink) =>
- @drink = drink
- """
- throwsCompileError throwsA, bare: yes
- test "super in a bound function in a constructor", ->
- throwsB = """
- class A
- class B extends A
- constructor: do => super
- """
- throwsCompileError throwsB, bare: yes
- test "super in a bound function", ->
- class A
- constructor: (@drink) ->
- make: -> "Making a #{@drink}"
- class B extends A
- make: (@flavor) =>
- super() + " with #{@flavor}"
- b = new B('Machiato')
- eq b.make('vanilla'), "Making a Machiato with vanilla"
- # super in a bound function in a bound function
- class C extends A
- make: (@flavor) =>
- func = () =>
- super() + " with #{@flavor}"
- func()
- c = new C('Machiato')
- eq c.make('vanilla'), "Making a Machiato with vanilla"
- # bound function in a constructor
- class D extends A
- constructor: (drink) ->
- super(drink)
- x = =>
- eq @drink, "Machiato"
- x()
- d = new D('Machiato')
- eq d.make(), "Making a Machiato"
- # duplicate
- test "super in a try/catch", ->
- classA = """
- class A
- constructor: (param) ->
- throw "" unless param
- """
- throwsB = """
- class B extends A
- constructor: ->
- try
- super()
- """
- throwsC = """
- ctor = ->
- try
- super()
- class C extends A
- constructor: ctor
- """
- throws -> CoffeeScript.run classA + throwsB, bare: yes
- throws -> CoffeeScript.run classA + throwsC, bare: yes
- test "mixed ES6 and CS6 classes with a four-level inheritance chain", ->
- # Extended test
- # ES2015+ class interoperability
- ```
- class Base {
- constructor (greeting) {
- this.greeting = greeting || 'hi';
- }
- func (string) {
- return 'zero/' + string;
- }
- static staticFunc (string) {
- return 'static/' + string;
- }
- }
- ```
- class FirstChild extends Base
- func: (string) ->
- super('one/') + string
- ```
- class SecondChild extends FirstChild {
- func (string) {
- return super.func('two/' + string);
- }
- }
- ```
- thirdCtor = ->
- @array = [1, 2, 3]
- class ThirdChild extends SecondChild
- constructor: ->
- super()
- thirdCtor.call this
- func: (string) ->
- super('three/') + string
- result = (new ThirdChild).func 'four'
- ok result is 'zero/one/two/three/four'
- ok Base.staticFunc('word') is 'static/word'
- # exercise extends in a nested class
- test "nested classes with super", ->
- class Outer
- constructor: ->
- @label = 'outer'
- class @Inner
- constructor: ->
- @label = 'inner'
- class @ExtendedInner extends @Inner
- constructor: ->
- tmp = super()
- @label = tmp.label + ' extended'
- @extender: () =>
- class ExtendedSelf extends @
- constructor: ->
- tmp = super()
- @label = tmp.label + ' from this'
- new ExtendedSelf
- eq (new Outer).label, 'outer'
- eq (new Outer.Inner).label, 'inner'
- eq (new Outer.ExtendedInner).label, 'inner extended'
- eq (Outer.extender()).label, 'outer from this'
- test "Static methods generate 'static' keywords", ->
- compile = """
- class CheckStatic
- constructor: (@drink) ->
- @className: -> 'CheckStatic'
- c = new CheckStatic('Machiato')
- """
- result = CoffeeScript.compile compile, bare: yes
- ok result.match(' static ')
- test "Static methods in nested classes", ->
- class Outer
- @name: -> 'Outer'
- class @Inner
- @name: -> 'Inner'
- eq Outer.name(), 'Outer'
- eq Outer.Inner.name(), 'Inner'
- test "mixed constructors with inheritance and ES6 super", ->
- identity = (f) -> f
- class TopClass
- constructor: (arg) ->
- @prop = 'top-' + arg
- ```
- class SuperClass extends TopClass {
- constructor (arg) {
- identity(super('super-' + arg));
- }
- }
- ```
- class SubClass extends SuperClass
- constructor: ->
- identity super 'sub'
- ok (new SubClass).prop is 'top-super-sub'
- test "ES6 static class methods can be overriden", ->
- class A
- @name: -> 'A'
- class B extends A
- @name: -> 'B'
- eq A.name(), 'A'
- eq B.name(), 'B'
- # If creating static by direct assignment rather than ES6 static keyword
- test "ES6 Static methods should set `this` to undefined // ES6 ", ->
- class A
- @test: ->
- eq this, undefined
- # Ensure that our object prototypes work with ES6
- test "ES6 prototypes can be overriden", ->
- class A
- className: 'classA'
- ```
- class B {
- test () {return "B";};
- }
- ```
- b = new B
- a = new A
- eq a.className, 'classA'
- eq b.test(), 'B'
- Object.setPrototypeOf(b, a)
- eq b.className, 'classA'
- # This shouldn't throw,
- # as we only change inheritance not object construction
- # This may be an issue with ES, rather than CS construction?
- #eq b.test(), 'B'
- class D extends B
- B::test = () -> 'D'
- eq (new D).test(), 'D'
- # TODO: implement this error check
- # test "ES6 conformance to extending non-classes", ->
- # A = (@title) ->
- # 'Title: ' + @
- # class B extends A
- # b = new B('caffeinated')
- # eq b.title, 'caffeinated'
- # # Check inheritance chain
- # A::getTitle = () -> @title
- # eq b.getTitle(), 'caffeinated'
- # throwsC = """
- # C = {title: 'invalid'}
- # class D extends {}
- # """
- # # This should catch on compile and message should be "class can only extend classes and functions."
- # throws -> CoffeeScript.run throwsC, bare: yes
- # TODO: Evaluate future compliance with "strict mode";
- # test "Class function environment should be in `strict mode`, ie as if 'use strict' was in use", ->
- # class A
- # # this might be a meaningless test, since these are likely to be runtime errors and different
- # # for every browser. Thoughts?
- # constructor: () ->
- # # Ivalid: prop reassignment
- # @state = {prop: [1], prop: {a: 'a'}}
- # # eval reassignment
- # @badEval = eval;
- # # Should throw, but doesn't
- # a = new A
- test "only one method named constructor allowed", ->
- throwsA = """
- class A
- constructor: (@first) ->
- constructor: (@last) ->
- """
- throwsCompileError throwsA, bare: yes
- test "If the constructor of a child class does not call super,it should return an object.", ->
- nonce = {}
- class A
- class B extends A
- constructor: ->
- return nonce
- eq nonce, new B
- test "super can only exist in extended classes", ->
- throwsA = """
- class A
- constructor: (@name) ->
- super()
- """
- throwsCompileError throwsA, bare: yes
- # --- CS1 classes compatability breaks ---
- test "CS6 Class extends a CS1 compiled class", ->
- ```
- // Generated by CoffeeScript 1.11.1
- var BaseCS1, ExtendedCS1,
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
- BaseCS1 = (function() {
- function BaseCS1(drink) {
- this.drink = drink;
- }
- BaseCS1.prototype.make = function() {
- return "making a " + this.drink;
- };
- BaseCS1.className = function() {
- return 'BaseCS1';
- };
- return BaseCS1;
- })();
- ExtendedCS1 = (function(superClass) {
- extend(ExtendedCS1, superClass);
- function ExtendedCS1(flavor) {
- this.flavor = flavor;
- ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
- }
- ExtendedCS1.prototype.make = function() {
- return "making a " + this.drink + " with " + this.flavor;
- };
- ExtendedCS1.className = function() {
- return 'ExtendedCS1';
- };
- return ExtendedCS1;
- })(BaseCS1);
- ```
- class B extends BaseCS1
- eq B.className(), 'BaseCS1'
- b = new B('machiato')
- eq b.make(), "making a machiato"
- test "CS6 Class extends an extended CS1 compiled class", ->
- ```
- // Generated by CoffeeScript 1.11.1
- var BaseCS1, ExtendedCS1,
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
- BaseCS1 = (function() {
- function BaseCS1(drink) {
- this.drink = drink;
- }
- BaseCS1.prototype.make = function() {
- return "making a " + this.drink;
- };
- BaseCS1.className = function() {
- return 'BaseCS1';
- };
- return BaseCS1;
- })();
- ExtendedCS1 = (function(superClass) {
- extend(ExtendedCS1, superClass);
- function ExtendedCS1(flavor) {
- this.flavor = flavor;
- ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
- }
- ExtendedCS1.prototype.make = function() {
- return "making a " + this.drink + " with " + this.flavor;
- };
- ExtendedCS1.className = function() {
- return 'ExtendedCS1';
- };
- return ExtendedCS1;
- })(BaseCS1);
- ```
- class B extends ExtendedCS1
- eq B.className(), 'ExtendedCS1'
- b = new B('vanilla')
- eq b.make(), "making a cafe ole with vanilla"
- test "CS6 Class extends a CS1 compiled class with super()", ->
- ```
- // Generated by CoffeeScript 1.11.1
- var BaseCS1, ExtendedCS1,
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
- BaseCS1 = (function() {
- function BaseCS1(drink) {
- this.drink = drink;
- }
- BaseCS1.prototype.make = function() {
- return "making a " + this.drink;
- };
- BaseCS1.className = function() {
- return 'BaseCS1';
- };
- return BaseCS1;
- })();
- ExtendedCS1 = (function(superClass) {
- extend(ExtendedCS1, superClass);
- function ExtendedCS1(flavor) {
- this.flavor = flavor;
- ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
- }
- ExtendedCS1.prototype.make = function() {
- return "making a " + this.drink + " with " + this.flavor;
- };
- ExtendedCS1.className = function() {
- return 'ExtendedCS1';
- };
- return ExtendedCS1;
- })(BaseCS1);
- ```
- class B extends ExtendedCS1
- constructor: (@shots) ->
- super('caramel')
- make: () ->
- super() + " and #{@shots} shots of espresso"
- eq B.className(), 'ExtendedCS1'
- b = new B('three')
- eq b.make(), "making a cafe ole with caramel and three shots of espresso"
- test 'Bound method called normally before binding is ok', ->
- class Base
- constructor: ->
- @setProp()
- eq @derivedBound(), 3
- class Derived extends Base
- setProp: ->
- @prop = 3
- derivedBound: =>
- @prop
- d = new Derived
- test 'Bound method called as callback after super() is ok', ->
- class Base
- class Derived extends Base
- constructor: (@prop = 3) ->
- super()
- f = @derivedBound
- eq f(), 3
- derivedBound: =>
- @prop
- d = new Derived
- {derivedBound} = d
- eq derivedBound(), 3
- test 'Bound method of base class called as callback is ok', ->
- class Base
- constructor: (@prop = 3) ->
- f = @baseBound
- eq f(), 3
- baseBound: =>
- @prop
- b = new Base
- {baseBound} = b
- eq baseBound(), 3
- test 'Bound method of prop-named class called as callback is ok', ->
- Hive = {}
- class Hive.Bee
- constructor: (@prop = 3) ->
- f = @baseBound
- eq f(), 3
- baseBound: =>
- @prop
- b = new Hive.Bee
- {baseBound} = b
- eq baseBound(), 3
- test 'Bound method of class with expression base class called as callback is ok', ->
- calledB = no
- B = ->
- throw new Error if calledB
- calledB = yes
- class
- class A extends B()
- constructor: (@prop = 3) ->
- super()
- f = @derivedBound
- eq f(), 3
- derivedBound: =>
- @prop
- b = new A
- {derivedBound} = b
- eq derivedBound(), 3
- test 'Bound method of class with expression class name called as callback is ok', ->
- calledF = no
- obj = {}
- B = class
- f = ->
- throw new Error if calledF
- calledF = yes
- obj
- class f().A extends B
- constructor: (@prop = 3) ->
- super()
- g = @derivedBound
- eq g(), 3
- derivedBound: =>
- @prop
- a = new obj.A
- {derivedBound} = a
- eq derivedBound(), 3
- test 'Bound method of anonymous child class called as callback is ok', ->
- f = ->
- B = class
- class extends B
- constructor: (@prop = 3) ->
- super()
- g = @derivedBound
- eq g(), 3
- derivedBound: =>
- @prop
- a = new (f())
- {derivedBound} = a
- eq derivedBound(), 3
- test 'Bound method of immediately instantiated class with expression base class called as callback is ok', ->
- calledF = no
- obj = {}
- B = class
- f = ->
- throw new Error if calledF
- calledF = yes
- obj
- a = new class f().A extends B
- constructor: (@prop = 3) ->
- super()
- g = @derivedBound
- eq g(), 3
- derivedBound: =>
- @prop
- {derivedBound} = a
- eq derivedBound(), 3
- test "#4591: super.x.y, super['x'].y", ->
- class A
- x:
- y: 1
- z: -> 2
- class B extends A
- constructor: ->
- super()
- @w = super.x.y
- @v = super['x'].y
- @u = super.x['y']
- @t = super.x.z()
- @s = super['x'].z()
- @r = super.x['z']()
- b = new B
- eq 1, b.w
- eq 1, b.v
- eq 1, b.u
- eq 2, b.t
- eq 2, b.s
- eq 2, b.r
- test "#4464: backticked expressions in class body", ->
- class A
- `get x() { return 42; }`
- class B
- `get x() { return 42; }`
- constructor: ->
- @y = 84
- a = new A
- eq 42, a.x
- b = new B
- eq 42, b.x
- eq 84, b.y
- test "#4724: backticked expression in a class body with hoisted member", ->
- class A
- `get x() { return 42; }`
- hoisted: 84
- a = new A
- eq 42, a.x
- eq 84, a.hoisted
- test "#4822: nested anonymous classes use non-conflicting variable names", ->
- Class = class
- @a: class
- @b: 1
- eq Class.a.b, 1
- test "#4827: executable class body wrappers have correct context", ->
- test = ->
- class @A
- class @B extends @A
- @property = 1
- o = {}
- test.call o
- ok typeof o.A is typeof o.B is 'function'
- test "#4868: Incorrect ‘Can’t call super with @params’ error", ->
- class A
- constructor: (@func = ->) ->
- @x = 1
- @func()
- class B extends A
- constructor: ->
- super -> @x = 2
- a = new A
- b = new B
- eq 1, a.x
- eq 2, b.x
- class C
- constructor: (@c = class) -> @c
- class D extends C
- constructor: ->
- super class then constructor: (@a) -> @a = 3
- d = new (new D).c
- eq 3, d.a
- test "#4609: Support new.target", ->
- class A
- constructor: ->
- @calledAs = new.target.name
- class B extends A
- b = new B
- eq b.calledAs, 'B'
- newTarget = null
- Foo = ->
- newTarget = !!new.target
- Foo()
- eq newTarget, no
- newTarget = null
- new Foo()
- eq newTarget, yes
- test "#5085: Bug: @ reference to class not maintained in do block", ->
- thisFoo = 'initial foo'
- thisBar = 'initial bar'
- fn = (o) -> o.bar()
- class A
- @foo = 'foo assigned in class'
- do => thisFoo = @foo
- fn bar: => thisBar = @foo
- eq thisFoo, 'foo assigned in class'
- eq thisBar, 'foo assigned in class'
- test "#5204: Computed class property", ->
- foo = 'bar'
- class A
- [foo]: 'baz'
- a = new A()
- eq a.bar, 'baz'
- eq A::bar, 'baz'
- test "#5204: Static computed class property", ->
- foo = 'bar'
- qux = 'quux'
- class A
- @[foo]: 'baz'
- @[qux]: -> 3
- eq A.bar, 'baz'
- eq A.quux(), 3