Compare commits

...

61 Commits

Author SHA1 Message Date
Tj Holowaychuk
1bb798d963 Release 2.3.10 2011-05-27 09:20:03 -07:00
Tj Holowaychuk
91997e9c53 Added req.route, exposing the current route. Closes #11
this can be used with a dynamicHelper to expose the _last_
route that was matched, aka the end-point when rendering a template.

this is a `Route` instance, so it has .path, .regexp, etc.
2011-05-27 09:12:49 -07:00
Tj Holowaychuk
1393187040 Merge branch 'refactor/executable' 2011-05-26 10:31:29 -07:00
Tj Holowaychuk
6e69c880d9 Added package.json generation support to express(1) 2011-05-26 10:31:21 -07:00
Tj Holowaychuk
59dcd03972 removed suggestions 2011-05-26 10:18:56 -07:00
Tj Holowaychuk
11482546a2 Fixed call to app.param() function for optional params. Closes #682 2011-05-26 09:56:04 -07:00
Tj Holowaychuk
1ce43dd347 added failing test for #682 2011-05-26 09:48:07 -07:00
Tj Holowaychuk
d1bfe137d4 test to ensure catch of invalid uri 2011-05-25 15:50:32 -07:00
Tj Holowaychuk
9d7452cdc2 more tests 2011-05-25 10:56:56 -07:00
Tj Holowaychuk
d9cee90efc Release 2.3.9 2011-05-25 10:18:26 -07:00
Tj Holowaychuk
175aa08500 more tests 2011-05-25 10:16:11 -07:00
Tj Holowaychuk
c9ff6198d3 more tests 2011-05-25 10:15:21 -07:00
Tj Holowaychuk
f026218c82 misc view refactoring 2011-05-25 10:10:23 -07:00
Tj Holowaychuk
5bc86b9e29 more tests 2011-05-25 09:54:55 -07:00
Tj Holowaychuk
5830ac9936 more tests 2011-05-25 09:54:06 -07:00
Tj Holowaychuk
d7c6c9a9f9 Release 2.3.8 2011-05-24 21:53:08 -07:00
Tj Holowaychuk
9c87eed60e Fixed OPTIONS regression preventing custom routing for this method 2011-05-24 21:52:52 -07:00
Tj Holowaychuk
f15eb6d5ef added options test 2011-05-24 21:41:37 -07:00
Tj Holowaychuk
5b33788359 express-mongoose link 2011-05-24 08:45:44 -07:00
Tj Holowaychuk
11ec3ccd48 removed some old invalid docs
new stuff to come
2011-05-23 17:16:02 -07:00
Tj Holowaychuk
9d498ba3f1 misc 2011-05-23 16:59:16 -07:00
Tj Holowaychuk
15d4047180 link 2011-05-23 16:57:36 -07:00
Tj Holowaychuk
44eae73843 Release 2.3.7 2011-05-23 15:54:17 -07:00
Tj Holowaychuk
d5b1c70731 removed invalidParamReturnValue() 2011-05-23 15:48:58 -07:00
Tj Holowaychuk
e78dc18cfd Removed app.param() callback with retval support
this is now abstract and will be going into express-params
along with some other cases
2011-05-23 15:40:41 -07:00
Tj Holowaychuk
4d122923e9 docs 2011-05-23 15:33:40 -07:00
Tj Holowaychuk
b1a7310263 Added support for app.param(fn) to define param logic 2011-05-23 15:30:34 -07:00
Tj Holowaychuk
d6ef90d98d Moved param() retval logic to Router#param() 2011-05-23 15:06:59 -07:00
Tj Holowaychuk
85df59ac31 comma first 2011-05-23 14:56:35 -07:00
Tj Holowaychuk
b789a28581 refactored with nextRoute() 2011-05-23 14:52:43 -07:00
Tj Holowaychuk
4068e7f444 added route Collection tests 2011-05-23 14:13:31 -07:00
Tj Holowaychuk
80e9ffbf5d Refactored router. Closes #639 2011-05-23 13:48:31 -07:00
Tj Holowaychuk
610fc92ca3 arity < 3 considered return-style param 2011-05-23 09:52:11 -07:00
Tj Holowaychuk
0f5dc9bdb2 Added checking of app.param() fn retval
functions returning NaN, == null, or === false are considered
a failure and the next route is executed
2011-05-23 09:38:01 -07:00
TJ Holowaychuk
c24b2faec5 Merge pull request #671 from jpalardy/patch-1
Documentation typo :uri -> :url for logger()
2011-05-22 10:18:01 -07:00
Jonathan Palardy
c407f58dc2 :uri -> :url
Example won't work otherwise. I checked the code :-)
2011-05-21 23:17:57 -07:00
Tj Holowaychuk
380da0e202 Removed module.parent check from express(1) generated app. Closes #670 2011-05-21 14:17:24 -07:00
Tj Holowaychuk
9e5e7a1526 added more route tests 2011-05-20 15:46:36 -07:00
Tj Holowaychuk
9016b5aaae added more route tests 2011-05-20 15:46:05 -07:00
Tj Holowaychuk
45f168e873 Release 2.3.6 2011-05-20 09:42:01 -07:00
Tj Holowaychuk
799938683d Merge branch 'refactor/dev-deps' 2011-05-20 09:39:34 -07:00
Tj Holowaychuk
7128f2d11f fixed last examples 2011-05-20 09:39:29 -07:00
Tj Holowaychuk
0634bf0b0d fixed redis example 2011-05-20 09:38:52 -07:00
Tj Holowaychuk
f9e48c2972 fixed markdown example 2011-05-20 09:29:22 -07:00
Tj Holowaychuk
909960c0b3 refactoring examples more 2011-05-20 09:26:47 -07:00
Tj Holowaychuk
8a7876f4d1 cleaning up examples 2011-05-20 09:16:35 -07:00
Tj Holowaychuk
286c92b13b docs 2011-05-20 09:15:39 -07:00
Tj Holowaychuk
b9872a278f docs 2011-05-20 09:15:35 -07:00
Tj Holowaychuk
1b34fd7efa misc 2011-05-20 09:11:15 -07:00
Tj Holowaychuk
f05c351762 Fixed view caching, should not be enabled in development 2011-05-20 09:01:05 -07:00
Tj Holowaychuk
8323f19e96 fixing examples 2011-05-20 08:56:25 -07:00
Tj Holowaychuk
565eda9ee5 docs 2011-05-20 08:40:26 -07:00
Tj Holowaychuk
7aea7194d1 example docs 2011-05-20 08:39:43 -07:00
Tj Holowaychuk
2f68957c8c fixed tests 2011-05-20 08:37:23 -07:00
Tj Holowaychuk
4ca848e526 ignore node_modules 2011-05-20 08:35:20 -07:00
Tj Holowaychuk
31a8c7c19c test docs 2011-05-20 08:35:11 -07:00
Tj Holowaychuk
f1c435e050 removed support submods 2011-05-20 08:31:13 -07:00
Tj Holowaychuk
fac75a9bff connect 1.4.1 2011-05-20 08:29:02 -07:00
Tj Holowaychuk
4fe03ab223 dev deps 2011-05-20 08:28:02 -07:00
TJ Holowaychuk
c6122da59b Merge pull request #668 from joemccann/master
Simple Update to Markdown example
2011-05-20 08:07:21 -07:00
Joe McCann
127f77964e Updated markdown example to latest version of node-markdown and modified the compile method. 2011-05-20 09:16:53 -05:00
56 changed files with 834 additions and 538 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ lib-cov
*.swo
benchmarks/graphs
testing.js
node_modules/

