|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const { Class, extend, mix, obscure } = require('sdk/core/heritage'); |
|
8 |
|
9 exports['test extend'] = function(assert) { |
|
10 let ancestor = { a: 1 }; |
|
11 let descendant = extend(ancestor, { |
|
12 b: 2, |
|
13 get c() { return 3 }, |
|
14 d: function() { return 4 } |
|
15 }); |
|
16 |
|
17 assert.ok(ancestor.isPrototypeOf(descendant), |
|
18 'descendant inherits from ancestor'); |
|
19 assert.ok(descendant.b, 2, 'proprety was implemented'); |
|
20 assert.ok(descendant.c, 3, 'getter was implemented'); |
|
21 assert.ok(descendant.d(), 4, 'method was implemented'); |
|
22 |
|
23 /* Will be fixed once Bug 674195 is shipped. |
|
24 assert.ok(Object.isFrozen(descendant), |
|
25 'extend returns frozen objects'); |
|
26 */ |
|
27 }; |
|
28 |
|
29 exports['test mix'] = function(assert) { |
|
30 let ancestor = { a: 1 } |
|
31 let mixed = mix(extend(ancestor, { b: 1, c: 1 }), { c: 2 }, { d: 3 }); |
|
32 |
|
33 assert.deepEqual(JSON.parse(JSON.stringify(mixed)), { b: 1, c: 2, d: 3 }, |
|
34 'properties mixed as expected'); |
|
35 assert.ok(ancestor.isPrototypeOf(mixed), |
|
36 'first arguments ancestor is ancestor of result'); |
|
37 }; |
|
38 |
|
39 exports['test obscure'] = function(assert) { |
|
40 let fixture = mix({ a: 1 }, obscure({ b: 2 })); |
|
41 |
|
42 assert.equal(fixture.a, 1, 'a property is included'); |
|
43 assert.equal(fixture.b, 2, 'b proprety is included'); |
|
44 assert.ok(!Object.getOwnPropertyDescriptor(fixture, 'b').enumerable, |
|
45 'obscured properties are non-enumerable'); |
|
46 }; |
|
47 |
|
48 exports['test inheritance'] = function(assert) { |
|
49 let Ancestor = Class({ |
|
50 name: 'ancestor', |
|
51 method: function () { |
|
52 return 'hello ' + this.name; |
|
53 } |
|
54 }); |
|
55 |
|
56 assert.ok(Ancestor() instanceof Ancestor, |
|
57 'can be instantiated without new'); |
|
58 assert.ok(new Ancestor() instanceof Ancestor, |
|
59 'can also be instantiated with new'); |
|
60 assert.ok(Ancestor() instanceof Class, |
|
61 'if ancestor not specified than defaults to Class'); |
|
62 assert.ok(Ancestor.prototype.extends, Class.prototype, |
|
63 'extends of prototype points to ancestors prototype'); |
|
64 |
|
65 |
|
66 assert.equal(Ancestor().method(), 'hello ancestor', |
|
67 'instance inherits defined properties'); |
|
68 |
|
69 let Descendant = Class({ |
|
70 extends: Ancestor, |
|
71 name: 'descendant' |
|
72 }); |
|
73 |
|
74 assert.ok(Descendant() instanceof Descendant, |
|
75 'instantiates correctly'); |
|
76 assert.ok(Descendant() instanceof Ancestor, |
|
77 'Inherits for passed `extends`'); |
|
78 assert.equal(Descendant().method(), 'hello descendant', |
|
79 'propreties inherited'); |
|
80 }; |
|
81 |
|
82 exports['test immunity against __proto__'] = function(assert) { |
|
83 let Foo = Class({ name: 'foo', hacked: false }); |
|
84 |
|
85 let Bar = Class({ extends: Foo, name: 'bar' }); |
|
86 |
|
87 assert.throws(function() { |
|
88 Foo.prototype.__proto__ = { hacked: true }; |
|
89 if (Foo() instanceof Base && !Foo().hacked) |
|
90 throw Error('can not change prototype chain'); |
|
91 }, 'prototype chain is immune to __proto__ hacks'); |
|
92 |
|
93 assert.throws(function() { |
|
94 Foo.prototype.__proto__ = { hacked: true }; |
|
95 if (Bar() instanceof Foo && !Bar().hacked) |
|
96 throw Error('can not change prototype chain'); |
|
97 }, 'prototype chain of decedants immune to __proto__ hacks'); |
|
98 }; |
|
99 |
|
100 exports['test super'] = function(assert) { |
|
101 var Foo = Class({ |
|
102 initialize: function initialize(options) { |
|
103 this.name = options.name; |
|
104 } |
|
105 }); |
|
106 |
|
107 var Bar = Class({ |
|
108 extends: Foo, |
|
109 initialize: function Bar(options) { |
|
110 Foo.prototype.initialize.call(this, options); |
|
111 this.type = 'bar'; |
|
112 } |
|
113 }); |
|
114 |
|
115 var bar = Bar({ name: 'test' }); |
|
116 |
|
117 assert.equal(bar.type, 'bar', 'bar initializer was called'); |
|
118 assert.equal(bar.name, 'test', 'bar initializer called Foo initializer'); |
|
119 }; |
|
120 |
|
121 exports['test initialize'] = function(assert) { |
|
122 var Dog = Class({ |
|
123 initialize: function initialize(name) { |
|
124 this.name = name; |
|
125 }, |
|
126 type: 'dog', |
|
127 bark: function bark() { |
|
128 return 'Ruff! Ruff!' |
|
129 } |
|
130 }); |
|
131 |
|
132 var fluffy = Dog('Fluffy'); // instatiation |
|
133 assert.ok(fluffy instanceof Dog, |
|
134 'instanceof works as expected'); |
|
135 assert.ok(fluffy instanceof Class, |
|
136 'inherits form Class if not specified otherwise'); |
|
137 assert.ok(fluffy.name, 'fluffy', |
|
138 'initialize unless specified otherwise'); |
|
139 }; |
|
140 |
|
141 exports['test complements regular inheritace'] = function(assert) { |
|
142 let Base = Class({ name: 'base' }); |
|
143 |
|
144 function Type() { |
|
145 // ... |
|
146 } |
|
147 Type.prototype = Object.create(Base.prototype); |
|
148 Type.prototype.run = function() { |
|
149 // ... |
|
150 }; |
|
151 |
|
152 let value = new Type(); |
|
153 |
|
154 assert.ok(value instanceof Type, 'creates instance of Type'); |
|
155 assert.ok(value instanceof Base, 'inherits from Base'); |
|
156 assert.equal(value.name, 'base', 'inherits properties from Base'); |
|
157 |
|
158 |
|
159 let SubType = Class({ |
|
160 extends: Type, |
|
161 sub: 'type' |
|
162 }); |
|
163 |
|
164 let fixture = SubType(); |
|
165 |
|
166 assert.ok(fixture instanceof Base, 'is instance of Base'); |
|
167 assert.ok(fixture instanceof Type, 'is instance of Type'); |
|
168 assert.ok(fixture instanceof SubType, 'is instance of SubType'); |
|
169 |
|
170 assert.equal(fixture.sub, 'type', 'proprety is defined'); |
|
171 assert.equal(fixture.run, Type.prototype.run, 'proprety is inherited'); |
|
172 assert.equal(fixture.name, 'base', 'inherits base properties'); |
|
173 }; |
|
174 |
|
175 exports['test extends object'] = function(assert) { |
|
176 let prototype = { constructor: function() { return this; }, name: 'me' }; |
|
177 let Foo = Class({ |
|
178 extends: prototype, |
|
179 value: 2 |
|
180 }); |
|
181 let foo = new Foo(); |
|
182 |
|
183 assert.ok(foo instanceof Foo, 'instance of Foo'); |
|
184 assert.ok(!(foo instanceof Class), 'is not instance of Class'); |
|
185 assert.ok(prototype.isPrototypeOf(foo), 'inherits from given prototype'); |
|
186 assert.equal(Object.getPrototypeOf(Foo.prototype), prototype, |
|
187 'contsructor prototype inherits from extends option'); |
|
188 assert.equal(foo.value, 2, 'property is defined'); |
|
189 assert.equal(foo.name, 'me', 'prototype proprety is inherited'); |
|
190 }; |
|
191 |
|
192 |
|
193 var HEX = Class({ |
|
194 hex: function hex() { |
|
195 return '#' + this.color; |
|
196 } |
|
197 }); |
|
198 |
|
199 var RGB = Class({ |
|
200 red: function red() { |
|
201 return parseInt(this.color.substr(0, 2), 16); |
|
202 }, |
|
203 green: function green() { |
|
204 return parseInt(this.color.substr(2, 2), 16); |
|
205 }, |
|
206 blue: function blue() { |
|
207 return parseInt(this.color.substr(4, 2), 16); |
|
208 } |
|
209 }); |
|
210 |
|
211 var CMYK = Class({ |
|
212 black: function black() { |
|
213 var color = Math.max(Math.max(this.red(), this.green()), this.blue()); |
|
214 return (1 - color / 255).toFixed(4); |
|
215 }, |
|
216 magenta: function magenta() { |
|
217 var K = this.black(); |
|
218 return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); |
|
219 }, |
|
220 yellow: function yellow() { |
|
221 var K = this.black(); |
|
222 return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); |
|
223 }, |
|
224 cyan: function cyan() { |
|
225 var K = this.black(); |
|
226 return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); |
|
227 } |
|
228 }); |
|
229 |
|
230 var Color = Class({ |
|
231 implements: [ HEX, RGB, CMYK ], |
|
232 initialize: function initialize(color) { |
|
233 this.color = color; |
|
234 } |
|
235 }); |
|
236 |
|
237 exports['test composition'] = function(assert) { |
|
238 var pink = Color('FFC0CB'); |
|
239 |
|
240 assert.equal(pink.red(), 255, 'red() works'); |
|
241 assert.equal(pink.green(), 192, 'green() works'); |
|
242 assert.equal(pink.blue(), 203, 'blue() works'); |
|
243 |
|
244 assert.equal(pink.magenta(), 0.2471, 'magenta() works'); |
|
245 assert.equal(pink.yellow(), 0.2039, 'yellow() works'); |
|
246 assert.equal(pink.cyan(), 0.0000, 'cyan() works'); |
|
247 |
|
248 assert.ok(pink instanceof Color, 'is instance of Color'); |
|
249 assert.ok(pink instanceof Class, 'is instance of Class'); |
|
250 }; |
|
251 |
|
252 var Point = Class({ |
|
253 initialize: function initialize(x, y) { |
|
254 this.x = x; |
|
255 this.y = y; |
|
256 }, |
|
257 toString: function toString() { |
|
258 return this.x + ':' + this.y; |
|
259 } |
|
260 }) |
|
261 |
|
262 var Pixel = Class({ |
|
263 extends: Point, |
|
264 implements: [ Color ], |
|
265 initialize: function initialize(x, y, color) { |
|
266 Color.prototype.initialize.call(this, color); |
|
267 Point.prototype.initialize.call(this, x, y); |
|
268 }, |
|
269 toString: function toString() { |
|
270 return this.hex() + '@' + Point.prototype.toString.call(this) |
|
271 } |
|
272 }); |
|
273 |
|
274 exports['test compostion with inheritance'] = function(assert) { |
|
275 var pixel = Pixel(11, 23, 'CC3399'); |
|
276 |
|
277 assert.equal(pixel.toString(), '#CC3399@11:23', 'stringifies correctly'); |
|
278 assert.ok(pixel instanceof Pixel, 'instance of Pixel'); |
|
279 assert.ok(pixel instanceof Point, 'instance of Point'); |
|
280 }; |
|
281 |
|
282 exports['test composition with objects'] = function(assert) { |
|
283 var A = { a: 1, b: 1 }; |
|
284 var B = Class({ b: 2, c: 2 }); |
|
285 var C = { c: 3 }; |
|
286 var D = { d: 4 }; |
|
287 |
|
288 var ABCD = Class({ |
|
289 implements: [ A, B, C, D ], |
|
290 e: 5 |
|
291 }); |
|
292 |
|
293 var f = ABCD(); |
|
294 |
|
295 assert.equal(f.a, 1, 'inherits A.a'); |
|
296 assert.equal(f.b, 2, 'inherits B.b overrides A.b'); |
|
297 assert.equal(f.c, 3, 'inherits C.c overrides B.c'); |
|
298 assert.equal(f.d, 4, 'inherits D.d'); |
|
299 assert.equal(f.e, 5, 'implements e'); |
|
300 }; |
|
301 |
|
302 require("test").run(exports); |