# 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 "#5323: new.target can be the argument of a function", ->
  fn = (arg) -> arg
  fn new.target

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
