Launch Week Day 3: Introducing Organization Notifications in Socket.Learn More
Socket
Book a DemoSign in
Socket

pagelet

Package Overview
Dependencies
Maintainers
5
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pagelet - npm Package Compare versions

Comparing version
0.8.2
to
1.0.0-alpha
+32
helpers.js
'use strict';
var path = require('path');
/**
* Helper function to resolve assets on the pagelet.
*
* @param {Function} constructor The Pagelet constructor
* @param {String|Array} keys Name(s) of the property, e.g. [css, js].
* @param {String} dir Optional absolute directory to resolve from.
* @returns {Pagelet}
* @api private
*/
exports.resolve = function resolve(constructor, keys, dir) {
var prototype = constructor.prototype;
keys = Array.isArray(keys) ? keys : [keys];
keys.forEach(function each(key) {
if (!prototype[key]) return;
var stack = Array.isArray(prototype[key])
? prototype[key]
: [prototype[key]];
prototype[key] = stack.filter(Boolean).map(function map(file) {
if (/^(http:|https:)?\/\//.test(file)) return file;
return path.resolve(dir || prototype.directory, file);
});
});
return constructor;
};
The MIT License (MIT)
Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman, the Contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
* { color: red }
describe('Helpers', function () {
'use strict';
var Pagelet = require('../').extend({ name: 'test' })
, custom = '/unexisting/absolute/path/to/prepend'
, helpers = require('../helpers')
, assume = require('assume');
describe('.resolve', function () {
var pagelet, P;
beforeEach(function () {
P = Pagelet.extend({
directory: __dirname,
view: 'fixtures/view.html',
css: 'fixtures/style.css',
js: '//cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js',
dependencies: [
'http://code.jquery.com/jquery-2.0.0.js',
'fixtures/custom.js'
]
});
pagelet = new P;
});
afterEach(function each() {
pagelet = null;
});
it('is a function', function () {
assume(helpers.resolve).to.be.a('function');
});
it('will resolve provided property on prototype', function () {
var result = helpers.resolve(P, 'css');
assume(result).to.equal(P);
assume(P.prototype.css).to.be.an('array');
assume(P.prototype.css.length).to.equal(1);
assume(P.prototype.css[0]).to.equal(__dirname + '/fixtures/style.css');
});
it('can resolve multiple properties at once', function () {
helpers.resolve(P, ['css', 'js']);
assume(P.prototype.css).to.be.an('array');
assume(P.prototype.js).to.be.an('array');
assume(P.prototype.css.length).to.equal(1);
assume(P.prototype.js.length).to.equal(1);
});
it('can be provided with a custom source directory', function () {
helpers.resolve(P, 'css', custom);
assume(P.prototype.css[0]).to.equal(custom + '/fixtures/style.css');
});
it('only resolves local files', function () {
helpers.resolve(P, 'js', custom);
assume(P.prototype.js[0]).to.not.include(custom);
assume(P.prototype.js[0]).to.equal('//cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js');
});
it('can handle property values that are already an array', function () {
helpers.resolve(P, 'dependencies', custom);
assume(P.prototype.dependencies.length).to.equal(2);
assume(P.prototype.dependencies[0]).to.not.include(custom);
assume(P.prototype.dependencies[0]).to.equal('http://code.jquery.com/jquery-2.0.0.js');
assume(P.prototype.dependencies[1]).to.equal(custom + '/fixtures/custom.js');
});
it('removes undefined values from the array before processing', function () {
var Undef = P.extend({
dependencies: P.prototype.dependencies.concat(
undefined
)
});
assume(Undef.prototype.dependencies.length).to.equal(3);
helpers.resolve(Undef, 'dependencies', custom);
assume(Undef.prototype.dependencies.length).to.equal(2);
assume(Undef.prototype.dependencies).to.not.include(undefined);
});
it('can be overriden', function () {
P.resolve = function () {
throw new Error('fucked');
};
P.on({});
});
});
});
+20
-3
language: node_js
node_js:
- "0.12"
- "0.11"
- "0.10"
- "0.11"
- "0.9"
- "iojs-v1.1"
- "iojs-v1.0"
before_install:
- "npm install -g npm@2.1.18"
script:
- "npm run test-travis"
after_script:
- "npm install coveralls@2.11.x && cat coverage/lcov.info | coveralls"
matrix:
fast_finish: true
allow_failures:
- node_js: "0.11"
- node_js: "0.9"
- node_js: "iojs-v1.1"
- node_js: "iojs-v1.0"
notifications:
irc:
channels: "irc.freenode.org#bigpipe"
channels:
- "irc.freenode.org#bigpipe"
on_success: change
on_failure: change
+803
-345
'use strict';
var jstringify = require('json-stringify-safe')
var Formidable = require('formidable').IncomingForm
, jstringify = require('json-stringify-safe')
, fabricate = require('fabricator')
, helpers = require('./helpers')
, debug = require('diagnostics')
, dot = require('dot-component')
, Stream = require('stream')
, Temper = require('temper')
, destroy = require('demolish')
, Route = require('routable')
, fuse = require('fusing')
, path = require('path');
, async = require('async')
, path = require('path')
, url = require('url');

@@ -15,5 +19,9 @@ //

//
var slice = Array.prototype.slice
, temper;
var slice = Array.prototype.slice;
//
// Methods that needs data buffering.
//
var operations = 'POST, PUT, DELETE, PATCH'.toLowerCase().split(', ');
/**

@@ -31,4 +39,4 @@ * Simple helper function to generate some what unique id's for given

/**
* A pagelet is the representation of an item, section, column, widget on the
* page. It's basically a small sand boxed application within your page.
* A pagelet is the representation of an item, section, column or widget.
* It's basically a small sandboxed application within your application.
*

@@ -39,22 +47,44 @@ * @constructor

function Pagelet(options) {
if (!this) return new Pagelet(options);
this.fuse();
options = options || {};
this.writable('_active', null); // Are we active.
this.writable('substream', null); // Substream from Primus.
this.writable('temper', options.temper || temper); // Template parser.
//
// Use the temper instance on Pipe if available.
//
if (options.pipe && options.pipe._temper) options.temper = options.pipe._temper;
this.writable('id', options.id || [1, 1, 1, 1].map(generator).join('-'));
this._enabled = []; // Contains all enabled pagelets.
this._disabled = []; // Contains all disable pagelets.
this._active = null; // Are we active.
this._req = options.req; // Incoming HTTP request.
this._res = options.res; // Incoming HTTP response.
this._pipe = options.pipe; // Actual pipe instance.
this._params = options.params; // Params extracted from the route.
this._temper = options.temper; // Attach the Temper instance.
this._append = options.append || false; // Append content client-side.
this.bootstrap = options.bootstrap; // Reference to bootstrap Pagelet.
this.debug = debug('pagelet:'+ this.name); // Namespaced debug method
//
// Add an correctly namespaced debug method so it easier to see which pagelet
// is called by just checking the name of it.
// Allow overriding the reference to parent pagelet.
// A reference to the parent is normally set on the
// constructor prototype by optimize.
//
this.readable('debug', debug('pagelet:'+ this.name));
if (options.parent) this._parent = options.parent;
}
fuse(Pagelet, Stream, { emits: false });
fuse(Pagelet, require('eventemitter3'));
/**
* Unique id, useful for internal querying.
*
* @type {String}
* @public
*/
Pagelet.writable('id', null);
/**
* The name of this pagelet so it can checked to see if's enabled. In addition

@@ -69,36 +99,33 @@ * to that, it can be injected in to placeholders using this name.

/**
* When enabled we will stream the submit of each form that is within a Pagelet
* to the server instead of using the default full page refreshes. After sending
* the data the resulting HTML will be used to only update the contents of the
* pagelet.
* The HTTP pathname that we should be matching against.
*
* If you want to opt-out of this with one form you can add
* a `data-pagelet-async="false"` attribute to the form element.
* @type {String|RegExp}
* @public
*/
Pagelet.writable('path', null);
/**
* Which HTTP methods should this pagelet accept. It can be a comma
* separated string or an array.
*
* @type {Boolean}
* @type {String|Array}
* @public
*/
Pagelet.writable('streaming', false);
Pagelet.writable('method', 'GET');
/**
* These methods can be remotely called from the client. Please note that they
* are not set to the client, it will merely be executing on the server side.
* The default status code that we should send back to the user.
*
* ```js
* Pagelet.extend({
* RPC: [
* 'methodname',
* 'another'
* ],
* @type {Number}
* @public
*/
Pagelet.writable('statusCode', 200);
/**
* The pagelets that need to be loaded as children of this pagelet.
*
* methodname: function methodname(reply) {
*
* }
* }).on(module);
* ```
*
* @type {Array}
* @type {Object}
* @public
*/
Pagelet.writable('RPC', []);
Pagelet.writable('pagelets', {});

@@ -112,6 +139,46 @@ /**

*/
Pagelet.writable('mode', 'html');
Pagelet.writable('namespace', 'html');
/**
* Conditionally load this pagelet. It can also be used authorization handler.
* With what kind of generation mode do we need to output the generated
* pagelets. We're supporting 3 different modes:
*
* - sync: Fully render without any fancy flushing of pagelets.
* - async: Render all pagelets async and flush them as fast as possible.
* - pipeline: Same as async but in the specified order.
*
* @type {String}
* @public
*/
Pagelet.writable('mode', 'async');
/**
* Optional template engine preference. Useful when we detect the wrong template
* engine based on the view's file name.
*
* @type {String}
* @public
*/
Pagelet.writable('engine', '');
/**
* Save the location where we got our resources from, this will help us with
* fetching assets from the correct location.
*
* @type {String}
* @public
*/
Pagelet.writable('directory', '');
/**
* The environment that we're running this pagelet in. If this is set to
* `development` It would be verbose.
*
* @type {String}
* @public
*/
Pagelet.writable('env', (process.env.NODE_ENV || 'development').toLowerCase());
/**
* Conditionally load this pagelet. It can also be used as authorization handler.
* If the incoming request is not authorized you can prevent this pagelet from

@@ -121,3 +188,3 @@ * showing. The assigned function receives 3 arguments.

* - req, the http request that initialized the pagelet
* - list, array of pagelets that will be tried if this pagelet
* - list, array of pagelets that will be tried
* - done, a callback function that needs to be called with only a boolean.

@@ -127,3 +194,3 @@ *

* Pagelet.extend({
* if: function conditional(req, left, done) {
* if: function conditional(req, list, done) {
* done(true); // True indicates that the request is authorized for access.

@@ -190,3 +257,3 @@ * }

*/
Pagelet.writable('view', '');
Pagelet.writable('view', null);

@@ -230,3 +297,3 @@ /**

/**
* The JavaScript files needed for this page. The location can be a string or
* The JavaScript files needed for this pagelet. The location can be a string or
* multiple paths in an array. This file needs to be included in order for

@@ -268,2 +335,34 @@ * this pagelet to function.

/**
* Reference to parent Pagelet name.
*
* @type {Object}
* @private
*/
Pagelet.writable('_parent', null);
/**
* Set of optimized children Pagelet.
*
* @type {Object}
* @private
*/
Pagelet.writable('_children', {});
/**
* Cataloged dependencies by extension.
*
* @type {Object}
* @private
*/
Pagelet.writable('_dependencies', {});
/**
* Default content type of the Pagelet.
*
* @type {Object}
* @private
*/
Pagelet.writable('_contentType', 'text/html');
/**
* Default asynchronous get function. Override to provide specific data to the

@@ -280,2 +379,198 @@ * render function.

/**
* Get parameters that were extracted from the route.
*
* @type {Object}
* @public
*/
Pagelet.readable('params', {
enumerable: false,
get: function params() {
return this._params || this.bootstrap._params || Object.create(null);
}
}, true);
/**
* Report the length of the queue (e.g. amount of children). The length
* is increased with one as the reporting pagelet is part of the queue.
*
* @return {Number} Length of queue
* @api private
*/
Pagelet.set('length', function length() {
return this._children.length;
});
/**
* Get and initialize a given child Pagelet.
*
* @param {String} name Name of the child pagelet.
* @returns {Array} The pagelet instances.
* @api public
*/
Pagelet.readable('child', function child(name) {
if (Array.isArray(name)) name = name[0];
return (this.has(name) || this.has(name, true) || []).slice(0);
});
/**
* Helper to invoke a specific route with an optionally provided method.
* Useful for serving a pagelet after handling POST requests for example.
*
* @param {String} route Registered path.
* @param {String} method Optional HTTP verb.
* @returns {Pagelet} fluent interface.
*/
Pagelet.readable('serve', function serve(route, method) {
var req = this._req
, res = this._res;
req.method = (method || 'get').toUpperCase();
req.uri = url.parse(route);
this._pipe.router(req, res);
return this;
});
/**
* Helper to check if the pagelet has a child pagelet by name, must use
* prototype.name since pagelets are not always constructed yet.
*
* @param {String} name Name of the pagelet.
* @param {String} enabled Make sure that we use the enabled array.
* @returns {Array} The constructors of matching Pagelets.
* @api public
*/
Pagelet.readable('has', function has(name, enabled) {
if (!name) return [];
if (enabled) return this._enabled.filter(function filter(pagelet) {
return pagelet.name === name;
});
var pagelets = this._children
, i = pagelets.length
, pagelet;
while (i--) {
pagelet = pagelets[i][0];
if (
pagelet.prototype && pagelet.prototype.name === name
|| pagelets.name === name
) return pagelets[i];
}
return [];
});
/**
* Render execution flow.
*
* @api private
*/
Pagelet.readable('init', function init() {
var method = this._req.method.toLowerCase()
, pagelet = this;
//
// Only start reading the incoming POST request when we accept the incoming
// method for read operations. Render in a regular mode if we do not accept
// these requests.
//
if (~operations.indexOf(method)) {
var pagelets = this.child(this._req.query._pagelet)
, reader = this.read(pagelet);
this.debug('Processing %s request', method);
async.whilst(function work() {
return !!pagelets.length;
}, function process(next) {
var Child = pagelets.shift()
, child;
if (!(method in Pagelet.prototype)) return next();
child = new Child({ pipe: pagelet._pipe });
child.conditional(pagelet._req, pagelets, function allowed(accepted) {
if (!accepted) {
if (child.destroy) child.destroy();
return next();
}
reader.before(child[method], child);
});
}, function nothing() {
if (method in pagelet) {
reader.before(pagelet[method], pagelet);
} else {
pagelet[pagelet.mode]();
}
});
} else {
this[this.mode]();
}
});
/**
* Start buffering and reading the incoming request.
*
* @returns {Form}
* @api private
*/
Pagelet.readable('read', function read() {
var form = new Formidable
, pagelet = this
, fields = {}
, files = {}
, context
, before;
form.on('progress', function progress(received, expected) {
//
// @TODO if we're not sure yet if we should handle this form, we should only
// buffer it to a predefined amount of bytes. Once that limit is reached we
// need to `form.pause()` so the client stops uploading data. Once we're
// given the heads up, we can safely resume the form and it's uploading.
//
}).on('field', function field(key, value) {
fields[key] = value;
}).on('file', function file(key, value) {
files[key] = value;
}).on('error', function error(err) {
pagelet.capture(err, true);
fields = files = {};
}).on('end', function end() {
form.removeAllListeners();
if (before) {
before.call(context, fields, files);
}
});
/**
* Add a hook for adding a completion callback.
*
* @param {Function} callback
* @returns {Form}
* @api public
*/
form.before = function befores(callback, contexts) {
if (form.listeners('end').length) {
form.resume(); // Resume a possible buffered post.
before = callback;
context = contexts;
return form;
}
callback.call(contexts || context, fields, files);
return form;
};
return form.parse(this._req);
});
/**
* A safe and fast(er) alternative to the `json-stringify-save` as uses the

@@ -313,2 +608,252 @@ * replacer to make the transformation save. This is really costly for larger

/**
* Discover pagelets that we're allowed to use.
*
* @returns {Pagelet} fluent interface
* @api private
*/
Pagelet.readable('discover', function discover() {
if (!this.length) return this.emit('discover');
var req = this._req
, res = this._res
, pagelet = this;
//
// We need to do an async map/filter of the pagelets, in order to this as
// efficient as possible we're going to use a reduce.
//
async.reduce(this._children, {
disabled: [],
enabled: []
}, function reduce(memo, children, next) {
children = children.slice(0);
var child, last;
async.whilst(function work() {
return children.length && !child;
}, function work(next) {
var Child = children.shift()
, test = new Child({
bootstrap: pagelet.bootstrap,
pipe: pagelet._pipe,
res: res,
req: req
});
test.conditional(req, children, function conditionally(accepted) {
if (last && last.destroy) last.destroy();
if (accepted) child = test;
else last = test;
next(!!child);
});
}, function found() {
if (child) memo.enabled.push(child);
else memo.disabled.push(last);
next(undefined, memo);
});
}, function discovered(err, children) {
pagelet._disabled = children.disabled;
pagelet._enabled = children.enabled.concat(pagelet);
pagelet._enabled.forEach(function initialize(child) {
if ('function' === typeof child.initialize) child.initialize();
});
pagelet.debug('Initialized all allowed pagelets');
pagelet.emit('discover');
});
return this;
});
/**
* Mode: Synchronous
* Output the pagelets fully rendered in the HTML template.
*
* @TODO remove pagelet's that have `authorized` set to `false`
* @TODO Also write the CSS and JavaScript.
*
* @api private
*/
Pagelet.readable('sync', function synchronous() {
var pagelet = this;
//
// Because we're synchronously rendering the pagelets we need to discover
// which one's are enabled before we send the bootstrap code so it can include
// the CSS files of the enabled pagelets in the HEAD of the page so there is
// styling available.
//
pagelet.once('discover', function discovered() {
pagelet.debug('Processing the pagelets in `sync` mode');
async.each(pagelet._enabled.concat(pagelet._disabled), function render(child, next) {
pagelet.debug('Invoking pagelet %s/%s render', child.name, child.id);
child.render({ mode: 'sync' }, function rendered(error, content) {
if (error) return render(child.capture(error), next);
child.write(content);
next();
});
}, function done() {
pagelet.bootstrap.render().reduce().end();
});
}).discover();
});
/**
* Mode: Asynchronous
* Output the pagelets as fast as possible.
*
* @api private
*/
Pagelet.readable('async', function asynchronous() {
var pagelet = this;
//
// Flush the initial headers asap so the browser can start detect encoding
// start downloading assets and prepare for rendering additional pagelets.
//
pagelet.bootstrap.render().flush(function headers(error) {
if (error) return pagelet.capture(error, true);
pagelet.once('discover', function discovered() {
pagelet.debug('Processing the pagelets in `async` mode');
async.each(pagelet._enabled.concat(pagelet._disabled), function render(child, next) {
pagelet.debug('Invoking pagelet %s/%s render', child.name, child.id);
child.render({
data: pagelet._pipe._compiler.pagelet(child)
}, function rendered(error, content) {
if (error) return render(child.capture(error), next);
child.write(content).flush(next);
});
}, pagelet.end.bind(pagelet));
}).discover();
});
});
/**
* Mode: pipeline
* Output the pagelets as fast as possible but in order.
*
* @returns {Pagelet} fluent interface.
* @api private
*/
Pagelet.readable('pipeline', function render() {
throw new Error('Not Implemented');
});
/**
* Process the pagelet for an async or pipeline based render flow.
*
* @param {String} name Optional name, defaults to pagelet.name.
* @param {Mixed} chunk Content of Pagelet.
* @returns {Bootstrap} Reference to bootstrap Pagelet.
* @api private
*/
Pagelet.readable('write', function write(name, chunk) {
if (!chunk) {
chunk = name;
name = this.name;
}
//
// The chunk could potentially be an error, capture it before
// its pushed to the queue.
//
if (chunk instanceof Error) return this.capture(chunk);
this.debug('Queueing data chunk');
return this.bootstrap.queue(name, this._parent, chunk);
});
/**
* Close the connection once all pagelets are sent.
*
* @param {Mixed} chunk Fragment of data.
* @returns {Boolean} Closed the connection.
* @api private
*/
Pagelet.readable('end', function end(chunk) {
var pagelet = this;
//
// Write data chunk to the queue.
//
if (chunk) this.write(chunk);
//
// Do not close the connection before all pagelets are send.
//
if (this.bootstrap.length > 0) {
this.debug('Not all pagelets have been written, (%s out of %s)',
this.bootstrap.length, this.length
);
return false;
}
//
// Everything is processed, close the connection and clean up references.
//
this.bootstrap.flush(function close(error) {
if (error) return pagelet.capture(error, true);
pagelet.debug('Closed the connection');
pagelet._res.end();
});
return true;
});
/**
* We've received an error. Close down pagelet and display a 500
* error Pagelet instead.
*
* @TODO handle the case when we've already flushed the initial bootstrap code
* to the client and we're presented with an error.
*
* @param {Error} error Optional error argument to trigger the error pagelet.
* @param {Boolean} bootstrap Trigger full bootstrap if true.
* @returns {Pagelet} Reference to Pagelet.
* @api private
*/
Pagelet.readable('capture', function capture(error, bootstrap) {
this.debug('Captured an error: %s, displaying error pagelet instead', error);
return this._pipe.status(this, 500, error, bootstrap);
});
/**
* The Content-Type of the response. This defaults to text/html with a charset
* preset inherited from the charset property.
*
* @type {String}
* @public
*/
Pagelet.set('contentType', function get() {
return this._contentType +';charset='+ this.charset;
}, function set(value) {
return this._contentType = value;
});
/**
* Returns reference to bootstrap Pagelet, which could be the Pagelet itself.
* Allows more chaining and valid bootstrap Pagelet references.
*
* @type {String}
* @public
*/
Pagelet.set('bootstrap', function get() {
return !this._bootstrap && this.name === 'bootstrap' ? this : this._bootstrap || {};
}, function set(value) {
return this._bootstrap = value;
});
/**
* Checks if we're an active Pagelet or if we still need to a do an check

@@ -350,4 +895,6 @@ * against the `if` function.

var context = options.context || this
, compiler = this._pipe._compiler
, mode = options.mode || 'async'
, data = options.data || {}
, temper = this.temper
, temper = this._temper
, query = this.query

@@ -367,18 +914,14 @@ , pagelet = this;

if (!active) content = '';
if (mode === 'sync') return fn.call(context, undefined, content);
if (options.substream || pagelet.page && pagelet.page.mode === 'sync') {
data.view = content;
return fn.call(context, undefined, data);
}
data.id = data.id || pagelet.id; // Pagelet id.
data.path = data.path || pagelet.path; // Reference to the path.
data.mode = data.mode || pagelet.mode; // Pagelet render mode.
data.remove = active ? false : pagelet.remove; // Remove from DOM.
data.parent = pagelet._parent; // Send parent name along.
data.append = pagelet._append; // Content should be appended.
data.remaining = pagelet.bootstrap.length; // Remaining pagelets number.
data.id = data.id || pagelet.id; // Pagelet id.
data.mode = data.mode || pagelet.mode; // Pagelet render mode.
data.rpc = data.rpc || pagelet.RPC; // RPC methods.
data.remove = active ? false : pagelet.remove; // Remove from DOM.
data.streaming = !!pagelet.streaming; // Submit streaming.
data.parent = pagelet._parent; // Send parent name along.
data.hash = {
error: temper.fetch(pagelet.error).hash.client, // MD5 hash of error view.
client: temper.fetch(pagelet.view).hash.client // MD5 hash of client view.
};
data.error = compiler.resolve(pagelet.error); // Path of error view.
data.client = compiler.resolve(pagelet.view); // Path of client view.

@@ -397,2 +940,3 @@ data = pagelet.stringify(data, function sanitize(key, data) {

fn.call(context, undefined, pagelet.fragment
.replace(/\{pagelet:id\}/g, pagelet.id)
.replace(/\{pagelet:name\}/g, pagelet.name)

@@ -406,3 +950,3 @@ .replace(/\{pagelet:template\}/g, content.replace(/<!--(.|\s)*?-->/, ''))

return this.conditional(this.page.req, options.pagelets, function auth(enabled) {
return this.conditional(this._req, options.pagelets, function auth(enabled) {
if (!enabled) return fragment('');

@@ -419,2 +963,8 @@

//
// Add some template defaults.
//
result = result || {};
if (!('path' in result)) result.path = pagelet.path;
//
// We've made it this far, but now we have to cross our fingers and HOPE

@@ -429,7 +979,7 @@ // that our given template can actually handle the data correctly

if (err) {
pagelet.debug('render %s/%s resulted in a error', pagelet.name, pagelet.id, err);
pagelet.debug('Render %s/%s resulted in a error', pagelet.name, pagelet.id, err);
throw err; // Throw so we can capture it again.
}
content = view(result || {});
content = view(result);
} catch (e) {

@@ -457,3 +1007,3 @@ //

//
if ('object' === typeof result && Array.isArray(query)) {
if ('object' === typeof result && Array.isArray(query) && query.length) {
data.data = query.reduce(function find(memo, q) {

@@ -471,119 +1021,2 @@ memo[q] = dot.get(result, q);

/**
* Connect with a Primus substream.
*
* @param {Spark} spark The Primus connection.
* @param {Function} next The completion callback
* @returns {Pagelet}
* @api private
*/
Pagelet.readable('connect', function connect(spark, next) {
var pagelet = this;
/**
* Create a new Substream.
*
* @param {Boolean} enabled Allowed to use this pagelet.
* @returns {Pagelet}
* @api private
*/
return this.conditional(spark.request, [], function substream(enabled) {
if (!enabled) return next(new Error('Unauthorized to access this pagelet'));
var stream = pagelet.substream = spark.substream(pagelet.name)
, log = debug('pagelet:primus:'+ pagelet.name);
log('created a new substream');
stream.once('end', pagelet.emits('end', function (arg) {
log('closing substream');
return arg;
}));
stream.on('data', function streamed(data) {
log('incoming packet %s', data.type);
switch (data.type) {
case 'rpc':
pagelet.call(data);
break;
case 'emit':
Stream.prototype.emit.apply(pagelet, [data.name].concat(data.args));
break;
case 'get':
pagelet.render({ substream: true }, function renderd(err, fragment) {
stream.write({ type: 'fragment', frag: fragment, err: err });
});
break;
case 'post':
case 'put':
if (!(data.type in pagelet)) {
return stream.write({ type: data.type, err: new Error('Method not supported by pagelet') });
}
pagelet[data.type](data.body || {}, data.files || [], function processed(err, context) {
if (err) return stream.write({ type: 'err', err: err });
pagelet.render({ data: context, substream: true }, function rendered(err, fragment) {
if (err) return stream.write({ type: 'err', err: err });
stream.write({ type: 'fragment', frag: fragment, err: err });
});
});
break;
default:
log('unknown packet type %s, ignoring packet', data.type);
break;
}
});
next(undefined, pagelet);
return pagelet;
});
});
/**
* Simple emit wrapper that returns a function that emits an event once it's
* called
*
* ```js
* example.on('close', example.emits('close'));
* ```
*
* @param {String} event Name of the event that we should emit.
* @param {Function} parser The last argument, if it's a function is a arg parser
* @api public
*/
Pagelet.prototype.emits = function emits() {
var args = slice.call(arguments, 0)
, self = this
, parser;
//
// Assume that if the last given argument is a function, it would be
// a parser.
//
if ('function' === typeof args[args.length - 1]) {
parser = args.pop();
}
return function emit(arg) {
if (!self.listeners(args[0]).length) return false;
if (parser) {
arg = parser.apply(self, arguments);
if (!Array.isArray(arg)) arg = [arg];
} else {
arg = slice.call(arguments, 0);
}
return Stream.prototype.emit.apply(self, args.concat(arg));
};
};
/**
* Authenticate the Pagelet.

@@ -600,2 +1033,7 @@ *

if ('function' !== typeof fn) {
fn = list;
list = [];
}
/**

@@ -624,34 +1062,2 @@ * Callback for the `pagelet.if` function to see if we're enabled or disabled.

/**
* Call an RPC method.
*
* @param {Object} data The RPC call information.
* @api private
*/
Pagelet.readable('call', function calls(data) {
var index = this.RPC.indexOf(data.method)
, fn = this[data.method]
, pagelet = this
, err;
if (!~index || 'function' !== typeof fn) return this.substream.write({
args: [new Error('RPC method is not known')],
type: 'rpc',
id: data.id
});
//
// Our RPC pattern is a callback first pattern, where the callback is the
// first argument that a function receives. This makes it a lot easier to add
// a variable length of arguments to a function call.
//
fn.apply(pagelet, [function reply() {
pagelet.substream.write({
args: slice.call(arguments, 0),
type: 'rpc',
id: data.id
});
}].concat(data.args));
});
/**
* Destroy the pagelet and remove all the back references so it can be safely

@@ -662,40 +1068,10 @@ * garbage collected.

*/
Pagelet.readable('destroy', function destroy() {
if (this.substream) this.substream.end();
Pagelet.readable('destroy', destroy([
'_temper', '_pipe', '_enabled', '_disabled', '_pagelets'
], {
after: 'removeAllListeners'
}));
this.temper = null;
this.removeAllListeners();
return this;
});
/**
* Helper function to resolve assets on the pagelet.
*
* @param {String|Array} keys Name(s) of the property, e.g. [css, js].
* @param {String} dir Optional absolute directory to resolve from.
* @returns {Pagelet}
* @api public
*/
Pagelet.resolve = function resolve(keys, dir) {
var prototype = this.prototype;
keys = Array.isArray(keys) ? keys : [keys];
keys.forEach(function each(key) {
if (!prototype[key]) return;
var stack = Array.isArray(prototype[key])
? prototype[key]
: [prototype[key]];
prototype[key] = stack.filter(Boolean).map(function map(file) {
if (/^(http:|https:)?\/\//.test(file)) return file;
return path.resolve(dir || prototype.directory, file);
});
});
return this;
};
/**
* Expose the Pagelet on the exports and parse our the directory. This ensures

@@ -721,14 +1097,7 @@ * that we can properly resolve all relative assets:

prototype.error = prototype.error
? path.resolve(dir, prototype.error)
: path.resolve(__dirname, 'error.html');
//
// Map all dependencies to an absolute path or URL.
// Resolve the view and error templates to ensure
// absolute paths are provided to Temper.
//
Pagelet.resolve.call(this, ['css', 'js', 'dependencies']);
//
// Resolve the view to make sure an absolute path is provided to Temper.
//
if (prototype.error) prototype.error = path.resolve(dir, prototype.error);
if (prototype.view) prototype.view = path.resolve(dir, prototype.view);

@@ -740,124 +1109,213 @@

/**
* Optimize the prototypes of the Pagelet to reduce work when we're actually
* serving the requests.
* Discover all pagelets recursive. Fabricate will create constructable
* instances from the provided value of prototype.pagelets.
*
* @param {String} parent Reference to the parent pagelet name.
* @return {Array} collection of pagelets instances.
* @api public
*/
Pagelet.children = function children(parent, stack) {
var pagelets = this.prototype.pagelets
, log = debug('pagelet:'+ parent);
stack = stack || [];
if (!pagelets || !Object.keys(pagelets).length) return stack;
return fabricate(pagelets, {
source: this.prototype.directory,
recursive: 'string' === typeof pagelets
}).reduce(function each(stack, Pagelet) {
//
// Pagelet could be conditional, simple crawl this function
// again to get the children of each conditional.
//
if (Array.isArray(Pagelet)) return Pagelet.reduce(each, []);
var name = Pagelet.prototype.name;
log('Recursive discovery of child pagelet %s', name);
//
// We need to extend the pagelet if it already has a _parent name reference
// or will accidentally override it. This can happen when you extend a parent
// pagelet with children and alter the parent's name. The extended parent and
// regular parent still point to the same child pagelets. So when we try to
// set the proper parent, these pagelets will override the _parent property
// unless we create a new fresh instance and set it on that instead.
//
if (Pagelet.prototype._parent && name !== parent) {
Pagelet = Pagelet.extend();
}
Pagelet.prototype._parent = parent;
return Pagelet.children(name, stack.concat(Pagelet));
}, stack);
};
/**
* Optimize the prototypes of Pagelets to reduce work when we're actually
* serving the requests via BigPipe.
*
* Options:
*
* - temper: A custom temper instance we want to use to compile the templates.
* - transform: Transformation callback so plugins can hook in the optimizer.
*
* @param {Object} options Optimization configuration.
* @param {Function} next Completion callback for async execution.
* @returns {Pagelet}
* @api private
* @api public
*/
Pagelet.optimize = function optimize(options, next) {
var prototype = this.prototype
, name = prototype.name
, async = false
, err;
Pagelet.optimize = function optimize(options, done) {
if ('function' === typeof options) {
done = options;
options = {};
}
options = options || {};
options.temper = options.temper || temper || (temper = new Temper()) ;
var stack = []
, Pagelet = this
, pipe = options.pipe || {}
, transform = options.transform || {}
, temper = options.temper || pipe._temper
, before, after;
//
// Prefetch the template if a view is available. Resolve the view
// to make sure an absolute path is provided to Temper.
// Check if before listener is found. Add before emit to the stack.
// This async function will be called before optimize.
//
if (prototype.view) {
prototype.view = path.resolve(prototype.directory, prototype.view);
options.temper.prefetch(prototype.view, prototype.engine);
}
if (pipe._events && 'transform:pagelet:before' in pipe._events) {
before = pipe._events['transform:pagelet:before'].length || 1;
//
// Ensure we have a custom error page when we fail to render this fragment.
//
if (prototype.error) {
options.temper.prefetch(prototype.error, path.extname(prototype.error).slice(1));
stack.push(function run(next) {
var n = 0;
transform.before(Pagelet, function ran(error, Pagelet) {
if (error || ++n === before) return next(error, Pagelet);
});
});
}
//
// Map all dependencies to an absolute path or URL.
// If transform.before was not pushed on the stack, optimizer needs
// to called with a reference to Pagelet.
//
Pagelet.resolve.call(this, ['css', 'js', 'dependencies']);
stack.push(!stack.length ? async.apply(optimizer, Pagelet) : optimizer);
//
// Support lowercase variant of RPC
// Check if after listener is found. Add after emit to the stack.
// This async function will be called after optimize.
//
if ('rpc' in prototype) {
prototype.RPC = prototype.rpc;
delete prototype.rpc;
}
if (pipe._events && 'transform:pagelet:after' in pipe._events) {
after = pipe._events['transform:pagelet:after'].length || 1;
if ('string' === typeof prototype.RPC) {
prototype.RPC = prototype.RPC.split(/[\s|\,]+/);
stack.push(function run(Pagelet, next) {
var n = 0;
transform.after(Pagelet, function ran(error, Pagelet) {
if (error || ++n === after) return next(error, Pagelet);
});
});
}
//
// Validate the existance of the RPC methods, this reduces possible typo's
// Run the stack in series. This ensures that before hooks are run
// prior to optimizing and after hooks are ran post optimizing.
//
prototype.RPC.forEach(function validate(method) {
if (!(method in prototype)) return err = new Error(
name +' is missing RPC function `'+ method +'` on prototype'
);
async.waterfall(stack, done);
if ('function' !== typeof prototype[method]) return err = new Error(
name +'#'+ method +' is not function which is required for RPC usage'
);
});
/**
* Optimize the pagelet. This function is called by default as part of
* the async stack.
*
* @param {Function} next Completion callback
* @api private
*/
function optimizer(Pagelet, next) {
var prototype = Pagelet.prototype
, method = prototype.method
, router = prototype.path
, name = prototype.name
, log = debug('pagelet:'+ name);
//
// Allow plugins to hook in the transformation process, so emit it when
// all our transformations are done and before we create a copy of the
// "fixed" properties which later can be re-used again to restore
// a generated instance to it's original state.
//
if ('function' === typeof options.transform && !err) {
if (options.transform.length === 2) async = true;
options.transform(this, next);
}
//
// Generate a unique ID used for real time connection lookups.
//
prototype.id = options.id || [1, 1, 1, 1].map(generator).join('-');
if (!async) process.nextTick(next.bind(next, err));
//
// Parse the methods to an array of accepted HTTP methods. We'll only accept
// these requests and should deny every other possible method.
//
log('Optimizing pagelet');
if (!Array.isArray(method)) method = method.split(/[\s\,]+?/);
Pagelet.method = method.filter(Boolean).map(function transformation(method) {
return method.toUpperCase();
});
return this;
};
//
// Add the actual HTTP route and available HTTP methods.
//
if (router) {
log('Instantiating router for path %s', router);
Pagelet.router = new Route(router);
}
/**
* Discover all pagelets recursive. Fabricate will create constructable instances
* from the provided value of prototype.pagelets.
*
* @param {Pagelet} parent Reference to the parent pagelet.
* @return {Array} collection of pagelets instances.
* @api public
*/
Pagelet.traverse = function traverse(parent) {
var pagelets = this.prototype.pagelets
, log = debug('bigpipe:pagelet')
, found = [this];
//
// Prefetch the template if a view is available. The view property is
// mandatory but it's quite silly to enforce this if the pagelet is
// just doing a redirect. We can check for this edge case by
// checking if the set statusCode is in the 300~ range.
//
if (prototype.view) {
prototype.view = path.resolve(prototype.directory, prototype.view);
temper.prefetch(prototype.view, prototype.engine);
} else if (!(prototype.statusCode >= 300 && prototype.statusCode < 400)) {
return next(new Error(
'The '+ name +' pagelet for path '+ router +' should have a .view property.'
));
}
if (!pagelets) return found;
//
// Ensure we have a custom error pagelet when we fail to render this fragment.
//
if (prototype.error) {
temper.prefetch(prototype.error, path.extname(prototype.error).slice(1));
}
pagelets = fabricate(pagelets, { recursive: false });
pagelets.forEach(function each(Pagelet) {
log('Recursive discovery of child pagelets from %s', parent);
//
// Map all dependencies to an absolute path or URL.
//
helpers.resolve(Pagelet, ['css', 'js', 'dependencies']);
//
// We need to extend the pagelet if it already has a _parent name reference
// or will accidentally override it. This you have Pagelet with child
// pagelet. And you extend the parent pagelet so it receives a new name. But
// the extended parent and regular parent still point to the same child
// pagelet. So when we try to traverse these pagelets we will override
// _parent property unless we create a new fresh instance and set it on that
// instead.
// Find all child pagelets and optimize the found children.
//
if (Pagelet.prototype._parent && Pagelet.prototype.name !== parent) {
Pagelet = Pagelet.extend();
}
async.map(Pagelet.children(name), function map(Child, step) {
if (Array.isArray(Child)) return async.map(Child, map, step);
Pagelet.prototype._parent = parent;
Child.optimize({
temper: temper,
pipe: pipe,
transform: {
before: pipe.emits && pipe.emits('transform:pagelet:before'),
after: pipe.emits && pipe.emits('transform:pagelet:after')
}
}, step);
}, function optimized(error, children) {
log('optimized all %d child pagelets', children.length);
Array.prototype.push.apply(found, Pagelet.traverse(Pagelet.prototype.name));
});
if (error) return next(error);
return found;
//
// Store the optimized children on the prototype, wrapping the Pagelet
// in an array makes it a lot easier to work with conditional Pagelets.
//
prototype._children = children.map(function map(Pagelet) {
return Array.isArray(Pagelet) ? Pagelet : [Pagelet];
});
//
// Always return a reference to the parent Pagelet.
// Otherwise the stack of parents would be infested
// with children returned by this async.map.
//
next(null, Pagelet);
});
}
};

@@ -864,0 +1322,0 @@

{
"name": "pagelet",
"version": "0.8.2",
"version": "1.0.0-alpha",
"description": "pagelet",
"main": "index.js",
"scripts": {
"test": "illuminati",
"coverage": "NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha $(find test -name '*.test.js') --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true && rm -rf ./coverage"
"100%": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
"test": "mocha $(find test -name '*.test.js')",
"watch": "mocha --watch $(find test -name '*.test.js')",
"coverage": "istanbul cover ./node_modules/.bin/_mocha -- $(find test -name '*.test.js')",
"test-travis": "istanbul cover node_modules/.bin/_mocha --report lcovonly -- $(find test -name '*.test.js')"
},

@@ -25,20 +28,21 @@ "repository": {

"dependencies": {
"async": "0.9.x",
"demolish": "1.0.x",
"diagnostics": "0.0.x",
"dot-component": "0.1.x",
"eventemitter3": "0.1.x",
"fabricator": "0.4.x",
"fusing": "0.3.x",
"formidable": "1.0.x",
"fusing": "1.0.x",
"json-stringify-safe": "5.0.x",
"routable": "0.0.x",
"temper": "0.2.x"
},
"devDependencies": {
"illuminati": "0.0.x",
"assume": "0.0.x",
"coveralls": "2.10.x",
"istanbul": "0.2.x",
"mocha": "1.20.x",
"mocha-lcov-reporter": "0.0.x",
"primus": "2.4.x",
"substream": "0.1.x",
"ws": "0.4.x"
"assume": "1.1.x",
"bigpipe": "bigpipe/bigpipe",
"istanbul": "0.3.x",
"mocha": "2.1.x",
"pre-commit": "1.0.x"
}
}

@@ -1,7 +0,10 @@

# Pagelet [![Build Status][status]](https://travis-ci.org/bigpipe/pagelet) [![NPM version][npmimgurl]](http://badge.fury.io/js/pagelet) [![Coverage Status][coverage]](http://coveralls.io/r/bigpipe/pagelet?branch=master)
# Pagelet
[status]: https://travis-ci.org/bigpipe/pagelet.png
[npmimgurl]: https://badge.fury.io/js/pagelet.png
[coverage]: http://coveralls.io/repos/bigpipe/pagelet/badge.png?branch=master
[![Version npm][version]](http://browsenpm.org/package/pagelet)[![Build Status][build]](https://travis-ci.org/bigpipe/pagelet)[![Dependencies][david]](https://david-dm.org/bigpipe/pagelet)[![Coverage Status][cover]](https://coveralls.io/r/bigpipe/pagelet?branch=master)
[version]: http://img.shields.io/npm/v/pagelet.svg?style=flat-square
[build]: http://img.shields.io/travis/bigpipe/pagelet/master.svg?style=flat-square
[david]: https://img.shields.io/david/bigpipe/pagelet.svg?style=flat-square
[cover]: http://img.shields.io/coveralls/bigpipe/pagelet/master.svg?style=flat-square
## Installation

@@ -279,4 +282,4 @@

Pagelet.extend({
if: function conditional(req, next) {
next(false);
if: function conditional(req, next) {
next(false);
},

@@ -283,0 +286,0 @@ remove: false

@@ -5,6 +5,7 @@ describe('Pagelet', function () {

var Pagelet = require('../').extend({ name: 'test' })
, custom = '/unexisting/absolute/path/to/prepend'
, Temper = require('temper')
, Pipe = require('bigpipe')
, assume = require('assume')
, pagelet
, P;
, server = require('http').createServer()
, pagelet, P;

@@ -15,3 +16,4 @@ //

//
var temper = { prefetch: function () {} };
var temper = new Temper
, pipe = new Pipe(server);

@@ -30,3 +32,3 @@ beforeEach(function () {

pagelet = new P();
pagelet = new P;
});

@@ -38,10 +40,31 @@

it('rendering is asynchronously', function (done) {
it('rendering is asynchronous', function (done) {
pagelet.get(pagelet.emits('called'));
// Listening only till after the event is potentially emitted, will ensure
// callbacks are called asynchronously by pagelet#render.
// callbacks are called asynchronously by pagelet.render.
pagelet.on('called', done);
});
it('can have reference to temper', function () {
pagelet = new P({ temper: temper });
var property = Object.getOwnPropertyDescriptor(pagelet, '_temper');
assume(pagelet._temper).to.be.an('object');
assume(property.writable).to.equal(true);
assume(property.enumerable).to.equal(true);
assume(property.configurable).to.equal(true);
});
it('can have reference to pipe instance', function () {
pagelet = new P({ pipe: pipe });
var property = Object.getOwnPropertyDescriptor(pagelet, '_pipe');
assume(pagelet._pipe).to.be.an('object');
assume(pagelet._pipe).to.be.instanceof(Pipe);
assume(property.writable).to.equal(true);
assume(property.enumerable).to.equal(true);
assume(property.configurable).to.equal(true);
});
describe('.on', function () {

@@ -61,169 +84,43 @@ it('sets the pathname', function () {

it('resolves the `error` view');
it('resolves the `css` files in to an array');
it('resolves the `js` files in to an array');
it('resolves the `dependencies` files in to an array');
});
it('resolves the view', function () {
assume(P.prototype.view).to.equal('fixtures/view.html');
describe('.resolve', function () {
it('is a function', function () {
assume(Pagelet.resolve).to.be.a('function');
assume(P.resolve).to.be.a('function');
assume(Pagelet.resolve).to.equal(P.resolve);
P.on(module);
assume(P.prototype.view).to.equal(__dirname +'/fixtures/view.html');
});
it('will resolve provided property on prototype', function () {
var result = P.resolve('css');
assume(result).to.equal(P);
assume(P.prototype.css).to.be.an('array');
assume(P.prototype.css.length).to.equal(1);
assume(P.prototype.css[0]).to.equal(__dirname + '/fixtures/style.css');
});
it('can resolve multiple properties at once', function () {
P.resolve(['css', 'js']);
assume(P.prototype.css).to.be.an('array');
assume(P.prototype.js).to.be.an('array');
assume(P.prototype.css.length).to.equal(1);
assume(P.prototype.js.length).to.equal(1);
});
it('can be provided with a custom source directory', function () {
P.resolve('css', custom);
assume(P.prototype.css[0]).to.equal(custom + '/fixtures/style.css');
});
it('only resolves local files', function () {
P.resolve('js', custom);
assume(P.prototype.js[0]).to.not.include(custom);
assume(P.prototype.js[0]).to.equal('//cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js');
});
it('can handle property values that are already an array', function () {
P.resolve('dependencies', custom);
assume(P.prototype.dependencies.length).to.equal(2);
assume(P.prototype.dependencies[0]).to.not.include(custom);
assume(P.prototype.dependencies[0]).to.equal('http://code.jquery.com/jquery-2.0.0.js');
assume(P.prototype.dependencies[1]).to.equal(custom + '/fixtures/custom.js');
});
it('removes undefined values from the array before processing', function () {
var Undef = P.extend({
dependencies: P.prototype.dependencies.concat(
undefined
)
});
assume(Undef.prototype.dependencies.length).to.equal(3);
Undef.resolve('dependencies', custom);
assume(Undef.prototype.dependencies.length).to.equal(2);
assume(Undef.prototype.dependencies).to.not.include(undefined);
});
it('can be overriden', function () {
P.resolve = function () {
throw new Error('fucked');
};
P.on({});
});
it('resolves the `error` view');
});
describe('.optimize', function () {
it('is a function', function () {
assume(Pagelet.optimize).to.be.a('function');
assume(P.optimize).to.be.a('function');
assume(Pagelet.optimize).to.equal(P.optimize);
describe('.discover', function () {
it('emits discover and returns immediatly if the parent pagelet has no children', function (done) {
pagelet.once('discover', done);
pagelet.discover();
});
it('uses the supplied temper for prefetching', function (next) {
var calls = 0;
P.optimize({
temper: {
prefetch: function () {
++calls;
}
}
}, function (err) {
if (err) return next(err);
/* Disabled for now, might return before 1.0.0
it('initializes pagelets by allocating from the Pagelet.freelist', function (done) {
var Hero = require(__dirname + '/fixtures/pagelets/hero').optimize(app.temper)
, Faq = require(__dirname + '/fixtures/pages/faq').extend({ pagelets: [ Hero ] })
, pageletFreelist = sinon.spy(Hero.freelist, 'alloc')
, faq = new Faq(app);
assume(calls).to.equal(2);
next();
faq.once('discover', function () {
assume(pageletFreelist).to.be.calledOnce;
done();
});
});
it('resolves the view', function (next) {
assume(P.prototype.view).to.equal('fixtures/view.html');
P.optimize({}, function () {
assume(P.prototype.view).to.equal(__dirname +'/fixtures/view.html');
next();
});
});
it('prefetches the `view`');
it('prefetches the `error` view');
it('allows rpc as a string', function (next) {
var X = P.extend({
RPC: 'fixtures, bar',
fixtures: function () {},
bar: function () {}
});
X.optimize({ temper: temper }, function (err) {
if (err) return next(err);
assume(X.prototype.RPC).to.be.a('array');
assume(X.prototype.RPC).to.have.length(2);
assume(X.prototype.RPC).to.include('bar');
assume(X.prototype.RPC).to.include('fixtures');
next();
});
});
it('checks if all rpc functions are available', function (next) {
var X = P.extend({
RPC: 'fixtures, bar',
bar: function () {}
});
X.optimize({ temper: temper }, function (err) {
assume(err).to.be.a('error');
assume(err.message).to.include('fixtures');
next();
});
});
it('allows lowercase rpc', function (next) {
var X = P.extend({
rpc: ['fixtures', 'bar'],
bar: function () {}
});
X.optimize({ temper: temper }, function (err) {
assume(err).to.be.a('error');
assume(err.message).to.include('fixtures');
next();
});
});
faq.discover();
});*/
});
describe('.traverse', function () {
describe('.children', function () {
it('is a function', function () {
assume(Pagelet.traverse).to.be.a('function');
assume(P.traverse).to.be.a('function');
assume(Pagelet.traverse).to.equal(P.traverse);
assume(Pagelet.children).to.be.a('function');
assume(P.children).to.be.a('function');
assume(Pagelet.children).to.equal(P.children);
});
it('returns an array', function () {
var one = P.traverse()
var one = P.children()
, recur = P.extend({

@@ -233,17 +130,16 @@ pagelets: {

}
}).traverse('this one');
}).children('this one');
assume(one).to.be.an('array');
assume(one.length).to.equal(1);
assume(one.length).to.equal(0);
assume(recur).to.be.an('array');
assume(recur.length).to.equal(2);
assume(recur.length).to.equal(1);
});
it('will at least return the pagelet', function () {
var single = P.traverse();
it('will only return children of the pagelet', function () {
var single = P.children();
assume(single[0].prototype._parent).to.equal(undefined);
assume(single[0].prototype.directory).to.equal(__dirname);
assume(single[0].prototype.view).to.equal('fixtures/view.html');
assume(single).to.be.an('array');
assume(single.length).to.equal(0);
});

@@ -261,9 +157,8 @@

}
}).traverse('multiple');
}).children('multiple');
assume(recur).is.an('array');
assume(recur.length).to.equal(3);
assume(recur[1].prototype.name).to.equal('child');
assume(recur[2].prototype.name).to.equal('another');
assume(recur.length).to.equal(2);
assume(recur[0].prototype.name).to.equal('child');
assume(recur[1].prototype.name).to.equal('another');
});

@@ -278,8 +173,12 @@

}
}).traverse('parental');
}).children('parental');
assume(recur[0].prototype._parent).to.equal(undefined);
assume(recur[1].prototype._parent).to.equal('parental');
assume(recur[0].prototype._parent).to.equal('parental');
});
});
describe('.optimize', function () {
it('should prepare an async call stack');
it('should provide optimizer with Pagelet reference if no transform:before event');
});
});
describe('Pagelet', function () {
'use strict';
var Primus = require('primus')
, assume = require('assume')
, port = 1024
, pagelet
, primus
, client
, http;
//
// Pre-configure an Pagelet with some default values so we can test if every
// property is correctly configured.
//
var Pagelet = require('../').extend({
name: 'test',
directory: __dirname,
view: 'fixtures/view.html'
});
beforeEach(function (next) {
pagelet = new Pagelet();
http = require('http').createServer();
primus = new Primus(http, {
transformer: 'websockets',
plugin: {
substream: require('substream')
}
});
http.port = port++;
http.listen(http.port, next);
});
describe('.connect', function () {
it('connects without errors', function (next) {
assume(pagelet.substream).to.be.a('null');
client = primus.on('connection', function (spark) {
pagelet.connect(spark, function connected(err) {
if (err) return next(err);
assume(pagelet.substream).to.not.be.a('null');
assume(pagelet.substream.write).to.be.a('function');
spark.end();
next();
});
}).Socket('http://localhost:'+ http.port);
});
it('returns an error if unauthorized', function (next) {
var Authorized = Pagelet.extend({
if: function conditional(req, enabled) {
enabled(false);
}
});
pagelet = new Authorized();
client = primus.on('connection', function (spark) {
pagelet.connect(spark, function connected(err) {
if (!err) throw new Error('Shit is fucked, no auth, ERROR');
assume(pagelet.substream).to.be.a('null');
spark.end();
next();
});
}).Socket('http://localhost:'+ http.port);
});
});
});

Sorry, the diff of this file is not supported yet