Comparar commits
74 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 2f6dfbc165 | |||
| 49cb53d735 | |||
| 54f1a51a10 | |||
| c6a2674c2b | |||
| 282a10ec83 | |||
| 50276a06df | |||
| f452250f88 | |||
| 9d44e237a5 | |||
| 9f0e5899c2 | |||
| e351a02a06 | |||
| cd167ec777 | |||
| 3290412477 | |||
| acf0128fb4 | |||
| e91ee22a89 | |||
| 45ef08cf99 | |||
| baa7d12ed6 | |||
| 822de581b3 | |||
| 3863a76fc8 | |||
| 9727fac291 | |||
| 5cadbcbbd7 | |||
| 8ee0294672 | |||
| 99789c3182 | |||
| 406a7f4fc7 | |||
| 5a11f82e0e | |||
| 33eca37ec9 | |||
| fbd9cdd11e | |||
| 6ec6657512 | |||
| 8e91d2039a | |||
| 1879648be7 | |||
| 490770171d | |||
| 621063cc18 | |||
| 821defc11b | |||
| dbc1709e0e | |||
| 4d1bda0601 | |||
| 1a9a3674c2 | |||
| 99b7e74422 | |||
| add0a43c40 | |||
| 3dc7c6a254 | |||
| e645123fbd | |||
| 4b104db212 | |||
| 7cdbca0dc9 | |||
| 7d5f06b048 | |||
| e823e31550 | |||
| 34de3ede95 | |||
| 35c7317004 | |||
| 2a89f375f4 | |||
| 8d06b6752a | |||
| dba453345a | |||
| 1f76d5c7d6 | |||
| 8309537527 | |||
| d0f14f8488 | |||
| cee857af9a | |||
| c8dd169ad9 | |||
| 5495fbcd0c | |||
| eb94667ec8 | |||
| cf31355515 | |||
| 604c359a1c | |||
| dad64b8a3b | |||
| ffa432baa2 | |||
| 5c4a356348 | |||
| ef949aec20 | |||
| eaea3f188d | |||
| 818135789b | |||
| 2bbe573748 | |||
| 9efb15b267 | |||
| 5ce1306b75 | |||
| 75c85663ad | |||
| 8c1bcc4c47 | |||
| 200d09c7bd | |||
| ef86474830 | |||
| e2fee9b353 | |||
| 542cab1123 | |||
| 315c05034f | |||
| 148d34629b |
@@ -1,4 +1,47 @@
|
||||
|
||||
0.4.0 / 2010-02-11
|
||||
==================
|
||||
|
||||
* Added flash() example to sample upload app
|
||||
* Added high level restful http client module (express/http)
|
||||
* Changed; RESTful route functions double as HTTP clients. Closes #69
|
||||
* Changed; throwing error when routes are added at runtime
|
||||
* Changed; defaulting render() context to the current Request. Closes #197
|
||||
* Updated haml submodule
|
||||
|
||||
0.3.0 / 2010-02-11
|
||||
==================
|
||||
|
||||
* Updated haml / sass submodules. Closes #200
|
||||
* Added flash message support. Closes #64
|
||||
* Added accepts() now allows multiple args. fixes #117
|
||||
* Added support for plugins to halt. Closes #189
|
||||
* Added alternate layout support. Closes #119
|
||||
* Removed Route#run(). Closes #188
|
||||
* Fixed broken specs due to use(Cookie) missing
|
||||
|
||||
0.2.1 / 2010-02-05
|
||||
==================
|
||||
|
||||
* Added "plot" format option for Profiler (for gnuplot processing)
|
||||
* Added request number to Profiler plugin
|
||||
* Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8
|
||||
* Fixed issue with routes not firing when not files are present. Closes #184
|
||||
* Fixed process.Promise -> events.Promise
|
||||
|
||||
0.2.0 / 2010-02-03
|
||||
==================
|
||||
|
||||
* Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180
|
||||
* Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174
|
||||
* Added expiration support to cache api with reaper. Closes #133
|
||||
* Added cache Store.Memory#reap()
|
||||
* Added Cache; cache api now uses first class Cache instances
|
||||
* Added abstract session Store. Closes #172
|
||||
* Changed; cache Memory.Store#get() utilizing Collection
|
||||
* Renamed MemoryStore -> Store.Memory
|
||||
* Fixed use() of the same plugin several time will always use latest options. Closes #176
|
||||
|
||||
0.1.0 / 2010-02-03
|
||||
==================
|
||||
|
||||
|
||||
+1
-1
@@ -115,9 +115,9 @@ Express is currently being developed with node --version:
|
||||
## Contributors
|
||||
|
||||
* TJ Holowaychuk (visionmedia) <tj@vision-media.ca>
|
||||
* Aaron Heckmann (aheckmann) <aaron.heckmann+github@gmail.com>
|
||||
* Ciaran Jessup (ciaranj) <ciaranj@gmail.com>
|
||||
* Gareth Jones (csausdev) <gareth.jones@sensis.com.au>
|
||||
* Aaron Heckmann (aheckmann) <aaron.heckmann+github@gmail.com>
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ configure(function(){
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: fiveMinutes, reapInterval: oneMinute })
|
||||
use(Session, { lifetime: fiveMinutes, reapInterval: oneMinute })
|
||||
set('root', __dirname)
|
||||
enable('cache static files')
|
||||
//enable('cache view contents')
|
||||
})
|
||||
|
||||
require('express/http')
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils')
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
use(Cookie)
|
||||
use(Session)
|
||||
use(Flash)
|
||||
set('root', __dirname)
|
||||
enable('cache view contents')
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
@@ -16,12 +18,18 @@ get('/', function(){
|
||||
})
|
||||
|
||||
get('/upload', function(){
|
||||
this.render('upload.haml.html')
|
||||
this.render('upload.haml.html', {
|
||||
locals: {
|
||||
flashes: this.flash('info')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
post('/upload', function(){
|
||||
var self = this
|
||||
$(this.param('images')).each(function(image){
|
||||
puts('uploaded ' + image.filename + ' to ' + image.tempfile)
|
||||
puts(image.filename + ' -> ' + image.tempfile)
|
||||
self.flash('info', 'Uploaded ' + image.filename)
|
||||
})
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
@@ -5,4 +5,10 @@
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper= body
|
||||
#wrapper
|
||||
%h1 Upload
|
||||
:if flashes
|
||||
%ul.messages.info
|
||||
:each msg in flashes
|
||||
%li= msg
|
||||
.body= body
|
||||
|
||||
@@ -12,6 +12,8 @@ h1, h2
|
||||
h1
|
||||
:text-shadow 1px 2px 2px #ddd
|
||||
:font-size 60px
|
||||
h2
|
||||
:margin-top 15px
|
||||
|
||||
#wrapper
|
||||
:position relative
|
||||
@@ -54,13 +56,13 @@ form
|
||||
.panel
|
||||
:float left
|
||||
:width 100%
|
||||
:clear both
|
||||
|
||||
a
|
||||
:color #1ABFF1
|
||||
:-webkit-transition-property padding
|
||||
:-webkit-transition-duration 0.15s
|
||||
a:hover
|
||||
:padding 0 5px
|
||||
a:hover:before
|
||||
:content 'visit: '
|
||||
:margin-bottom 15px
|
||||
|
||||
.messages
|
||||
:margin 0
|
||||
:padding 0
|
||||
:border 1px solid #eee
|
||||
=box-shadow 2px 2px 5px #eee
|
||||
li
|
||||
:padding 5px 10px
|
||||
:list-style none
|
||||
@@ -1,11 +1,18 @@
|
||||
%h1 Upload
|
||||
:if typeof images !== 'undefined'
|
||||
.images
|
||||
:each img in images
|
||||
%img{ src: img }
|
||||
|
||||
%h2 Singles
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[0]' }
|
||||
%input{ type: 'file', name: 'images[1]' }
|
||||
%input{ type: 'file', name: 'images[2]' }
|
||||
.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
|
||||
%h2 Multiple
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
|
||||
.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
var path = require('path')
|
||||
require.paths.unshift(__dirname + '/support/js-oo/lib')
|
||||
require.paths.unshift(__dirname + '/support/ejs/lib')
|
||||
require.paths.unshift(__dirname + '/support/haml/lib')
|
||||
|
||||
+8
-17
@@ -14,6 +14,7 @@ process.mixin(require('express/dsl'))
|
||||
*/
|
||||
|
||||
var multipart = require('multipart'),
|
||||
events = require('events'),
|
||||
File = require('file').File,
|
||||
utils = require('express/utils')
|
||||
|
||||
@@ -43,19 +44,6 @@ Route = Class({
|
||||
this.fn = fn
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute this route's #fn with _args_,
|
||||
* against _context_ or GLOBAL.
|
||||
*
|
||||
* @param {array} args
|
||||
* @return {mixed}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
run: function(args, context) {
|
||||
return this.fn.apply(context || GLOBAL, args)
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize _path_. When a RegExp it is simply returned,
|
||||
* otherwise a string is converted to a regular expression
|
||||
@@ -121,7 +109,7 @@ Router = Class({
|
||||
route: function(){
|
||||
var route = this.matchingRoute()
|
||||
if (route)
|
||||
return route.run(this.request.captures.slice(1), this.request)
|
||||
return route.fn.apply(this.request, this.request.captures.slice(1))
|
||||
else if (this.request.accepts('html') && set('helpful 404'))
|
||||
this.request.halt(404, require('express/pages/not-found').render(this.request))
|
||||
else
|
||||
@@ -213,6 +201,7 @@ Server = Class({
|
||||
|
||||
run: function(port, host, backlog){
|
||||
var self = this
|
||||
this.running = true
|
||||
if (host !== undefined) this.host = host
|
||||
if (port !== undefined) this.port = port
|
||||
if (backlog !== undefined) this.backlog = backlog
|
||||
@@ -223,13 +212,13 @@ Server = Class({
|
||||
if (request.headers['content-type'] &&
|
||||
request.headers['content-type'].indexOf('multipart/form-data') !== -1) {
|
||||
var stream = new multipart.Stream(request),
|
||||
promise = new process.Promise,
|
||||
promise = new events.Promise,
|
||||
pendingFiles = 0
|
||||
request.params = { post: {}}
|
||||
promise.timeout(set('upload timeout') || 5000)
|
||||
stream.addListener('part', function(part){
|
||||
if (part.filename) {
|
||||
var file = new File(part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(), 'w')
|
||||
var file = new File(part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(), 'w', { encoding: 'binary' })
|
||||
++pendingFiles
|
||||
part.pos = 0
|
||||
part.addListener('body', function(chunk){
|
||||
@@ -257,6 +246,7 @@ Server = Class({
|
||||
utils.mergeParam(part.name, part.buf, request.params.post)
|
||||
})
|
||||
}).addListener('complete', function(){
|
||||
if (!pendingFiles) promise.emitSuccess()
|
||||
promise.addCallback(function(){ self.route(request, response) })
|
||||
})
|
||||
}
|
||||
@@ -280,6 +270,7 @@ Server = Class({
|
||||
route: function(request, response){
|
||||
request = new Request(request, response)
|
||||
request.trigger('request')
|
||||
if (request.response.finished) return
|
||||
try {
|
||||
if (typeof (body = (new Router(request)).route()) == 'string')
|
||||
request.halt(200, body)
|
||||
@@ -300,7 +291,7 @@ Server = Class({
|
||||
// --- Express
|
||||
|
||||
Express = {
|
||||
version: '0.1.0',
|
||||
version: '0.4.0',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
|
||||
+16
-1
@@ -1,6 +1,12 @@
|
||||
|
||||
// Express - DSL - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('express/http')
|
||||
|
||||
/**
|
||||
* Return a routing function for _method_.
|
||||
*
|
||||
@@ -13,7 +19,12 @@ function route(method) {
|
||||
return function(path, options, fn){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
if (path.indexOf('http://') === 0)
|
||||
return http[method].apply(this, arguments)
|
||||
else if (!Express.server.running)
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
else
|
||||
throw new Error('cannot create route ' + method.toUpperCase() + " `" + path + "' at runtime")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +80,10 @@ exports.disable = function(option) {
|
||||
|
||||
exports.run = function() {
|
||||
configure(Express.environment = process.ENV.EXPRESS_ENV || 'development')
|
||||
$(Express.plugins).each(function(plugin){
|
||||
if ('init' in plugin.klass)
|
||||
plugin.klass.init(plugin.options)
|
||||
})
|
||||
Express.server.run.apply(Express.server, arguments)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
// Express - HTTP - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var http = require('http'),
|
||||
events = require('events'),
|
||||
parse = require('url').parse,
|
||||
queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* Request using the given _method_, _url_
|
||||
* followed by optional _headers_, and _data_.
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {string} url
|
||||
* @param {hash} headers
|
||||
* @param {hash} data
|
||||
* @param {Promise} promise
|
||||
* @return {Promise}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, url, headers, data, promise) {
|
||||
var buf = '',
|
||||
promise = promise || new events.Promise,
|
||||
url = parse(url),
|
||||
path = url.pathname || '/',
|
||||
search = url.search || '',
|
||||
hash = url.hash || '',
|
||||
port = url.port || 80,
|
||||
headers = process.mixin(headers, { host: url.hostname }),
|
||||
client = http.createClient(port, url.hostname)
|
||||
client.addListener('error', function(e){
|
||||
promise.emitError(new Error("client failed to " + method + " `" + url.href + "'"))
|
||||
})
|
||||
if (data) {
|
||||
data = queryString.stringify(data)
|
||||
headers['content-length'] = data.length
|
||||
headers['content-type'] = 'application/x-www-form-urlencoded'
|
||||
}
|
||||
var request = client.request(method, path + search + hash, headers)
|
||||
if (data) request.sendBody(data)
|
||||
request.finish(function(response){
|
||||
if (response.statusCode < 200 || response.statusCode >= 400)
|
||||
promise.emitError(new Error('request failed with status ' + response.statusCode + ' "' + http.STATUS_CODES[response.statusCode] + '"'))
|
||||
else if (response.statusCode >= 300 && response.statusCode < 400)
|
||||
request(method, response.headers.location, headers, data, promise)
|
||||
else {
|
||||
response.setBodyEncoding('utf8')
|
||||
response
|
||||
.addListener('body', function(chunk){ buf += chunk })
|
||||
.addListener('complete', function(){ promise.emitSuccess(buf, response) })
|
||||
}
|
||||
})
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTTP Client function for the given _method_,
|
||||
* which optionally may _allowData_ to be passed.
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {bool} allowData
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function client(method, allowData) {
|
||||
return function(url, headers, data) {
|
||||
if (allowData) data = data || {}
|
||||
return request(method.toUpperCase(), url, headers, data)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Public API
|
||||
|
||||
exports.get = exports.view = client('get')
|
||||
exports.post = exports.create = client('post', true)
|
||||
exports.put = exports.update = client('put', true)
|
||||
exports.del = exports.destroy = client('delete', true)
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
/**
|
||||
* Push _plugin_ with _options_ to the plugin stack.
|
||||
* If _plugin_ has already been pushed, then it's options
|
||||
* will override any previously set.
|
||||
*
|
||||
* @param {Plugin} plugin
|
||||
* @param {hash} options
|
||||
@@ -10,7 +12,12 @@
|
||||
*/
|
||||
|
||||
exports.use = function(plugin, options) {
|
||||
if ('init' in plugin) plugin.init(options)
|
||||
if (Express.environment === 'test' && 'init' in plugin)
|
||||
plugin.init(options)
|
||||
$(Express.plugins).each(function(other, i){
|
||||
if (other.klass === plugin)
|
||||
delete Express.plugins[i]
|
||||
})
|
||||
Express.plugins.push({
|
||||
klass: plugin,
|
||||
options: options
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Express - Plugins - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
process.mixin(require('express/plugins/hooks'))
|
||||
process.mixin(require('express/plugins/flash'))
|
||||
process.mixin(require('express/plugins/cache'))
|
||||
process.mixin(require('express/plugins/cookie'))
|
||||
process.mixin(require('express/plugins/session'))
|
||||
process.mixin(require('express/plugins/profiler'))
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
|
||||
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
// --- Cache
|
||||
|
||||
var Cache = Class({
|
||||
|
||||
/**
|
||||
* Initialize cache with _key_ and _val_.
|
||||
*/
|
||||
|
||||
init: function(key, val) {
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.created = Number(new Date)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = Class({
|
||||
@@ -71,7 +86,7 @@ exports.Store.Memory = exports.Store.extend({
|
||||
|
||||
set: function(key, val) {
|
||||
this.__super__(key, val)
|
||||
return this.data[key] = val
|
||||
return this.data[key] = new Cache(key, val), val
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -95,14 +110,15 @@ exports.Store.Memory = exports.Store.extend({
|
||||
get: function(key) {
|
||||
this.__super__(key)
|
||||
if (key.indexOf('*') === -1)
|
||||
return this.data[key]
|
||||
var vals = {},
|
||||
regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
vals[key] = this.data[key]
|
||||
return vals
|
||||
return this.data[key] instanceof Cache ?
|
||||
this.data[key].val :
|
||||
null
|
||||
var regexp = this.normalize(key)
|
||||
return $(this.data).reduce({}, function(vals, cache){
|
||||
if (regexp.test(cache.key))
|
||||
vals[cache.key] = cache.val
|
||||
return vals
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -127,6 +143,22 @@ exports.Store.Memory = exports.Store.extend({
|
||||
delete this.data[key]
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap caches older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
$(this.data).each(function(cache){
|
||||
if (cache.created < threshold)
|
||||
self.clear(cache.key)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert the given key matching _pattern_
|
||||
* into a RegExp.
|
||||
@@ -143,17 +175,44 @@ exports.Store.Memory = exports.Store.extend({
|
||||
}
|
||||
})
|
||||
|
||||
// --- Cache
|
||||
|
||||
exports.Cache = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize extensions.
|
||||
* Initialize memory store and start reaper.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - dataStore constructor name of cache data store, defaults to Store.Memory
|
||||
* - lifetime lifetime of cache in milliseconds, defaults to one day
|
||||
* - reapInterval, reapEvery interval in milliseconds in which to reap old caches, defaults to one hour
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
Request.include({
|
||||
cache: new exports.Store.Memory
|
||||
})
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
Request.include({ cache: this.store })
|
||||
this.startReaper()
|
||||
},
|
||||
|
||||
/**
|
||||
* Start reaper.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
var self = this,
|
||||
oneDay = 86400000,
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
// Express - Flash - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
exports.Flash = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize extensions.
|
||||
*/
|
||||
|
||||
init: function(){
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Get / set flash _key_ and _val_.
|
||||
*
|
||||
* When a flash _key_ and _val_ are present,
|
||||
* it will persist in the session until outputted.
|
||||
* The _val_ pushed is returned.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* this.flash('info', 'email sent')
|
||||
* this.flash('info', 'email received')
|
||||
* this.flash('info')
|
||||
* // => ['email sent', 'email received']
|
||||
*
|
||||
* this.flash('info')
|
||||
* // => null
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
flash: function(key, val) {
|
||||
if (!this.session.flash) this.session.flash = {}
|
||||
if (!(key in this.session.flash)) this.session.flash[key] = []
|
||||
if (val)
|
||||
return this.session.flash[key].push(val), val
|
||||
else if (key) {
|
||||
var vals = this.session.flash[key]
|
||||
delete this.session.flash[key]
|
||||
if (vals.length) return vals
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
// Express - Hooks - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var before = [],
|
||||
after = []
|
||||
exports.callbacks = { before: [], after: [] }
|
||||
|
||||
/**
|
||||
* Add a _fn_ to be excuted before a request.
|
||||
@@ -12,7 +11,7 @@ var before = [],
|
||||
*/
|
||||
|
||||
exports.before = function(fn) {
|
||||
before.push(fn)
|
||||
exports.callbacks.before.push(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +22,7 @@ exports.before = function(fn) {
|
||||
*/
|
||||
|
||||
exports.after = function(fn) {
|
||||
after.push(fn)
|
||||
exports.callbacks.after.push(fn)
|
||||
}
|
||||
|
||||
// --- Hooks
|
||||
@@ -36,7 +35,8 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
process.mixin(GLOBAL, exports)
|
||||
process.mixin(GLOBAL, { before: exports.before,
|
||||
after: exports.after })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
$(before).each(function(fn){
|
||||
$(exports.callbacks.before).each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
})
|
||||
},
|
||||
@@ -59,7 +59,7 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
$(after).each(function(fn){
|
||||
$(exports.callbacks.after).each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
|
||||
// Express - Profiler - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var n = 0
|
||||
|
||||
exports.Profiler = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize profiler options.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - format 'plot' outputs request duration in milliseconds only
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
}
|
||||
},
|
||||
|
||||
// --- Events
|
||||
|
||||
on: {
|
||||
|
||||
/**
|
||||
@@ -17,9 +39,13 @@ exports.Profiler = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
puts(event.request.method + ' ' +
|
||||
event.request.url.pathname + ': ' +
|
||||
(Number(new Date) - this.start) + ' ms')
|
||||
if (exports.Profiler.format === 'plot')
|
||||
puts(Number(new Date) - this.start)
|
||||
else
|
||||
puts(event.request.method + ' ' +
|
||||
event.request.url.pathname + ': ' +
|
||||
(Number(new Date) - this.start) + ' ms' +
|
||||
' #' + ++n)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -31,9 +31,31 @@ var Session = Class({
|
||||
}
|
||||
})
|
||||
|
||||
// --- MemoryStore
|
||||
// --- Store
|
||||
|
||||
exports.MemoryStore = Class({
|
||||
exports.Store = Class({
|
||||
|
||||
/**
|
||||
* Convert to '[NAME Store]'.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
return '[' + this.name + ' Store]'
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store.Memory
|
||||
|
||||
exports.Store.Memory = exports.Store.extend({
|
||||
|
||||
/**
|
||||
* Datastore name.
|
||||
*/
|
||||
|
||||
name: 'Memory',
|
||||
|
||||
/**
|
||||
* Initialize in-memory session store.
|
||||
@@ -116,6 +138,8 @@ exports.MemoryStore = Class({
|
||||
}
|
||||
})
|
||||
|
||||
// --- Session
|
||||
|
||||
exports.Session = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
@@ -124,9 +148,9 @@ exports.Session = Plugin.extend({
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - dataStore constructor name of session data store, defaults to MemoryStore
|
||||
* - lifetime lifetime of session in milliseconds, defaults to one day
|
||||
* - reapInterval interval in milliseconds in which to reap old sessions, defaults to one hour
|
||||
* - dataStore constructor name of session data store, defaults to Store.Memory
|
||||
* - lifetime lifetime of session in milliseconds, defaults to one day
|
||||
* - reapInterval, reapEvery interval in milliseconds in which to reap old sessions, defaults to one hour
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
@@ -134,7 +158,7 @@ exports.Session = Plugin.extend({
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
this.store = new (this.dataStore || exports.MemoryStore)(options)
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
},
|
||||
|
||||
@@ -150,7 +174,7 @@ exports.Session = Plugin.extend({
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || oneHour)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -51,9 +51,10 @@ exports.View = Plugin.extend({
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - layout: Whether or not to use a layout. Defaults to true
|
||||
* - context: Most engines support an evaluation context (the 'this' keyword)
|
||||
* - layout: The layout to use, none when falsey. Defaults to 'layout'
|
||||
* - locals: Most engines support a hash of local variable names / values.
|
||||
* - context: Most engines support an evaluation context (the 'this' keyword).
|
||||
* Defaults to the current Request instance.
|
||||
*
|
||||
* @param {string} view
|
||||
* @param {hash} options
|
||||
@@ -67,12 +68,13 @@ exports.View = Plugin.extend({
|
||||
path = set('views') + '/' + view,
|
||||
type = path.split('.').slice(-2)[0],
|
||||
ext = utils.extname(path),
|
||||
layout = options.layout === undefined ? true : options.layout
|
||||
layout = options.layout === undefined ? 'layout' : options.layout
|
||||
options.context = options.context || this
|
||||
self.contentType(ext)
|
||||
function render(content) {
|
||||
content = engine[type].render(content, options)
|
||||
if (layout)
|
||||
self.render('layout.' + type + '.' + ext, process.mixin(true, options, {
|
||||
self.render(layout + '.' + type + '.' + ext, process.mixin(true, options, {
|
||||
layout: false,
|
||||
locals: {
|
||||
body: content
|
||||
|
||||
@@ -113,19 +113,31 @@ exports.Request = Class({
|
||||
|
||||
/**
|
||||
* Check if Accept header includes the mime type
|
||||
* for the given _path_, which calls mime.type().
|
||||
* for any of the given paths, which calls mime.type().
|
||||
*
|
||||
* When no Accept header is present true will be
|
||||
* returned as stated in the HTTP specification.
|
||||
*
|
||||
* @param {string} path
|
||||
* Example:
|
||||
*
|
||||
* this.accepts('png')
|
||||
* this.accepts('png', 'jpg', 'gif')
|
||||
* this.accepts('image.png')
|
||||
* this.accepts('path/to/image.png')
|
||||
*
|
||||
* @param {mixed} ...
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
accepts: function(path) {
|
||||
return this.header('accept') ?
|
||||
this.header('accept').indexOf(mime.type(path)) !== -1 :
|
||||
accepts: function() {
|
||||
var accept = this.header('accept')
|
||||
return accept ?
|
||||
$(arguments).any(function(path){
|
||||
var type = mime.type(path)
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0]+'/*') !== -1
|
||||
}) :
|
||||
true
|
||||
},
|
||||
|
||||
|
||||
+23
-12
@@ -1,6 +1,12 @@
|
||||
|
||||
// Express - Helpers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* JSON aliases.
|
||||
*/
|
||||
@@ -99,25 +105,30 @@ exports.escapeRegexp = function(string, chars) {
|
||||
/**
|
||||
* Merge param _key_ and _val_ into _params_. Key
|
||||
* should be a query string key such as 'user[name]',
|
||||
* and _val_ is it's associated value. The root _params_
|
||||
* and _val_ is it's associated object. The root _params_
|
||||
* object is returned.
|
||||
*
|
||||
* This is primarily used by Express for merging multi-part
|
||||
* body values together.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {mixed} val
|
||||
* @return {hash}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.mergeParam = function(key, val, params) {
|
||||
var keys = key.match(/(\w+)/g),
|
||||
orig = params
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (i == len - 1)
|
||||
params[keys[i]] = val
|
||||
else
|
||||
params = (params[keys[i]] = params[keys[i]] || {})
|
||||
var orig = params,
|
||||
keys = key.trim().match(/\w+/g),
|
||||
array = /\[\]$/.test(key)
|
||||
$(keys).reduce(queryString.parseQuery(key), function(parts, key, i){
|
||||
if (i === keys.length - 1)
|
||||
if (key in params)
|
||||
params[key] instanceof Array ?
|
||||
params[key].push(val) :
|
||||
params[key] = [params[key], val]
|
||||
else
|
||||
params[key] = array ? [val] : val
|
||||
if (!(key in params)) params[key] = {}
|
||||
params = params[key]
|
||||
return parts[key]
|
||||
})
|
||||
return orig
|
||||
}
|
||||
+1
-1
Submodule lib/support/haml updated: b1fa42226b...908480ed5d
+1
-1
Submodule lib/support/sass updated: dfc1cd027f...0dd500e7cd
@@ -43,6 +43,7 @@ specs = {
|
||||
'plugins.hooks',
|
||||
'plugins.cookie',
|
||||
'plugins.session',
|
||||
'plugins.flash',
|
||||
],
|
||||
dependant: [
|
||||
'element-collection'
|
||||
@@ -64,4 +65,5 @@ switch (process.ARGV[2]) {
|
||||
run([process.ARGV[2]])
|
||||
}
|
||||
|
||||
Express.environment = 'test'
|
||||
JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: true }).report()
|
||||
@@ -19,92 +19,101 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Store'
|
||||
describe 'Memory'
|
||||
before_each
|
||||
store = new cache.Store.Memory
|
||||
describe 'cache Store.Memory'
|
||||
before_each
|
||||
store = new cache.Store.Memory
|
||||
end
|
||||
|
||||
describe '#toString()'
|
||||
it 'should return [Memory Store]'
|
||||
store.toString().should.eql '[Memory Store]'
|
||||
end
|
||||
|
||||
describe '#toString()'
|
||||
it 'should return [Memory Store]'
|
||||
store.toString().should.eql '[Memory Store]'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set()'
|
||||
describe 'given a key and value'
|
||||
it 'should set the cache data'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should override existing data'
|
||||
store.set('foo', 'bar')
|
||||
store.set('foo', 'baz')
|
||||
store.get('foo').should.eql 'baz'
|
||||
end
|
||||
|
||||
it 'should return data'
|
||||
store.set('foo', 'bar').should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set()'
|
||||
describe 'given a key and value'
|
||||
it 'should set the cache data'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
end
|
||||
|
||||
describe 'given an abitrary key'
|
||||
it 'should throw an error'
|
||||
-{ store.set({}, 'foo') }.should.throw_error
|
||||
end
|
||||
it 'should override existing data'
|
||||
store.set('foo', 'bar')
|
||||
store.set('foo', 'baz')
|
||||
store.get('foo').should.eql 'baz'
|
||||
end
|
||||
|
||||
describe 'given an abitrary value'
|
||||
it 'should serialize as JSON'
|
||||
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
|
||||
end
|
||||
it 'should return data'
|
||||
store.set('foo', 'bar').should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get()'
|
||||
describe 'given a key'
|
||||
it 'should return cached value'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should unserialize JSON data'
|
||||
store.set('user', { name: 'tj' })
|
||||
store.get('user').should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should return a set of caches'
|
||||
store.set('user:1', 'a')
|
||||
store.set('user:2', 'b')
|
||||
store.set('foo', 'bar')
|
||||
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
|
||||
end
|
||||
describe 'given an abitrary key'
|
||||
it 'should throw an error'
|
||||
-{ store.set({}, 'foo') }.should.throw_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear()'
|
||||
describe 'given a key'
|
||||
it 'should delete previous data'
|
||||
store.set('foo', 'bar')
|
||||
store.clear('foo')
|
||||
store.get('foo').should.be_null
|
||||
end
|
||||
describe 'given an abitrary value'
|
||||
it 'should serialize as JSON'
|
||||
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get()'
|
||||
describe 'given a key'
|
||||
it 'should return cached value'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should clear a set of caches'
|
||||
store.set('user:one', '1')
|
||||
store.set('user:two', '2')
|
||||
store.clear('user:*')
|
||||
store.get('user:one').should.be_null
|
||||
store.get('user:two').should.be_null
|
||||
end
|
||||
it 'should unserialize JSON data'
|
||||
store.set('user', { name: 'tj' })
|
||||
store.get('user').should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should return a set of caches'
|
||||
store.set('user:1', 'a')
|
||||
store.set('user:2', 'b')
|
||||
store.set('foo', 'bar')
|
||||
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear()'
|
||||
describe 'given a key'
|
||||
it 'should delete previous data'
|
||||
store.set('foo', 'bar')
|
||||
store.clear('foo')
|
||||
store.get('foo').should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should clear a set of caches'
|
||||
store.set('user:one', '1')
|
||||
store.set('user:two', '2')
|
||||
store.clear('user:*')
|
||||
store.get('user:one').should.be_null
|
||||
store.get('user:two').should.be_null
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reap()'
|
||||
it 'should destroy caches older than the given age in milliseconds'
|
||||
store.set('user:one', '1')
|
||||
store.data['user:one'].created = Number(new Date) - 300
|
||||
store.set('user:two', '2')
|
||||
store.data['user:two'].created = Number(new Date) - 100
|
||||
store.reap(200)
|
||||
store.get('user:one').should.be_null
|
||||
store.get('user:two').should.not.be_null
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cookie').Cookie)
|
||||
use(require('express/plugins/session').Session)
|
||||
use(require('express/plugins/flash').Flash)
|
||||
Session.store.clear()
|
||||
end
|
||||
|
||||
describe 'Flash'
|
||||
describe 'flash()'
|
||||
it 'should push a flash message'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/', function(){ return this.flash('info', 'email received') })
|
||||
get('/info', function(){ return this.flash('info').join(', ') })
|
||||
get('/messages', function(){ return this.flash('info') || 'empty' })
|
||||
|
||||
post('/', headers).body.should.eql 'email sent'
|
||||
get('/', headers).body.should.eql 'email received'
|
||||
get('/info', headers).body.should.eql 'email sent, email received'
|
||||
get('/messages').body.should.eql 'empty'
|
||||
// TODO: seperate once segfault is fixed...
|
||||
end
|
||||
|
||||
it 'should return the message pushed'
|
||||
get('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/').body.should.eql 'email sent'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,10 @@
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/hooks').Hooks)
|
||||
hooks = require('express/plugins/hooks')
|
||||
use(hooks.Hooks)
|
||||
hooks.callbacks.before = []
|
||||
hooks.callbacks.after = []
|
||||
end
|
||||
|
||||
describe 'Hooks'
|
||||
@@ -17,6 +20,15 @@ describe 'Express'
|
||||
get('/user', function(){})
|
||||
get('/user').body.should.eql 'foobar'
|
||||
end
|
||||
|
||||
it 'should be able to halt the request'
|
||||
GLOBAL.before(function(){
|
||||
this.halt(404, 'woo!')
|
||||
})
|
||||
get('/user', function(){ return 'fail' })
|
||||
get('/user').status.should.eql 404
|
||||
get('/user').body.should.eql 'woo!'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after()'
|
||||
|
||||
@@ -22,9 +22,9 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'MemoryStore'
|
||||
describe 'session Store.Memory'
|
||||
before_each
|
||||
memory = new (require('express/plugins/session').MemoryStore)
|
||||
memory = new (require('express/plugins/session').Store.Memory)
|
||||
end
|
||||
|
||||
it 'should persist'
|
||||
@@ -40,6 +40,12 @@ describe 'Express'
|
||||
get('/login', headers).body.should.eql 'tj'
|
||||
end
|
||||
|
||||
describe '#toString()'
|
||||
it 'should return [Memory Store]'
|
||||
memory.toString().should.eql '[Memory Store]'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch()'
|
||||
describe 'when the session does not exist'
|
||||
it 'should return a new Session'
|
||||
|
||||
@@ -58,6 +58,29 @@ describe 'Express'
|
||||
get('/user', { headers: { accept: null }}).body.should.eql 'true'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'should allow multiple arguments'
|
||||
it 'should return true if any mime type is present'
|
||||
get('/user', function(){ return this.accepts('jpeg', 'png').toString() })
|
||||
get('/user', { headers: { accept: 'image/gif,image/png' }}).body.should.eql 'true'
|
||||
end
|
||||
|
||||
it 'should return false if none of the mime types are present'
|
||||
get('/user', function(){ return this.accepts('jpeg', 'png').toString() })
|
||||
get('/user', { headers: { accept: 'text/plain,text/html' }}).body.should.eql 'false'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a media type range was sent'
|
||||
it 'should return true if the group media type matches'
|
||||
get('/user', function(){ return this.accepts('html').toString() })
|
||||
get('/user', { headers: { accept: 'text/plain,text/*' }}).body.should.eql 'true'
|
||||
end
|
||||
it 'should return false if the group media type does not match'
|
||||
get('/user', function(){ return this.accepts('ogg').toString() })
|
||||
get('/user', { headers: { accept: 'text/plain,text/*' }}).body.should.eql 'false'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#halt()'
|
||||
|
||||
@@ -68,5 +68,71 @@ describe 'Express'
|
||||
params.user.email.should.eql 'tj@vision-media.ca'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with an object as value'
|
||||
it 'should preserve it'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', { name: 1 }, params)
|
||||
utils.mergeParam('images[]', { name: 2 }, params)
|
||||
params.images.should.eql [{ name: 1}, { name: 2 }]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'key[number]'
|
||||
it 'should merge correctly'
|
||||
params = { images: { one: 'foo.png' }}
|
||||
utils.mergeParam('images[0]', 'bar.png', params)
|
||||
params.images.one.should.eql 'foo.png'
|
||||
params.images[0].should.eql 'bar.png'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'key[]'
|
||||
describe 'with a single value'
|
||||
it 'should still be an array'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
params.images.should.eql ['1']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with empty params'
|
||||
it 'should merge correctly'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
utils.mergeParam('images[]', '2', params)
|
||||
params.images.should.eql ['1', '2']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with populated params'
|
||||
it 'should convert to an array'
|
||||
params = { images: 'foo.png'}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
params.images.should.eql ['foo.png', '1']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with several merges'
|
||||
it 'should push values'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
utils.mergeParam('images[]', '2', params)
|
||||
utils.mergeParam('images[]', '3', params)
|
||||
params.images.should.eql ['1', '2', '3']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when nested'
|
||||
it 'should marge correctly'
|
||||
params = {}
|
||||
utils.mergeParam('user[tj][images][]', '1', params)
|
||||
utils.mergeParam('user[tj][images][]', '2', params)
|
||||
utils.mergeParam('user[tj][images][]', '3', params)
|
||||
params.user.should.eql { tj: { images: ['1', '2', '3'] }}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
Referência em uma Nova Issue
Bloquear um usuário