-
Notifications
You must be signed in to change notification settings - Fork 4
/
ConstructorTest.js
203 lines (171 loc) · 6.33 KB
/
ConstructorTest.js
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
/**
* Tests class module constructor creation
*
* Copyright (C) 2014 Mike Gerwitz
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
require( 'common' ).testCase(
{
setUp: function()
{
this.Sut = this.require( 'class' );
},
/**
* As a sanity check, ensure that the constructor is not invoked upon
* defining the class. (Note that the case of ensuring that it is not
* called when creating a subtype is handled by the ExtendTest case.)
*/
'Constructor should not be invoked before instantiation': function()
{
var called = false;
this.Sut.extend( { __construct: function() { called = true; } } );
this.assertNotEqual( called, true );
},
/**
* Since __construct is a special method that is not recognized by
* ECMAScript itself, we must ensure that it is invoked when the class
* is instantiated. Further, it should only be called a single time,
* which is particularly important if it produces side-effects.
*/
'Constructor should be invoked once upon instantiation': function()
{
var called = 0;
var Foo = this.Sut.extend(
{
__construct: function() { called++; }
} );
// note that we're not yet testing the more consise new-less
// invocation style
new Foo();
this.assertEqual( called, 1 );
},
/**
* Once invoked, the __construct method should be bound to the newly
* created instance.
*/
'Constructor should be invoked within context of new instance':
function()
{
var expected = Math.random();
var Foo = this.Sut.extend(
{
val: null,
__construct: function() { this.val = expected; }
} );
// if `this' was bound to the instance, then __construct should set
// VAL to EXPECTED
var inst = new Foo();
this.assertEqual( inst.val, expected );
},
/**
* All arguments passed to the constructor (that is, by invoking the
* ``class'') should be passed to __construct, unchanged and
* uncopied---that is, references should be retained.
*/
'Constructor arguments should be passed unchanged to __construct':
function()
{
var args = [ "foo", { bar: 'baz' }, [ 'moo', 'cow' ] ],
given = null;
var Foo = this.Sut.extend(
{
__construct: function()
{
given = Array.prototype.slice.call( arguments, 0 );
}
} );
new Foo( args[ 0 ], args[ 1 ], args[ 2 ] );
// make sure we have everything and didn't get anything extra
this.assertEqual( given.length, args.length );
var i = args.length;
while ( i-- )
{
this.assertStrictEqual( given[ i ], args[ i ],
"Ctor argument mismatch: " + i
);
}
},
/**
* If a subtype does not define its own constructor, then its parent's
* should be called by default. Note that this behavior---as is clear by
* the name __construct---is modelled after PHP; Java classes, for
* instance, do not inherit their parents' constructors.
*/
'Parent constructor should be invoked for subtype if not overridden':
function()
{
var called = false;
var Sub = this.Sut.extend(
{
__construct: function() { called = true; }
} ).extend( {} );
new Sub();
this.assertOk( called );
},
/**
* Classes created through ease.js do not require use of the `new'
* keyword, which allows for a much more natural, concise, and less
* error-prone syntax. Ensure that a new instance is created even when
* it is omitted.
*
* The rest of the tests above would then stand, since they use the
* `new' keyword and this concise format has no choice but to ultimately
* do the same; otherwise, it would not be recognized by instanceof.
*/
'Constructor does not require `new\' keyword': function()
{
var Foo = this.Sut.extend( {} );
this.assertOk( new Foo() instanceof Foo ); // sanity check
this.assertOk( Foo() instanceof Foo );
},
/**
* In certain OO languages, one would prevent a class from being
* instantiated by declaring the constructor as protected or private. To
* me (Mike Gerwitz), this is cryptic. A better method would simply be
* to throw an exception. Perhaps, in the future, an alternative will be
* provided for consistency.
*
* The constructor must be public. (It is for this reason that you will
* often see the convention of omitting visibility keywords entirely for
* __construct, since public is the default and there is no other
* option.)
*/
'__construct must be public': function()
{
var Sut = this.Sut;
this.assertThrows( function()
{
Sut( { 'protected __construct': function() {} } );
}, TypeError, "Constructor should not be able to be protected" );
this.assertThrows( function()
{
Sut( { 'private __construct': function() {} } );
}, TypeError, "Constructor should not be able to be private" );
},
/**
* When a constructor is instantiated conventionally in ECMAScript, the
* instance's `constructor' property is set to the constructor that was
* used to instantiate it. The same should be true for class instances.
*
* This will also be important for reflection.
*/
'`constructor\' property is properly set to class object': function()
{
var Foo = this.Sut.extend( {} );
this.assertStrictEqual( Foo().constructor, Foo );
},
} );