diff --git a/History.md b/History.md index 94cb8ed..53ce0d1 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,9 @@ # Changes # +## 1.1.0 / 2015-06-21 + - Fixed #15: Added legacy `.new()` function + - Fixed deprecated warning on package.json + ## 1.0.2 / 2015-04-07 - Fixed #16: instances properties defined through objects are not mutable diff --git a/Readme.md b/Readme.md index ebaf221..50880b8 100644 --- a/Readme.md +++ b/Readme.md @@ -1,36 +1,43 @@ -# selfish # +# selfish [![Build Status](https://secure.travis-ci.org/Gozala/selfish.png)](http://travis-ci.org/Gozala/selfish) Class-free, pure prototypal multiple inheritance that lets you write expressive, well-structured code. -## Install ## +## Install -### server-side ### +### server-side - npm install selfish - -### client-side ### +```bash +npm install selfish +``` - bower install selfish +### client-side +```bash +bower install selfish +``` -## Require ## +## Require -### server-side ### +### server-side - var Base = require('!raw.github.com/Gozala/selfish/v1.0.0/selfish').Base +```js +var Base = require('!raw.github.com/Gozala/selfish/v1.0.0/selfish').Base +``` -### client-side RequireJS ### +### client-side RequireJS - define(['path/to/selfish'], function(selfish) { - var Base = selfish.Base; - } +```js +define(['path/to/selfish'], function(selfish) { + var Base = selfish.Base; +}) +``` -## Examples ## +## Examples -### Basics ### +### Basics ```js // Instead of creating classes, you create prototype objects. Let's look @@ -44,10 +51,15 @@ var Dog = Base.extend({ // Forget about classes, javascript is a prototypal language: typeof Dog // object -// Use new operator to create an instance +// Use the new operator to create an instance: var dog = new Dog() dog.bark() // 'Ruff! Ruff!' +// Alternatively you can use the legacy new() function but keep in mind that +// the new operator is faster in most browsers +var dog = Dog.new() +dog.bark() // 'Ruff! Ruff!' + // Forget about special `instanceof` operator, use JS native method instead: Dog.prototype.isPrototypeOf(dog) // true @@ -76,7 +88,7 @@ pet.call('Benzy') // 'Ruff! Ruff!' ``` -### Object composition ### +### Object composition ```js // In some programs recombining reusable pieces of code is a better option: @@ -137,7 +149,7 @@ pink.yellow() // 0.2039 pink.cyan() // 0.0000 ``` -### Combining composition & inheritance ### +### Combining composition & inheritance ```js var Pixel = Color.extend({ @@ -163,7 +175,7 @@ Color.prototype.isPrototypeOf(pixel) // true Color.prototype.isPrototypeOf(Pixel.prototype) // true ``` -### TODO ### +### TODO This is a list of things I may introduce in newer versions. diff --git a/bower.json b/bower.json index dee8963..6be3790 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "selfish", "main": "selfish.js", - "version": "1.0.2", + "version": "1.1.0", "homepage": "https://github.com/Gozala/selfish", "authors": [ "Irakli Gozalishvili (http://jeditoolkit.com)", diff --git a/package.json b/package.json index 1769182..dabb97b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "selfish", "id": "selfish", - "version": "1.0.2", + "version": "1.1.0", "description": "Class-free, pure prototypal multiple inheritance", "keywords": [ "oop", @@ -67,10 +67,5 @@ "scripts": { "test": "./node_modules/.bin/mocha" }, - "licenses": [ - { - "type": "MIT", - "url": "http://jeditoolkit.com/LICENSE" - } - ] + "license": "MIT" } diff --git a/selfish.js b/selfish.js index 68d798d..bf6d0cd 100644 --- a/selfish.js +++ b/selfish.js @@ -72,6 +72,29 @@ */ Base.prototype.initialize = function BaseInitialize() {}; + /** + * Constructs an instance. An alternative maker function to using new keyword. + * + * @examples + * + * var Pet = Dog.extend({ + * initialize: function initialize(options) { + * this.name = options.name; + * }, + * call: function(name) { + * return this.name === name ? this.bark() : ''; + * }, + * name: null + * }); + * var pet = Pet.new({ name: 'Benzy', breed: 'Labrador' }); + * pet.call('Benzy'); // 'Ruff! Ruff!' + */ + Base.new = function() { + var instance = Object.create(Base.prototype); + Base.apply(instance, arguments) + return instance; + }; + /** * Takes any number of argument objects and returns frozen, composite object * that inherits from `this` object and combines all of the own properties of @@ -182,6 +205,11 @@ }); // Copy the prototype methods that allows us to do inheritance constructor.extend = this.extend; + constructor.new = function() { + var instance = Object.create(constructor.prototype); + constructor.apply(instance, arguments) + return instance; + }; // Generate the prototype and extend it var descriptor = Object.create(this.prototype); diff --git a/test/test-selfish-new-function-syntax.js b/test/test-selfish-new-function-syntax.js new file mode 100644 index 0000000..3ef8246 --- /dev/null +++ b/test/test-selfish-new-function-syntax.js @@ -0,0 +1,421 @@ +(function() { + "use strict"; + + var selfish = require("../selfish"); + var should = require("should"); + var Base = selfish.Base; + + describe(".new function syntax", function() { + describe("#isPrototypeOf", function() { + it("Base is a prototype of Base.new()", function() { + Base.prototype.isPrototypeOf(Base.new()).should.be.true; + }); + it("Base is a prototype of Base.extend()", function() { + Base.prototype.isPrototypeOf(Base.extend().prototype).should.be.true; + }); + it("Base is a prototype of (Base.extend()).new()", function() { + Base.prototype.isPrototypeOf((Base.extend()).new()).should.be.true; + }); + it("Base.extend() in not prototype of Base.extend()", function() { + Base.extend().prototype.isPrototypeOf(Base.extend().prototype).should.be.false; + }); + it("Base.extend() in not prototype of (Base.extend()).new()", function() { + Base.extend().prototype.isPrototypeOf((Base.extend()).new()).should.be.false; + }); + it("Base.new() is not prototype of Base.new()", function() { + (Base.new()).isPrototypeOf(Base.new()).should.be.false; + }); + it("Base.new() is not prototype of Base.extend()", function() { + (Base.new()).isPrototypeOf(Base.extend().prototype).should.be.false; + }); + it("Base.extend() is not prototype of Base.new()", function() { + Base.extend().prototype.isPrototypeOf(Base.new()).should.be.false; + }); + }); + + describe("#Inheritance", function() { + var Parent, Child; + before(function() { + Parent = Base.extend({ + name: "parent", + method: function() { + return "hello " + this.name; + } + }); + Child = Parent.extend({ + name: "child" + }); + }); + + describe("Parent prototype", function() { + it("Parent.prototype name is parent", function() { + Parent.prototype.name.should.be.eql("parent"); + }); + it("method works on prototype", function() { + Parent.prototype.method().should.be.eql("hello parent"); + }); + }); + + describe("Parent instances", function() { + var p; + before(function() { + p = Parent.new(); + }); + it("Parent instance inherits name", function() { + var p = Parent.new(); + p.name.should.be.eql(Parent.prototype.name); + }); + it("method behaves same on the prototype", function() { + p.method().should.be.eql(Parent.prototype.method()); + }); + }); + + it("Parent decedent inherits name", function() { + Parent.extend({}).prototype.name.should.be.eql(Parent.prototype.name); + }); + + describe("Child prototype", function() { + it("Child overrides name", function() { + Child.prototype.name.should.not.be.eql(Parent.prototype.name); + }); + it("method refers to instance property", function() { + Child.prototype.method().should.be.eql("hello child"); + }); + it("Child inherits method", function() { + Child.prototype.method.should.be.eql(Parent.prototype.method); + }); + }); + + describe("Child instances", function() { + var c; + before(function() { + c = Child.new(); + }); + + it("Child instances inherit name", function() { + c.name.should.be.eql(Child.prototype.name); + }); + it("Child instances inherit method", function() { + c.method.should.be.eql(Parent.prototype.method); + }); + }); + + it("Child decedents inherit name", function() { + Child.extend().prototype.name.should.be.eql(Child.prototype.name); + }); + + it("Child decedent inherit method", function() { + Child.extend().prototype.method.should.be.eql(Parent.prototype.method); + }); + + it("method may be overridden", function() { + var CC = Child.extend({ + name: "decedent" + }); + var cc = CC.new(); + cc.method().should.be.eql("hello decedent"); + }); + }); + + describe("#prototype immutability", function() { + it("Base is immutable", function() { + (function() { + Base.extend = null; + }).should.throw(); + }); + it("Base prototype is immutable", function() { + (function() { + Base.prototype.foo = null; + }).should.throw(); + }); + it("Base prototype is non-configurable", function() { + (function() { + Base.foo = null; + }).should.throw(); + }); + it("Can't delete properties on Base", function() { + (function() { + delete Base.extend; + }).should.throw(); + }); + }); + + describe("#extended prototype immutability", function() { + var Foo, Bar; + before(function() { + Foo = Base.extend({ + name: "hello", + rename: function rename(name) { + this.name = name; + }, + nothing: function nothing() {} + }); + Bar = Foo.extend({ + rename: function rename() { + return this.name; + } + }); + }); + it("Can't change prototype properties", function() { + (function() { + Foo.extend = null; + }).should.throw(); + }); + + it("Can't add properties", function() { + (function() { + Foo.prototype.bar = null; + }).should.throw(); + (function() { + Foo.bar = null; + }).should.throw(); + }); + + it("Can't remove prototype properties", function() { + (function() { + delete Foo.prototype.name; + }).should.throw(); + }); + + it("Method's can't mutate prototypes", function() { + (function() { + Foo.prototype.rename("new name"); + }).should.throw(); + }); + + it("properties may be overridden on decedents", function() { + Bar.prototype.rename().should.be.eql(Foo.prototype.name); + Bar.prototype.rename.should.not.be.eql(Foo.prototype.rename); + }); + }); + + describe("#instance mutability", function() { + var Foo, f1; + before(function() { + Foo = Base.extend({ + name: "foo", + init: function init(number) { + this.number = number; + } + }); + f1 = Foo.new(); + }); + it("instance is mutable", function() { + should(f1.alias).be.ko; + f1.alias = "f1"; + f1.alias.should.be.eql("f1"); + }); + it("own properties are deletable", function() { + f1.alias = "f1"; + ("alias" in f1).should.be.true; + delete f1.alias; + ("alias" in f1).should.be.false; + }); + it("methods can mutate instance's own properties", function() { + f1.init(1); + f1.number.should.be.eql(1); + f1.init(3); + f1.number.should.be.eql(3); + }); + it("properties can be directly mutated", function() { + f1.name.should.be.eql("foo"); + (function() { + f1.name = "lol"; + }).should.not.throw(); + f1.name.should.be.eql("lol"); + }); + describe("#Deeper inheritance mutability", function() { + var A, B, C; + before(function() { + A = Base.extend({ + name: "A", + hello: function() { + return this.name; + } + }); + B = A.extend({ + varB: "B" + }); + C = B.extend({ + varC: "C" + }); + }); + it("A instance can modify name", function() { + var a = A.new(); + a.name.should.be.eql("A"); + a.hello().should.be.eql("A"); + (function() { + a.name = "Foo"; + }).should.not.throw(); + a.name.should.be.eql("Foo"); + a.hello().should.be.eql("Foo"); + }); + it("B instance inherits A.name", function() { + var b = B.new(); + b.name.should.be.eql("A"); + b.hello().should.be.eql("A"); + }); + it("B instance varB is mutable", function() { + var b = B.new(); + b.varB.should.be.eql("B"); + (function() { + b.varB = "Foo"; + }).should.not.throw(); + b.varB.should.be.eql("Foo"); + }); + it("B instance can modify name", function() { + var c = C.new(); + (function() { + c.name = "Foo"; + }).should.not.throw(); + c.name.should.be.eql("Foo"); + c.hello().should.be.eql("Foo"); + }); + it("C instance varC is mutable", function() { + var c = C.new(); + c.varC.should.be.eql("C"); + (function() { + c.varC = "Foo"; + }).should.not.throw(); + c.varC.should.be.eql("Foo"); + }); + it("C instance can modify name", function() { + var c = C.new(); + (function() { + c.name = "Foo"; + }).should.not.throw(); + c.name.should.be.eql("Foo"); + c.hello().should.be.eql("Foo"); + }); + it("C instance can modify varB", function() { + var c = C.new(); + (function() { + c.varB = "Foo"; + }).should.not.throw(); + c.varB.should.be.eql("Foo"); + }); + }); + }); + + describe("#Super", function() { + var Foo, Bar, bar, C, c; + before(function() { + Foo = Base.extend({ + initialize: function Foo(options) { + this.name = options.name; + } + }); + + Bar = Foo.extend({ + initialize: function Bar(options) { + Foo.prototype.initialize.call(this, options); + this.type = "bar"; + } + }); + + C = Foo.extend({ + initialize: function C(options) { + Foo.prototype.initialize.call(this, options); + this.type = "C"; + } + }); + + bar = Bar.new({ + name: "test" + }); + + c = C.new({ + name: "not bar" + }); + }); + + it("Bar is prototype of Bar.new()", function() { + Bar.prototype.isPrototypeOf(bar).should.be.true; + }); + it("Foo is prototype of Bar.new()", function() { + Foo.prototype.isPrototypeOf(bar).should.be.true; + }); + it("Base is prototype of Bar.new()", function() { + Base.prototype.isPrototypeOf(bar).should.be.true; + }); + it("C is not prototype of Bar.new()", function() { + C.prototype.isPrototypeOf(bar).should.be.false; + }); + it("bar initializer was called", function() { + bar.type.should.be.eql("bar"); + }); + it("bar initializer called Foo initializer", function() { + bar.name.should.be.eql("test"); + }); + }); + + describe("#Complex inheritance", function() { + var HEX, RGB, CMYK, Color; + before(function() { + HEX = Base.extend({ + hex: function hex() { + return "#" + this.color; + } + }); + + RGB = Base.extend({ + 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); + } + }); + + CMYK = Base.extend(RGB.prototype, { + black: function black() { + var color = Math.max(Math.max(this.red(), this.green()), this.blue()); + return (1 - color / 255).toFixed(4); + }, + cyan: function cyan() { + var K = this.black(); + return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).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); + } + }); + + Color = Base.extend(HEX.prototype, RGB.prototype, CMYK.prototype, { + initialize: function Color(hex) { + this.color = hex; + } + }); + }); + + it("CMYK includes RGB but RGB is not his prototype", function() { + RGB.prototype.isPrototypeOf(CMYK.prototype).should.be.false; + var c = CMYK.new(); + RGB.prototype.isPrototypeOf(c).should.be.false; + CMYK.prototype.isPrototypeOf(c).should.be.true; + }); + + it("Color have hex, red and yellow", function() { + should(Color.prototype.hex).be.a.Function; + should(Color.prototype.red).be.a.Function; + should(Color.prototype.yellow).be.a.Function; + }); + + it("Color instance have hex, red and yellow", function() { + var c = Color.new("#ff0000"); + should(c.hex).be.a.Function; + should(c.red).be.a.Function; + should(c.yellow).be.a.Function; + }); + }); + }); + +})(); diff --git a/test/test-selfish.js b/test/test-selfish.js index abbd11c..3cce7c5 100644 --- a/test/test-selfish.js +++ b/test/test-selfish.js @@ -5,414 +5,417 @@ var should = require("should"); var Base = selfish.Base; - describe("#isPrototypeOf", function() { - it("Base is a prototype of new Base()", function() { - Base.prototype.isPrototypeOf(new Base()).should.be.true; - }); - it("Base is a prototype of Base.extend()", function() { - Base.prototype.isPrototypeOf(Base.extend().prototype).should.be.true; - }); - it("Base is a prototype of new (Base.extend())", function() { - Base.prototype.isPrototypeOf(new(Base.extend())()).should.be.true; - }); - it("Base.extend() in not prototype of Base.extend()", function() { - Base.extend().prototype.isPrototypeOf(Base.extend().prototype).should.be.false; - }); - it("Base.extend() in not prototype of new (Base.extend())", function() { - Base.extend().prototype.isPrototypeOf(new(Base.extend())()).should.be.false; - }); - it("new Base() is not prototype of new Base()", function() { - (new Base()).isPrototypeOf(new Base()).should.be.false; - }); - it("new Base() is not prototype of Base.extend()", function() { - (new Base()).isPrototypeOf(Base.extend().prototype).should.be.false; - }); - it("Base.extend() is not prototype of new Base()", function() { - Base.extend().prototype.isPrototypeOf(new Base()).should.be.false; - }); - }); - - describe("#Inheritance", function() { - var Parent, Child; - before(function() { - Parent = Base.extend({ - name: "parent", - method: function() { - return "hello " + this.name; - } + describe("new keyword syntax", function() { + describe("#isPrototypeOf", function() { + it("Base is a prototype of new Base()", function() { + Base.prototype.isPrototypeOf(new Base()).should.be.true; }); - Child = Parent.extend({ - name: "child" + it("Base is a prototype of Base.extend()", function() { + Base.prototype.isPrototypeOf(Base.extend().prototype).should.be.true; }); - }); - - describe("Parent prototype", function() { - it("Parent.prototype name is parent", function() { - Parent.prototype.name.should.be.eql("parent"); + it("Base is a prototype of new (Base.extend())", function() { + Base.prototype.isPrototypeOf(new(Base.extend())()).should.be.true; }); - it("method works on prototype", function() { - Parent.prototype.method().should.be.eql("hello parent"); + it("Base.extend() in not prototype of Base.extend()", function() { + Base.extend().prototype.isPrototypeOf(Base.extend().prototype).should.be.false; }); - }); - - describe("Parent instances", function() { - var p; - before(function() { - p = new Parent(); - }); - it("Parent instance inherits name", function() { - var p = new Parent(); - p.name.should.be.eql(Parent.prototype.name); + it("Base.extend() in not prototype of new (Base.extend())", function() { + Base.extend().prototype.isPrototypeOf(new(Base.extend())()).should.be.false; }); - it("method behaves same on the prototype", function() { - p.method().should.be.eql(Parent.prototype.method()); - }); - }); - - it("Parent decedent inherits name", function() { - Parent.extend({}).prototype.name.should.be.eql(Parent.prototype.name); - }); - - describe("Child prototype", function() { - it("Child overrides name", function() { - Child.prototype.name.should.not.be.eql(Parent.prototype.name); + it("new Base() is not prototype of new Base()", function() { + (new Base()).isPrototypeOf(new Base()).should.be.false; }); - it("method refers to instance property", function() { - Child.prototype.method().should.be.eql("hello child"); + it("new Base() is not prototype of Base.extend()", function() { + (new Base()).isPrototypeOf(Base.extend().prototype).should.be.false; }); - it("Child inherits method", function() { - Child.prototype.method.should.be.eql(Parent.prototype.method); + it("Base.extend() is not prototype of new Base()", function() { + Base.extend().prototype.isPrototypeOf(new Base()).should.be.false; }); }); - describe("Child instances", function() { - var c; + describe("#Inheritance", function() { + var Parent, Child; before(function() { - c = new Child(); + Parent = Base.extend({ + name: "parent", + method: function() { + return "hello " + this.name; + } + }); + Child = Parent.extend({ + name: "child" + }); }); - it("Child instances inherit name", function() { - c.name.should.be.eql(Child.prototype.name); - }); - it("Child instances inherit method", function() { - c.method.should.be.eql(Parent.prototype.method); + describe("Parent prototype", function() { + it("Parent.prototype name is parent", function() { + Parent.prototype.name.should.be.eql("parent"); + }); + it("method works on prototype", function() { + Parent.prototype.method().should.be.eql("hello parent"); + }); }); - }); - it("Child decedents inherit name", function() { - Child.extend().prototype.name.should.be.eql(Child.prototype.name); - }); + describe("Parent instances", function() { + var p; + before(function() { + p = new Parent(); + }); + it("Parent instance inherits name", function() { + var p = new Parent(); + p.name.should.be.eql(Parent.prototype.name); + }); + it("method behaves same on the prototype", function() { + p.method().should.be.eql(Parent.prototype.method()); + }); + }); - it("Child decedent inherit method", function() { - Child.extend().prototype.method.should.be.eql(Parent.prototype.method); - }); + it("Parent decedent inherits name", function() { + Parent.extend({}).prototype.name.should.be.eql(Parent.prototype.name); + }); - it("method may be overridden", function() { - var CC = Child.extend({ - name: "decedent" + describe("Child prototype", function() { + it("Child overrides name", function() { + Child.prototype.name.should.not.be.eql(Parent.prototype.name); + }); + it("method refers to instance property", function() { + Child.prototype.method().should.be.eql("hello child"); + }); + it("Child inherits method", function() { + Child.prototype.method.should.be.eql(Parent.prototype.method); + }); }); - var cc = new CC(); - cc.method().should.be.eql("hello decedent"); - }); - }); - describe("#prototype immutability", function() { - it("Base is immutable", function() { - (function() { - Base.extend = null; - }).should.throw(); - }); - it("Base prototype is immutable", function() { - (function() { - Base.prototype.foo = null; - }).should.throw(); - }); - it("Base prototype is non-configurable", function() { - (function() { - Base.foo = null; - }).should.throw(); - }); - it("Can't delete properties on Base", function() { - (function() { - delete Base.extend; - }).should.throw(); - }); - }); + describe("Child instances", function() { + var c; + before(function() { + c = new Child(); + }); - describe("#extended prototype immutability", function() { - var Foo, Bar; - before(function() { - Foo = Base.extend({ - name: "hello", - rename: function rename(name) { - this.name = name; - }, - nothing: function nothing() {} - }); - Bar = Foo.extend({ - rename: function rename() { - return this.name; - } + it("Child instances inherit name", function() { + c.name.should.be.eql(Child.prototype.name); + }); + it("Child instances inherit method", function() { + c.method.should.be.eql(Parent.prototype.method); + }); }); - }); - it("Can't change prototype properties", function() { - (function() { - Foo.extend = null; - }).should.throw(); - }); - it("Can't add properties", function() { - (function() { - Foo.prototype.bar = null; - }).should.throw(); - (function() { - Foo.bar = null; - }).should.throw(); - }); - - it("Can't remove prototype properties", function() { - (function() { - delete Foo.prototype.name; - }).should.throw(); - }); + it("Child decedents inherit name", function() { + Child.extend().prototype.name.should.be.eql(Child.prototype.name); + }); - it("Method's can't mutate prototypes", function() { - (function() { - Foo.prototype.rename("new name"); - }).should.throw(); - }); + it("Child decedent inherit method", function() { + Child.extend().prototype.method.should.be.eql(Parent.prototype.method); + }); - it("properties may be overridden on decedents", function() { - Bar.prototype.rename().should.be.eql(Foo.prototype.name); - Bar.prototype.rename.should.not.be.eql(Foo.prototype.rename); + it("method may be overridden", function() { + var CC = Child.extend({ + name: "decedent" + }); + var cc = new CC(); + cc.method().should.be.eql("hello decedent"); + }); }); - }); - describe("#instance mutability", function() { - var Foo, f1; - before(function() { - Foo = Base.extend({ - name: "foo", - init: function init(number) { - this.number = number; - } + describe("#prototype immutability", function() { + it("Base is immutable", function() { + (function() { + Base.extend = null; + }).should.throw(); + }); + it("Base prototype is immutable", function() { + (function() { + Base.prototype.foo = null; + }).should.throw(); + }); + it("Base prototype is non-configurable", function() { + (function() { + Base.foo = null; + }).should.throw(); + }); + it("Can't delete properties on Base", function() { + (function() { + delete Base.extend; + }).should.throw(); }); - f1 = new Foo(); - }); - it("instance is mutable", function() { - should(f1.alias).be.ko; - f1.alias = "f1"; - f1.alias.should.be.eql("f1"); - }); - it("own properties are deletable", function() { - f1.alias = "f1"; - ("alias" in f1).should.be.true; - delete f1.alias; - ("alias" in f1).should.be.false; - }); - it("methods can mutate instance's own properties", function() { - f1.init(1); - f1.number.should.be.eql(1); - f1.init(3); - f1.number.should.be.eql(3); - }); - it("properties can be directly mutated", function() { - f1.name.should.be.eql("foo"); - (function() { - f1.name = "lol"; - }).should.not.throw(); - f1.name.should.be.eql("lol"); }); - describe("#Deeper inheritance mutability", function() { - var A, B, C; + + describe("#extended prototype immutability", function() { + var Foo, Bar; before(function() { - A = Base.extend({ - name: "A", - hello: function() { + Foo = Base.extend({ + name: "hello", + rename: function rename(name) { + this.name = name; + }, + nothing: function nothing() {} + }); + Bar = Foo.extend({ + rename: function rename() { return this.name; } }); - B = A.extend({ - varB: "B" - }); - C = B.extend({ - varC: "C" - }); }); - it("A instance can modify name", function() { - var a = new A(); - a.name.should.be.eql("A"); - a.hello().should.be.eql("A"); + it("Can't change prototype properties", function() { (function() { - a.name = "Foo"; - }).should.not.throw(); - a.name.should.be.eql("Foo"); - a.hello().should.be.eql("Foo"); + Foo.extend = null; + }).should.throw(); }); - it("B instance inherits A.name", function() { - var b = new B(); - b.name.should.be.eql("A"); - b.hello().should.be.eql("A"); - }); - it("B instance varB is mutable", function() { - var b = new B(); - b.varB.should.be.eql("B"); + + it("Can't add properties", function() { (function() { - b.varB = "Foo"; - }).should.not.throw(); - b.varB.should.be.eql("Foo"); - }); - it("B instance can modify name", function() { - var c = new C(); + Foo.prototype.bar = null; + }).should.throw(); (function() { - c.name = "Foo"; - }).should.not.throw(); - c.name.should.be.eql("Foo"); - c.hello().should.be.eql("Foo"); + Foo.bar = null; + }).should.throw(); }); - it("C instance varC is mutable", function() { - var c = new C(); - c.varC.should.be.eql("C"); + + it("Can't remove prototype properties", function() { (function() { - c.varC = "Foo"; - }).should.not.throw(); - c.varC.should.be.eql("Foo"); + delete Foo.prototype.name; + }).should.throw(); }); - it("C instance can modify name", function() { - var c = new C(); + + it("Method's can't mutate prototypes", function() { (function() { - c.name = "Foo"; - }).should.not.throw(); - c.name.should.be.eql("Foo"); - c.hello().should.be.eql("Foo"); + Foo.prototype.rename("new name"); + }).should.throw(); + }); + + it("properties may be overridden on decedents", function() { + Bar.prototype.rename().should.be.eql(Foo.prototype.name); + Bar.prototype.rename.should.not.be.eql(Foo.prototype.rename); }); - it("C instance can modify varB", function() { - var c = new C(); + }); + + describe("#instance mutability", function() { + var Foo, f1; + before(function() { + Foo = Base.extend({ + name: "foo", + init: function init(number) { + this.number = number; + } + }); + f1 = new Foo(); + }); + it("instance is mutable", function() { + should(f1.alias).be.ko; + f1.alias = "f1"; + f1.alias.should.be.eql("f1"); + }); + it("own properties are deletable", function() { + f1.alias = "f1"; + ("alias" in f1).should.be.true; + delete f1.alias; + ("alias" in f1).should.be.false; + }); + it("methods can mutate instance's own properties", function() { + f1.init(1); + f1.number.should.be.eql(1); + f1.init(3); + f1.number.should.be.eql(3); + }); + it("properties can be directly mutated", function() { + f1.name.should.be.eql("foo"); (function() { - c.varB = "Foo"; + f1.name = "lol"; }).should.not.throw(); - c.varB.should.be.eql("Foo"); + f1.name.should.be.eql("lol"); + }); + describe("#Deeper inheritance mutability", function() { + var A, B, C; + before(function() { + A = Base.extend({ + name: "A", + hello: function() { + return this.name; + } + }); + B = A.extend({ + varB: "B" + }); + C = B.extend({ + varC: "C" + }); + }); + it("A instance can modify name", function() { + var a = new A(); + a.name.should.be.eql("A"); + a.hello().should.be.eql("A"); + (function() { + a.name = "Foo"; + }).should.not.throw(); + a.name.should.be.eql("Foo"); + a.hello().should.be.eql("Foo"); + }); + it("B instance inherits A.name", function() { + var b = new B(); + b.name.should.be.eql("A"); + b.hello().should.be.eql("A"); + }); + it("B instance varB is mutable", function() { + var b = new B(); + b.varB.should.be.eql("B"); + (function() { + b.varB = "Foo"; + }).should.not.throw(); + b.varB.should.be.eql("Foo"); + }); + it("B instance can modify name", function() { + var c = new C(); + (function() { + c.name = "Foo"; + }).should.not.throw(); + c.name.should.be.eql("Foo"); + c.hello().should.be.eql("Foo"); + }); + it("C instance varC is mutable", function() { + var c = new C(); + c.varC.should.be.eql("C"); + (function() { + c.varC = "Foo"; + }).should.not.throw(); + c.varC.should.be.eql("Foo"); + }); + it("C instance can modify name", function() { + var c = new C(); + (function() { + c.name = "Foo"; + }).should.not.throw(); + c.name.should.be.eql("Foo"); + c.hello().should.be.eql("Foo"); + }); + it("C instance can modify varB", function() { + var c = new C(); + (function() { + c.varB = "Foo"; + }).should.not.throw(); + c.varB.should.be.eql("Foo"); + }); }); }); - }); - describe("#Super", function() { - var Foo, Bar, bar, C, c; - before(function() { - Foo = Base.extend({ - initialize: function Foo(options) { - this.name = options.name; - } - }); + describe("#Super", function() { + var Foo, Bar, bar, C, c; + before(function() { + Foo = Base.extend({ + initialize: function Foo(options) { + this.name = options.name; + } + }); - Bar = Foo.extend({ - initialize: function Bar(options) { - Foo.prototype.initialize.call(this, options); - this.type = "bar"; - } - }); + Bar = Foo.extend({ + initialize: function Bar(options) { + Foo.prototype.initialize.call(this, options); + this.type = "bar"; + } + }); - C = Foo.extend({ - initialize: function C(options) { - Foo.prototype.initialize.call(this, options); - this.type = "C"; - } - }); + C = Foo.extend({ + initialize: function C(options) { + Foo.prototype.initialize.call(this, options); + this.type = "C"; + } + }); - bar = new Bar({ - name: "test" + bar = new Bar({ + name: "test" + }); + + c = new C({ + name: "not bar" + }); }); - c = new C({ - name: "not bar" + it("Bar is prototype of new Bar()", function() { + Bar.prototype.isPrototypeOf(bar).should.be.true; + }); + it("Foo is prototype of new Bar()", function() { + Foo.prototype.isPrototypeOf(bar).should.be.true; + }); + it("Base is prototype of new Bar()", function() { + Base.prototype.isPrototypeOf(bar).should.be.true; + }); + it("C is not prototype of new Bar()", function() { + C.prototype.isPrototypeOf(bar).should.be.false; + }); + it("bar initializer was called", function() { + bar.type.should.be.eql("bar"); + }); + it("bar initializer called Foo initializer", function() { + bar.name.should.be.eql("test"); }); }); - it("Bar is prototype of new Bar()", function() { - Bar.prototype.isPrototypeOf(bar).should.be.true; - }); - it("Foo is prototype of new Bar()", function() { - Foo.prototype.isPrototypeOf(bar).should.be.true; - }); - it("Base is prototype of new Bar()", function() { - Base.prototype.isPrototypeOf(bar).should.be.true; - }); - it("C is not prototype of new Bar()", function() { - C.prototype.isPrototypeOf(bar).should.be.false; - }); - it("bar initializer was called", function() { - bar.type.should.be.eql("bar"); - }); - it("bar initializer called Foo initializer", function() { - bar.name.should.be.eql("test"); - }); - }); + describe("#Complex inheritance", function() { + var HEX, RGB, CMYK, Color; + before(function() { + HEX = Base.extend({ + hex: function hex() { + return "#" + this.color; + } + }); + + RGB = Base.extend({ + 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); + } + }); - describe("#Complex inheritance", function() { - var HEX, RGB, CMYK, Color; - before(function() { - HEX = Base.extend({ - hex: function hex() { - return "#" + this.color; - } - }); - - RGB = Base.extend({ - 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); - } - }); - - CMYK = Base.extend(RGB.prototype, { - black: function black() { - var color = Math.max(Math.max(this.red(), this.green()), this.blue()); - return (1 - color / 255).toFixed(4); - }, - cyan: function cyan() { - var K = this.black(); - return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).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); - } - }); - - Color = Base.extend(HEX.prototype, RGB.prototype, CMYK.prototype, { - initialize: function Color(hex) { - this.color = hex; - } + CMYK = Base.extend(RGB.prototype, { + black: function black() { + var color = Math.max(Math.max(this.red(), this.green()), this.blue()); + return (1 - color / 255).toFixed(4); + }, + cyan: function cyan() { + var K = this.black(); + return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).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); + } + }); + + Color = Base.extend(HEX.prototype, RGB.prototype, CMYK.prototype, { + initialize: function Color(hex) { + this.color = hex; + } + }); }); - }); - it("CMYK includes RGB but RGB is not his prototype", function() { - RGB.prototype.isPrototypeOf(CMYK.prototype).should.be.false; - var c = new CMYK(); - RGB.prototype.isPrototypeOf(c).should.be.false; - CMYK.prototype.isPrototypeOf(c).should.be.true; - }); + it("CMYK includes RGB but RGB is not his prototype", function() { + RGB.prototype.isPrototypeOf(CMYK.prototype).should.be.false; + var c = new CMYK(); + RGB.prototype.isPrototypeOf(c).should.be.false; + CMYK.prototype.isPrototypeOf(c).should.be.true; + }); - it("Color have hex, red and yellow", function() { - should(Color.prototype.hex).be.a.Function; - should(Color.prototype.red).be.a.Function; - should(Color.prototype.yellow).be.a.Function; - }); + it("Color have hex, red and yellow", function() { + should(Color.prototype.hex).be.a.Function; + should(Color.prototype.red).be.a.Function; + should(Color.prototype.yellow).be.a.Function; + }); - it("Color instance have hex, red and yellow", function() { - var c = new Color("#ff0000"); - should(c.hex).be.a.Function; - should(c.red).be.a.Function; - should(c.yellow).be.a.Function; + it("Color instance have hex, red and yellow", function() { + var c = new Color("#ff0000"); + should(c.hex).be.a.Function; + should(c.red).be.a.Function; + should(c.yellow).be.a.Function; + }); }); }); + })();