1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00
jashkenas--coffeescript/test/classes.coffee
Chris Connelly 27eff5ca77 Fix #4464: backticked expressions in class body (#4712)
* Fix #4464: backticked expressions in class body should be left in the body, not hoisted

* Fix #4464: backticked expressions in class body should be left in the body, not hoisted

* Simplify fix for #4464

This uses more of the existing machinery for moving class body
expressions into the initializer.

* Clarify the purpose of Class::addInitializerExpression

* Further clarify the purpose of Class::addInitializerExpression

* Add reference to class fields; format
 comment wrapping

* Reapply 1d3af8c432, that got lost because of rebase/force-push shenanigans

* Updated output
2017-09-20 19:11:05 -07:00

1850 lines
38 KiB
CoffeeScript

# 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 "#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", ->
throws ->
CoffeeScript.compile('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", ->
throws -> CoffeeScript.compile """
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')
"""
throws -> CoffeeScript.compile 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')
"""
throws -> CoffeeScript.compile classA + throwsB, bare: yes
# Race condition with @ and super
throwsC = """
class C extends A
constructor: (@params) ->
super(@params)
c = new C('Machiato')
"""
throws -> CoffeeScript.compile 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')
"""
throws -> CoffeeScript.compile 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
"""
throws -> CoffeeScript.compile throwsA, bare: yes
test "super in a bound function in a constructor", ->
throwsB = """
class A
class B extends A
constructor: do => super
"""
throws -> CoffeeScript.compile 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
# TODO: new.target needs support Separate issue
# test "ES6 support for new.target (functions and constructors)", ->
# throwsA = """
# class A
# constructor: () ->
# a = new.target.name
# """
# throws -> CoffeeScript.compile throwsA, bare: yes
test "only one method named constructor allowed", ->
throwsA = """
class A
constructor: (@first) ->
constructor: (@last) ->
"""
throws -> CoffeeScript.compile 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()
"""
throws -> CoffeeScript.compile 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