aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.12/test/test-heritage.js
blob: 493ffddee0acdc8d262b65974fac5a4114223686 (plain)
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { Class, extend, mix, obscure } = require('sdk/core/heritage');

exports['test extend'] = function(assert) {
  let ancestor = { a: 1 };
  let descendant = extend(ancestor, {
    b: 2,
    get c() { return 3 },
    d: function() { return 4 }
  });

  assert.ok(ancestor.isPrototypeOf(descendant),
            'descendant inherits from ancestor');
  assert.ok(descendant.b, 2, 'proprety was implemented');
  assert.ok(descendant.c, 3, 'getter was implemented');
  assert.ok(descendant.d(), 4, 'method was implemented');

  /* Will be fixed once Bug 674195 is shipped.
  assert.ok(Object.isFrozen(descendant),
            'extend returns frozen objects');
  */
};

exports['test mix'] = function(assert) {
  let ancestor = { a: 1 }
  let mixed = mix(extend(ancestor, { b: 1, c: 1 }), { c: 2 }, { d: 3 });

  assert.deepEqual(JSON.parse(JSON.stringify(mixed)), { b: 1, c: 2, d: 3 },
                   'properties mixed as expected');
  assert.ok(ancestor.isPrototypeOf(mixed),
            'first arguments ancestor is ancestor of result');
};

exports['test obscure'] = function(assert) {
  let fixture = mix({ a: 1 }, obscure({ b: 2 }));

  assert.equal(fixture.a, 1, 'a property is included');
  assert.equal(fixture.b, 2, 'b proprety is included');
  assert.ok(!Object.getOwnPropertyDescriptor(fixture, 'b').enumerable,
            'obscured properties are non-enumerable');
};

exports['test inheritance'] = function(assert) {
  let Ancestor = Class({
    name: 'ancestor',
    method: function () {
      return 'hello ' + this.name;
    }
  });

  assert.ok(Ancestor() instanceof Ancestor,
            'can be instantiated without new');
  assert.ok(new Ancestor() instanceof Ancestor,
            'can also be instantiated with new');
  assert.ok(Ancestor() instanceof Class,
            'if ancestor not specified than defaults to Class');
  assert.ok(Ancestor.prototype.extends, Class.prototype,
            'extends of prototype points to ancestors prototype');


  assert.equal(Ancestor().method(), 'hello ancestor',
               'instance inherits defined properties');

  let Descendant = Class({
    extends: Ancestor,
    name: 'descendant'
  });

  assert.ok(Descendant() instanceof Descendant,
            'instantiates correctly');
  assert.ok(Descendant() instanceof Ancestor,
            'Inherits for passed `extends`');
  assert.equal(Descendant().method(), 'hello descendant',
               'propreties inherited');
};

exports['test prototype immutability'] = function(assert) {
  let Foo = Class({
    name: 'hello',
    rename: function rename(name) {
      this.name = name;
    }
  });

  /* Disable until release with Bug 674195 fix is shipped
  assert.ok(Object.isFrozen(Foo), 'Foo is frozen');
  assert.ok(Object.isFrozen(Foo.prototype), 'Foo prototype is frozen');
  assert.ok(Object.isFrozen(Object.getPrototypeOf(Foo.prototype)),
            'Class.prototype is frozen');
  assert.equal(Object.getPrototypeOf(Object.getPrototypeOf(Foo.prototype)),
               null, 'prototype of Class.prototype is null');
  */

  assert.throws(function() {
    var override = function() {};
    Foo.prototype.extend = override;
    if (Foo.prototype.extend !== override)
      throw Error('Property was not set');
  }, 'Can not change prototype properties');

  assert.throws(function() {
    Foo.prototype.foo = 'bar';
    if (Foo.prototype.foo !== 'bar')
      throw Error('Property was not set');
  }, 'Can not add prototype properties');

  assert.throws(function() {
    delete Foo.prototype.name;
    if ('name' in Foo.prototype)
      throw Error('Property was not deleted');
  }, 'Can not remove prototype properties');

  var Bar = Class({
    extends: Foo,
    rename: function rename() {
      return this.name;
    }
  });

  assert.equal(Bar().rename(), 'hello',
               'properties may be overided on decedents');
};

exports['test immunity against __proto__'] = function(assert) {
  let Foo = Class({ name: 'foo', hacked: false });

  let Bar = Class({ extends: Foo, name: 'bar' });

  assert.throws(function() {
    Foo.prototype.__proto__ = { hacked: true };
    if (Foo() instanceof Base && !Foo().hacked)
      throw Error('can not change prototype chain');
  }, 'prototype chain is immune to __proto__ hacks');

  assert.throws(function() {
    Foo.prototype.__proto__ = { hacked: true };
    if (Bar() instanceof Foo && !Bar().hacked)
      throw Error('can not change prototype chain');
  }, 'prototype chain of decedants immune to __proto__ hacks');
};

exports['test instance mutability'] = function(assert) {
  let Foo = Class({
    name: 'foo',
    initialize: function initialize(number) {
      this.number = number;
    }
  });

  let f1 = Foo();

  assert.throws(function() {
    f1.name = 'f1';
    if (f1.name !== 'f1')
      throw Error('Property was not set');
  }, 'can not change prototype properties');

  f1.alias = 'f1';
  assert.equal(f1.alias, 'f1', 'instance is mutable');

  delete f1.alias;
  assert.ok(!('alias' in f1), 'own properties are deletable');

  f1.initialize(1);
  assert.equal(f1.number, 1, 'method can mutate instances own properties');
};

