michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { Class, extend, mix, obscure } = require('sdk/core/heritage'); michael@0: michael@0: exports['test extend'] = function(assert) { michael@0: let ancestor = { a: 1 }; michael@0: let descendant = extend(ancestor, { michael@0: b: 2, michael@0: get c() { return 3 }, michael@0: d: function() { return 4 } michael@0: }); michael@0: michael@0: assert.ok(ancestor.isPrototypeOf(descendant), michael@0: 'descendant inherits from ancestor'); michael@0: assert.ok(descendant.b, 2, 'proprety was implemented'); michael@0: assert.ok(descendant.c, 3, 'getter was implemented'); michael@0: assert.ok(descendant.d(), 4, 'method was implemented'); michael@0: michael@0: /* Will be fixed once Bug 674195 is shipped. michael@0: assert.ok(Object.isFrozen(descendant), michael@0: 'extend returns frozen objects'); michael@0: */ michael@0: }; michael@0: michael@0: exports['test mix'] = function(assert) { michael@0: let ancestor = { a: 1 } michael@0: let mixed = mix(extend(ancestor, { b: 1, c: 1 }), { c: 2 }, { d: 3 }); michael@0: michael@0: assert.deepEqual(JSON.parse(JSON.stringify(mixed)), { b: 1, c: 2, d: 3 }, michael@0: 'properties mixed as expected'); michael@0: assert.ok(ancestor.isPrototypeOf(mixed), michael@0: 'first arguments ancestor is ancestor of result'); michael@0: }; michael@0: michael@0: exports['test obscure'] = function(assert) { michael@0: let fixture = mix({ a: 1 }, obscure({ b: 2 })); michael@0: michael@0: assert.equal(fixture.a, 1, 'a property is included'); michael@0: assert.equal(fixture.b, 2, 'b proprety is included'); michael@0: assert.ok(!Object.getOwnPropertyDescriptor(fixture, 'b').enumerable, michael@0: 'obscured properties are non-enumerable'); michael@0: }; michael@0: michael@0: exports['test inheritance'] = function(assert) { michael@0: let Ancestor = Class({ michael@0: name: 'ancestor', michael@0: method: function () { michael@0: return 'hello ' + this.name; michael@0: } michael@0: }); michael@0: michael@0: assert.ok(Ancestor() instanceof Ancestor, michael@0: 'can be instantiated without new'); michael@0: assert.ok(new Ancestor() instanceof Ancestor, michael@0: 'can also be instantiated with new'); michael@0: assert.ok(Ancestor() instanceof Class, michael@0: 'if ancestor not specified than defaults to Class'); michael@0: assert.ok(Ancestor.prototype.extends, Class.prototype, michael@0: 'extends of prototype points to ancestors prototype'); michael@0: michael@0: michael@0: assert.equal(Ancestor().method(), 'hello ancestor', michael@0: 'instance inherits defined properties'); michael@0: michael@0: let Descendant = Class({ michael@0: extends: Ancestor, michael@0: name: 'descendant' michael@0: }); michael@0: michael@0: assert.ok(Descendant() instanceof Descendant, michael@0: 'instantiates correctly'); michael@0: assert.ok(Descendant() instanceof Ancestor, michael@0: 'Inherits for passed `extends`'); michael@0: assert.equal(Descendant().method(), 'hello descendant', michael@0: 'propreties inherited'); michael@0: }; michael@0: michael@0: exports['test immunity against __proto__'] = function(assert) { michael@0: let Foo = Class({ name: 'foo', hacked: false }); michael@0: michael@0: let Bar = Class({ extends: Foo, name: 'bar' }); michael@0: michael@0: assert.throws(function() { michael@0: Foo.prototype.__proto__ = { hacked: true }; michael@0: if (Foo() instanceof Base && !Foo().hacked) michael@0: throw Error('can not change prototype chain'); michael@0: }, 'prototype chain is immune to __proto__ hacks'); michael@0: michael@0: assert.throws(function() { michael@0: Foo.prototype.__proto__ = { hacked: true }; michael@0: if (Bar() instanceof Foo && !Bar().hacked) michael@0: throw Error('can not change prototype chain'); michael@0: }, 'prototype chain of decedants immune to __proto__ hacks'); michael@0: }; michael@0: michael@0: exports['test super'] = function(assert) { michael@0: var Foo = Class({ michael@0: initialize: function initialize(options) { michael@0: this.name = options.name; michael@0: } michael@0: }); michael@0: michael@0: var Bar = Class({ michael@0: extends: Foo, michael@0: initialize: function Bar(options) { michael@0: Foo.prototype.initialize.call(this, options); michael@0: this.type = 'bar'; michael@0: } michael@0: }); michael@0: michael@0: var bar = Bar({ name: 'test' }); michael@0: michael@0: assert.equal(bar.type, 'bar', 'bar initializer was called'); michael@0: assert.equal(bar.name, 'test', 'bar initializer called Foo initializer'); michael@0: }; michael@0: michael@0: exports['test initialize'] = function(assert) { michael@0: var Dog = Class({ michael@0: initialize: function initialize(name) { michael@0: this.name = name; michael@0: }, michael@0: type: 'dog', michael@0: bark: function bark() { michael@0: return 'Ruff! Ruff!' michael@0: } michael@0: }); michael@0: michael@0: var fluffy = Dog('Fluffy'); // instatiation michael@0: assert.ok(fluffy instanceof Dog, michael@0: 'instanceof works as expected'); michael@0: assert.ok(fluffy instanceof Class, michael@0: 'inherits form Class if not specified otherwise'); michael@0: assert.ok(fluffy.name, 'fluffy', michael@0: 'initialize unless specified otherwise'); michael@0: }; michael@0: michael@0: exports['test complements regular inheritace'] = function(assert) { michael@0: let Base = Class({ name: 'base' }); michael@0: michael@0: function Type() { michael@0: // ... michael@0: } michael@0: Type.prototype = Object.create(Base.prototype); michael@0: Type.prototype.run = function() { michael@0: // ... michael@0: }; michael@0: michael@0: let value = new Type(); michael@0: michael@0: assert.ok(value instanceof Type, 'creates instance of Type'); michael@0: assert.ok(value instanceof Base, 'inherits from Base'); michael@0: assert.equal(value.name, 'base', 'inherits properties from Base'); michael@0: michael@0: michael@0: let SubType = Class({ michael@0: extends: Type, michael@0: sub: 'type' michael@0: }); michael@0: michael@0: let fixture = SubType(); michael@0: michael@0: assert.ok(fixture instanceof Base, 'is instance of Base'); michael@0: assert.ok(fixture instanceof Type, 'is instance of Type'); michael@0: assert.ok(fixture instanceof SubType, 'is instance of SubType'); michael@0: michael@0: assert.equal(fixture.sub, 'type', 'proprety is defined'); michael@0: assert.equal(fixture.run, Type.prototype.run, 'proprety is inherited'); michael@0: assert.equal(fixture.name, 'base', 'inherits base properties'); michael@0: }; michael@0: michael@0: exports['test extends object'] = function(assert) { michael@0: let prototype = { constructor: function() { return this; }, name: 'me' }; michael@0: let Foo = Class({ michael@0: extends: prototype, michael@0: value: 2 michael@0: }); michael@0: let foo = new Foo(); michael@0: michael@0: assert.ok(foo instanceof Foo, 'instance of Foo'); michael@0: assert.ok(!(foo instanceof Class), 'is not instance of Class'); michael@0: assert.ok(prototype.isPrototypeOf(foo), 'inherits from given prototype'); michael@0: assert.equal(Object.getPrototypeOf(Foo.prototype), prototype, michael@0: 'contsructor prototype inherits from extends option'); michael@0: assert.equal(foo.value, 2, 'property is defined'); michael@0: assert.equal(foo.name, 'me', 'prototype proprety is inherited'); michael@0: }; michael@0: michael@0: michael@0: var HEX = Class({ michael@0: hex: function hex() { michael@0: return '#' + this.color; michael@0: } michael@0: }); michael@0: michael@0: var RGB = Class({ michael@0: red: function red() { michael@0: return parseInt(this.color.substr(0, 2), 16); michael@0: }, michael@0: green: function green() { michael@0: return parseInt(this.color.substr(2, 2), 16); michael@0: }, michael@0: blue: function blue() { michael@0: return parseInt(this.color.substr(4, 2), 16); michael@0: } michael@0: }); michael@0: michael@0: var CMYK = Class({ michael@0: black: function black() { michael@0: var color = Math.max(Math.max(this.red(), this.green()), this.blue()); michael@0: return (1 - color / 255).toFixed(4); michael@0: }, michael@0: magenta: function magenta() { michael@0: var K = this.black(); michael@0: return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); michael@0: }, michael@0: yellow: function yellow() { michael@0: var K = this.black(); michael@0: return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); michael@0: }, michael@0: cyan: function cyan() { michael@0: var K = this.black(); michael@0: return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); michael@0: } michael@0: }); michael@0: michael@0: var Color = Class({ michael@0: implements: [ HEX, RGB, CMYK ], michael@0: initialize: function initialize(color) { michael@0: this.color = color; michael@0: } michael@0: }); michael@0: michael@0: exports['test composition'] = function(assert) { michael@0: var pink = Color('FFC0CB'); michael@0: michael@0: assert.equal(pink.red(), 255, 'red() works'); michael@0: assert.equal(pink.green(), 192, 'green() works'); michael@0: assert.equal(pink.blue(), 203, 'blue() works'); michael@0: michael@0: assert.equal(pink.magenta(), 0.2471, 'magenta() works'); michael@0: assert.equal(pink.yellow(), 0.2039, 'yellow() works'); michael@0: assert.equal(pink.cyan(), 0.0000, 'cyan() works'); michael@0: michael@0: assert.ok(pink instanceof Color, 'is instance of Color'); michael@0: assert.ok(pink instanceof Class, 'is instance of Class'); michael@0: }; michael@0: michael@0: var Point = Class({ michael@0: initialize: function initialize(x, y) { michael@0: this.x = x; michael@0: this.y = y; michael@0: }, michael@0: toString: function toString() { michael@0: return this.x + ':' + this.y; michael@0: } michael@0: }) michael@0: michael@0: var Pixel = Class({ michael@0: extends: Point, michael@0: implements: [ Color ], michael@0: initialize: function initialize(x, y, color) { michael@0: Color.prototype.initialize.call(this, color); michael@0: Point.prototype.initialize.call(this, x, y); michael@0: }, michael@0: toString: function toString() { michael@0: return this.hex() + '@' + Point.prototype.toString.call(this) michael@0: } michael@0: }); michael@0: michael@0: exports['test compostion with inheritance'] = function(assert) { michael@0: var pixel = Pixel(11, 23, 'CC3399'); michael@0: michael@0: assert.equal(pixel.toString(), '#CC3399@11:23', 'stringifies correctly'); michael@0: assert.ok(pixel instanceof Pixel, 'instance of Pixel'); michael@0: assert.ok(pixel instanceof Point, 'instance of Point'); michael@0: }; michael@0: michael@0: exports['test composition with objects'] = function(assert) { michael@0: var A = { a: 1, b: 1 }; michael@0: var B = Class({ b: 2, c: 2 }); michael@0: var C = { c: 3 }; michael@0: var D = { d: 4 }; michael@0: michael@0: var ABCD = Class({ michael@0: implements: [ A, B, C, D ], michael@0: e: 5 michael@0: }); michael@0: michael@0: var f = ABCD(); michael@0: michael@0: assert.equal(f.a, 1, 'inherits A.a'); michael@0: assert.equal(f.b, 2, 'inherits B.b overrides A.b'); michael@0: assert.equal(f.c, 3, 'inherits C.c overrides B.c'); michael@0: assert.equal(f.d, 4, 'inherits D.d'); michael@0: assert.equal(f.e, 5, 'implements e'); michael@0: }; michael@0: michael@0: require("test").run(exports);