├── src ├── commonjs │ ├── pre.js │ └── post.js ├── amd │ ├── post.js │ └── pre.js └── p.js ├── .jshintrc ├── .npmignore ├── .gitignore ├── .travis.yml ├── index.js ├── examples ├── varargs.js ├── ninja.js ├── shapes.js └── lottery.coffee ├── package.json ├── LICENSE ├── Makefile ├── CHANGELOG.md ├── README.md └── test └── p.test.js /src/commonjs/pre.js: -------------------------------------------------------------------------------- 1 | // pass 2 | -------------------------------------------------------------------------------- /src/amd/post.js: -------------------------------------------------------------------------------- 1 | return P; 2 | }); 3 | -------------------------------------------------------------------------------- /src/commonjs/post.js: -------------------------------------------------------------------------------- 1 | exports.P = P; 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailing": true 3 | } 4 | -------------------------------------------------------------------------------- /src/amd/pre.js: -------------------------------------------------------------------------------- 1 | define('pjs', function() { 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # empty - here to override the automatic usage of .gitignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.tgz 2 | /build 3 | 4 | npm-*.log 5 | /node_modules 6 | 7 | *.un~ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # see http://travis-ci.org/jayferd/pjs 2 | language: node_js 3 | node_js: 4 | - 0.10 5 | - 0.11 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.P = require('./build/p.commonjs').P; 2 | exports.version = require('./package.json').version; 3 | -------------------------------------------------------------------------------- /examples/varargs.js: -------------------------------------------------------------------------------- 1 | var Breakfast = P(function(breakfast) { 2 | breakfast.init = function(bacon, eggs) { 3 | this.bacon = bacon; 4 | this.eggs = eggs; 5 | }; 6 | }); 7 | 8 | Breakfast('bacon', 'eggs') // => { bacon: 'bacon', eggs: 'eggs' } 9 | 10 | // it's just a function. Use `apply` to call it with varargs. 11 | // NB: this is impossible with traditional JS and the `new` keyword. 12 | var ingredients = ['bacon', 'eggs']; 13 | Breakfast.apply(null, ingredients) // => { bacon: 'bacon', eggs: 'eggs' } 14 | -------------------------------------------------------------------------------- /examples/ninja.js: -------------------------------------------------------------------------------- 1 | var Person = P(function(person) { 2 | person.init = function(isDancing) { this.dancing = isDancing }; 3 | person.dance = function() { return this.dancing }; 4 | }); 5 | 6 | var Ninja = P(Person, function(ninja, person) { 7 | ninja.init = function() { person.init.call(this, false) }; 8 | ninja.swingSword = function() { return 'swinging sword!' }; 9 | }); 10 | 11 | var p = Person(true); 12 | p.dance(); // => true 13 | 14 | var n = Ninja(); 15 | n.dance(); // => false 16 | n.swingSword(); // => 'swinging sword!' 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pjs", 3 | "version": "5.1.1", 4 | "description": "A lightweight class system. It's just prototypes!", 5 | "keywords": ["class", "pjs", "P", "inheritance", "super"], 6 | "author": "Jeanine Adkisson ", 7 | "repository": "git://github.com/jneen/pjs", 8 | "license": "MIT", 9 | 10 | "files": ["index.js", "src", "test", "Makefile", "package.json", "README.md", "CHANGELOG.md", "build/p.commonjs.js"], 11 | "main": "index.js", 12 | "devDependencies": { 13 | "mocha": "*", 14 | "uglify-js": "*" 15 | }, 16 | "scripts": { 17 | "test": "make test" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/shapes.js: -------------------------------------------------------------------------------- 1 | // from http://onestepback.org/articles/poly/ 2 | 3 | var Shape = P(function(shape) { 4 | shape.moveTo = 5 | shape.init = function(x, y) { this.x = x; this.y = y; }; 6 | shape.move = function(x, y) { this.moveTo(this.x + x, this.y + y); }; 7 | }); 8 | 9 | var Rectangle = P(Shape, function(rect, shape) { 10 | // @override 11 | rect.init = function(x, y, w, h) { 12 | shape.init.call(this, x, y); 13 | this.w = w; 14 | this.h = h; 15 | }; 16 | }); 17 | 18 | var Circle = P(Shape, function(circle, shape) { 19 | // @override 20 | circle.init = function(x, y, radius) { 21 | shape.init.call(this, x, y); 22 | this.radius = radius; 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # MIT license. See http://www.opensource.org/licenses/mit-license.php 2 | 3 | Copyright (c) 2011, 2012 Jeanine Adkisson. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # -*- globals -*- # 2 | SRC_DIR = src 3 | BUILD_DIR = build 4 | CLEAN += $(BUILD_DIR)/* 5 | SRC = $(SRC_DIR)/p.js 6 | 7 | .PHONY: all 8 | all: minify commonjs amd report 9 | 10 | # -*- minification -*- # 11 | UGLIFYJS ?= ./node_modules/.bin/uglifyjs 12 | UGLIFY_OPTS += -m -c hoist_vars=true,unsafe=true 13 | UGLY = $(BUILD_DIR)/p.min.js 14 | 15 | $(UGLY): $(SRC) 16 | $(UGLIFYJS) $(UGLIFY_OPTS) $< -o $@ 17 | 18 | %.min.js: %.js 19 | $(UGLIFYJS) $(UGLIFY_OPTS) $< -o $@ 20 | 21 | minify: $(UGLY) 22 | 23 | # special builds 24 | COMMONJS = $(BUILD_DIR)/p.commonjs.js 25 | 26 | $(BUILD_DIR)/p.%.js: $(SRC_DIR)/%/pre.js $(SRC) $(SRC_DIR)/%/post.js 27 | mkdir -p $(BUILD_DIR) 28 | cat $^ > $@ 29 | 30 | .PHONY: commonjs 31 | commonjs: $(COMMONJS) 32 | 33 | .PHONY: amd 34 | amd: $(BUILD_DIR)/p.amd.js $(BUILD_DIR)/p.amd.min.js 35 | 36 | .PHONY: report 37 | report: $(UGLY) 38 | wc -c $(UGLY) 39 | 40 | # -*- testing -*- # 41 | MOCHA ?= ./node_modules/.bin/mocha 42 | TESTS = ./test/*.test.js 43 | .PHONY: test 44 | test: $(COMMONJS) 45 | $(MOCHA) $(TESTS) 46 | 47 | # -*- packaging -*- # 48 | 49 | VERSION = $(shell node -e 'console.log(require("./package.json").version)') 50 | PACKAGE = pjs-$(VERSION).tgz 51 | CLEAN += pjs-*.tgz 52 | 53 | $(PACKAGE): clean commonjs test 54 | npm pack . 55 | 56 | .PHONY: package 57 | package: $(PACKAGE) 58 | 59 | .PHONY: publish 60 | publish: $(PACKAGE) 61 | npm publish $(PACKAGE) 62 | 63 | # -*- cleanup -*- # 64 | .PHONY: clean 65 | clean: 66 | rm -f $(CLEAN) 67 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## version 5.1.1: 2014-06-25 2 | 3 | * Update name, email, and repository for npm 4 | 5 | ## version 5.1.0: 2014-03-09 6 | 7 | * Statically distribute build/p.commonjs.js 8 | 9 | ## version 5.0.0: 2013-10-27 10 | 11 | * Allow idiomatic (coffeescript or es6) subclassing of pjs classes, at the 12 | cost of subtly breaking back-compat with `this.constructor(...)`. To fix 13 | this, just put a `new` in front of the call. 14 | 15 | ## version 4.0.0: 2013-06-13 16 | 17 | * remove .mixin, and add .extend (see #18) 18 | 19 | ## version 3.1.0: 2013-06-13 20 | 21 | * Add .p as an alias for .prototype 22 | * slight minifier optimization 23 | 24 | ## version 3.0.2: 2013-04-04 25 | 26 | * Build process fixes (thanks @danro!) 27 | * rename BareConstructor to SuperclassBare (@laughinghan) 28 | 29 | ## version 3.0.1: 2013-01-28 30 | (bad release) 31 | 32 | * Fix #13: don't call the constructor when making the new prototype. 33 | 34 | ## version 3.0.0: 2013-01-18 35 | 36 | * Introduce `MyClass.Bare` as a way of allocating uninitialized 37 | instances 38 | * Created classes will now create instances in exactly the same way 39 | no matter what the calling context. In particular this means 40 | `new` works as expected: 41 | 42 | ``` js 43 | new MyClass(1, 2) // calls MyClass::init with arguments (1, 2) 44 | ``` 45 | 46 | ## version 2.0.2: 2013-01-17 47 | 48 | * Started a CHANGELOG 49 | * Removed support for the `.fn` property which was buggy and unused 50 | * Down to 525 bytes minified 51 | -------------------------------------------------------------------------------- /examples/lottery.coffee: -------------------------------------------------------------------------------- 1 | # see http://mislav.uniqpath.com/poignant-guide/book/chapter-5.html 2 | {P} = require('p') 3 | {uniq, intersection} = require('underscore') 4 | 5 | randInt = (max) -> Math.floor(max * Math.random()) 6 | 7 | exports.LotteryTicket = LotteryTicket = P (ticket) -> 8 | MAX = 25 9 | MIN = 1 10 | ticket.init = (picks...) -> 11 | @picks = picks 12 | @purchased = new Date 13 | if uniq(picks).length isnt 3 14 | throw new Error("three unique numbers must be picked.") 15 | 16 | for pick in picks 17 | unless pick in [MIN..MAX] 18 | throw new Error("all three picks must be between #{MIN} and #{MAX}.") 19 | 20 | ticket.score = (winner) -> intersection(@picks, winner.picks).length 21 | 22 | randomPick = -> MIN + randInt(MAX) 23 | @random = -> 24 | try 25 | @(randomPick(), randomPick(), randomPick()) 26 | catch e 27 | @random() 28 | 29 | LotteryDraw = ( -> 30 | tickets = {} 31 | 32 | buy: (customer, tickets...) -> 33 | tickets[customer] ?= [] 34 | tickets[customer] = tickets[customer].concat(tickets) 35 | 36 | play: -> 37 | draw = LotteryTicket.random() 38 | winners = {} 39 | for buyer, ticketList in tickets 40 | for ticket in ticketList 41 | score = ticket.score(draw) 42 | (winners[buyer] ?= []).push([ticket, score]) unless score is 0 43 | 44 | tickets = {} 45 | winners 46 | )() 47 | 48 | # buy some tickets 49 | LotteryDraw.buy 'Gram-yol', 50 | LotteryTicket(25, 14, 33), LotteryTicket(12, 11, 29) 51 | LotteryDraw.buy 'Tarker-azain', LotteryTicket(13, 15, 29) 52 | LotteryDraw.buy 'Bramlor-exxon', LotteryTicket(2, 6, 14) 53 | 54 | # play and report 55 | for winner, ticket in LotteryDraw.play() 56 | console.log("#{winner} won on #{tickets.length} ticket(s)!") 57 | for ticket, score in ticket 58 | console.log("\t#{ticket.picks.join(', ')}: #{score} 59 | -------------------------------------------------------------------------------- /src/p.js: -------------------------------------------------------------------------------- 1 | var P = (function(prototype, ownProperty, undefined) { 2 | return function P(_superclass /* = Object */, definition) { 3 | // handle the case where no superclass is given 4 | if (definition === undefined) { 5 | definition = _superclass; 6 | _superclass = Object; 7 | } 8 | 9 | // C is the class to be returned. 10 | // 11 | // When called, creates and initializes an instance of C, unless 12 | // `this` is already an instance of C, then just initializes `this`; 13 | // either way, returns the instance of C that was initialized. 14 | // 15 | // TODO: the Chrome inspector shows all created objects as `C` 16 | // rather than `Object`. Setting the .name property seems to 17 | // have no effect. Is there a way to override this behavior? 18 | function C() { 19 | var self = this instanceof C ? this : new Bare; 20 | self.init.apply(self, arguments); 21 | return self; 22 | } 23 | 24 | // C.Bare is a class with a noop constructor. Its prototype will be 25 | // the same as C, so that instances of C.Bare are instances of C. 26 | // `new MyClass.Bare` then creates new instances of C without 27 | // calling .init(). 28 | function Bare() {} 29 | C.Bare = Bare; 30 | 31 | // Extend the prototype chain: first use Bare to create an 32 | // uninitialized instance of the superclass, then set up Bare 33 | // to create instances of this class. 34 | var _super = Bare[prototype] = _superclass[prototype]; 35 | var proto = Bare[prototype] = C[prototype] = C.p = new Bare; 36 | 37 | // pre-declaring the iteration variable for the loop below to save 38 | // a `var` keyword after minification 39 | var key; 40 | 41 | // set the constructor property on the prototype, for convenience 42 | proto.constructor = C; 43 | 44 | C.extend = function(def) { return P(C, def); } 45 | 46 | return (C.open = function(def) { 47 | if (typeof def === 'function') { 48 | // call the defining function with all the arguments you need 49 | // extensions captures the return value. 50 | def = def.call(C, proto, _super, C, _superclass); 51 | } 52 | 53 | // ...and extend it 54 | if (typeof def === 'object') { 55 | for (key in def) { 56 | if (ownProperty.call(def, key)) { 57 | proto[key] = def[key]; 58 | } 59 | } 60 | } 61 | 62 | // if no init, assume we're inheriting from a non-Pjs class, so 63 | // default to using the superclass constructor. 64 | if (!('init' in proto)) proto.init = _superclass; 65 | 66 | return C; 67 | })(definition); 68 | } 69 | 70 | // as a minifier optimization, we've closured in a few helper functions 71 | // and the string 'prototype' (C[p] is much shorter than C.prototype) 72 | })('prototype', ({}).hasOwnProperty); 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/jneen/pjs.png)](http://travis-ci.org/jneen/pjs) 2 | 3 | # P.js 4 | 5 | P.js is a lightweight layer over javascript's built-in inheritance system that keeps all the good stuff and hides all the crap. 6 | 7 | ## just show me some code already 8 | 9 | Okay. 10 | 11 | ``` js 12 | // adapted from coffeescript.org 13 | // P.js exposes the `P` variable 14 | var Animal = P(function(animal) { 15 | animal.init = function(name) { this.name = name; }; 16 | 17 | animal.move = function(meters) { 18 | console.log(this.name+" moved "+meters+"m."); 19 | } 20 | }); 21 | 22 | var Snake = P(Animal, function(snake, animal) { 23 | snake.move = function() { 24 | console.log("Slithering..."); 25 | animal.move.call(this, 5); 26 | }; 27 | }); 28 | 29 | var Horse = P(Animal, function(horse, animal) { 30 | horse.move = function() { 31 | console.log("Galloping..."); 32 | animal.move.call(this, 45); 33 | }; 34 | }); 35 | 36 | var sam = Snake("Sammy the Python") 37 | , tom = Horse("Tommy the Palomino") 38 | ; 39 | 40 | sam.move() 41 | tom.move() 42 | ``` 43 | 44 | ## how is pjs different from X 45 | 46 | Most class systems for JS let you define classes by passing an object. P.js lets you pass a function instead, which allows you to closure private methods and macros. It's also <0.4kb minified (`make report`: 467). 47 | 48 | ### why doesn't pjs suck? 49 | 50 | Unlike [some][prototypejs] [other][classjs] [frameworks][joose] [out][zjs] [there][structr], Pjs doesn't do any of this: 51 | 52 | - interfaces, abstract static factory factories, [and][joose] [other][prototypejs] [bloat][zjs] 53 | - use Object.create (it even works in IE < 8!) 54 | - break `instanceof` 55 | - [hack functions onto `this` at runtime][classjs] 56 | - rely on magical object keys which don't minify (the only special name is `init`) 57 | 58 | [prototypejs]: http://prototypejs.org/learn/class-inheritance 59 | [classjs]: https://github.com/kilhage/class.js 60 | [zjs]: http://code.google.com/p/zjs/ 61 | [joose]: http://joose.it 62 | [structr]: https://www.npmjs.com/package/structr 63 | 64 | ## what can i do with pjs? 65 | 66 | - inheritable constructors (via the optional `init` method) 67 | - closure-based "private" methods (see below) 68 | - easily call `super` on public methods without any dirty hacks 69 | - instantiate your objects without calling the constructor (absolutely necessary for inheritance) 70 | - construct objects with variable arguments 71 | 72 | ## how do i use pjs? 73 | 74 | You can call `P` in a few different ways: 75 | 76 | ``` js 77 | // this defines a class that inherits directly from Object. 78 | P(function(proto, super, class, superclass) { 79 | // define private methods as regular functions that take 80 | // `self` (or `me`, or `it`, or anything you really want) 81 | function myPrivateMethod(self, arg1, arg2) { 82 | // ... 83 | } 84 | 85 | proto.init = function() { 86 | myPrivateMethod(this, 1, 2) 87 | }; 88 | 89 | // you can also return an object from this function, which will 90 | // be merged into the prototype. 91 | return { thing: 3 }; 92 | }); 93 | 94 | // this defines a class that inherits from MySuperclass 95 | P(MySuperclass, function(proto, super, class, superclass) { 96 | proto.init = function() { 97 | // call superclass methods with super.method.call(this, ...) 98 | // or super.method.apply(this, arguments) 99 | super.init.call(this); 100 | }; 101 | }); 102 | 103 | // for shorthand, you can pass an object in lieu of the function argument, 104 | // but you lose the niceness of super and private methods. 105 | P({ init: function(a) { this.thing = a } }); 106 | 107 | MyClass = P(function(p) { p.init = function(a, b) { console.log("init!", a, b) }; }); 108 | // instantiate objects by calling the class as a function 109 | MyClass(1, 2) // => init!, 1, 2 110 | 111 | // to initialize with varargs, use `apply` like any other function. 112 | var argsList = [1, 2]; 113 | MyClass.apply(null, argsList) // init!, 1, 2 114 | 115 | // you can use it like an idiomatic class: 116 | // `new` is optional, not really recommended. 117 | new MyClass(1, 2) // => init!, 1, 2 118 | // non-pjs idiomatic subclass 119 | function Subclass(a) { MyClass.call(this, a, a); } 120 | new Subclass(3) // => init!, 3, 3 121 | new Subclass(3) instanceof MyClass // => true 122 | 123 | // `new` may be used to "force" instantiation when ambiguous, 124 | // for example in a factory method that creates new instances 125 | MyClass.prototype.clone = function(a, b) { 126 | return new this.constructor(a, b); 127 | }; 128 | // because without `new`, `this.constructor(a, b)` is equivalent to 129 | // `MyClass.call(this, a, b)` which as we saw in the previous example 130 | // mutates `this` rather than creating new instances 131 | 132 | // allocate uninitialized objects with .Bare 133 | // (much like Ruby's Class#allocate) 134 | new MyClass.Bare // nothing logged 135 | new MyClass.Bare instanceof MyClass // => true 136 | 137 | // you can use `.open` to reopen a class. This has the same behavior 138 | // as the regular definitions. 139 | // note that _super will still be set to the class's prototype 140 | MyClass = P({ a: 1 }); 141 | var myInst = MyClass(); 142 | MyClass.open(function(proto) { proto.a = 2 }); 143 | myInst.a // => 2 144 | MyClass.open(function(proto, _super) { /* _super is Object.prototype here */ }); 145 | 146 | // you can also use `.extend(definition)` to create new subclasses. This is equivalent 147 | // to calling P with two arguments. 148 | var Subclass = MyClass.extend({ a: 3 }); 149 | ``` 150 | 151 | ## how do i use pjs in node.js? 152 | 153 | Assuming you have it installed (via `npm install pjs`), you can import it with 154 | 155 | ``` js 156 | var P = require('pjs').P; 157 | ``` 158 | 159 | and go about your business. 160 | 161 | ## what is all this Makefile stuff about 162 | 163 | It's super useful! In addition to `make`, Pjs uses some build tools written on 164 | [Node][]. With the [Node Package Manager][npm] that comes with recent versions 165 | of it, just run 166 | 167 | npm install 168 | 169 | from the root directory of the repo and `make` will start working. 170 | 171 | [Node]: http://nodejs.org/#download 172 | [npm]: http://npmjs.org 173 | 174 | Here are the things you can build: 175 | 176 | - `make minify` 177 | generates `build/p.min.js` 178 | 179 | - `make commonjs` 180 | generates `build/p.commonjs.js`, which is the same but has `exports.P = P` at the end 181 | 182 | - `make amd` 183 | generates `build/p.amd.js`, which is the same but has `define(P)` at the end 184 | 185 | - `make test` 186 | runs the test suite using the commonjs version. Requires `mocha`. 187 | -------------------------------------------------------------------------------- /test/p.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , P = require('./../index').P 3 | ; 4 | 5 | describe('P', function() { 6 | describe('creating idiomatic classes', function() { 7 | var MyClass = P(function(p) { 8 | p.foo = 1 9 | }); 10 | 11 | it('creates functions', function() { 12 | assert.equal('function', typeof MyClass); 13 | }); 14 | 15 | it('uses the prototype', function() { 16 | assert.equal(1, MyClass.prototype.foo); 17 | }); 18 | 19 | it('respects `instanceof`', function() { 20 | assert.ok(new MyClass instanceof MyClass); 21 | assert.ok(MyClass() instanceof MyClass); 22 | }); 23 | 24 | it('respects `.constructor`', function() { 25 | var o = MyClass(); 26 | assert.ok(o.constructor === MyClass); 27 | 28 | var o2 = new o.constructor(); 29 | assert.ok(o2 instanceof MyClass); 30 | assert.ok(o2.foo === 1); 31 | 32 | var o3 = o.constructor.call(null); 33 | assert.ok(o3 instanceof MyClass); 34 | assert.ok(o3.foo === 1); 35 | }); 36 | }); 37 | 38 | describe('init', function() { 39 | var MyClass = P(function(p) { 40 | p.init = function() { 41 | this.initCalled = true; 42 | this.initArgs = arguments; 43 | }; 44 | 45 | p.initCalled = false; 46 | }); 47 | 48 | it('is called when the class is called plainly', function() { 49 | assert.ok(MyClass().initCalled); 50 | assert.equal(3, MyClass(1,2,3).initArgs[2]); 51 | assert.equal(2, MyClass.apply(null, [1, 2, 3]).initArgs[1]); 52 | }); 53 | 54 | it('is called when the class is called with `new`', function() { 55 | assert.ok((new MyClass).initCalled); 56 | assert.equal(3, (new MyClass(1,2,3)).initArgs[2]); 57 | }); 58 | 59 | it('is not called when the Bare class is called with `new`', function() { 60 | assert.ok(!(new MyClass.Bare).initCalled); 61 | }); 62 | 63 | it('maintains instanceof when instantiated with Bare', function() { 64 | assert.ok(new MyClass.Bare instanceof MyClass); 65 | }); 66 | }); 67 | 68 | describe('inheritance', function() { 69 | // see examples/ninja.js 70 | var Person = P(function(person) { 71 | person.init = function(isDancing) { this.dancing = isDancing }; 72 | person.dance = function() { return this.dancing }; 73 | }); 74 | 75 | var Ninja = P(Person, function(ninja, person) { 76 | ninja.init = function() { person.init.call(this, false) }; 77 | ninja.swingSword = function() { return 'swinging sword!' }; 78 | }); 79 | 80 | var ninja = Ninja(); 81 | 82 | it('respects instanceof', function() { 83 | assert.ok(ninja instanceof Person); 84 | }); 85 | 86 | it('inherits methods (also super)', function() { 87 | assert.equal(false, ninja.dance()); 88 | }); 89 | }); 90 | 91 | describe('inheriting non-pjs classes', function() { 92 | function IdiomaticClass() { 93 | this.initialized = true; 94 | } 95 | 96 | IdiomaticClass.prototype.initialized = false; 97 | 98 | it('inherits without calling the constructor', function() { 99 | var MySubclass = P(IdiomaticClass, {}); 100 | assert.equal(false, MySubclass.prototype.initialized); 101 | assert.equal(true, MySubclass().initialized); 102 | }); 103 | }); 104 | 105 | // for coffeescript or es6 subclassing of pjs classes 106 | describe('idiomatic subclassing of Pjs classes', function() { 107 | var MyClass = P({ 108 | init: function() { this.initCalled = true; }, 109 | initCalled: false, 110 | foo: 3 111 | }); 112 | 113 | function IdiomaticSubclass() { 114 | MyClass.call(this); 115 | } 116 | 117 | // coffeescript does something slightly different here with __extends, 118 | // but it's the same behavior 119 | IdiomaticSubclass.prototype = new MyClass.Bare; 120 | 121 | it('inherits properly', function() { 122 | var obj = new IdiomaticSubclass(); 123 | assert.ok(obj instanceof IdiomaticSubclass); 124 | assert.ok(obj instanceof MyClass); 125 | assert.equal(true, obj.initCalled); 126 | assert.equal(3, obj.foo) 127 | }); 128 | }); 129 | 130 | describe('inheriting builtins', function() { 131 | describe('Error', function() { 132 | var MyError = P(Error, {}); 133 | 134 | try { 135 | throw MyError('o noes'); 136 | } catch(e) { 137 | assert.ok(e instanceof MyError); 138 | assert.ok(e instanceof Error); 139 | } 140 | }); 141 | 142 | describe('RegExp', function() { 143 | var MyRegExp = P(RegExp, {}) 144 | , re = MyRegExp('a(b+)c') 145 | ; 146 | 147 | assert.ok(re instanceof RegExp); 148 | // pending: doesn't work yet 149 | // assert.ok(MyRegExp('a(b+)c').test('abbbbc')) 150 | }); 151 | 152 | describe('String', function() { 153 | var MyString = P(String, {}) 154 | , str = MyString('foo') 155 | ; 156 | 157 | assert.ok(str instanceof String); 158 | // pending: doesn't work yet 159 | // assert.equal('foo', str.toString()); 160 | }); 161 | 162 | describe('Array', function() { 163 | var MyArray = P(Array, {}) 164 | , ary = MyArray(1,2,3) 165 | ; 166 | 167 | assert.ok(ary instanceof Array); 168 | // apparently the Array constructor isn't destructive :( 169 | // when you `apply` it to an instance of Array, it just creates 170 | // a new one for you. Bah. 171 | 172 | // assert.equal(3, ary.length); 173 | // assert.equal(1, ary[0]); 174 | }); 175 | }); 176 | 177 | describe('definition', function() { 178 | it('passes the prototype as the first arg', function() { 179 | var proto; 180 | var MyClass = P(function(p) { proto = p; }); 181 | 182 | assert.equal(proto, MyClass.prototype); 183 | }); 184 | 185 | it('passes the superclass prototype as the second arg', function() { 186 | var _super; 187 | P(Error, function(a, b) { _super = b; }); 188 | assert.equal(_super, Error.prototype); 189 | }); 190 | 191 | it('passes the class itself as the third arg', function() { 192 | var klass; 193 | var MyClass = P(function(a, b, c) { klass = c; }); 194 | 195 | assert.equal(klass, MyClass); 196 | }); 197 | 198 | it('passes the superclass as the fourth argument', function() { 199 | var sclass; 200 | var MyClass = P(function(a, b, c, d) { sclass = d; }); 201 | assert.equal(Object, sclass); 202 | 203 | P(MyClass, function(a, b, c, d) { sclass = d; }); 204 | assert.equal(MyClass, sclass); 205 | }); 206 | 207 | it('passes the class itself as `this`', function() { 208 | var klass; 209 | var MyClass = P(function() { klass = this; }); 210 | assert.equal(MyClass, klass); 211 | }); 212 | 213 | }); 214 | 215 | describe('open', function() { 216 | var C = P(); 217 | 218 | it('reopens the class, affecting existing instances', function() { 219 | C.open({ a: 1 }); 220 | var inst = C(); 221 | C.open({ a: 2 }); 222 | 223 | assert.equal(inst.a, 2); 224 | }); 225 | }); 226 | 227 | describe('extend', function() { 228 | var C = P(); 229 | var count = 0; 230 | it('extends the class', function() { 231 | var mixin1 = function(proto) { 232 | proto.foo = 1; 233 | }; 234 | 235 | var mixin2 = { foo: 2 }; 236 | 237 | assert.equal(C().foo, undefined); 238 | 239 | assert.equal(C.extend(mixin1)().foo, 1); 240 | 241 | // chainable! 242 | assert.equal(C.extend(mixin1).extend(mixin2)().foo, 2); 243 | }); 244 | 245 | it('supports _super', function() { 246 | var mixin1 = function(proto) { 247 | proto.foo = function() { return 1 }; 248 | } 249 | 250 | var mixin2 = function(proto, _super) { 251 | proto.foo = function() { return _super.foo.call(this) + 1 }; 252 | } 253 | 254 | assert.equal(C.extend(mixin1).extend(mixin2)().foo(), 2); 255 | }); 256 | }); 257 | }); 258 | --------------------------------------------------------------------------------