exports['test super'] = function(assert) {
  var Foo = Class({
    initialize: function initialize(options) {
      this.name = options.name;
    }
  });

  var Bar = Class({
    extends: Foo,
    initialize: function Bar(options) {
      Foo.prototype.initialize.call(this, options);
      this.type = 'bar';
    }
  });

  var bar = Bar({ name: 'test' });

  assert.equal(bar.type, 'bar', 'bar initializer was called');
  assert.equal(bar.name, 'test', 'bar initializer called Foo initializer');
};

exports['test initialize'] = function(assert) {
  var Dog = Class({
    initialize: function initialize(name) {
      this.name = name;
    },
    type: 'dog',
    bark: function bark() {
      return 'Ruff! Ruff!'
    }
  });

  var fluffy = Dog('Fluffy');   // instatiation
  assert.ok(fluffy instanceof Dog,
            'instanceof works as expected');
  assert.ok(fluffy instanceof Class,
            'inherits form Class if not specified otherwise');
  assert.ok(fluffy.name, 'fluffy',
            'initialize unless specified otherwise');
};

exports['test complements regular inheritace'] = function(assert) {
  let Base = Class({ name: 'base' });

  function Type() {
      // ...
  }
  Type.prototype = Object.create(Base.prototype);
  Type.prototype.run = function() {
    // ...
  };

  let value = new Type();

  assert.ok(value instanceof Type, 'creates instance of Type');
  assert.ok(value instanceof Base, 'inherits from Base');
  assert.equal(value.name, 'base', 'inherits properties from Base');


  let SubType = Class({
    extends: Type,
    sub: 'type'
  });

  let fixture = SubType();

  assert.ok(fixture instanceof Base, 'is instance of Base');
  assert.ok(fixture instanceof Type, 'is instance of Type');
  assert.ok(fixture instanceof SubType, 'is instance of SubType');

  assert.equal(fixture.sub, 'type', 'proprety is defined');
  assert.equal(fixture.run, Type.prototype.run, 'proprety is inherited');
  assert.equal(fixture.name, 'base', 'inherits base properties');
};

exports['test extends object'] = function(assert) {
  let prototype = { constructor: function() { return this; }, name: 'me' };
  let Foo = Class({
    extends: prototype,
    value: 2
  });
  let foo = new Foo();

  assert.ok(foo instanceof Foo, 'instance of Foo');
  assert.ok(!(foo instanceof Class), 'is not instance of Class');
  assert.ok(prototype.isPrototypeOf(foo), 'inherits from given prototype');
  assert.equal(Object.getPrototypeOf(Foo.prototype), prototype,
               'contsructor prototype inherits from extends option');
  assert.equal(foo.value, 2, 'property is defined');
  assert.equal(foo.name, 'me', 'prototype proprety is inherited');
};


var HEX = Class({
  hex: function hex() {
    return '#' + this.color;
  }
});

var RGB = Class({
  red: function red() {
    return parseInt(this.color.substr(0, 2), 16);
  },
  green: function green() {
    return parseInt(this.color.substr(2, 2), 16);
  },
  blue: function blue() {
    return parseInt(this.color.substr(4, 2), 16);
  }
});

var CMYK = Class({
  black: function black() {
    var color = Math.max(Math.max(this.red(), this.green()), this.blue());
    return (1 - color / 255).toFixed(4);
  },
  magenta: function magenta() {
    var K = this.black();
    return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
  },
  yellow: function yellow() {
    var K = this.black();
    return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
  },
  cyan: function cyan() {
    var K = this.black();
    return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
  }
});

var Color = Class({
  implements: [ HEX, RGB, CMYK ],
  initialize: function initialize(color) {
    this.color = color;
  }
});

exports['test composition'] = function(assert) {
  var pink = Color('FFC0CB');

  assert.equal(pink.red(), 255, 'red() works');
  assert.equal(pink.green(), 192, 'green() works');
  assert.equal(pink.blue(), 203, 'blue() works');

  assert.equal(pink.magenta(), 0.2471, 'magenta() works');
  assert.equal(pink.yellow(), 0.2039, 'yellow() works');
  assert.equal(pink.cyan(), 0.0000, 'cyan() works');

  assert.ok(pink instanceof Color, 'is instance of Color');
  assert.ok(pink instanceof Class, 'is instance of Class');
};

var Point = Class({
  initialize: function initialize(x, y) {
    this.x = x;
    this.y = y;
  },
  toString: function toString() {
    return this.x + ':' + this.y;
  }
})

var Pixel = Class({
  extends: Point,
  implements: [ Color ],
  initialize: function initialize(x, y, color) {
    Color.prototype.initialize.call(this, color);
    Point.prototype.initialize.call(this, x, y);
  },
  toString: function toString() {
    return this.hex() + '@' + Point.prototype.toString.call(this)
  }
});

exports['test compostion with inheritance'] = function(assert) {
  var pixel = Pixel(11, 23, 'CC3399');

  assert.equal(pixel.toString(), '#CC3399@11:23', 'stringifies correctly');
  assert.ok(pixel instanceof Pixel, 'instance of Pixel');
  assert.ok(pixel instanceof Point, 'instance of Point');
};

exports['test composition with objects'] = function(assert) {
  var A = { a: 1, b: 1 };
  var B = Class({ b: 2, c: 2 });
  var C = { c: 3 };
  var D = { d: 4 };

  var ABCD = Class({
    implements: [ A, B, C, D ],
    e: 5
  });

  var f = ABCD();

  assert.equal(f.a, 1, 'inherits A.a');
  assert.equal(f.b, 2, 'inherits B.b overrides A.b');
  assert.equal(f.c, 3, 'inherits C.c overrides B.c');
  assert.equal(f.d, 4, 'inherits D.d');
  assert.equal(f.e, 5, 'implements e');
};

require("test").run(exports);