-
Notifications
You must be signed in to change notification settings - Fork 0
/
mixins.spec.coffee
213 lines (150 loc) · 6.82 KB
/
mixins.spec.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
require("must/register")
expect = require 'must'
{Mixin, mixins} = require './index.coffee'
randomId = (length=8) ->
# Taken from: https://coffeescript-cookbook.github.io/chapters/strings/generating-a-unique-id
id = ""
id += Math.random().toString(36).substr(2) while id.length < length
id.substr 0, length
describe 'Mixin', ->
describe 'composition', ->
class UUIDentifiable
id: -> @_id ?= randomId()
class AutoUUID
uuid: -> @_id ?= randomId()
class Named extends Mixin
constructor:(@name)->
nickname: -> "nick#{@name}"
@staticName:->"static#{@name}"
class TestClass extends Mixin
@with UUIDentifiable
@with AutoUUID
@with Named
constructor: (name) ->
super name
@staticMethod:->"static#{@name}"
uuid:->super() # override leaves imported method accessible via super
class TestSubClass extends TestClass
@staticName:->"Overwritten#{@name}"
nickname: -> "Overwritten#{super()}"
describe 'method and property inclusion', ->
it 'works for instance (prototype) and static (class) properties', ->
testclass = new TestClass()
expect(testclass.id().length).to.equal 8
expect(testclass.id()).to.be testclass.id() # repeatably return the same value
expect(testclass._id).to.be testclass.id() # from the underlying variable
expect(testclass.uuid()).to.be testclass.id() # and used by the two different methods
expect(testclass.name).to.be.undefined()
expect(testclass.nickname()).to.be "nickundefined"
testclass.name = "testclass"
expect(testclass.nickname()).to.be "nicktestclass"
expect(TestClass.staticName()).to.be "staticTestClass"
it 'preserves semantics of per instance variables (no accidently shared state between instances)', ->
testclass = new TestClass()
testclass2 = new TestClass("testclass2")
expect(testclass.id()).not.to.be testclass2.id()
expect(testclass.uuid).to.be testclass2.uuid # methods are the same
expect(testclass.uuid()).not.to.be testclass2.uuid() # values produced are not (both instances have own copy of variables)
expect(testclass2.name).to.be "testclass2"
expect(testclass2.nickname()).to.be "nicktestclass2"
testclass2.name = "renamed"
expect(testclass2.nickname()).to.be "nickrenamed"
expect(TestClass.staticName()).to.be "staticTestClass"
it 'supports method overriding for instance and static methods', ->
testclass = new TestSubClass "testsub"
expect(testclass.id().length).to.equal 8
expect(testclass.id()).to.be testclass.id() # repeatably return the same value
expect(testclass.name).to.be "testsub"
expect(testclass.nickname()).to.be "Overwrittennicktestsub"
expect(TestSubClass.staticName()).to.be "OverwrittenTestSubClass"
it 'allows to compose of composed mixins',->
class AutoId
constructor:-> @_id= randomId()
class MixinOne extends Mixin
@with AutoId
render:->"rendered:MixinOne"
class MixinTwo extends Mixin
@with AutoId
@with Named
render:-> "rendered:MixinTwo"+super()
class CompositeMixins extends Mixin
@with MixinOne, MixinTwo
constructor:(properties) -> super properties
class CompositeMixinSubclass extends CompositeMixins
constructor: (properties) -> super properties
class MixedSuperclass extends mixins AutoId, MixinOne, MixinTwo
composite = new CompositeMixins 'named-composite-module'
expect(composite.name).to.be 'named-composite-module'
compisiteSub = new CompositeMixinSubclass 'composite-subclass'
expect(compisiteSub.name).to.be 'composite-subclass'
mixedSuper = new MixedSuperclass 'mixed-super'
expect(mixedSuper.name).to.be 'mixed-super'
it 'supports type validation:throws an error if required type is not found', ->
validationError = null
try
class Love
class Child extends Mixin
@expects Love
class Parent extends Mixin
#@with Love #People need love but this test will fail if love is given to child
@with Child
catch err
# Construction of Parent fails with error
validationError = err
expect(validationError).not.to.be.null()
expect(validationError.toString().indexOf "requires Love").not.to.be -1
describe 'super bindings', ->
class Named
constructor:(@name)->
fullname:->@name
class Person extends Named
fullname:-> "Person #{super()}"
class PersonAndPosition extends Mixin
@with Person
constructor: (name, @position) -> super name
fullname: -> "#{super()} in position of #{@position}"
describe 'of subclass mixed into Mixin bind to their former super class', ->
it 'when using @with', ->
Captain = new PersonAndPosition 'Fitz Roy', 'Captain'
expect(Captain.fullname()).to.be 'Person Fitz Roy in position of Captain'
it 'when using extends mixins', ->
class PersonAndPositionMixedSuper extends mixins Person
constructor: (name, @position) -> super name
fullname: -> "#{super()} in position of #{@position}"
Captain = new PersonAndPositionMixedSuper 'Fitz Roy', 'Captain'
expect(Captain.fullname()).to.be 'Person Fitz Roy in position of Captain'
it 'when using coffeescript class extensions', ->
class PersonAndPositionExtendsSuper extends PersonAndPosition
fullname:->super()
Captain = new PersonAndPositionExtendsSuper 'Fitz Roy', 'Captain'
expect(Captain.fullname()).to.be 'Person Fitz Roy in position of Captain'
describe 'constructor hierarchies', ->
trackedConstructorCalls = 0
calledConstructors = []
class TrackedConstructor
constructor: ->
trackedConstructorCalls++
calledConstructors.push 'TrackedConstructor'
class TrackedOne extends TrackedConstructor
constructor: ->
super()
calledConstructors.push 'TrackedOne'
class TrackedTwo extends TrackedConstructor
constructor: ->
super()
calledConstructors.push 'TrackedTwo'
class Third extends Mixin
constructor:->calledConstructors.push 'Third'
class TestClass extends Mixin
@with TrackedOne
@with TrackedTwo
@with Third
constructor: (name) ->
super name
calledConstructors.push 'TestClass'
it 'are called in order of mixin inclusion (isomorphism between definition and execution)', ->
trackedConstructorCalls = 0
calledConstructors = []
testClass = new TestClass()
expect(trackedConstructorCalls).to.be 2 # TrackedOne and TrackedTwo
expect(calledConstructors).to.eql ['TrackedConstructor', 'TrackedOne', 'TrackedConstructor', 'TrackedTwo', 'Third', 'TestClass']