30
.gitmodules vendored
View File

@@ -1,30 +0,0 @@
[submodule "support/expresso"]
path = support/expresso
url = git://github.com/visionmedia/expresso.git
[submodule "support/haml"]
path = support/haml
url = git://github.com/visionmedia/haml.js.git
[submodule "support/ejs"]
path = support/ejs
url = git://github.com/visionmedia/ejs.git
[submodule "support/connect-form"]
path = support/connect-form
url = git://github.com/visionmedia/connect-form.git
[submodule "support/connect"]
path = support/connect
url = git://github.com/senchalabs/connect.git
[submodule "support/should"]
path = support/should
url = git://github.com/visionmedia/should.js.git
[submodule "support/formidable"]
path = support/formidable
url = git://github.com/felixge/node-formidable.git
[submodule "support/jade"]
path = support/jade
url = git://github.com/visionmedia/jade.git
[submodule "support/qs"]
path = support/qs
url = git://github.com/visionmedia/node-querystring.git
[submodule "support/mime"]
path = support/mime
url = https://github.com/bentomas/node-mime.git

View File

@@ -1,4 +1,38 @@
2.3.10 / 2011-05-27
==================
* Added `req.route`, exposing the current route
* Added _package.json_ generation support to `express(1)`
* Fixed call to `app.param()` function for optional params. Closes #682
2.3.9 / 2011-05-25
==================
* Fixed bug-ish with `../' in `res.partial()` calls
2.3.8 / 2011-05-24
==================
* Fixed `app.options()`
2.3.7 / 2011-05-23
==================
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
* Added support for `app.param(fn)` to define param logic
* Removed `app.param()` support for callback with return value
* Removed module.parent check from express(1) generated app. Closes #670
* Refactored router. Closes #639
2.3.6 / 2011-05-20
==================
* Changed; using devDependencies instead of git submodules
* Fixed redis session example
* Fixed markdown example
* Fixed view caching, should not be enabled in development
2.3.5 / 2011-05-20
==================

View File

@@ -1,17 +1,13 @@
DOCS = $(shell find docs/*.md)
HTMLDOCS =$(DOCS:.md=.html)
TESTS = $(shell find test/*.test.js)
test:
@NODE_ENV=test ./support/expresso/bin/expresso \
@NODE_ENV=test ./node_modules/.bin/expresso \
-I lib \
-I support \
-I support/connect/lib \
-I support/haml/lib \
-I support/jade/lib \
-I support/ejs/lib \
$(TESTFLAGS) \
test/*.test.js
$(TESTS)
test-cov:
@TESTFLAGS=--cov $(MAKE) test

View File

@@ -62,6 +62,8 @@ The following are the major contributors of Express (in no specific order).
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
* [express-params](https://github.com/visionmedia/express-params) param pre-condition functions
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
@@ -76,6 +78,26 @@ Express 1.x is compatible with node 0.2.x and connect < 1.0.
Express 2.x is compatible with node 0.4.x and connect 1.x
## Viewing Examples
First install the dev dependencies to install all the example / test suite deps:
$ npm install
then run whichever tests you want:
$ node examples/jade/app.js
## Running Tests
To run the test suite first invoke the following command within the repo, installing the development dependencies:
$ npm install
then run the tests:
$ make test
## License
(The MIT License)

View File

@@ -11,7 +11,7 @@ var fs = require('fs')
* Framework version.
*/
var version = '2.3.5';
var version = '2.3.10';
/**
* Add session support.
@@ -217,12 +217,8 @@ var app = [
, ' });'
, '});'
, ''
, '// Only listen on $ node app.js'
, ''
, 'if (!module.parent) {'
, ' app.listen(3000);'
, ' console.log("Express server listening on port %d", app.address().port);'
, '}'
, 'app.listen(3000);'
, 'console.log("Express server listening on port %d", app.address().port);'
, ''
].join('\n');
@@ -347,19 +343,22 @@ function createApplicationAt(path) {
// Template support
app = app.replace(':TEMPLATE', templateEngine);
write(path + '/app.js', app);
// package.json
var json = '{\n';
json += ' "name": "application-name"\n';
json += ' , "version": "0.0.1"\n';
json += ' , "private": true\n';
if (cssEngine || templateEngine) {
var comma = cssEngine ? ' ,' : '';
json += ' , "dependencies": {\n';
if (cssEngine) json += ' "' + cssEngine + '": ">= 0.0.1"\n';
if (templateEngine) json += ' ' + comma + ' "' + templateEngine + '": ">= 0.0.1"\n';
json += ' }\n';
}
json += '}';
// Suggestions
process.on('exit', function(){
if (cssEngine) {
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
, cssEngine
, cssEngine);
}
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
, templateEngine
, templateEngine);
});
write(path + '/package.json', json);
write(path + '/app.js', app);
});
}

View File

@@ -1,10 +1,9 @@
### Development Dependencies
Express development dependencies are stored within the _./support_ directory. To
update them execute:
First install the dev dependencies by executing the following command in the repo's directory:
$ git submodule update --init
$ npm install
### Running Tests

View File

@@ -1,4 +1,3 @@
### Installation
$ npm install express
@@ -236,7 +235,7 @@ passed to _express.createServer()_ as you would with a regular Connect server. F
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks, in a progressive manor.
app.use(express.logger({ format: ':method :uri' }));
app.use(express.logger({ format: ':method :url' }));
Typically with connect middleware you would _require('connect')_ like so:
@@ -481,14 +480,6 @@ Doing so, as mentioned drastically improves our route readability, and allows us
res.send('user ' + req.user.name);
});
For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to _next(err)_.
app.param('number', function(n){ return parseInt(n, 10); });
We may also apply the same callback to several placeholders, for example a route GET _/commits/:from-:to_ are both numbers, so we may define them as an array:
app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
### View Rendering
View filenames take the form "&lt;name&gt;.&lt;engine&gt;", where &lt;engine&gt; is the name

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -24,13 +21,20 @@ app.get('/files/:file(*)', function(req, res, next){
, path = __dirname + '/files/' + file;
// either res.download(path) and let
// express handle failures, or provide
// a callback
// a callback as shown below
res.download(path, function(err){
// if an error occurs in this callback
// the file most likely does not exist,
// and it's safe to respond or next(err)
if (err) return next(err);
// the response has invoked .end()
// so you cannnot respond here (of course)
// but the callback is handy for statistics etc.
// the file has been transferred, do not respond
// from here, though you may use this callback
// for stats etc.
console.log('transferred %s', path);
}, function(err){
// this second optional callback is used when
// an error occurs during transmission
});
});

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -25,4 +22,5 @@ app.get('/next', function(req, res, next){
// text/html, and application/json responses to aid in development
app.use('/', express.errorHandler({ dump: true, stack: true }));
app.listen(3000);
app.listen(3000);
console.log('app listening on port 3000');

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,5 +1,3 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.

View File

@@ -1,15 +1,10 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
// $ npm install markdown
/**
* Module dependencies.
*/
var express = require('../../lib/express')
, md = require('markdown').markdown;
, md = require('node-markdown').Markdown;
var app = express.createServer();
@@ -19,7 +14,7 @@ var app = express.createServer();
app.register('.md', {
compile: function(str, options){
var html = md.toHTML(str);
var html = md(str);
return function(locals){
return html.replace(/\{([^}]+)\}/g, function(_, name){
return locals[name];

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -15,7 +12,7 @@ var app = express.createServer(
express.logger(),
// Required by session() middleware
express.cookieDecoder(),
express.cookieParser(),
// Populates:
// - req.session

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -28,7 +28,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.3.5';
exports.version = '2.3.10';
/**
* Shortcut for `new Server(...)`.

View File

@@ -12,8 +12,10 @@
var qs = require('qs')
, connect = require('connect')
, router = require('./router')
, methods = router.methods.concat('del', 'all')
, Router = require('./router')
, view = require('./view')
, toArray = require('./utils').toArray
, methods = router.methods.concat('del', 'all')
, url = require('url')
, utils = connect.utils;
@@ -86,11 +88,11 @@ app.init = function(middleware){
// apply middleware
if (middleware) middleware.forEach(self.use.bind(self));
// use router, expose as app.get(), etc
var fn = router(function(app){ self.routes = app; }, this);
// router
this.routes = new Router(this);
this.__defineGetter__('router', function(){
this.__usedRouter = true;
return fn;
return self.routes.middleware;
});
// default locals
@@ -113,38 +115,62 @@ app.init = function(middleware){
// so that they disregard definition position.
this.on('listening', this.registerErrorHandlers.bind(this));
// route lookup methods
this.remove = function(url){
return self.remove.all(url);
};
this.match = function(url){
return self.match.all(url);
};
this.lookup = function(url){
return self.lookup.all(url);
};
// route manipulation methods
methods.forEach(function(method){
self.match[method] = function(url){
return self.router.match(url, 'all' == method
? null
: method);
};
self.remove[method] = function(url){
return self.router.remove(url, 'all' == method
? null
: method);
};
self.lookup[method] = function(path){
return self.router.lookup(path, 'all' == method
? null
: method);
return self.routes.lookup(method, path);
};
self.match[method] = function(path){
return self.routes.match(method, path);
};
self.remove[method] = function(path){
return self.routes.lookup(method, path).remove();
};
});
// del -> delete
self.lookup.del = self.lookup.delete;
self.match.del = self.match.delete;
self.remove.del = self.remove.delete;
};
/**
* Remove routes matching the given `path`.
*
* @param {Route} path
* @return {Boolean}
* @api public
*/
app.remove = function(path){
return this.routes.lookup('all', path).remove();
};
/**
* Lookup routes defined with a path
* equivalent to `path`.
*
* @param {Stirng} path
* @return {Array}
* @api public
*/
app.lookup = function(path){
return this.routes.lookup('all', path);
};
/**
* Lookup routes matching the given `url`.
*
* @param {Stirng} url
* @return {Array}
* @api public
*/
app.match = function(url){
return this.routes.match('all', url);
};
/**
@@ -289,54 +315,44 @@ app.dynamicHelpers = function(obj){
* Map the given param placeholder `name`(s) to the given callback `fn`.
*
* Param mapping is used to provide pre-conditions to routes
* which us normalized placeholders. For example ":user_id" may
* attempt to load the user from the database, where as ":num" may
* pass the value through `parseInt(num, 10)`.
* which us normalized placeholders. This callback has the same
* signature as regular middleware, for example below when ":userId"
* is used this function will be invoked in an attempt to load the user.
*
* When the callback function accepts only a single argument, the
* value of placeholder is passed:
* app.param('userId', function(req, res, next, id){
* User.find(id, function(err, user){
* if (err) {
* next(err);
* } else if (user) {
* req.user = user;
* next();
* } else {
* next(new Error('failed to load user'));
* }
* });
* });
*
* app.param('page', function(n){ return parseInt(n, 10); });
*
* After which "/users/:page" would automatically provide us with
* an integer for `req.params.page`. If desired we could use the callback
* signature shown below, and immediately `next(new Error('invalid page'))`
* when `parseInt` fails.
*
* Alternatively the callback may accept the request, response, next, and
* the value, acting like middlware:
*
* app.param('userId', function(req, res, next, id){
* User.find(id, function(err, user){
* if (err) {
* next(err);
* } else if (user) {
* req.user = user;
* next();
* } else {
* next(new Error('failed to load user'));
* }
* });
* });
*
* Now every time ":userId" is present, the associated user object
* will be loaded and assigned before the route handler is invoked.
*
* @param {String|Array} name
* @param {String|Array|Function} name
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.param = function(name, fn){
// array
if (Array.isArray(name)) {
name.forEach(function(name){
this.param(name, fn);
}, this);
// param logic
} else if ('function' == typeof name) {
this.routes.param(name);
// single
} else {
if (':' == name[0]) name = name.substr(1);
this.routes.param(name, fn);
}
return this;
};
@@ -464,37 +480,45 @@ app.redirect = function(key, url){
*/
app.configure = function(env, fn){
if ('function' == typeof env) {
fn = env, env = 'all';
}
if ('all' == env || env == this.settings.env) {
fn.call(this);
}
if ('function' == typeof env) fn = env, env = 'all';
if ('all' == env || env == this.settings.env) fn.call(this);
return this;
};
// Generate routing methods
/**
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
*/
methods.forEach(function(method){
app[method] = function(path){
var self = this;
// Lookup
if (1 == arguments.length) {
return this.router.lookup(path, 'all' == method
? null
: method);
}
// Ensure router is mounted
if (1 == arguments.length) return this.routes.lookup(method, path);
var args = [method].concat(toArray(arguments));
if (!this.__usedRouter) this.use(this.router);
// Generate the route
this.routes[method].apply(this, arguments);
return this;
};
return this.routes._route.apply(this.routes, args);
}
});
// Alias delete as "del"
/**
* Special-cased "all" method, applying the given route `path`,
* middleware, and callback to _every_ HTTP method.
*
* @param {String} path
* @param {Function} ...
* @return {Server} for chaining
* @api public
*/
app.all = function(path){
var args = arguments;
if (1 == args.length) return this.routes.lookup('all', path);
methods.forEach(function(method){
if ('all' == method) return;
app[method].apply(this, args);
}, this);
return this;
};
// del -> delete alias
app.del = app.delete;

View File

@@ -106,7 +106,7 @@ res.send = function(body, headers, status){
}
// strip irrelevant headers
if (204 === status) {
if (204 == status) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
}

53
lib/router/collection.js Normal file
View File

@@ -0,0 +1,53 @@
/*!
* Express - router - Collection
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Expose `Collection`.
*/
module.exports = Collection;
/**
* Initialize a new route `Collection`
* with the given `router`.
*
* @param {Router} router
* @api private
*/
function Collection(router) {
Array.apply(this, arguments);
this.router = router;
}
/**
* Inherit from `Array.prototype`.
*/
Collection.prototype.__proto__ = Array.prototype;
/**
* Remove the routes in this collection.
*
* @return {Collection} of routes removed
* @api public
*/
Collection.prototype.remove = function(){
var router = this.router
, len = this.length
, ret = new Collection(this.router);
for (var i = 0; i < len; ++i) {
if (router.remove(this[i])) {
ret.push(this[i]);
}
}
return ret;
};

View File

@@ -1,6 +1,6 @@
/*!
* Express - router
* Express - Router
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
@@ -9,323 +9,375 @@
* Module dependencies.
*/
var utils = require('../utils')
var Route = require('./route')
, Collection = require('./collection')
, utils = require('../utils')
, parse = require('url').parse
, _methods = require('./methods')
, Route = require('./route');
, toArray = utils.toArray;
/**
* Expose router.
* Expose `Router` constructor.
*/
exports = module.exports = router;
exports = module.exports = Router;
/**
* Expose methods.
* Expose HTTP methods.
*/
exports.methods = _methods;
var methods = exports.methods = require('./methods');
/**
* Provides Sinatra-like routing capabilities.
*
* @param {Function} fn
* @return {Function}
* Initialize a new `Router` with the given `app`.
*
* @param {express.HTTPServer} app
* @api private
*/
function router(fn, app){
var self = this
, methods = {}
, routes = {}
, params = {};
function Router(app) {
var self = this;
this.app = app;
this.routes = {};
this.params = {};
this._params = [];
if (!fn) throw new Error('router provider requires a callback function');
// Generate method functions
_methods.forEach(function(method){
methods[method] = generateMethodFunction(method.toUpperCase());
});
// Alias del -> delete
methods.del = methods.delete;
// Apply callback to all methods
methods.all = function(){
var args = arguments;
_methods.forEach(function(name){
methods[name].apply(this, args);
});
return self;
this.middleware = function(req, res, next){
self._dispatch(req, res, next);
};
}
// Register param callback
methods.param = function(name, fn){
params[name] = fn;
};
fn.call(this, methods);
/**
* Register a param callback `fn` for the given `name`.
*
* @param {String|Function} name
* @param {Function} fn
* @return {Router} for chaining
* @api public
*/
function generateMethodFunction(name) {
var localRoutes = routes[name] = routes[name] || [];
return function(path, fn){
var keys = []
, middleware = [];
// slice middleware
if (arguments.length > 2) {
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
fn = middleware.pop();
middleware = utils.flatten(middleware);
}
if (!path) throw new Error(name + ' route requires a path');
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
var options = { sensitive: app.enabled('case sensitive routes') };
var route = new Route(name, path, fn, options);
route.middleware = middleware;
localRoutes.push(route);
return self;
};
Router.prototype.param = function(name, fn){
// param logic
if ('function' == typeof name) {
this._params.push(name);
return;
}
function router(req, res, next){
var route
, self = this;
// apply param functions
var params = this._params
, len = params.length
, ret;
(function pass(i){
if (route = match(req, routes, i)) {
var i = 0
, keys = route.keys;
req.params = route.params;
// Param preconditions
(function param(err) {
try {
var key = keys[i++]
, val = req.params[key]
, fn = params[key];
if ('route' == err) {
pass(req._route_index + 1);
// Error
} else if (err) {
next(err);
// Param has callback
} else if (fn) {
// Return style
if (1 == fn.length) {
req.params[key] = fn(val);
param();
// Middleware style
} else {
fn(req, res, param, val);
}
// Finished processing params
} else if (!key) {
// route middleware
i = 0;
(function nextMiddleware(err){
var fn = route.middleware[i++];
if ('route' == err) {
pass(req._route_index + 1);
} else if (err) {
next(err);
} else if (fn) {
fn(req, res, nextMiddleware);
} else {
route.callback.call(self, req, res, function(err){
if (err) {
next(err);
} else {
pass(req._route_index + 1);
}
});
}
})();
// More params
} else {
param();
}
} catch (err) {
next(err);
}
})();
} else if ('OPTIONS' == req.method) {
options(req, res, routes);
} else {
next();
}
})();
};
router.remove = function(path, method, ret){
var ret = ret || []
, route;
// method specific remove
if (method) {
method = method.toUpperCase();
if (routes[method]) {
for (var i = 0; i < routes[method].length; ++i) {
route = routes[method][i];
if (path == route.path) {
route.index = i;
routes[method].splice(i, 1);
ret.push(route);
--i;
}
}
}
// global remove
} else {
_methods.forEach(function(method){
router.remove(path, method, ret);
});
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
}
}
return ret;
};
// ensure we end up with a
// middleware function
if ('function' != typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
router.lookup = function(path, method, ret){
ret = ret || [];
// method specific lookup
if (method) {
method = method.toUpperCase();
if (routes[method]) {
routes[method].forEach(function(route, i){
if (path == route.path) {
route.index = i;
ret.push(route);
}
});
}
// global lookup
} else {
_methods.forEach(function(method){
router.lookup(path, method, ret);
});
}
return ret;
};
router.match = function(url, method, ret){
var ret = ret || []
, i = 0
, route
, req;
// method specific matches
if (method) {
method = method.toUpperCase();
req = { url: url, method: method };
while (route = match(req, routes, i)) {
i = req._route_index + 1;
route.index = i;
ret.push(route);
}
// global matches
} else {
_methods.forEach(function(method){
router.match(url, method, ret);
});
}
return ret;
};
router.routes = routes;
return router;
}
this.params[name] = fn;
return this;
};
/**
* Respond to OPTIONS.
* Remove the given `route`, returns
* a bool indicating if the route was present
* or not.
*
* @param {ServerRequest} req
* @param {ServerResponse} req
* @param {Array} routes
* @param {Route} route
* @return {Boolean}
* @api public
*/
Router.prototype.remove = function(route){
var routes = this.routes[route.method]
, len = routes.length;
for (var i = 0; i < len; ++i) {
if (route == routes[i]) {
routes.splice(i, 1);
return true;
}
}
};
/**
* Return routes with route paths matching `path`.
*
* @param {String} method
* @param {String} path
* @return {Collection}
* @api public
*/
Router.prototype.lookup = function(method, path){
return this.find(function(route){
return path == route.path
&& (route.method == method
|| method == 'all');
});
};
/**
* Return routes with regexps that match the given `url`.
*
* @param {String} method
* @param {String} url
* @return {Collection}
* @api public
*/
Router.prototype.match = function(method, url){
return this.find(function(route){
return route.match(url)
&& (route.method == method
|| method == 'all');
});
};
/**
* Find routes based on the return value of `fn`
* which is invoked once per route.
*
* @param {Function} fn
* @return {Collection}
* @api public
*/
Router.prototype.find = function(fn){
var len = methods.length
, ret = new Collection(this)
, method
, routes
, route;
for (var i = 0; i < len; ++i) {
method = methods[i];
routes = this.routes[method];
if (!routes) continue;
for (var j = 0, jlen = routes.length; j < jlen; ++j) {
route = routes[j];
if (fn(route)) ret.push(route);
}
}
return ret;
};
/**
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Function} next
* @api private
*/
function options(req, res, routes) {
var pathname = parse(req.url).pathname
, body = optionsFor(pathname, routes).join(',');
res.send(body, { Allow: body });
}
Router.prototype._dispatch = function(req, res, next){
var params = this.params
, self = this;
// route dispatch
(function pass(i){
var route
, keys
, ret;
// match next route
function nextRoute() {
pass(req._route_index + 1);
}
// match route
req.route = route = self._match(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
// no route
if (!route) return next();
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
(function param(err) {
var key = keys[i++]
, val = key && req.params[key.name]
, fn = key && params[key.name]
, ret;
try {
if ('route' == err) {
nextRoute();
} else if (err) {
next(err);
} else if (fn && undefined !== val) {
fn(req, res, param, val);
} else if (key) {
param();
} else {
i = 0;
middleware();
}
} catch (err) {
next(err);
}
})();
// invoke route middleware
function middleware(err) {
var fn = route.middleware[i++];
if ('route' == err) {
nextRoute();
} else if (err) {
next(err);
} else if (fn) {
fn(req, res, middleware);
} else {
done();
}
};
// invoke middleware callback
function done() {
route.callback.call(self, req, res, function(err){
if (err) return next(err);
pass(req._route_index + 1);
});
}
})(0);
};
/**
* Return OPTIONS array for the given `path`, matching `routes`.
* Respond to __OPTIONS__ method.
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @api private
*/
Router.prototype._options = function(req, res){
var path = parse(req.url).pathname
, body = this._optionsFor(path).join(',');
res.send(body, { Allow: body });
};
/**
* Return an array of HTTP verbs or "options" for `path`.
*
* @param {String} path
* @param {Array} routes
* @return {Array}
* @api private
*/
function optionsFor(path, routes) {
return _methods.filter(function(method){
var arr = routes[method.toUpperCase()];
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i].regexp.test(path)) return true;
Router.prototype._optionsFor = function(path){
var self = this;
return methods.filter(function(method){
var routes = self.routes[method];
if (!routes || 'options' == method) return;
for (var i = 0, len = routes.length; i < len; ++i) {
if (routes[i].match(path)) return true;
}
}).map(function(method){
return method.toUpperCase();
});
}
};
/**
* Attempt to match the given request to
* one of the routes. When successful
* a route function is returned.
* Attempt to match a route for `req`
* starting from offset `i`.
*
* @param {ServerRequest} req
* @param {Object} routes
* @return {Function}
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
*/
function match(req, routes, i) {
var captures
, method = req.method
, i = i || 0;
Router.prototype._match = function(req, i){
var method = req.method.toLowerCase()
, url = parse(req.url)
, path = url.pathname
, routes = this.routes
, captures
, route
, keys;
// pass HEAD to GET routes
if ('HEAD' == method) method = 'GET';
if ('head' == method) method = 'get';
// routes for this method
if (routes = routes[method]) {
var url = parse(req.url)
, pathname = url.pathname;
// matching routes
for (var len = routes.length; i < len; ++i) {
var route = routes[i]
, fn = route.callback
, path = route.regexp
, keys = route.keys;
// match
if (captures = path.exec(pathname)) {
route = routes[i];
if (captures = route.match(path)) {
keys = route.keys;
route.params = [];
for (var j = 1, l = captures.length; j < l; ++j) {
var key = keys[j-1],
val = 'string' == typeof captures[j]
// params from capture groups
for (var j = 1, jlen = captures.length; j < jlen; ++j) {
var key = keys[j-1]
, val = 'string' == typeof captures[j]
? decodeURIComponent(captures[j])
: captures[j];
if (key) {
route.params[key] = val;
route.params[key.name] = val;
} else {
route.params.push(val);
}
}
// all done
req._route_index = i;
return route;
}
}
}
}
};
/**
* Route `method`, `path`, and optional middleware
* to the callback `fn`.
*
* @param {String} method
* @param {String} path
* @param {Function} ...
* @param {Function} fn
* @return {Router} for chaining
* @api private
*/
Router.prototype._route = function(method, path, fn){
var app = this.app
, middleware = [];
// slice middleware
if (arguments.length > 3) {
middleware = toArray(arguments, 2);
fn = middleware.pop();
middleware = utils.flatten(middleware);
}
// ensure path and callback are given
if (!path) throw new Error(method + 'route requires a path');
if (!fn) throw new Error(method + ' route ' + path + ' requires a callback');
// create the route
var route = new Route(method, path, fn, {
sensitive: app.enabled('case sensitive routes')
, middleware: middleware
});
// add it
(this.routes[method] = this.routes[method] || [])
.push(route);
return this;
};

View File

@@ -18,6 +18,7 @@ module.exports = Route;
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `middleware` array of middleware
*
* @param {String} method
* @param {String} path
@@ -30,10 +31,23 @@ function Route(method, path, fn, options) {
options = options || {};
this.callback = fn;
this.path = path;
this.regexp = normalize(path, this.keys = [], options.sensitive);
this.method = method;
this.regexp = normalize(path, this.keys = [], options.sensitive);
this.middleware = options.middleware;
}
/**
* Check if this route matches `path` and return captures made.
*
* @param {String} path
* @return {Array}
* @api private
*/
Route.prototype.match = function(path){
return this.regexp.exec(path);
};
/**
* Normalize the given path string,
* returning a regular expression.
@@ -56,7 +70,7 @@ function normalize(path, keys, sensitive) {
.concat('/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push(key);
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)

View File

@@ -120,3 +120,20 @@ exports.parseRange = function(size, str){
});
return valid ? arr : undefined;
};
/**
* Fast alternative to `Array.prototype.slice.call()`.
*
* @param {Arguments} args
* @param {Number} n
* @return {Array}
* @api public
*/
exports.toArray = function(args, i){
var arr = []
, len = args.length
, i = i || 0;
for (; i < len; ++i) arr.push(args[i]);
return arr;
};

View File

@@ -127,11 +127,6 @@ exports.lookup = function(view, options){
function renderPartial(res, view, options, parentLocals, parent){
var collection, object, locals;
// Inherit parent view extension when not present
if (parent && !~view.indexOf('.')) {
view += parent.extension;
}
if (options) {
// collection
if (options.collection) {
@@ -269,7 +264,7 @@ res.partial = function(view, options, fn){
parent.dirname = app.set('views') || process.cwd() + '/views';
// utilize "view engine" option
if (viewEngine) parent.extension = '.' + viewEngine;
if (viewEngine) parent.engine = viewEngine;
// render the partial
try {
@@ -340,11 +335,12 @@ res._render = function(view, opts, fn, parent, sub){
, helpers = app._locals
, dynamicHelpers = app.dynamicViewHelpers
, viewOptions = app.set('view options')
, cacheViews = app.enabled('view cache')
, root = app.set('views') || process.cwd() + '/views';
// cache id
var cid = view + (parent ? ':' + parent.path : '');
var cid = app.enabled('view cache')
? view + (parent ? ':' + parent.path : '')
: false;
// merge "view options"
if (viewOptions) merge(options, viewOptions);

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.3.5",
"version": "2.3.10",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,10 +10,22 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": ">= 1.4.0 < 2.0.0",
"connect": ">= 1.4.1 < 2.0.0",
"mime": ">= 0.0.1",
"qs": ">= 0.0.6"
},
"devDependencies": {
"connect-form": "0.2.1",
"ejs": "0.4.2",
"expresso": "0.7.2",
"hamljs": "0.5.1",
"jade": "0.11.0",
"stylus": "0.13.0",
"should": "0.2.1",
"express-messages": "0.0.2",
"node-markdown": ">= 0.0.1",
"connect-redis": ">= 0.0.1"
},
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
"repository": "git://github.com/visionmedia/express",
"main": "index",

Submodule support/connect deleted from 76816734b8

Submodule support/ejs deleted from 499c4815a5

Submodule support/expresso deleted from 855e0dea07

Submodule support/haml deleted from 55bb6fdc79

Submodule support/jade deleted from 02f6fa456f

Submodule support/mime deleted from ade33a43be

Submodule support/qs deleted from 2b9796e54e

Submodule support/should deleted from 607f8734e8

View File

@@ -461,5 +461,17 @@ module.exports = {
assert.response(app,
{ url: '/another' },
{ body: 'got /another' });
},
'invalid chars': function(){
var app = express.createServer();
app.get('/:name', function(req, res, next){
res.send('invalid');
});
assert.response(app,
{ url: '/%a0' },
{ status: 500 });
}
};

View File

@@ -1 +1,3 @@
h1 Forum Thread
h1 Forum Thread
!= partial('../hello')
!= partial('../hello.haml')

View File

@@ -74,6 +74,38 @@ module.exports = {
});
},
'test precedence': function(){
var app = express.createServer();
var hits = [];
app.all('*', function(req, res, next){
hits.push('all');
next();
});
app.get('/foo', function(req, res, next){
hits.push('GET /foo');
next();
});
app.get('/foo', function(req, res, next){
hits.push('GET /foo2');
next();
});
app.put('/foo', function(req, res, next){
hits.push('PUT /foo');
next();
});
assert.response(app,
{ url: '/foo' },
function(){
hits.should.eql(['all', 'GET /foo', 'GET /foo2']);
});
},
'test named capture groups': function(){
var app = express.createServer();
@@ -81,6 +113,22 @@ module.exports = {
res.send('user ' + req.params.id);
});
app.post('/pin/save/:lat(\\d+.\\d+)/:long(\\d+.\\d+)', function(req, res){
res.send(req.params.lat + ' ' + req.params.long);
});
app.post('/pin/save2/:lat([0-9]+.[0-9]+)/:long([0-9]+.[0-9]+)', function(req, res){
res.send(req.params.lat + ' ' + req.params.long);
});
assert.response(app,
{ url: '/pin/save/1.2/3.4', method: 'POST' },
{ body: '1.2 3.4' });
assert.response(app,
{ url: '/pin/save2/1.2/3.4', method: 'POST' },
{ body: '1.2 3.4' });
assert.response(app,
{ url: '/user/12' },
{ body: 'user 12' });
@@ -90,7 +138,7 @@ module.exports = {
{ body: 'Cannot GET /user/ab' });
},
'test .param()': function(){
'test app.param()': function(){
var app = express.createServer();
var users = [
@@ -101,9 +149,6 @@ module.exports = {
, { name: 'bandit' }
];
function integer(n){ return parseInt(n, 10); };
app.param(['to', 'from'], integer);
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
next();
@@ -116,13 +161,6 @@ module.exports = {
res.send('user ' + req.user.name);
});
app.get('/users/:from-:to', function(req, res, next){
var names = users.slice(req.params.from, req.params.to).map(function(user){
return user.name;
});
res.send('users ' + names.join(', '));
});
assert.response(app,
{ url: '/user/0' },
{ body: 'user tj' });
@@ -130,10 +168,35 @@ module.exports = {
assert.response(app,
{ url: '/user/1' },
{ body: 'user tobi' });
},
'test app.param() optional execution': function(beforeExit){
var app = express.createServer()
, calls = 0;
var months = ['Jan', 'Feb', 'Mar'];
app.param('month', function(req, res, next, n){
req.params.month = months[n];
++calls;
next();
});
app.get('/calendar/:month?', function(req, res, next){
res.send(req.params.month || months[0]);
});
assert.response(app,
{ url: '/users/0-3' },
{ body: 'users tj, tobi, loki' });
{ url: '/calendar' },
{ body: 'Jan' });
assert.response(app,
{ url: '/calendar/1' },
{ body: 'Feb' });
beforeExit(function(){
calls.should.equal(1);
});
},
'test OPTIONS': function(){
@@ -165,9 +228,8 @@ module.exports = {
route.callback.should.be.a('function');
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('GET');
route.index.should.equal(1);
route.keys.should.eql(['id']);
route.method.should.equal('get');
route.keys.should.eql([{ name: 'id', optional: false }]);
app.get('/user').should.have.length(1);
app.get('/user/:id').should.have.length(1);
@@ -228,10 +290,9 @@ module.exports = {
route.callback.should.be.a('function');
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('GET');
route.index.should.equal(2);
route.keys.should.eql(['id']);
route.params.id.should.equal('12');
route.method.should.equal('get');
route.keys.should.eql([{ name: 'id', optional: false }]);
//route.params.id.should.equal('12');
app.match.get('/user').should.have.length(1);
app.match.get('/user/12').should.have.length(2);
@@ -242,6 +303,20 @@ module.exports = {
app.match.all('/user/123').should.have.length(3);
app.match('/user/123').should.have.length(3);
},
'test Collection': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/edit', function(){});
var ret = app.match.all('/user/12').remove();
ret.should.have.length(3);
app.match.all('/user/12').should.have.length(0);
app.get('/user/:id').should.have.length(0);
},
'test "case sensitive routes" setting': function(){
var app = express.createServer();
@@ -263,5 +338,48 @@ module.exports = {
assert.response(app,
{ url: '/Account' },
{ body: 'Account' });
},
'override OPTIONS default': function(){
var app = express.createServer();
app.get('/', function(req, res, next){
});
app.options('/foo', function(req, res, next){
res.header('Allow', 'GET')
res.send('whatever');
});
assert.response(app,
{ url: '/', method: 'OPTIONS' },
{ body: 'GET', headers: { Allow: 'GET' }});
assert.response(app,
{ url: '/foo', method: 'OPTIONS' },
{ body: 'whatever', headers: { Allow: 'GET' }});
},
'test req.route': function(){
var app = express.createServer();
var routes = [];
app.get('/:foo?', function(req, res, next){
routes.push(req.route.path);
next();
});
app.get('/foo', function(req, res, next){
routes.push(req.route.path);
next();
});
assert.response(app,
{ url: '/foo' },
function(){
routes.should.eql(['/:foo?', '/foo']);
});
}
};

View File

@@ -105,6 +105,7 @@ module.exports = {
'test #render()': function(){
var app = create();
app.set('view engine', 'jade');
app.register('haml', require('hamljs'));
app.get('/', function(req, res){
res.render('index.jade', { layout: false });
@@ -667,6 +668,67 @@ module.exports = {
{ body: '<p>two</p>' });
},
'test #partial() relative lookup with "view engine"': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res, next){
res.render('forum/thread', { layout: false });
});
app.get('/2', function(req, res, next){
res.render('forum/../forum/thread', { layout: false });
});
assert.response(app,
{ url: '/2' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
assert.response(app,
{ url: '/' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
},
'test #partial() relative lookup without "view engine"': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('forum/thread.jade', { layout: false });
});
app.get('/2', function(req, res, next){
res.render('forum/../forum/thread.jade', { layout: false });
});
assert.response(app,
{ url: '/2' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
assert.response(app,
{ url: '/' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
},
'test #partial() relative lookup': function(){
var app = create();
app.get('/', function(req, res, next){
res.partial('forum/thread.jade');
});
app.get('/2', function(req, res, next){
res.partial('forum/../forum/thread.jade');
});
assert.response(app,
{ url: '/2' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
assert.response(app,
{ url: '/' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
},
'test #partial() with several calls': function(){
var app = create();