├── test ├── page.js ├── mocha.opts ├── index.html ├── support │ └── jsdom.js ├── vendor │ ├── mocha.css │ └── chai.js └── tests.js ├── .npmignore ├── .travis.yml ├── .gitignore ├── Makefile ├── examples ├── transitions │ ├── bg.png │ ├── logo.png │ ├── index.html │ ├── app.js │ └── style.css ├── profile │ ├── style.css │ ├── index.html │ └── app.js ├── query-string │ ├── style.css │ ├── app.js │ ├── index.html │ └── query.js ├── state │ ├── style.css │ ├── index.html │ └── app.js ├── album │ ├── style.css │ ├── index.html │ └── app.js ├── server │ ├── app.js │ └── index.html ├── list.jade ├── notfound │ └── index.html ├── basic │ └── index.html ├── enterprisejs │ └── index.html ├── chrome │ ├── chrome.css │ └── index.html ├── hash │ └── index.html └── index.js ├── component.json ├── bower.json ├── package.json ├── History.md ├── index.js └── Readme.md /test/page.js: -------------------------------------------------------------------------------- 1 | ../index.js -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -R spec 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | !build 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: ["0.10"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | testing 3 | .jshint 4 | coverage.html 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/serve test 4 | 5 | .PHONY: test -------------------------------------------------------------------------------- /examples/transitions/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/page.js/HEAD/examples/transitions/bg.png -------------------------------------------------------------------------------- /examples/transitions/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/page.js/HEAD/examples/transitions/logo.png -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page", 3 | "repo": "visionmedia/page.js", 4 | "version": "1.3.7", 5 | "description": "Tiny client-side router (~1200 bytes)", 6 | "keywords": ["page", "route", "router", "routes", "pushState"], 7 | "scripts": ["index.js"], 8 | "license": "MIT" 9 | } 10 | -------------------------------------------------------------------------------- /examples/profile/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 200 16px "Helvetica Neue", Helvetica, Arial; 4 | } 5 | 6 | h1 { 7 | font-weight: 300; 8 | } 9 | 10 | a { 11 | color: #036dff; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } -------------------------------------------------------------------------------- /examples/query-string/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 200 16px "Helvetica Neue", Helvetica, Arial; 4 | } 5 | 6 | h1 { 7 | font-weight: 300; 8 | } 9 | 10 | a { 11 | color: #036dff; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } -------------------------------------------------------------------------------- /examples/state/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 200 16px "Helvetica Neue", Helvetica, Arial; 4 | } 5 | 6 | h1 { 7 | font-weight: 300; 8 | } 9 | 10 | a { 11 | color: #036dff; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | -------------------------------------------------------------------------------- /examples/album/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 200 16px "Helvetica Neue", Helvetica, Arial; 4 | } 5 | 6 | h1 { 7 | font-weight: 300; 8 | } 9 | 10 | a { 11 | color: #036dff; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | 19 | img { 20 | margin: 5px; 21 | width: 150px; 22 | height: 100px; 23 | } -------------------------------------------------------------------------------- /examples/server/app.js: -------------------------------------------------------------------------------- 1 | 2 | page.base('/server'); 3 | page('/', index) 4 | page('/contact', contact) 5 | page({ dispatch: false }) 6 | 7 | function index() { 8 | document.querySelector('p') 9 | .textContent = 'Index page generated on the client!'; 10 | } 11 | 12 | function contact() { 13 | document.querySelector('p') 14 | .textContent = 'Contact page generated on the client!'; 15 | } -------------------------------------------------------------------------------- /examples/query-string/app.js: -------------------------------------------------------------------------------- 1 | 2 | page.base('/query-string'); 3 | page('*', parse) 4 | page('/', show) 5 | page() 6 | 7 | function parse(ctx, next) { 8 | ctx.query = qs.parse(location.search.slice(1)); 9 | next(); 10 | } 11 | 12 | function show(ctx) { 13 | if (Object.keys(ctx.query).length) { 14 | document 15 | .querySelector('pre') 16 | .textContent = JSON.stringify(ctx.query, null, 2); 17 | } 18 | } -------------------------------------------------------------------------------- /examples/query-string/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Query string 5 | 6 | 7 | 8 |

Query string

9 |
Add a query-string! Maybe: user[name]=tobi
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server 5 | 6 | 7 | 8 | 9 |

Server

10 |

Initial content generated by the server!

11 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/state/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Profile 5 | 6 | 7 | 8 | 9 |

Profile

10 |

11 | 12 | 13 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | page.js - tests 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/album/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Profile 5 | 6 | 7 | 8 | 9 |

Album

10 |

11 | 12 |
13 | 14 | 15 |
16 |

View more Grim Fandango photos.

17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/list.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html 3 | head 4 | title page.js examples 5 | style 6 | body { 7 | padding: 50px; 8 | font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | } 10 | body 11 | h1 Examples 12 | ul 13 | for name in examples 14 | li 15 | a(href='/#{name}')= name 16 | if ('hash' == name) 17 | span 18 | | - 19 | a(href="/hash/section?query=string") section 20 | span 21 | | - 22 | a(href="/hash/section?query=string#subsection") subsection 23 | -------------------------------------------------------------------------------- /examples/profile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Profile 5 | 6 | 7 | 8 | 9 |

Profile

10 |

11 | 12 | 13 | 18 | 19 | Cycle through users 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/transitions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Page.js - transitions 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 |

13 | 14 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page", 3 | "version": "1.3.7", 4 | "description": "Tiny client-side router (~1200 bytes)", 5 | "keywords": ["page", "route", "router", "routes", "pushState"], 6 | "main": "index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/visionmedia/page.js" 10 | }, 11 | "ignore": [ 12 | ".gitignore", 13 | ".npmignore", 14 | ".travis.yml", 15 | "component.json", 16 | "bower.json", 17 | "examples", 18 | "History.md", 19 | "Makefile", 20 | "package.json", 21 | "Readme.md", 22 | "test" 23 | ], 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /test/support/jsdom.js: -------------------------------------------------------------------------------- 1 | before(function(next) { 2 | require('jsdom').env({ 3 | html: '', 4 | done: function(errors, window) { 5 | window.console = console; 6 | window.history.replaceState(null, '', '/'); 7 | global.window = window; 8 | global.location = window.location; 9 | global.document = window.document; 10 | global.history = window.history; 11 | window._$jscoverage = global._$jscoverage; 12 | if (errors) { 13 | errors.forEach(console.log); 14 | throw new Error(errors[0].data.error); 15 | } 16 | next(); 17 | } 18 | }); 19 | }) 20 | -------------------------------------------------------------------------------- /examples/transitions/app.js: -------------------------------------------------------------------------------- 1 | 2 | // content 3 | 4 | var content = document.querySelector('#content'); 5 | 6 | // current page indicator 7 | 8 | var p = document.querySelector('#page'); 9 | 10 | // "mount" it 11 | 12 | page.base('/transitions'); 13 | 14 | // transition "middleware" 15 | 16 | page('*', function(ctx, next){ 17 | if (ctx.init) { 18 | next(); 19 | } else { 20 | content.classList.add('transition'); 21 | setTimeout(function(){ 22 | content.classList.remove('transition'); 23 | next(); 24 | }, 300); 25 | } 26 | }) 27 | 28 | // regular pages 29 | 30 | page('/', function(){ 31 | p.textContent = ''; 32 | }); 33 | 34 | page('/contact', function(){ 35 | p.textContent = 'contact page'; 36 | }); 37 | 38 | page('/about', function(){ 39 | p.textContent = 'about page'; 40 | }); 41 | 42 | page() -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page", 3 | "description": "Tiny client-side router (~1200 bytes)", 4 | "version": "1.3.7", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/visionmedia/page.js.git" 8 | }, 9 | "component": { 10 | "scripts": { 11 | "page": "index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "test": "./node_modules/.bin/mocha", 16 | "coverage": "(cp index.js index.js~; ./node_modules/.bin/jscoverage index.js; mv index-cov.js index.js; ./node_modules/.bin/mocha -R html-cov > coverage.html); mv index.js~ index.js" 17 | }, 18 | "devDependencies": { 19 | "mocha": "^1.18.2", 20 | "chai": "^1.9.0", 21 | "should": "*", 22 | "express": "3.4.4", 23 | "jade": "0.26.1", 24 | "serve": "*", 25 | "jscoverage": "^0.3.8", 26 | "jsdom": "^0.10.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/notfound/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Not Found 5 | 6 | 7 | 8 |

Not Found

9 |

10 | 16 | 17 | 45 | 46 | -------------------------------------------------------------------------------- /examples/transitions/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background: url(/transitions/bg.png); 4 | text-align: center; 5 | font: 200 14px "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | letter-spacing: .1em; 7 | } 8 | 9 | #content { 10 | margin: 100px auto; 11 | padding: 100px 50px 80px 50px; 12 | width: 500px; 13 | background: white; 14 | position: relative; 15 | opacity: 1; 16 | -webkit-border-radius: 2px; 17 | -webkit-box-shadow: 0 0 10px black; 18 | -webkit-transition: -webkit-transform 300ms ease-out, opacity 300ms ease-out; 19 | } 20 | 21 | #content.transition { 22 | -webkit-transform: translateX(100px); 23 | opacity: 0; 24 | } 25 | 26 | #page { 27 | margin-top: 40px; 28 | } 29 | 30 | nav { 31 | position: absolute; 32 | bottom: -26px; 33 | left: 0; 34 | } 35 | 36 | nav a { 37 | text-decoration: none; 38 | color: rgba(255,255,255,.3); 39 | background: -webkit-linear-gradient(#444, #3a3a3a); 40 | display: inline-block; 41 | padding: 5px 10px; 42 | font-size: 12px; 43 | -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.6), inset 0 -1px 0 rgba(255,255,255,.05); 44 | -webkit-border-radius: 0 0 2px 2px; 45 | } 46 | 47 | nav a:hover { 48 | color: white; 49 | } 50 | 51 | nav a:active { 52 | -webkit-box-shadow: 0 0 2px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.05); 53 | } -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Basic 5 | 6 | 7 | 8 |

Basic

9 |

10 | 17 | 18 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/enterprisejs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EnterpriseJS 5 | 6 | 7 | 8 |

EnterpriseJS

9 |

10 | 15 | 16 | 51 | 52 | -------------------------------------------------------------------------------- /examples/chrome/chrome.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | -webkit-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | aside { 8 | float: left; 9 | width: 120px; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | font: 16px Helvetica, "Helvetica Neue", Arial, sans-serif; 15 | color: #5C6166; 16 | } 17 | 18 | h1,h2,h3 { 19 | margin: 0; 20 | font-weight: normal; 21 | } 22 | 23 | h1 { 24 | font-size: 18px; 25 | font-weight: 100; 26 | margin: 20px; 27 | } 28 | 29 | nav ul { 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | nav ul li { 35 | list-style: none; 36 | margin: 5px 0; 37 | padding: 0; 38 | padding-left: 15px; 39 | border-color: white; 40 | -webkit-transition: border-left 400ms; 41 | -moz-transition: border-left 400ms; 42 | } 43 | 44 | nav ul li.active { 45 | border-left: 5px solid #5C6166; 46 | padding-left: 10px; 47 | } 48 | 49 | nav ul li.active a { 50 | color: #5C6166; 51 | } 52 | 53 | nav ul li a { 54 | font-size: 13px; 55 | text-decoration: none; 56 | color: #999; 57 | font-weight: 100; 58 | } 59 | 60 | #content { 61 | padding: 20px; 62 | width: 500px; 63 | float: left; 64 | font-size: 14px; 65 | font-weight: 100; 66 | opacity: 1; 67 | margin-left: 0; 68 | -webkit-transition: opacity 200ms, margin-left 300ms; 69 | -moz-transition: opacity 200ms, margin-left 300ms; 70 | } 71 | 72 | #content.hide { 73 | opacity: 0; 74 | margin-left: -40px; 75 | } 76 | 77 | h2 { 78 | font-weight: normal; 79 | letter-spacing: 1px; 80 | font-size: 18px; 81 | border-bottom: 1px solid #eee; 82 | } -------------------------------------------------------------------------------- /examples/hash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hash 5 | 27 | 28 | 29 | 30 |

Hash

31 | 32 | 38 | 39 |
40 |

Atop

41 |

Btop

42 |

Ctop

43 |
44 | 45 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/state/app.js: -------------------------------------------------------------------------------- 1 | 2 | var avatars = { 3 | glottis: 'http://homepage.ntlworld.com/stureek/images/glottis03.jpg', 4 | manny: 'http://kprojekt.net/wp-content/uploads/manny.jpg', 5 | sal: 'http://homepage.ntlworld.com/stureek/images/sal01.jpg' 6 | }; 7 | 8 | page.base('/state'); 9 | page('/', index); 10 | page('/user/:name', load, show); 11 | page('*', notfound); 12 | page(); 13 | 14 | // everything below is not part of page.js 15 | // just callbacks etc.. 16 | 17 | function text(str) { 18 | document.querySelector('p').textContent = str; 19 | } 20 | 21 | function index() { 22 | text('Click a user below to load their avatar'); 23 | document.querySelector('img') 24 | .style.display = 'none'; 25 | } 26 | 27 | function load(ctx, next) { 28 | // check if we have .state.avatar already available 29 | // this could for example be a cached html fragment. 30 | if (ctx.state.avatar) { 31 | ctx.avatar = ctx.state.avatar; 32 | next(); 33 | return; 34 | } 35 | 36 | // pretend we're querying some database etc 37 | setTimeout(function(){ 38 | // you can assign properties to the context 39 | // for use between these functions. The .state 40 | // property is what's saved in history. 41 | ctx.state.avatar = ctx.avatar = avatars[ctx.params.name]; 42 | ctx.save(); 43 | next(); 44 | }, 600); 45 | } 46 | 47 | function show(ctx) { 48 | var img = document.querySelector('img'); 49 | img.src = ctx.avatar; 50 | img.style.display = 'block'; 51 | text('Showing ' + ctx.params.name); 52 | } 53 | 54 | function notfound() { 55 | document.querySelector('p') 56 | .textContent = 'not found'; 57 | } -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , join = require('path').join 8 | , fs = require('fs'); 9 | 10 | var app = express(); 11 | 12 | app.use(express.favicon()); 13 | app.use(express.logger('dev')); 14 | 15 | app.set('views', __dirname); 16 | app.set('view engine', 'jade'); 17 | app.enable('strict routing'); 18 | 19 | // load examples 20 | 21 | var examples = fs.readdirSync(__dirname).filter(function(file){ 22 | return fs.statSync(__dirname + '/' + file).isDirectory(); 23 | }); 24 | 25 | // routes 26 | 27 | /** 28 | * GET page.js 29 | */ 30 | 31 | app.get('/page.js', function(req, res){ 32 | res.sendfile(join(__dirname, '..', 'index.js')); 33 | }); 34 | 35 | /** 36 | * GET list of examples. 37 | */ 38 | 39 | app.get('/', function(req, res){ 40 | res.render('list', { examples: examples }); 41 | }); 42 | 43 | /** 44 | * GET /:example -> /:example/ 45 | */ 46 | 47 | app.get('/:example', function(req, res){ 48 | res.redirect('/' + req.params.example + '/'); 49 | }); 50 | 51 | /** 52 | * GET /:example/* as a file if it exists. 53 | */ 54 | 55 | app.get('/:example/:file(*)', function(req, res, next){ 56 | var file = req.params.file; 57 | if (!file) return next(); 58 | var name = req.params.example; 59 | var path = join(__dirname, name, file); 60 | fs.stat(path, function(err, stat){ 61 | if (err) return next(); 62 | res.sendfile(path); 63 | }); 64 | }); 65 | 66 | /** 67 | * GET /:example/* as index.html 68 | */ 69 | 70 | app.get('/:example/*', function(req, res){ 71 | var name = req.params.example; 72 | res.sendfile(join(__dirname, name, 'index.html')); 73 | }); 74 | 75 | app.listen(4000); 76 | console.log('Example server listening on port 4000'); 77 | -------------------------------------------------------------------------------- /examples/profile/app.js: -------------------------------------------------------------------------------- 1 | 2 | var avatars = { 3 | glottis: 'http://homepage.ntlworld.com/stureek/images/glottis03.jpg', 4 | manny: 'http://kprojekt.net/wp-content/uploads/manny.jpg', 5 | sal: 'http://homepage.ntlworld.com/stureek/images/sal01.jpg' 6 | }; 7 | 8 | page.base('/profile'); 9 | page('/', index); 10 | // display the index page again after 5s 11 | // only for user related pages 12 | page('/user/*', displayIndexAfter(5000)); 13 | // you dont need to use two, 14 | // but this demonstrates how 15 | // you can "filter" requests 16 | // then move on to the next callback 17 | // to separate concerns 18 | page('/user/:name', load); 19 | page('/user/:name', show); 20 | // or: 21 | // page('/user/:name', load, show); 22 | page('*', notfound); 23 | page(); 24 | 25 | // everything below is not part of page.js 26 | // just callbacks etc.. 27 | 28 | document.querySelector('#cycle').onclick = function(e){ 29 | var i = 0; 30 | var names = Object.keys(avatars); 31 | setInterval(function(){ 32 | var name = names[i++ % names.length]; 33 | page('/user/' + name); 34 | }, 1500); 35 | }; 36 | 37 | function text(str) { 38 | document.querySelector('p').textContent = str; 39 | } 40 | 41 | function displayIndexAfter(ms) { 42 | var id; 43 | return function(ctx, next){ 44 | id && clearTimeout(id); 45 | 46 | if ('/' != ctx.path) { 47 | id = setTimeout(function(){ 48 | page('/'); 49 | }, ms); 50 | } 51 | next(); 52 | } 53 | } 54 | 55 | function index() { 56 | text('Click a user below to load their avatar'); 57 | document.querySelector('img') 58 | .style.display = 'none'; 59 | } 60 | 61 | function load(ctx, next) { 62 | ctx.avatar = avatars[ctx.params.name]; 63 | next(); 64 | } 65 | 66 | function show(ctx) { 67 | var img = document.querySelector('img'); 68 | img.src = ctx.avatar; 69 | img.style.display = 'block'; 70 | text('Showing ' + ctx.params.name); 71 | } 72 | 73 | function notfound() { 74 | document.querySelector('p') 75 | .textContent = 'not found'; 76 | } -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.3.7 / 2013-09-09 3 | ================== 4 | 5 | * fix removal of fragment 6 | 7 | 1.3.6 / 2013-03-12 8 | ================== 9 | 10 | * fix links with target attribute 11 | 12 | 1.3.5 / 2013-02-12 13 | ================== 14 | 15 | * fix ctrl/cmd/shift clicks 16 | 17 | 1.3.4 / 2013-02-04 18 | ================== 19 | 20 | * add tmp .show() dispatch argument 21 | * add keywords to component.json 22 | 23 | 1.3.3 / 2012-12-14 24 | ================== 25 | 26 | * remove + support from path regexps 27 | 28 | 1.3.2 / 2012-11-26 29 | ================== 30 | 31 | * add explicit "#" check 32 | * add `window` to `addEventListener` calls 33 | 34 | 1.3.1 / 2012-09-21 35 | ================== 36 | 37 | * fix: onclick only when e.which == 1 38 | 39 | 1.3.0 / 2012-08-29 40 | ================== 41 | 42 | * add `page(fn)` support. Closes #27 43 | * add component.json 44 | * fix tests 45 | * fix examples 46 | 47 | 1.2.1 / 2012-08-02 48 | ================== 49 | 50 | * add transitions example 51 | * add exposing of `Context` and `Route` constructors 52 | * fix infinite loop issue unhandled paths containing query-strings 53 | 54 | 1.2.0 / 2012-07-05 55 | ================== 56 | 57 | * add `ctx.pathname` 58 | * add `ctx.querystring` 59 | * add support for passing a query-string through the dispatcher [ovaillancourt] 60 | * add `.defaultPrevented` support, ignoring page.js handling [ovaillancourt] 61 | 62 | 1.1.3 / 2012-06-18 63 | ================== 64 | 65 | * Added some basic client-side tests 66 | * Fixed initial dispatch in Firefox 67 | * Changed: no-op on subsequent `page()` calls. Closes #16 68 | 69 | 1.1.2 / 2012-06-13 70 | ================== 71 | 72 | * Fixed origin portno bug preventing :80 and :443 from working properly 73 | * Fixed: prevent cyclic refreshes. Closes #17 74 | 75 | 1.1.1 / 2012-06-11 76 | ================== 77 | 78 | * Added enterprisejs example 79 | * Added: join base for `.canonicalPath`. Closes #12 80 | * Fixed `location.origin` usage [fisch42] 81 | * Fixed `pushState()` when unhandled 82 | 83 | 1.1.0 / 2012-06-06 84 | ================== 85 | 86 | * Added `+` support to pathtoRegexp() 87 | * Added `page.base(path)` support 88 | * Added dispatch option to `page()`. Closes #10 89 | * Added `Context#originalPath` 90 | * Fixed unhandled links when .base is present. Closes #11 91 | * Fixed: `Context#path` to "/" 92 | 93 | 0.0.2 / 2012-06-05 94 | ================== 95 | 96 | * Added `make clean` 97 | * Added some mocha tests 98 | * Fixed: ignore fragments 99 | * Fixed: do not pushState on initial load 100 | -------------------------------------------------------------------------------- /examples/chrome/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Basic 5 | 6 | 7 | 8 | 9 | 19 | 20 |
21 | 22 | 26 | 27 | 31 | 32 | 36 | 37 | 41 | 42 | 105 | 106 | -------------------------------------------------------------------------------- /test/vendor/mocha.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | padding: 60px 50px; 5 | } 6 | 7 | #mocha ul, #mocha li { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | #mocha ul { 13 | list-style: none; 14 | } 15 | 16 | #mocha h1, #mocha h2 { 17 | margin: 0; 18 | } 19 | 20 | #mocha h1 { 21 | margin-top: 15px; 22 | font-size: 1em; 23 | font-weight: 200; 24 | } 25 | 26 | #mocha h1 a { 27 | text-decoration: none; 28 | color: inherit; 29 | } 30 | 31 | #mocha h1 a:hover { 32 | text-decoration: underline; 33 | } 34 | 35 | #mocha .suite .suite h1 { 36 | margin-top: 0; 37 | font-size: .8em; 38 | } 39 | 40 | #mocha h2 { 41 | font-size: 12px; 42 | font-weight: normal; 43 | cursor: pointer; 44 | } 45 | 46 | #mocha .suite { 47 | margin-left: 15px; 48 | } 49 | 50 | #mocha .test { 51 | margin-left: 15px; 52 | } 53 | 54 | #mocha .test:hover h2::after { 55 | position: relative; 56 | top: 0; 57 | right: -10px; 58 | content: '(view source)'; 59 | font-size: 12px; 60 | font-family: arial; 61 | color: #888; 62 | } 63 | 64 | #mocha .test.pending:hover h2::after { 65 | content: '(pending)'; 66 | font-family: arial; 67 | } 68 | 69 | #mocha .test.pass.medium .duration { 70 | background: #C09853; 71 | } 72 | 73 | #mocha .test.pass.slow .duration { 74 | background: #B94A48; 75 | } 76 | 77 | #mocha .test.pass::before { 78 | content: '✓'; 79 | font-size: 12px; 80 | display: block; 81 | float: left; 82 | margin-right: 5px; 83 | color: #00d6b2; 84 | } 85 | 86 | #mocha .test.pass .duration { 87 | font-size: 9px; 88 | margin-left: 5px; 89 | padding: 2px 5px; 90 | color: white; 91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -webkit-border-radius: 5px; 95 | -moz-border-radius: 5px; 96 | -ms-border-radius: 5px; 97 | -o-border-radius: 5px; 98 | border-radius: 5px; 99 | } 100 | 101 | #mocha .test.pass.fast .duration { 102 | display: none; 103 | } 104 | 105 | #mocha .test.pending { 106 | color: #0b97c4; 107 | } 108 | 109 | #mocha .test.pending::before { 110 | content: '◦'; 111 | color: #0b97c4; 112 | } 113 | 114 | #mocha .test.fail { 115 | color: #c00; 116 | } 117 | 118 | #mocha .test.fail pre { 119 | color: black; 120 | } 121 | 122 | #mocha .test.fail::before { 123 | content: '✖'; 124 | font-size: 12px; 125 | display: block; 126 | float: left; 127 | margin-right: 5px; 128 | color: #c00; 129 | } 130 | 131 | #mocha .test pre.error { 132 | color: #c00; 133 | } 134 | 135 | #mocha .test pre { 136 | display: inline-block; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | } 145 | 146 | #error { 147 | color: #c00; 148 | font-size: 1.5 em; 149 | font-weight: 100; 150 | letter-spacing: 1px; 151 | } 152 | 153 | #stats { 154 | position: fixed; 155 | top: 15px; 156 | right: 10px; 157 | font-size: 12px; 158 | margin: 0; 159 | color: #888; 160 | } 161 | 162 | #stats .progress { 163 | float: right; 164 | padding-top: 0; 165 | } 166 | 167 | #stats em { 168 | color: black; 169 | } 170 | 171 | #stats li { 172 | display: inline-block; 173 | margin: 0 5px; 174 | list-style: none; 175 | padding-top: 11px; 176 | } 177 | 178 | code .comment { color: #ddd } 179 | code .init { color: #2F6FAD } 180 | code .string { color: #5890AD } 181 | code .keyword { color: #8A6343 } 182 | code .number { color: #2F6FAD } 183 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var isNode = typeof window !== "object"; 2 | 3 | if (isNode) { 4 | require('./support/jsdom'); 5 | global.chai = require('chai'); 6 | global.page = require('../index'); 7 | } 8 | 9 | var expect = chai.expect; 10 | var called; 11 | 12 | // XXX: super lame hack 13 | 14 | before(function() { 15 | page('/', function(){ 16 | called = true; 17 | }) 18 | }) 19 | 20 | before(function() { 21 | if (isNode) { 22 | // jsdom seems to trigger popstate when replaceState happens, which should 23 | // not be the case 24 | page({ popstate: false }); 25 | } else { 26 | page(); 27 | } 28 | }) 29 | 30 | describe('page', function(){ 31 | describe('on page load', function(){ 32 | it('should invoke the matching callback', function(){ 33 | expect(called).to.equal(true); 34 | }) 35 | }) 36 | 37 | describe('ctx.querystring', function(){ 38 | it('should default to ""', function(done){ 39 | page('/querystring-default', function(ctx){ 40 | expect(ctx.querystring).to.equal(''); 41 | done(); 42 | }); 43 | 44 | page('/querystring-default'); 45 | }) 46 | 47 | it('should expose the query string', function(done){ 48 | page('/querystring', function(ctx){ 49 | expect(ctx.querystring).to.equal('hello=there'); 50 | done(); 51 | }); 52 | 53 | page('/querystring?hello=there'); 54 | }) 55 | }) 56 | 57 | describe('ctx.pathname', function(){ 58 | it('should default to ctx.path', function(done){ 59 | page('/pathname-default', function(ctx){ 60 | expect(ctx.pathname).to.equal('/pathname-default'); 61 | done(); 62 | }); 63 | 64 | page('/pathname-default'); 65 | }) 66 | 67 | it('should omit the query string', function(done){ 68 | page('/pathname', function(ctx){ 69 | expect(ctx.pathname).to.equal('/pathname'); 70 | done(); 71 | }); 72 | 73 | page('/pathname?hello=there'); 74 | }) 75 | }) 76 | 77 | describe('dispatcher', function(){ 78 | it('should ignore query strings', function(done){ 79 | page('/qs', function(ctx){ 80 | done(); 81 | }); 82 | 83 | page('/qs?test=true'); 84 | }) 85 | 86 | it('should ignore query strings with params', function(done){ 87 | page('/qs/:name', function(ctx){ 88 | expect(ctx.params.name).to.equal('tobi'); 89 | done(); 90 | }); 91 | 92 | page('/qs/tobi?test=true'); 93 | }) 94 | 95 | it('should invoke the matching callback', function(done){ 96 | page('/user/:name', function(ctx){ 97 | done(); 98 | }) 99 | 100 | page('/user/tj'); 101 | }) 102 | 103 | it('should populate ctx.params', function(done){ 104 | page('/blog/post/:name', function(ctx){ 105 | expect(ctx.params.name).to.equal('something'); 106 | done(); 107 | }) 108 | 109 | page('/blog/post/something'); 110 | }) 111 | 112 | describe('when next() is invoked', function(){ 113 | it('should invoke subsequent matching middleware', function(done){ 114 | page('/forum/*', function(ctx, next){ 115 | ctx.fullPath = ctx.params[0]; 116 | next(); 117 | }); 118 | 119 | page('/user', function(){ 120 | 121 | }); 122 | 123 | page('/forum/:fid/thread/:tid', function(ctx){ 124 | expect(ctx.fullPath).to.equal('1/thread/2'); 125 | expect(ctx.params.tid).to.equal('2'); 126 | done(); 127 | }); 128 | 129 | page('/forum/1/thread/2'); 130 | }) 131 | }) 132 | }) 133 | 134 | after(function(){ 135 | page('/'); 136 | }) 137 | }) 138 | -------------------------------------------------------------------------------- /examples/album/app.js: -------------------------------------------------------------------------------- 1 | 2 | var perPage = 6 3 | , prev = document.querySelector('#prev') 4 | , next = document.querySelector('#next'); 5 | 6 | page.base('/album'); 7 | page('/', redirect('/photos/0')); 8 | page('/photos/:page', photos) 9 | page('*', notfound); 10 | page(); 11 | 12 | function photos(ctx) { 13 | var page = ~~ctx.params.page; 14 | var from = page * perPage; 15 | var to = from + perPage; 16 | console.log('showing page %s : %s..%s', page, from, to); 17 | var photos = images.slice(from, to); 18 | display(photos); 19 | adjustPager(page); 20 | } 21 | 22 | function redirect(to) { 23 | return function(){ 24 | console.log('redirecting to %s', to); 25 | setTimeout(function(){ 26 | page(to); 27 | }, 0); // TODO: lame 28 | } 29 | } 30 | 31 | function notfound() { 32 | document.querySelector('p') 33 | .textContent = 'not found'; 34 | } 35 | 36 | function display(photos) { 37 | var el = document.querySelector('#photos'); 38 | el.innerHTML = ''; 39 | photos.forEach(function(photo){ 40 | var img = document.createElement('img'); 41 | img.src = photo; 42 | el.appendChild(img); 43 | }); 44 | } 45 | 46 | function adjustPager(page) { 47 | if (page) { 48 | prev.style.display = 'inline-block'; 49 | prev.setAttribute('href', '/album/photos/' + (page - 1)); 50 | } else { 51 | prev.style.display = 'none'; 52 | } 53 | 54 | next.setAttribute('href', '/album/photos/' + (page + 1)); 55 | } 56 | 57 | var images = [ 58 | 'http://upload.wikimedia.org/wikipedia/en/7/76/Grim_Fandango_artwork.jpg' 59 | , 'http://www.xblafans.com/wp-content/uploads//2011/08/Grim-Fandango1.jpg' 60 | , 'http://media.giantbomb.com/uploads/0/1371/190604-grimfandango106_super.jpg' 61 | , 'http://gamejunkienz.files.wordpress.com/2012/02/grimfandango.jpg' 62 | , 'http://onlyhdwallpapers.com/wallpaper/video_games_grim_fandango_lucas_arts_desktop_1024x768_wallpaper-305343.jpg' 63 | , 'http://lparchive.org/Grim-Fandango-(Screenshot)/Update%207/02176.gif' 64 | , 'http://bulk2.destructoid.com/ul/128679-GrimFandangoActionFigures.jpg' 65 | , 'http://www.gamasutra.com/features/20061103/grimfandango02.jpg' 66 | , 'http://metavideogame.files.wordpress.com/2011/05/grimhof_03_1081459316.jpg' 67 | , 'http://3.bp.blogspot.com/_zBnIHQUy4r4/SpxdDw1Z8tI/AAAAAAAABJM/FoCWfc8imnc/s400/GrimFandango1024x768.jpg' 68 | , 'http://www.deviantart.com/download/184571597/grim_fandango_by_domigorgon-d31w0ct.jpg' 69 | , 'http://vgboxart.com/boxes/PC/29535-grim-fandango.png?t=1243105773' 70 | , 'http://kastatic.com/i2/games/1/3/13230/10.png' 71 | , 'http://www.thunderboltgames.com/s/img600/grimfandango.jpg' 72 | , 'http://i2.listal.com/image/1425291/936full-grim-fandango-artwork.jpg' 73 | , 'http://www.xblafans.com/wp-content/uploads//2011/08/Grim-Fandango1.jpg' 74 | , 'http://media.giantbomb.com/uploads/0/1371/190604-grimfandango106_super.jpg' 75 | , 'http://gamejunkienz.files.wordpress.com/2012/02/grimfandango.jpg' 76 | , 'http://onlyhdwallpapers.com/wallpaper/video_games_grim_fandango_lucas_arts_desktop_1024x768_wallpaper-305343.jpg' 77 | , 'http://lparchive.org/Grim-Fandango-(Screenshot)/Update%207/02176.gif' 78 | , 'http://bulk2.destructoid.com/ul/128679-GrimFandangoActionFigures.jpg' 79 | , 'http://www.gamasutra.com/features/20061103/grimfandango02.jpg' 80 | , 'http://metavideogame.files.wordpress.com/2011/05/grimhof_03_1081459316.jpg' 81 | , 'http://3.bp.blogspot.com/_zBnIHQUy4r4/SpxdDw1Z8tI/AAAAAAAABJM/FoCWfc8imnc/s400/GrimFandango1024x768.jpg' 82 | , 'http://www.deviantart.com/download/184571597/grim_fandango_by_domigorgon-d31w0ct.jpg' 83 | , 'http://vgboxart.com/boxes/PC/29535-grim-fandango.png?t=1243105773' 84 | , 'http://kastatic.com/i2/games/1/3/13230/10.png' 85 | , 'http://www.thunderboltgames.com/s/img600/grimfandango.jpg' 86 | , 'http://i2.listal.com/image/1425291/936full-grim-fandango-artwork.jpg' 87 | , 'http://www.xblafans.com/wp-content/uploads//2011/08/Grim-Fandango1.jpg' 88 | , 'http://media.giantbomb.com/uploads/0/1371/190604-grimfandango106_super.jpg' 89 | , 'http://gamejunkienz.files.wordpress.com/2012/02/grimfandango.jpg' 90 | , 'http://onlyhdwallpapers.com/wallpaper/video_games_grim_fandango_lucas_arts_desktop_1024x768_wallpaper-305343.jpg' 91 | , 'http://lparchive.org/Grim-Fandango-(Screenshot)/Update%207/02176.gif' 92 | , 'http://bulk2.destructoid.com/ul/128679-GrimFandangoActionFigures.jpg' 93 | , 'http://www.gamasutra.com/features/20061103/grimfandango02.jpg' 94 | , 'http://metavideogame.files.wordpress.com/2011/05/grimhof_03_1081459316.jpg' 95 | , 'http://3.bp.blogspot.com/_zBnIHQUy4r4/SpxdDw1Z8tI/AAAAAAAABJM/FoCWfc8imnc/s400/GrimFandango1024x768.jpg' 96 | , 'http://www.deviantart.com/download/184571597/grim_fandango_by_domigorgon-d31w0ct.jpg' 97 | , 'http://vgboxart.com/boxes/PC/29535-grim-fandango.png?t=1243105773' 98 | , 'http://kastatic.com/i2/games/1/3/13230/10.png' 99 | , 'http://www.thunderboltgames.com/s/img600/grimfandango.jpg' 100 | , 'http://i2.listal.com/image/1425291/936full-grim-fandango-artwork.jpg' 101 | ]; -------------------------------------------------------------------------------- /examples/query-string/query.js: -------------------------------------------------------------------------------- 1 | (function(exports){ 2 | 3 | /** 4 | * Library version. 5 | */ 6 | 7 | exports.version = '0.4.2'; 8 | 9 | /** 10 | * Object#toString() ref for stringify(). 11 | */ 12 | 13 | var toString = Object.prototype.toString; 14 | 15 | /** 16 | * Cache non-integer test regexp. 17 | */ 18 | 19 | var isint = /^[0-9]+$/; 20 | 21 | function promote(parent, key) { 22 | if (parent[key].length == 0) return parent[key] = {}; 23 | var t = {}; 24 | for (var i in parent[key]) t[i] = parent[key][i]; 25 | parent[key] = t; 26 | return t; 27 | } 28 | 29 | function parse(parts, parent, key, val) { 30 | var part = parts.shift(); 31 | // end 32 | if (!part) { 33 | if (Array.isArray(parent[key])) { 34 | parent[key].push(val); 35 | } else if ('object' == typeof parent[key]) { 36 | parent[key] = val; 37 | } else if ('undefined' == typeof parent[key]) { 38 | parent[key] = val; 39 | } else { 40 | parent[key] = [parent[key], val]; 41 | } 42 | // array 43 | } else { 44 | var obj = parent[key] = parent[key] || []; 45 | if (']' == part) { 46 | if (Array.isArray(obj)) { 47 | if ('' != val) obj.push(val); 48 | } else if ('object' == typeof obj) { 49 | obj[Object.keys(obj).length] = val; 50 | } else { 51 | obj = parent[key] = [parent[key], val]; 52 | } 53 | // prop 54 | } else if (~part.indexOf(']')) { 55 | part = part.substr(0, part.length - 1); 56 | if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); 57 | parse(parts, obj, part, val); 58 | // key 59 | } else { 60 | if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); 61 | parse(parts, obj, part, val); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Merge parent key/val pair. 68 | */ 69 | 70 | function merge(parent, key, val){ 71 | if (~key.indexOf(']')) { 72 | var parts = key.split('[') 73 | , len = parts.length 74 | , last = len - 1; 75 | parse(parts, parent, 'base', val); 76 | // optimize 77 | } else { 78 | if (!isint.test(key) && Array.isArray(parent.base)) { 79 | var t = {}; 80 | for (var k in parent.base) t[k] = parent.base[k]; 81 | parent.base = t; 82 | } 83 | set(parent.base, key, val); 84 | } 85 | 86 | return parent; 87 | } 88 | 89 | /** 90 | * Parse the given obj. 91 | */ 92 | 93 | function parseObject(obj){ 94 | var ret = { base: {} }; 95 | Object.keys(obj).forEach(function(name){ 96 | merge(ret, name, obj[name]); 97 | }); 98 | return ret.base; 99 | } 100 | 101 | /** 102 | * Parse the given str. 103 | */ 104 | 105 | function parseString(str){ 106 | return String(str) 107 | .split('&') 108 | .reduce(function(ret, pair){ 109 | try{ 110 | pair = decodeURIComponent(pair.replace(/\+/g, ' ')); 111 | } catch(e) { 112 | // ignore 113 | } 114 | 115 | var eql = pair.indexOf('=') 116 | , brace = lastBraceInKey(pair) 117 | , key = pair.substr(0, brace || eql) 118 | , val = pair.substr(brace || eql, pair.length) 119 | , val = val.substr(val.indexOf('=') + 1, val.length); 120 | 121 | // ?foo 122 | if ('' == key) key = pair, val = ''; 123 | 124 | return merge(ret, key, val); 125 | }, { base: {} }).base; 126 | } 127 | 128 | /** 129 | * Parse the given query `str` or `obj`, returning an object. 130 | * 131 | * @param {String} str | {Object} obj 132 | * @return {Object} 133 | * @api public 134 | */ 135 | 136 | exports.parse = function(str){ 137 | if (null == str || '' == str) return {}; 138 | return 'object' == typeof str 139 | ? parseObject(str) 140 | : parseString(str); 141 | }; 142 | 143 | /** 144 | * Turn the given `obj` into a query string 145 | * 146 | * @param {Object} obj 147 | * @return {String} 148 | * @api public 149 | */ 150 | 151 | var stringify = exports.stringify = function(obj, prefix) { 152 | if (Array.isArray(obj)) { 153 | return stringifyArray(obj, prefix); 154 | } else if ('[object Object]' == toString.call(obj)) { 155 | return stringifyObject(obj, prefix); 156 | } else if ('string' == typeof obj) { 157 | return stringifyString(obj, prefix); 158 | } else { 159 | return prefix + '=' + obj; 160 | } 161 | }; 162 | 163 | /** 164 | * Stringify the given `str`. 165 | * 166 | * @param {String} str 167 | * @param {String} prefix 168 | * @return {String} 169 | * @api private 170 | */ 171 | 172 | function stringifyString(str, prefix) { 173 | if (!prefix) throw new TypeError('stringify expects an object'); 174 | return prefix + '=' + encodeURIComponent(str); 175 | } 176 | 177 | /** 178 | * Stringify the given `arr`. 179 | * 180 | * @param {Array} arr 181 | * @param {String} prefix 182 | * @return {String} 183 | * @api private 184 | */ 185 | 186 | function stringifyArray(arr, prefix) { 187 | var ret = []; 188 | if (!prefix) throw new TypeError('stringify expects an object'); 189 | for (var i = 0; i < arr.length; i++) { 190 | ret.push(stringify(arr[i], prefix + '['+i+']')); 191 | } 192 | return ret.join('&'); 193 | } 194 | 195 | /** 196 | * Stringify the given `obj`. 197 | * 198 | * @param {Object} obj 199 | * @param {String} prefix 200 | * @return {String} 201 | * @api private 202 | */ 203 | 204 | function stringifyObject(obj, prefix) { 205 | var ret = [] 206 | , keys = Object.keys(obj) 207 | , key; 208 | 209 | for (var i = 0, len = keys.length; i < len; ++i) { 210 | key = keys[i]; 211 | ret.push(stringify(obj[key], prefix 212 | ? prefix + '[' + encodeURIComponent(key) + ']' 213 | : encodeURIComponent(key))); 214 | } 215 | 216 | return ret.join('&'); 217 | } 218 | 219 | /** 220 | * Set `obj`'s `key` to `val` respecting 221 | * the weird and wonderful syntax of a qs, 222 | * where "foo=bar&foo=baz" becomes an array. 223 | * 224 | * @param {Object} obj 225 | * @param {String} key 226 | * @param {String} val 227 | * @api private 228 | */ 229 | 230 | function set(obj, key, val) { 231 | var v = obj[key]; 232 | if (undefined === v) { 233 | obj[key] = val; 234 | } else if (Array.isArray(v)) { 235 | v.push(val); 236 | } else { 237 | obj[key] = [v, val]; 238 | } 239 | } 240 | 241 | /** 242 | * Locate last brace in `str` within the key. 243 | * 244 | * @param {String} str 245 | * @return {Number} 246 | * @api private 247 | */ 248 | 249 | function lastBraceInKey(str) { 250 | var len = str.length 251 | , brace 252 | , c; 253 | for (var i = 0; i < len; ++i) { 254 | c = str[i]; 255 | if (']' == c) brace = false; 256 | if ('[' == c) brace = true; 257 | if ('=' == c && !brace) return i; 258 | } 259 | } 260 | })('undefined' == typeof exports ? qs = {} : exports); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | ;(function(){ 3 | 4 | /** 5 | * Perform initial dispatch. 6 | */ 7 | 8 | var dispatch = true; 9 | 10 | /** 11 | * Base path. 12 | */ 13 | 14 | var base = ''; 15 | 16 | /** 17 | * Running flag. 18 | */ 19 | 20 | var running; 21 | 22 | /** 23 | * Register `path` with callback `fn()`, 24 | * or route `path`, or `page.start()`. 25 | * 26 | * page(fn); 27 | * page('*', fn); 28 | * page('/user/:id', load, user); 29 | * page('/user/' + user.id, { some: 'thing' }); 30 | * page('/user/' + user.id); 31 | * page(); 32 | * 33 | * @param {String|Function} path 34 | * @param {Function} fn... 35 | * @api public 36 | */ 37 | 38 | function page(path, fn) { 39 | // 40 | if ('function' == typeof path) { 41 | return page('*', path); 42 | } 43 | 44 | // route to 45 | if ('function' == typeof fn) { 46 | var route = new Route(path); 47 | for (var i = 1; i < arguments.length; ++i) { 48 | page.callbacks.push(route.middleware(arguments[i])); 49 | } 50 | // show with [state] 51 | } else if ('string' == typeof path) { 52 | page.show(path, fn); 53 | // start [options] 54 | } else { 55 | page.start(path); 56 | } 57 | } 58 | 59 | /** 60 | * Callback functions. 61 | */ 62 | 63 | page.callbacks = []; 64 | 65 | /** 66 | * Get or set basepath to `path`. 67 | * 68 | * @param {String} path 69 | * @api public 70 | */ 71 | 72 | page.base = function(path){ 73 | if (0 == arguments.length) return base; 74 | base = path; 75 | }; 76 | 77 | /** 78 | * Bind with the given `options`. 79 | * 80 | * Options: 81 | * 82 | * - `click` bind to click events [true] 83 | * - `popstate` bind to popstate [true] 84 | * - `dispatch` perform initial dispatch [true] 85 | * 86 | * @param {Object} options 87 | * @api public 88 | */ 89 | 90 | page.start = function(options){ 91 | options = options || {}; 92 | if (running) return; 93 | running = true; 94 | if (false === options.dispatch) dispatch = false; 95 | if (false !== options.popstate) window.addEventListener('popstate', onpopstate, false); 96 | if (false !== options.click) window.addEventListener('click', onclick, false); 97 | if (!dispatch) return; 98 | var url = location.pathname + location.search + location.hash; 99 | page.replace(url, null, true, dispatch); 100 | }; 101 | 102 | /** 103 | * Unbind click and popstate event handlers. 104 | * 105 | * @api public 106 | */ 107 | 108 | page.stop = function(){ 109 | running = false; 110 | removeEventListener('click', onclick, false); 111 | removeEventListener('popstate', onpopstate, false); 112 | }; 113 | 114 | /** 115 | * Show `path` with optional `state` object. 116 | * 117 | * @param {String} path 118 | * @param {Object} state 119 | * @param {Boolean} dispatch 120 | * @return {Context} 121 | * @api public 122 | */ 123 | 124 | page.show = function(path, state, dispatch){ 125 | var ctx = new Context(path, state); 126 | if (false !== dispatch) page.dispatch(ctx); 127 | if (!ctx.unhandled) ctx.pushState(); 128 | return ctx; 129 | }; 130 | 131 | /** 132 | * Replace `path` with optional `state` object. 133 | * 134 | * @param {String} path 135 | * @param {Object} state 136 | * @return {Context} 137 | * @api public 138 | */ 139 | 140 | page.replace = function(path, state, init, dispatch){ 141 | var ctx = new Context(path, state); 142 | ctx.init = init; 143 | if (null == dispatch) dispatch = true; 144 | if (dispatch) page.dispatch(ctx); 145 | ctx.save(); 146 | return ctx; 147 | }; 148 | 149 | /** 150 | * Dispatch the given `ctx`. 151 | * 152 | * @param {Object} ctx 153 | * @api private 154 | */ 155 | 156 | page.dispatch = function(ctx){ 157 | var i = 0; 158 | 159 | function next() { 160 | var fn = page.callbacks[i++]; 161 | if (!fn) return unhandled(ctx); 162 | fn(ctx, next); 163 | } 164 | 165 | next(); 166 | }; 167 | 168 | /** 169 | * Unhandled `ctx`. When it's not the initial 170 | * popstate then redirect. If you wish to handle 171 | * 404s on your own use `page('*', callback)`. 172 | * 173 | * @param {Context} ctx 174 | * @api private 175 | */ 176 | 177 | function unhandled(ctx) { 178 | var current = window.location.pathname + window.location.search; 179 | if (current == ctx.canonicalPath) return; 180 | page.stop(); 181 | ctx.unhandled = true; 182 | window.location = ctx.canonicalPath; 183 | } 184 | 185 | /** 186 | * Initialize a new "request" `Context` 187 | * with the given `path` and optional initial `state`. 188 | * 189 | * @param {String} path 190 | * @param {Object} state 191 | * @api public 192 | */ 193 | 194 | function Context(path, state) { 195 | if ('/' == path[0] && 0 != path.indexOf(base)) path = base + path; 196 | var i = path.indexOf('?'); 197 | 198 | this.canonicalPath = path; 199 | this.path = path.replace(base, '') || '/'; 200 | 201 | this.title = document.title; 202 | this.state = state || {}; 203 | this.state.path = path; 204 | this.querystring = ~i ? path.slice(i + 1) : ''; 205 | this.pathname = ~i ? path.slice(0, i) : path; 206 | this.params = []; 207 | 208 | // fragment 209 | this.hash = ''; 210 | if (!~this.path.indexOf('#')) return; 211 | var parts = this.path.split('#'); 212 | this.path = parts[0]; 213 | this.hash = parts[1] || ''; 214 | this.querystring = this.querystring.split('#')[0]; 215 | } 216 | 217 | /** 218 | * Expose `Context`. 219 | */ 220 | 221 | page.Context = Context; 222 | 223 | /** 224 | * Push state. 225 | * 226 | * @api private 227 | */ 228 | 229 | Context.prototype.pushState = function(){ 230 | history.pushState(this.state, this.title, this.canonicalPath); 231 | }; 232 | 233 | /** 234 | * Save the context state. 235 | * 236 | * @api public 237 | */ 238 | 239 | Context.prototype.save = function(){ 240 | history.replaceState(this.state, this.title, this.canonicalPath); 241 | }; 242 | 243 | /** 244 | * Initialize `Route` with the given HTTP `path`, 245 | * and an array of `callbacks` and `options`. 246 | * 247 | * Options: 248 | * 249 | * - `sensitive` enable case-sensitive routes 250 | * - `strict` enable strict matching for trailing slashes 251 | * 252 | * @param {String} path 253 | * @param {Object} options. 254 | * @api private 255 | */ 256 | 257 | function Route(path, options) { 258 | options = options || {}; 259 | this.path = path; 260 | this.method = 'GET'; 261 | this.regexp = pathtoRegexp(path 262 | , this.keys = [] 263 | , options.sensitive 264 | , options.strict); 265 | } 266 | 267 | /** 268 | * Expose `Route`. 269 | */ 270 | 271 | page.Route = Route; 272 | 273 | /** 274 | * Return route middleware with 275 | * the given callback `fn()`. 276 | * 277 | * @param {Function} fn 278 | * @return {Function} 279 | * @api public 280 | */ 281 | 282 | Route.prototype.middleware = function(fn){ 283 | var self = this; 284 | return function(ctx, next){ 285 | if (self.match(ctx.path, ctx.params)) return fn(ctx, next); 286 | next(); 287 | }; 288 | }; 289 | 290 | /** 291 | * Check if this route matches `path`, if so 292 | * populate `params`. 293 | * 294 | * @param {String} path 295 | * @param {Array} params 296 | * @return {Boolean} 297 | * @api private 298 | */ 299 | 300 | Route.prototype.match = function(path, params){ 301 | var keys = this.keys 302 | , qsIndex = path.indexOf('?') 303 | , pathname = ~qsIndex ? path.slice(0, qsIndex) : path 304 | , m = this.regexp.exec(decodeURIComponent(pathname)); 305 | 306 | if (!m) return false; 307 | 308 | for (var i = 1, len = m.length; i < len; ++i) { 309 | var key = keys[i - 1]; 310 | 311 | var val = 'string' == typeof m[i] 312 | ? decodeURIComponent(m[i]) 313 | : m[i]; 314 | 315 | if (key) { 316 | params[key.name] = undefined !== params[key.name] 317 | ? params[key.name] 318 | : val; 319 | } else { 320 | params.push(val); 321 | } 322 | } 323 | 324 | return true; 325 | }; 326 | 327 | /** 328 | * Normalize the given path string, 329 | * returning a regular expression. 330 | * 331 | * An empty array should be passed, 332 | * which will contain the placeholder 333 | * key names. For example "/user/:id" will 334 | * then contain ["id"]. 335 | * 336 | * @param {String|RegExp|Array} path 337 | * @param {Array} keys 338 | * @param {Boolean} sensitive 339 | * @param {Boolean} strict 340 | * @return {RegExp} 341 | * @api private 342 | */ 343 | 344 | function pathtoRegexp(path, keys, sensitive, strict) { 345 | if (path instanceof RegExp) return path; 346 | if (path instanceof Array) path = '(' + path.join('|') + ')'; 347 | path = path 348 | .concat(strict ? '' : '/?') 349 | .replace(/\/\(/g, '(?:/') 350 | .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){ 351 | keys.push({ name: key, optional: !! optional }); 352 | slash = slash || ''; 353 | return '' 354 | + (optional ? '' : slash) 355 | + '(?:' 356 | + (optional ? slash : '') 357 | + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' 358 | + (optional || ''); 359 | }) 360 | .replace(/([\/.])/g, '\\$1') 361 | .replace(/\*/g, '(.*)'); 362 | return new RegExp('^' + path + '$', sensitive ? '' : 'i'); 363 | } 364 | 365 | /** 366 | * Handle "populate" events. 367 | */ 368 | 369 | function onpopstate(e) { 370 | if (e.state) { 371 | var path = e.state.path; 372 | page.replace(path, e.state); 373 | } 374 | } 375 | 376 | /** 377 | * Handle "click" events. 378 | */ 379 | 380 | function onclick(e) { 381 | if (1 != which(e)) return; 382 | if (e.metaKey || e.ctrlKey || e.shiftKey) return; 383 | if (e.defaultPrevented) return; 384 | 385 | // ensure link 386 | var el = e.target; 387 | while (el && 'A' != el.nodeName) el = el.parentNode; 388 | if (!el || 'A' != el.nodeName) return; 389 | 390 | // Ignore if tag has a "download" attribute 391 | if (el.getAttribute("download")) return; 392 | 393 | // ensure non-hash for the same path 394 | var link = el.getAttribute('href'); 395 | if (el.pathname == location.pathname && (el.hash || '#' == link)) return; 396 | 397 | // Check for mailto: in the href 398 | if (link.indexOf("mailto:") > -1) return; 399 | 400 | // check target 401 | if (el.target) return; 402 | 403 | // x-origin 404 | if (!sameOrigin(el.href)) return; 405 | 406 | // rebuild path 407 | var path = el.pathname + el.search + (el.hash || ''); 408 | 409 | // same page 410 | var orig = path + el.hash; 411 | 412 | path = path.replace(base, ''); 413 | if (base && orig == path) return; 414 | 415 | e.preventDefault(); 416 | page.show(orig); 417 | } 418 | 419 | /** 420 | * Event button. 421 | */ 422 | 423 | function which(e) { 424 | e = e || window.event; 425 | return null == e.which 426 | ? e.button 427 | : e.which; 428 | } 429 | 430 | /** 431 | * Check if `href` is the same origin. 432 | */ 433 | 434 | function sameOrigin(href) { 435 | var origin = location.protocol + '//' + location.hostname; 436 | if (location.port) origin += ':' + location.port; 437 | return 0 == href.indexOf(origin); 438 | } 439 | 440 | /** 441 | * Expose `page`. 442 | */ 443 | 444 | if ('undefined' == typeof module) { 445 | window.page = page; 446 | } else { 447 | module.exports = page; 448 | } 449 | 450 | })(); 451 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ![page router logo](http://f.cl.ly/items/3i3n001d0s1Q031r2q1P/page.png) 2 | 3 | Tiny ~1200 byte Express-inspired client-side router. 4 | 5 | ```js 6 | page('/', index) 7 | page('/user/:user', show) 8 | page('/user/:user/edit', edit) 9 | page('/user/:user/album', album) 10 | page('/user/:user/album/sort', sort) 11 | page('*', notfound) 12 | page() 13 | ``` 14 | 15 | ## Running examples 16 | 17 | To run examples do the following to install dev dependencies and run the example server: 18 | 19 | $ npm install 20 | $ node examples 21 | $ open http://localhost:3000 22 | 23 | Currently we have examples for: 24 | 25 | - `basic` minimal application showing basic routing 26 | - `notfound` similar to `basic` with single-page 404 support 27 | - `album` showing pagination and external links 28 | - `profile` simple user profiles 29 | - `query-string` shows how you can integrate plugins using the router 30 | - `state` illustrates how the history state may be used to cache data 31 | - `server` illustrates how to use the dispatch option to server initial content 32 | - `chrome` Google Chrome style administration interface 33 | - `transitions` Shows off a simple technique for adding transitions between "pages" 34 | 35 | __NOTE__: keep in mind these examples do not use jQuery or similar, so 36 | portions of the examples may be relatively verbose, though they're not 37 | directly related to page.js in any way. 38 | 39 | ## API 40 | 41 | ### page(path, callback[, callback ...]) 42 | 43 | Defines a route mapping `path` to the given `callback(s)`. 44 | 45 | ```js 46 | page('/', user.list) 47 | page('/user/:id', user.load, user.show) 48 | page('/user/:id/edit', user.load, user.edit) 49 | page('*', notfound) 50 | ``` 51 | 52 | Links that are not of the same origin are disregarded 53 | and will not be dispatched. 54 | 55 | ### page(callback) 56 | 57 | This is equivalent to `page('*', callback)` for generic "middleware". 58 | 59 | ### page(path) 60 | 61 | Navigate to the given `path`. 62 | 63 | ```js 64 | $('.view').click(function(e){ 65 | page('/user/12') 66 | e.preventDefault() 67 | }) 68 | ``` 69 | 70 | ### page.show(path) 71 | 72 | Identical to `page(path)` above. 73 | 74 | ### page([options]) 75 | 76 | Register page's `popstate` / `click` bindings. If you're 77 | doing selective binding you'll like want to pass `{ click: false }` 78 | to specify this yourself. The following options are available: 79 | 80 | - `click` bind to click events [__true__] 81 | - `popstate` bind to popstate [__true__] 82 | - `dispatch` perform initial dispatch [true] 83 | 84 | If you wish to load serve initial content 85 | from the server you likely will want to 86 | set `dispatch` to __false__. 87 | 88 | ### page.start([options]) 89 | 90 | Identical to `page([options])` above. 91 | 92 | ### page.stop() 93 | 94 | Unbind both the `popstate` and `click` handlers. 95 | 96 | ### page.base([path]) 97 | 98 | Get or set the base `path`. For example if page.js 99 | is operating within "/blog/*" set the base path to "/blog". 100 | 101 | ### Context 102 | 103 | Routes are passed `Context` objects, these may 104 | be used to share state, for example `ctx.user =`, 105 | as well as the history "state" `ctx.state` that 106 | the `pushState` API provides. 107 | 108 | #### Context#save() 109 | 110 | Saves the context using `replaceState()`. For example 111 | this is useful for caching HTML or other resources 112 | that were loaded for when a user presses "back". 113 | 114 | #### Context#canonicalPath 115 | 116 | Pathname including the "base" (if any) and query string "/admin/login?foo=bar". 117 | 118 | #### Context#path 119 | 120 | Pathname and query string "/login?foo=bar". 121 | 122 | #### Context#querystring 123 | 124 | Query string void of leading `?` such as "foo=bar", defaults to "". 125 | 126 | #### Context#pathname 127 | 128 | The pathname void of query string "/login". 129 | 130 | #### Context#state 131 | 132 | The `pushState` state object. 133 | 134 | #### Context#title 135 | 136 | The `pushState` title. 137 | 138 | ## Routing 139 | 140 | The router uses the same string-to-regexp conversion 141 | that Express does, so things like ":id", ":id?", and "*" work 142 | as you might expect. 143 | 144 | Another aspect that is much like Express is the ability to 145 | pass multiple callbacks. You can use this to your advantage 146 | to flatten nested callbacks, or simply to abstract components. 147 | 148 | ### Separating concerns 149 | 150 | For example suppose you had a route to _edit_ users, and a 151 | route to _view_ users. In both cases you need to load the user. 152 | One way to achieve this is with several callbacks as shown here: 153 | 154 | ```js 155 | page('/user/:user', load, show) 156 | page('/user/:user/edit', load, edit) 157 | ``` 158 | 159 | Using the `*` character we could alter this to match all 160 | routes prefixed with "/user" to achieve the same result: 161 | 162 | ```js 163 | page('/user/*', load) 164 | page('/user/:user', show) 165 | page('/user/:user/edit', edit) 166 | ``` 167 | 168 | Likewise `*` may be used as catch-alls after all routes 169 | acting as a 404 handler, before all routes, in-between and 170 | so on. For example: 171 | 172 | ```js 173 | page('/user/:user', load, show) 174 | page('*', function(){ 175 | $('body').text('Not found!') 176 | }) 177 | ``` 178 | 179 | ### Default 404 behaviour 180 | 181 | By default when a route is not matched, 182 | page.js will invoke `page.stop()` to unbind 183 | itself, and proceed with redirecting to the 184 | location requested. This means you may use 185 | page.js with a multi-page application _without_ 186 | explicitly binding to certain links. 187 | 188 | ### Working with parameters and contexts 189 | 190 | Much like `request` and `response` objects are 191 | passed around in Express, page.js has a single 192 | "Context" object. Using the previous examples 193 | of `load` and `show` for a user, we can assign 194 | arbitrary properties to `ctx` to maintain state 195 | between callbacks. 196 | 197 | First to build a `load` function that will load 198 | the user for subsequent routes you'll need to 199 | access the ":id" passed. You can do this with 200 | `ctx.params.NAME` much like Express: 201 | 202 | ```js 203 | function load(ctx, next){ 204 | var id = ctx.params.id 205 | } 206 | ``` 207 | 208 | Then perform some kind of action against the server, 209 | assigning the user to `ctx.user` for other routes to 210 | utilize. `next()` is then invoked to pass control to 211 | the following matching route in sequence, if any. 212 | 213 | ```js 214 | function load(ctx, next){ 215 | var id = ctx.params.id 216 | $.getJSON('/user/' + id + '.json', function(user){ 217 | ctx.user = user 218 | next() 219 | }) 220 | } 221 | ``` 222 | 223 | The "show" function might look something like this, 224 | however you may render templates or do anything you 225 | want. Note that here `next()` is _not_ invoked, because 226 | this is considered the "end point", and no routes 227 | will be matched until another link is clicked or 228 | `page(path)` is called. 229 | 230 | ```js 231 | function show(ctx){ 232 | $('body') 233 | .empty() 234 | .append('

' + ctx.user.name + '

'); 235 | } 236 | ``` 237 | 238 | Finally using them like so: 239 | 240 | ```js 241 | page('/user/:id', load, show) 242 | ``` 243 | 244 | ### Working with state 245 | 246 | When working with the `pushState` API, 247 | and thus page.js you may optionally provide 248 | state objects available when the user navigates 249 | the history. 250 | 251 | For example if you had a photo application 252 | and you performed a relatively expensive 253 | search to populate a list of images, 254 | normally when a user clicks "back" in 255 | the browser the route would be invoked 256 | and the query would be made yet-again. 257 | 258 | Perhaps the route callback looks like this: 259 | 260 | ```js 261 | function show(ctx){ 262 | $.getJSON('/photos', function(images){ 263 | displayImages(images) 264 | }) 265 | } 266 | ``` 267 | 268 | You may utilize the history's state 269 | object to cache this result, or any 270 | other values you wish. This makes it 271 | possible to completely omit the query 272 | when a user presses back, providing 273 | a much nicer experience. 274 | 275 | ```js 276 | function show(ctx){ 277 | if (ctx.state.images) { 278 | displayImages(ctx.state.images) 279 | } else { 280 | $.getJSON('/photos', function(images){ 281 | ctx.state.images = images 282 | ctx.save() 283 | displayImages(images) 284 | }) 285 | } 286 | } 287 | ``` 288 | 289 | __NOTE__: `ctx.save()` must be used 290 | if the state changes _after_ the first 291 | tick (xhr, setTimeout, etc), otherwise 292 | it is optional and the state will be 293 | saved after dispatching. 294 | 295 | ### Matching paths 296 | 297 | Here are some examples of what's possible 298 | with the string to `RegExp` conversion. 299 | 300 | Match an explicit path: 301 | 302 | ```js 303 | page('/about', callback) 304 | ``` 305 | 306 | Match with required parameter accessed via `ctx.params.name`: 307 | 308 | ```js 309 | page('/user/:name', callback) 310 | ``` 311 | 312 | Match with several params, for example `/user/tj/edit` or 313 | `/user/tj/view`. 314 | 315 | ```js 316 | page('/user/:name/:operation', callback) 317 | ``` 318 | 319 | Match with one optional and one required, now `/user/tj` 320 | will match the same route as `/user/tj/show` etc: 321 | 322 | ```js 323 | page('/user/:name/:operation?', callback) 324 | ``` 325 | 326 | Use the wildcard char `*` to match across segments, 327 | available via `ctx.params[N]` where __N__ is the 328 | index of `*` since you may use several. For example 329 | the following will match `/user/12/edit`, `/user/12/albums/2/admin` 330 | and so on. 331 | 332 | ```js 333 | page('/user/*', loadUser) 334 | ``` 335 | 336 | Named wildcard accessed, for example `/file/javascripts/jquery.js` 337 | would provide "/javascripts/jquery.js" as `ctx.params.file`: 338 | 339 | ```js 340 | page('/file/:file(*)', loadUser) 341 | ``` 342 | 343 | And of course `RegExp` literals, where the capture 344 | groups are available via `ctx.params[N]` where __N__ 345 | is the index of the capture group. 346 | 347 | ```js 348 | page(/^\/commits\/(\d+)\.\.(\d+)/, loadUser) 349 | ``` 350 | 351 | ## Plugins 352 | 353 | Currently there are no official plugins, 354 | however _examples/query-string/query.js_ 355 | will provide a parsed `ctx.query` object 356 | derived from [https://github.com/visionmedia/node-querystring](https://github.com/visionmedia/node-querystring). 357 | 358 | Usage by using "*" to match any path 359 | in order to parse the query-string: 360 | 361 | ```js 362 | page('*', parse) 363 | page('/', show) 364 | page() 365 | 366 | function parse(ctx, next) { 367 | ctx.query = qs.parse(location.search.slice(1)); 368 | next(); 369 | } 370 | 371 | function show(ctx) { 372 | if (Object.keys(ctx.query).length) { 373 | document 374 | .querySelector('pre') 375 | .textContent = JSON.stringify(ctx.query, null, 2); 376 | } 377 | } 378 | ``` 379 | 380 | ### Running tests 381 | 382 | ``` 383 | $ npm install 384 | $ make test 385 | $ open http://localhost:3000/ 386 | ``` 387 | 388 | ### Pull Requests 389 | 390 | * Break commits into a single objective. 391 | * An objective should be a chunk of code that is related but requires explaination. 392 | * Commits should be in the form of what-it-is: how-it-does-it and or why-it's-needed or what-it-is for trivial changes 393 | * Pull requests and commits should be a guide to the code. 394 | 395 | ## License 396 | 397 | (The MIT License) 398 | 399 | Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca> 400 | 401 | Permission is hereby granted, free of charge, to any person obtaining 402 | a copy of this software and associated documentation files (the 403 | 'Software'), to deal in the Software without restriction, including 404 | without limitation the rights to use, copy, modify, merge, publish, 405 | distribute, sublicense, and/or sell copies of the Software, and to 406 | permit persons to whom the Software is furnished to do so, subject to 407 | the following conditions: 408 | 409 | The above copyright notice and this permission notice shall be 410 | included in all copies or substantial portions of the Software. 411 | 412 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 413 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 414 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 415 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 416 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 417 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 418 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 419 | -------------------------------------------------------------------------------- /test/vendor/chai.js: -------------------------------------------------------------------------------- 1 | !function (name, definition) { 2 | if (typeof define == 'function' && typeof define.amd == 'object') define(definition); 3 | else this[name] = definition(); 4 | }('chai', function () { 5 | 6 | // CommonJS require() 7 | 8 | function require(p){ 9 | var path = require.resolve(p) 10 | , mod = require.modules[path]; 11 | if (!mod) throw new Error('failed to require "' + p + '"'); 12 | if (!mod.exports) { 13 | mod.exports = {}; 14 | mod.call(mod.exports, mod, mod.exports, require.relative(path)); 15 | } 16 | return mod.exports; 17 | } 18 | 19 | require.modules = {}; 20 | 21 | require.resolve = function (path){ 22 | var orig = path 23 | , reg = path + '.js' 24 | , index = path + '/index.js'; 25 | return require.modules[reg] && reg 26 | || require.modules[index] && index 27 | || orig; 28 | }; 29 | 30 | require.register = function (path, fn){ 31 | require.modules[path] = fn; 32 | }; 33 | 34 | require.relative = function (parent) { 35 | return function(p){ 36 | if ('.' != p[0]) return require(p); 37 | 38 | var path = parent.split('/') 39 | , segs = p.split('/'); 40 | path.pop(); 41 | 42 | for (var i = 0; i < segs.length; i++) { 43 | var seg = segs[i]; 44 | if ('..' == seg) path.pop(); 45 | else if ('.' != seg) path.push(seg); 46 | } 47 | 48 | return require(path.join('/')); 49 | }; 50 | }; 51 | 52 | 53 | require.register("assertion.js", function(module, exports, require){ 54 | /*! 55 | * chai 56 | * http://chaijs.com 57 | * Copyright(c) 2011-2012 Jake Luer 58 | * MIT Licensed 59 | */ 60 | 61 | 62 | /*! 63 | * Module dependencies. 64 | */ 65 | 66 | var AssertionError = require('./browser/error') 67 | , toString = Object.prototype.toString 68 | , util = require('./utils') 69 | , flag = util.flag; 70 | 71 | /*! 72 | * Module export. 73 | */ 74 | 75 | module.exports = Assertion; 76 | 77 | 78 | /*! 79 | * Assertion Constructor 80 | * 81 | * Creates object for chaining. 82 | * 83 | * @api private 84 | */ 85 | 86 | function Assertion (obj, msg, stack) { 87 | flag(this, 'ssfi', stack || arguments.callee); 88 | flag(this, 'object', obj); 89 | flag(this, 'message', msg); 90 | } 91 | 92 | /*! 93 | * ### Assertion.includeStack 94 | * 95 | * User configurable property, influences whether stack trace 96 | * is included in Assertion error message. Default of false 97 | * suppresses stack trace in the error message 98 | * 99 | * Assertion.includeStack = true; // enable stack on error 100 | * 101 | * @api public 102 | */ 103 | 104 | Assertion.includeStack = false; 105 | 106 | Assertion.addProperty = function (name, fn) { 107 | util.addProperty(this.prototype, name, fn); 108 | }; 109 | 110 | Assertion.addMethod = function (name, fn) { 111 | util.addMethod(this.prototype, name, fn); 112 | }; 113 | 114 | Assertion.addChainableMethod = function (name, fn, chainingBehavior) { 115 | util.addChainableMethod(this.prototype, name, fn, chainingBehavior); 116 | }; 117 | 118 | Assertion.overwriteProperty = function (name, fn) { 119 | util.overwriteProperty(this.prototype, name, fn); 120 | }; 121 | 122 | Assertion.overwriteMethod = function (name, fn) { 123 | util.overwriteMethod(this.prototype, name, fn); 124 | }; 125 | 126 | /*! 127 | * ### .assert(expression, message, negateMessage, expected, actual) 128 | * 129 | * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. 130 | * 131 | * @name assert 132 | * @param {Philosophical} expression to be tested 133 | * @param {String} message to display if fails 134 | * @param {String} negatedMessage to display if negated expression fails 135 | * @param {Mixed} expected value (remember to check for negation) 136 | * @param {Mixed} actual (optional) will default to `this.obj` 137 | * @api private 138 | */ 139 | 140 | Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual) { 141 | var msg = util.getMessage(this, arguments) 142 | , actual = util.getActual(this, arguments) 143 | , ok = util.test(this, arguments); 144 | 145 | if (!ok) { 146 | throw new AssertionError({ 147 | message: msg 148 | , actual: actual 149 | , expected: expected 150 | , stackStartFunction: (Assertion.includeStack) ? this.assert : flag(this, 'ssfi') 151 | }); 152 | } 153 | }; 154 | 155 | /*! 156 | * 157 | * ### ._obj 158 | * 159 | * Quick reference to stored `actual` value for plugin developers. 160 | * 161 | * @api private 162 | */ 163 | 164 | Object.defineProperty(Assertion.prototype, '_obj', 165 | { get: function () { 166 | return flag(this, 'object'); 167 | } 168 | , set: function (val) { 169 | flag(this, 'object', val); 170 | } 171 | }); 172 | 173 | /** 174 | * ### Language Chains 175 | * 176 | * The following are provide as chainable getters to 177 | * improve the readability of your assertions. They 178 | * do not provide an testing capability unless they 179 | * have been overwritten by a plugin. 180 | * 181 | * **Chains** 182 | * 183 | * - to 184 | * - be 185 | * - been 186 | * - is 187 | * - and 188 | * - have 189 | * - with 190 | * 191 | * @name language chains 192 | * @api public 193 | */ 194 | 195 | [ 'to', 'be', 'been' 196 | , 'is', 'and', 'have' 197 | , 'with' ].forEach(function (chain) { 198 | Object.defineProperty(Assertion.prototype, chain, 199 | { get: function () { 200 | return this; 201 | } 202 | , configurable: true 203 | }); 204 | }); 205 | 206 | /** 207 | * ### .not 208 | * 209 | * Negates any of assertions following in the chain. 210 | * 211 | * expect(foo).to.not.equal('bar'); 212 | * expect(goodFn).to.not.throw(Error); 213 | * expect({ foo: 'baz' }).to.have.property('foo') 214 | * .and.not.equal('bar'); 215 | * 216 | * @name not 217 | * @api public 218 | */ 219 | 220 | Object.defineProperty(Assertion.prototype, 'not', 221 | { get: function () { 222 | flag(this, 'negate', true); 223 | return this; 224 | } 225 | , configurable: true 226 | }); 227 | 228 | /** 229 | * ### .deep 230 | * 231 | * Sets the `deep` flag, later used by the `equal` and 232 | * `property` assertions. 233 | * 234 | * expect(foo).to.deep.equal({ bar: 'baz' }); 235 | * expect({ foo: { bar: { baz: 'quux' } } }) 236 | * .to.have.deep.property('foo.bar.baz', 'quux'); 237 | * 238 | * @name deep 239 | * @api public 240 | */ 241 | 242 | Object.defineProperty(Assertion.prototype, 'deep', 243 | { get: function () { 244 | flag(this, 'deep', true); 245 | return this; 246 | } 247 | , configurable: true 248 | }); 249 | 250 | /** 251 | * ### .a(type) 252 | * 253 | * The `a` and `an` assertions are aliases that can be 254 | * used either as language chains or to assert a value's 255 | * type (as revealed by `Object.prototype.toString`). 256 | * 257 | * // typeof 258 | * expect('test').to.be.a('string'); 259 | * expect({ foo: 'bar' }).to.be.an('object'); 260 | * expect(null).to.be.a('null'); 261 | * expect(undefined).to.be.an('undefined'); 262 | * 263 | * // language chain 264 | * expect(foo).to.be.an.instanceof(Foo); 265 | * 266 | * @name a 267 | * @alias an 268 | * @param {String} type 269 | * @api public 270 | */ 271 | 272 | function an(type) { 273 | var obj = flag(this, 'object') 274 | , klassStart = type.charAt(0).toUpperCase() 275 | , klass = klassStart + type.slice(1) 276 | , article = ~[ 'A', 'E', 'I', 'O', 'U' ].indexOf(klassStart) ? 'an ' : 'a '; 277 | 278 | this.assert( 279 | '[object ' + klass + ']' === toString.call(obj) 280 | , 'expected #{this} to be ' + article + type 281 | , 'expected #{this} not to be ' + article + type 282 | , '[object ' + klass + ']' 283 | , toString.call(obj) 284 | ); 285 | } 286 | 287 | Assertion.addChainableMethod('an', an); 288 | Assertion.addChainableMethod('a', an); 289 | 290 | /** 291 | * ### .include(value) 292 | * 293 | * The `include` and `contain` assertions can be used as either property 294 | * based language chains or as methods to assert the inclusion of an object 295 | * in an array or a substring in a string. When used as language chains, 296 | * they toggle the `contain` flag for the `keys` assertion. 297 | * 298 | * expect([1,2,3]).to.include(2); 299 | * expect('foobar').to.contain('foo'); 300 | * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); 301 | * 302 | * @name include 303 | * @alias contain 304 | * @param {Object|String|Number} obj 305 | * @api public 306 | */ 307 | 308 | function includeChainingBehavior () { 309 | flag(this, 'contains', true); 310 | } 311 | 312 | function include (val) { 313 | var obj = flag(this, 'object') 314 | this.assert( 315 | ~obj.indexOf(val) 316 | , 'expected #{this} to include ' + util.inspect(val) 317 | , 'expected #{this} to not include ' + util.inspect(val)); 318 | } 319 | 320 | Assertion.addChainableMethod('include', include, includeChainingBehavior); 321 | Assertion.addChainableMethod('contain', include, includeChainingBehavior); 322 | 323 | /** 324 | * ### .ok 325 | * 326 | * Asserts that the target is truthy. 327 | * 328 | * expect('everthing').to.be.ok; 329 | * expect(1).to.be.ok; 330 | * expect(false).to.not.be.ok; 331 | * expect(undefined).to.not.be.ok; 332 | * expect(null).to.not.be.ok; 333 | * 334 | * @name ok 335 | * @api public 336 | */ 337 | 338 | Object.defineProperty(Assertion.prototype, 'ok', 339 | { get: function () { 340 | this.assert( 341 | flag(this, 'object') 342 | , 'expected #{this} to be truthy' 343 | , 'expected #{this} to be falsy'); 344 | 345 | return this; 346 | } 347 | , configurable: true 348 | }); 349 | 350 | /** 351 | * ### .true 352 | * 353 | * Asserts that the target is `true`. 354 | * 355 | * expect(true).to.be.true; 356 | * expect(1).to.not.be.true; 357 | * 358 | * @name true 359 | * @api public 360 | */ 361 | 362 | Object.defineProperty(Assertion.prototype, 'true', 363 | { get: function () { 364 | this.assert( 365 | true === flag(this, 'object') 366 | , 'expected #{this} to be true' 367 | , 'expected #{this} to be false' 368 | , this.negate ? false : true 369 | ); 370 | 371 | return this; 372 | } 373 | , configurable: true 374 | }); 375 | 376 | /** 377 | * ### .false 378 | * 379 | * Asserts that the target is `false`. 380 | * 381 | * expect(false).to.be.false; 382 | * expect(0).to.not.be.false; 383 | * 384 | * @name false 385 | * @api public 386 | */ 387 | 388 | Object.defineProperty(Assertion.prototype, 'false', 389 | { get: function () { 390 | this.assert( 391 | false === flag(this, 'object') 392 | , 'expected #{this} to be false' 393 | , 'expected #{this} to be true' 394 | , this.negate ? true : false 395 | ); 396 | 397 | return this; 398 | } 399 | , configurable: true 400 | }); 401 | 402 | /** 403 | * ### .null 404 | * 405 | * Asserts that the target is `null`. 406 | * 407 | * expect(null).to.be.null; 408 | * expect(undefined).not.to.be.null; 409 | * 410 | * @name null 411 | * @api public 412 | */ 413 | 414 | Object.defineProperty(Assertion.prototype, 'null', 415 | { get: function () { 416 | this.assert( 417 | null === flag(this, 'object') 418 | , 'expected #{this} to be null' 419 | , 'expected #{this} not to be null' 420 | , this.negate ? false : true 421 | ); 422 | 423 | return this; 424 | } 425 | , configurable: true 426 | }); 427 | 428 | /** 429 | * ### .undefined 430 | * 431 | * Asserts that the target is `undefined`. 432 | * 433 | * expect(undefined).to.be.undefined; 434 | * expect(null).to.not.be.undefined; 435 | * 436 | * @name undefined 437 | * @api public 438 | */ 439 | 440 | Object.defineProperty(Assertion.prototype, 'undefined', 441 | { get: function () { 442 | this.assert( 443 | undefined === flag(this, 'object') 444 | , 'expected #{this} to be undefined' 445 | , 'expected #{this} not to be undefined' 446 | , this.negate ? false : true 447 | ); 448 | 449 | return this; 450 | } 451 | , configurable: true 452 | }); 453 | 454 | /** 455 | * ### .exist 456 | * 457 | * Asserts that the target is neither `null` nor `undefined`. 458 | * 459 | * var foo = 'hi' 460 | * , bar = null 461 | * , baz; 462 | * 463 | * expect(foo).to.exist; 464 | * expect(bar).to.not.exist; 465 | * expect(baz).to.not.exist; 466 | * 467 | * @name exist 468 | * @api public 469 | */ 470 | 471 | Object.defineProperty(Assertion.prototype, 'exist', 472 | { get: function () { 473 | this.assert( 474 | null != flag(this, 'object') 475 | , 'expected #{this} to exist' 476 | , 'expected #{this} to not exist' 477 | ); 478 | 479 | return this; 480 | } 481 | , configurable: true 482 | }); 483 | 484 | /** 485 | * ### .empty 486 | * 487 | * Asserts that the target's length is `0`. For arrays, it checks 488 | * the `length` property. For objects, it gets the count of 489 | * enumerable keys. 490 | * 491 | * expect([]).to.be.empty; 492 | * expect('').to.be.empty; 493 | * expect({}).to.be.empty; 494 | * 495 | * @name empty 496 | * @api public 497 | */ 498 | 499 | Object.defineProperty(Assertion.prototype, 'empty', 500 | { get: function () { 501 | var obj = flag(this, 'object') 502 | , expected = obj; 503 | 504 | if (Array.isArray(obj) || 'string' === typeof object) { 505 | expected = obj.length; 506 | } else if (typeof obj === 'object') { 507 | expected = Object.keys(obj).length; 508 | } 509 | 510 | this.assert( 511 | !expected 512 | , 'expected #{this} to be empty' 513 | , 'expected #{this} not to be empty'); 514 | 515 | return this; 516 | } 517 | , configurable: true 518 | }); 519 | 520 | /** 521 | * ### .arguments 522 | * 523 | * Asserts that the target is an arguments object. 524 | * 525 | * function test () { 526 | * expect(arguments).to.be.arguments; 527 | * } 528 | * 529 | * @name arguments 530 | * @alias Arguments 531 | * @api public 532 | */ 533 | 534 | function checkArguments () { 535 | var obj = flag(this, 'object') 536 | , type = Object.prototype.toString.call(obj); 537 | this.assert( 538 | '[object Arguments]' === type 539 | , 'expected #{this} to be arguments but got ' + type 540 | , 'expected #{this} to not be arguments' 541 | ); 542 | } 543 | 544 | Assertion.addProperty('arguments', checkArguments); 545 | Assertion.addProperty('Arguments', checkArguments); 546 | 547 | /** 548 | * ### .equal(value) 549 | * 550 | * Asserts that the target is strictly equal (`===`) to `value`. 551 | * Alternately, if the `deep` flag is set, asserts that 552 | * the target is deeply equal to `value`. 553 | * 554 | * expect('hello').to.equal('hello'); 555 | * expect(42).to.equal(42); 556 | * expect(1).to.not.equal(true); 557 | * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); 558 | * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); 559 | * 560 | * @name equal 561 | * @param {Mixed} value 562 | * @api public 563 | */ 564 | 565 | Assertion.prototype.equal = function (val) { 566 | var obj = flag(this, 'object'); 567 | if (flag(this, 'deep')) { 568 | return this.eql(val); 569 | } else { 570 | this.assert( 571 | val === obj 572 | , 'expected #{this} to equal #{exp}' 573 | , 'expected #{this} to not equal #{exp}' 574 | , val); 575 | } 576 | 577 | return this; 578 | }; 579 | 580 | /** 581 | * ### .eql(value) 582 | * 583 | * Asserts that the target is deeply equal to `value`. 584 | * 585 | * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); 586 | * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); 587 | * 588 | * @name eql 589 | * @param {Mixed} value 590 | * @api public 591 | */ 592 | 593 | Assertion.prototype.eql = function (obj) { 594 | this.assert( 595 | util.eql(obj, flag(this, 'object')) 596 | , 'expected #{this} to deeply equal #{exp}' 597 | , 'expected #{this} to not deeply equal #{exp}' 598 | , obj ); 599 | 600 | return this; 601 | }; 602 | 603 | /** 604 | * ### .above(value) 605 | * 606 | * Asserts that the target is greater than `value`. 607 | * 608 | * expect(10).to.be.above(5); 609 | * 610 | * @name above 611 | * @alias gt 612 | * @param {Number} value 613 | * @api public 614 | */ 615 | 616 | Assertion.prototype.above = function (val) { 617 | this.assert( 618 | flag(this, 'object') > val 619 | , 'expected #{this} to be above ' + val 620 | , 'expected #{this} to be below ' + val); 621 | 622 | return this; 623 | }; 624 | 625 | /** 626 | * ### .below(value) 627 | * 628 | * Asserts that the target is less than `value`. 629 | * 630 | * expect(5).to.be.below(10); 631 | * 632 | * @name below 633 | * @alias lt 634 | * @param {Number} value 635 | * @api public 636 | */ 637 | 638 | Assertion.prototype.below = function (val) { 639 | this.assert( 640 | flag(this, 'object') < val 641 | , 'expected #{this} to be below ' + val 642 | , 'expected #{this} to be above ' + val); 643 | 644 | return this; 645 | }; 646 | 647 | /** 648 | * ### .within(start, finish) 649 | * 650 | * Asserts that the target is within a range. 651 | * 652 | * expect(7).to.be.within(5,10); 653 | * 654 | * @name within 655 | * @param {Number} start lowerbound inclusive 656 | * @param {Number} finish upperbound inclusive 657 | * @api public 658 | */ 659 | 660 | Assertion.prototype.within = function (start, finish) { 661 | var obj = flag(this, 'object') 662 | , range = start + '..' + finish; 663 | 664 | this.assert( 665 | obj >= start && obj <= finish 666 | , 'expected #{this} to be within ' + range 667 | , 'expected #{this} to not be within ' + range); 668 | 669 | return this; 670 | }; 671 | 672 | /** 673 | * ### .instanceof(constructor) 674 | * 675 | * Asserts that the target is an instance of `constructor`. 676 | * 677 | * var Tea = function (name) { this.name = name; } 678 | * , Chai = new Tea('chai'); 679 | * 680 | * expect(Chai).to.be.an.instanceof(Tea); 681 | * expect([ 1, 2, 3 ]).to.be.instanceof(Array); 682 | * 683 | * @name instanceof 684 | * @param {Constructor} constructor 685 | * @alias instanceOf 686 | * @api public 687 | */ 688 | 689 | Assertion.prototype.instanceOf = function (constructor) { 690 | var name = util.getName(constructor); 691 | this.assert( 692 | flag(this, 'object') instanceof constructor 693 | , 'expected #{this} to be an instance of ' + name 694 | , 'expected #{this} to not be an instance of ' + name); 695 | 696 | return this; 697 | }; 698 | 699 | /** 700 | * ### .property(name, [value]) 701 | * 702 | * Asserts that the target has a property `name`, optionally asserting that 703 | * the value of that property is strictly equal to `value`. 704 | * If the `deep` flag is set, you can use dot- and bracket-notation for deep 705 | * references into objects and arrays. 706 | * 707 | * // simple referencing 708 | * var obj = { foo: 'bar' }; 709 | * expect(obj).to.have.property('foo'); 710 | * expect(obj).to.have.property('foo', 'bar'); 711 | * expect(obj).to.have.property('foo').to.be.a('string'); 712 | * 713 | * // deep referencing 714 | * var deepObj = { 715 | * green: { tea: 'matcha' } 716 | * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] 717 | * }; 718 | 719 | * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); 720 | * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); 721 | * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); 722 | * 723 | * @name property 724 | * @param {String} name 725 | * @param {Mixed} value (optional) 726 | * @returns value of property for chaining 727 | * @api public 728 | */ 729 | 730 | Assertion.prototype.property = function (name, val) { 731 | var obj = flag(this, 'object') 732 | , value = flag(this, 'deep') ? util.getPathValue(name, obj) : obj[name] 733 | , descriptor = flag(this, 'deep') ? 'deep property ' : 'property ' 734 | , negate = flag(this, 'negate'); 735 | 736 | if (negate && undefined !== val) { 737 | if (undefined === value) { 738 | throw new Error(util.inspect(obj) + ' has no ' + descriptor + util.inspect(name)); 739 | } 740 | } else { 741 | this.assert( 742 | undefined !== value 743 | , 'expected #{this} to have a ' + descriptor + util.inspect(name) 744 | , 'expected #{this} to not have ' + descriptor + util.inspect(name)); 745 | } 746 | 747 | if (undefined !== val) { 748 | this.assert( 749 | val === value 750 | , 'expected #{this} to have a ' + descriptor + util.inspect(name) + ' of #{exp}, but got #{act}' 751 | , 'expected #{this} to not have a ' + descriptor + util.inspect(name) + ' of #{act}' 752 | , val 753 | , value 754 | ); 755 | } 756 | 757 | flag(this, 'object', value); 758 | return this; 759 | }; 760 | 761 | /** 762 | * ### .ownProperty(name) 763 | * 764 | * Asserts that the target has an own property `name`. 765 | * 766 | * expect('test').to.have.ownProperty('length'); 767 | * 768 | * @name ownProperty 769 | * @alias haveOwnProperty 770 | * @param {String} name 771 | * @api public 772 | */ 773 | 774 | Assertion.prototype.ownProperty = function (name) { 775 | var obj = flag(this, 'object'); 776 | this.assert( 777 | obj.hasOwnProperty(name) 778 | , 'expected #{this} to have own property ' + util.inspect(name) 779 | , 'expected #{this} to not have own property ' + util.inspect(name)); 780 | return this; 781 | }; 782 | 783 | /** 784 | * ### .length(value) 785 | * 786 | * Asserts that the target's `length` property has the expected value. 787 | * 788 | * expect([1,2,3]).to.have.length(3); 789 | * expect('foobar').to.have.length(6); 790 | * 791 | * @name length 792 | * @alias lengthOf 793 | * @param {Number} length 794 | * @api public 795 | */ 796 | 797 | Assertion.prototype.length = function (n) { 798 | var obj = flag(this, 'object'); 799 | new Assertion(obj).to.have.property('length'); 800 | var len = obj.length; 801 | 802 | this.assert( 803 | len == n 804 | , 'expected #{this} to have a length of #{exp} but got #{act}' 805 | , 'expected #{this} to not have a length of #{act}' 806 | , n 807 | , len 808 | ); 809 | 810 | return this; 811 | }; 812 | 813 | /** 814 | * ### .match(regexp) 815 | * 816 | * Asserts that the target matches a regular expression. 817 | * 818 | * expect('foobar').to.match(/^foo/); 819 | * 820 | * @name match 821 | * @param {RegExp} RegularExpression 822 | * @api public 823 | */ 824 | 825 | Assertion.prototype.match = function (re) { 826 | var obj = flag(this, 'object'); 827 | this.assert( 828 | re.exec(obj) 829 | , 'expected #{this} to match ' + re 830 | , 'expected #{this} not to match ' + re); 831 | 832 | return this; 833 | }; 834 | 835 | 836 | /** 837 | * ### .string(string) 838 | * 839 | * Asserts that the string target contains another string. 840 | * 841 | * expect('foobar').to.have.string('bar'); 842 | * 843 | * @name string 844 | * @param {String} string 845 | * @api public 846 | */ 847 | 848 | Assertion.prototype.string = function (str) { 849 | var obj = flag(this, 'object'); 850 | new Assertion(obj).is.a('string'); 851 | 852 | this.assert( 853 | ~obj.indexOf(str) 854 | , 'expected #{this} to contain ' + util.inspect(str) 855 | , 'expected #{this} to not contain ' + util.inspect(str)); 856 | 857 | return this; 858 | }; 859 | 860 | /** 861 | * ### .keys(key1, [key2], [...]) 862 | * 863 | * Asserts that the target has exactly the given keys, or 864 | * asserts the inclusion of some keys when using the 865 | * `include` or `contain` modifiers. 866 | * 867 | * expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']); 868 | * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar'); 869 | * 870 | * @name keys 871 | * @alias key 872 | * @param {String...|Array} keys 873 | * @api public 874 | */ 875 | 876 | Assertion.prototype.keys = function(keys) { 877 | var obj = flag(this, 'object') 878 | , str 879 | , ok = true; 880 | 881 | keys = keys instanceof Array 882 | ? keys 883 | : Array.prototype.slice.call(arguments); 884 | 885 | if (!keys.length) throw new Error('keys required'); 886 | 887 | var actual = Object.keys(obj) 888 | , len = keys.length; 889 | 890 | // Inclusion 891 | ok = keys.every(function(key){ 892 | return ~actual.indexOf(key); 893 | }); 894 | 895 | // Strict 896 | if (!flag(this, 'negate') && !flag(this, 'contains')) { 897 | ok = ok && keys.length == actual.length; 898 | } 899 | 900 | // Key string 901 | if (len > 1) { 902 | keys = keys.map(function(key){ 903 | return util.inspect(key); 904 | }); 905 | var last = keys.pop(); 906 | str = keys.join(', ') + ', and ' + last; 907 | } else { 908 | str = util.inspect(keys[0]); 909 | } 910 | 911 | // Form 912 | str = (len > 1 ? 'keys ' : 'key ') + str; 913 | 914 | // Have / include 915 | str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; 916 | 917 | // Assertion 918 | this.assert( 919 | ok 920 | , 'expected #{this} to ' + str 921 | , 'expected #{this} to not ' + str 922 | , keys 923 | , Object.keys(obj) 924 | ); 925 | 926 | return this; 927 | } 928 | 929 | /** 930 | * ### .throw(constructor) 931 | * 932 | * Asserts that the function target will throw a specific error, or specific type of error 933 | * (as determined using `instanceof`), optionally with a RegExp or string inclusion test 934 | * for the error's message. 935 | * 936 | * var err = new ReferenceError('This is a bad function.'); 937 | * var fn = function () { throw err; } 938 | * expect(fn).to.throw(ReferenceError); 939 | * expect(fn).to.throw(Error); 940 | * expect(fn).to.throw(/bad function/); 941 | * expect(fn).to.not.throw('good function'); 942 | * expect(fn).to.throw(ReferenceError, /bad function/); 943 | * expect(fn).to.throw(err); 944 | * expect(fn).to.not.throw(new RangeError('Out of range.')); 945 | * 946 | * Please note that when a throw expectation is negated, it will check each 947 | * parameter independently, starting with error constructor type. The appropriate way 948 | * to check for the existence of a type of error but for a message that does not match 949 | * is to use `and`. 950 | * 951 | * expect(fn).to.throw(ReferenceError) 952 | * .and.not.throw(/good function/); 953 | * 954 | * @name throw 955 | * @alias throws 956 | * @alias Throw 957 | * @param {ErrorConstructor} constructor 958 | * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types 959 | * @api public 960 | */ 961 | 962 | Assertion.prototype.Throw = function (constructor, msg) { 963 | var obj = flag(this, 'object'); 964 | new Assertion(obj).is.a('function'); 965 | 966 | var thrown = false 967 | , desiredError = null 968 | , name = null; 969 | 970 | if (arguments.length === 0) { 971 | msg = null; 972 | constructor = null; 973 | } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { 974 | msg = constructor; 975 | constructor = null; 976 | } else if (constructor && constructor instanceof Error) { 977 | desiredError = constructor; 978 | constructor = null; 979 | msg = null; 980 | } else if (typeof constructor === 'function') { 981 | name = (new constructor()).name; 982 | } else { 983 | constructor = null; 984 | } 985 | 986 | try { 987 | obj(); 988 | } catch (err) { 989 | // first, check desired error 990 | if (desiredError) { 991 | this.assert( 992 | err === desiredError 993 | , 'expected #{this} to throw ' + util.inspect(desiredError) + ' but ' + util.inspect(err) + ' was thrown' 994 | , 'expected #{this} to not throw ' + util.inspect(desiredError) 995 | ); 996 | return this; 997 | } 998 | // next, check constructor 999 | if (constructor) { 1000 | this.assert( 1001 | err instanceof constructor 1002 | , 'expected #{this} to throw ' + name + ' but a ' + err.name + ' was thrown' 1003 | , 'expected #{this} to not throw ' + name ); 1004 | if (!msg) return this; 1005 | } 1006 | // next, check message 1007 | if (err.message && msg && msg instanceof RegExp) { 1008 | this.assert( 1009 | msg.exec(err.message) 1010 | , 'expected #{this} to throw error matching ' + msg + ' but got ' + util.inspect(err.message) 1011 | , 'expected #{this} to throw error not matching ' + msg 1012 | ); 1013 | return this; 1014 | } else if (err.message && msg && 'string' === typeof msg) { 1015 | this.assert( 1016 | ~err.message.indexOf(msg) 1017 | , 'expected #{this} to throw error including #{exp} but got #{act}' 1018 | , 'expected #{this} to throw error not including #{act}' 1019 | , msg 1020 | , err.message 1021 | ); 1022 | return this; 1023 | } else { 1024 | thrown = true; 1025 | } 1026 | } 1027 | 1028 | var expectedThrown = name ? name : desiredError ? util.inspect(desiredError) : 'an error'; 1029 | 1030 | this.assert( 1031 | thrown === true 1032 | , 'expected #{this} to throw ' + expectedThrown 1033 | , 'expected #{this} to not throw ' + expectedThrown); 1034 | 1035 | return this; 1036 | }; 1037 | 1038 | /** 1039 | * ### .respondTo(method) 1040 | * 1041 | * Asserts that the object or class target will respond to a method. 1042 | * 1043 | * Klass.prototype.bar = function(){}; 1044 | * expect(Klass).to.respondTo('bar'); 1045 | * expect(obj).to.respondTo('bar'); 1046 | * 1047 | * To check if a constructor will respond to a static function, 1048 | * set the `itself` flag. 1049 | * 1050 | * Klass.baz = function(){}; 1051 | * expect(Klass).itself.to.respondTo('baz'); 1052 | * 1053 | * @name respondTo 1054 | * @param {String} method 1055 | * @api public 1056 | */ 1057 | 1058 | Assertion.prototype.respondTo = function (method) { 1059 | var obj = flag(this, 'object') 1060 | , itself = flag(this, 'itself') 1061 | , context = ('function' === typeof obj && !itself) 1062 | ? obj.prototype[method] 1063 | : obj[method]; 1064 | 1065 | this.assert( 1066 | 'function' === typeof context 1067 | , 'expected #{this} to respond to ' + util.inspect(method) 1068 | , 'expected #{this} to not respond to ' + util.inspect(method) 1069 | , 'function' 1070 | , typeof context 1071 | ); 1072 | 1073 | return this; 1074 | }; 1075 | 1076 | /** 1077 | * ### .itself 1078 | * 1079 | * Sets the `itself` flag, later used by the `respondTo` assertion. 1080 | * 1081 | * function Foo() {} 1082 | * Foo.bar = function() {} 1083 | * Foo.prototype.baz = function() {} 1084 | * 1085 | * expect(Foo).itself.to.respondTo('bar'); 1086 | * expect(Foo).itself.not.to.respondTo('baz'); 1087 | * 1088 | * @name itself 1089 | * @api public 1090 | */ 1091 | Object.defineProperty(Assertion.prototype, 'itself', 1092 | { get: function () { 1093 | flag(this, 'itself', true); 1094 | return this; 1095 | } 1096 | , configurable: true 1097 | }); 1098 | 1099 | /** 1100 | * ### .satisfy(method) 1101 | * 1102 | * Asserts that the target passes a given truth test. 1103 | * 1104 | * expect(1).to.satisfy(function(num) { return num > 0; }); 1105 | * 1106 | * @name satisfy 1107 | * @param {Function} matcher 1108 | * @api public 1109 | */ 1110 | 1111 | Assertion.prototype.satisfy = function (matcher) { 1112 | var obj = flag(this, 'object'); 1113 | this.assert( 1114 | matcher(obj) 1115 | , 'expected #{this} to satisfy ' + util.inspect(matcher) 1116 | , 'expected #{this} to not satisfy' + util.inspect(matcher) 1117 | , this.negate ? false : true 1118 | , matcher(obj) 1119 | ); 1120 | 1121 | return this; 1122 | }; 1123 | 1124 | /** 1125 | * ### .closeTo(expected, delta) 1126 | * 1127 | * Asserts that the target is equal `expected`, to within a +/- `delta` range. 1128 | * 1129 | * expect(1.5).to.be.closeTo(1, 0.5); 1130 | * 1131 | * @name closeTo 1132 | * @param {Number} expected 1133 | * @param {Number} delta 1134 | * @api public 1135 | */ 1136 | 1137 | Assertion.prototype.closeTo = function (expected, delta) { 1138 | var obj = flag(this, 'object'); 1139 | this.assert( 1140 | (obj - delta === expected) || (obj + delta === expected) 1141 | , 'expected #{this} to be close to ' + expected + ' +/- ' + delta 1142 | , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta); 1143 | 1144 | return this; 1145 | }; 1146 | 1147 | /*! 1148 | * Aliases. 1149 | */ 1150 | 1151 | (function alias(name, as){ 1152 | Assertion.prototype[as] = Assertion.prototype[name]; 1153 | return alias; 1154 | }) 1155 | ('equal', 'eq') 1156 | ('above', 'gt') 1157 | ('below', 'lt') 1158 | ('length', 'lengthOf') 1159 | ('keys', 'key') 1160 | ('ownProperty', 'haveOwnProperty') 1161 | ('above', 'greaterThan') 1162 | ('below', 'lessThan') 1163 | ('Throw', 'throws') 1164 | ('Throw', 'throw') 1165 | ('instanceOf', 'instanceof'); 1166 | 1167 | }); // module: assertion.js 1168 | 1169 | require.register("browser/error.js", function(module, exports, require){ 1170 | /*! 1171 | * chai 1172 | * Copyright(c) 2011-2012 Jake Luer 1173 | * MIT Licensed 1174 | */ 1175 | 1176 | module.exports = AssertionError; 1177 | 1178 | function AssertionError (options) { 1179 | options = options || {}; 1180 | this.message = options.message; 1181 | this.actual = options.actual; 1182 | this.expected = options.expected; 1183 | this.operator = options.operator; 1184 | 1185 | if (options.stackStartFunction && Error.captureStackTrace) { 1186 | var stackStartFunction = options.stackStartFunction; 1187 | Error.captureStackTrace(this, stackStartFunction); 1188 | } 1189 | } 1190 | 1191 | AssertionError.prototype = Object.create(Error.prototype); 1192 | AssertionError.prototype.name = 'AssertionError'; 1193 | AssertionError.prototype.constructor = AssertionError; 1194 | 1195 | AssertionError.prototype.toString = function() { 1196 | return this.message; 1197 | }; 1198 | 1199 | }); // module: browser/error.js 1200 | 1201 | require.register("chai.js", function(module, exports, require){ 1202 | /*! 1203 | * chai 1204 | * Copyright(c) 2011-2012 Jake Luer 1205 | * MIT Licensed 1206 | */ 1207 | 1208 | var used = [] 1209 | , exports = module.exports = {}; 1210 | 1211 | /*! 1212 | * Chai version 1213 | */ 1214 | 1215 | exports.version = '1.0.4'; 1216 | 1217 | /*! 1218 | * Primary `Assertion` prototype 1219 | */ 1220 | 1221 | exports.Assertion = require('./assertion'); 1222 | 1223 | /*! 1224 | * Assertion Error 1225 | */ 1226 | 1227 | exports.AssertionError = require('./browser/error'); 1228 | 1229 | /*! 1230 | * Utils for plugins (not exported) 1231 | */ 1232 | 1233 | var util = require('./utils'); 1234 | 1235 | /** 1236 | * # .use(function) 1237 | * 1238 | * Provides a way to extend the internals of Chai 1239 | * 1240 | * @param {Function} 1241 | * @returns {this} for chaining 1242 | * @api public 1243 | */ 1244 | 1245 | exports.use = function (fn) { 1246 | if (!~used.indexOf(fn)) { 1247 | fn(this, util); 1248 | used.push(fn); 1249 | } 1250 | 1251 | return this; 1252 | }; 1253 | 1254 | /*! 1255 | * Expect interface 1256 | */ 1257 | 1258 | var expect = require('./interface/expect'); 1259 | exports.use(expect); 1260 | 1261 | /*! 1262 | * Should interface 1263 | */ 1264 | 1265 | var should = require('./interface/should'); 1266 | exports.use(should); 1267 | 1268 | /*! 1269 | * Assert interface 1270 | */ 1271 | 1272 | var assert = require('./interface/assert'); 1273 | exports.use(assert); 1274 | 1275 | }); // module: chai.js 1276 | 1277 | require.register("interface/assert.js", function(module, exports, require){ 1278 | /*! 1279 | * chai 1280 | * Copyright(c) 2011-2012 Jake Luer 1281 | * MIT Licensed 1282 | */ 1283 | 1284 | 1285 | module.exports = function (chai, util) { 1286 | 1287 | /*! 1288 | * Chai dependencies. 1289 | */ 1290 | 1291 | var Assertion = chai.Assertion 1292 | , flag = util.flag; 1293 | 1294 | /*! 1295 | * Module export. 1296 | */ 1297 | 1298 | /** 1299 | * ### assert(expression, message) 1300 | * 1301 | * Write your own test expressions. 1302 | * 1303 | * assert('foo' !== 'bar', 'foo is not bar'); 1304 | * assert(Array.isArray([]), 'empty arrays are arrays'); 1305 | * 1306 | * @param {Mixed} expression to test for truthiness 1307 | * @param {String} message to display on error 1308 | * @name assert 1309 | * @api public 1310 | */ 1311 | 1312 | var assert = chai.assert = function (express, errmsg) { 1313 | var test = new Assertion(null); 1314 | test.assert( 1315 | express 1316 | , errmsg 1317 | , '[ negation message unavailable ]' 1318 | ); 1319 | }; 1320 | 1321 | /** 1322 | * ### .fail(actual, expected, [message], [operator]) 1323 | * 1324 | * Throw a failure. Node.js `assert` module-compatible. 1325 | * 1326 | * @name fail 1327 | * @param {Mixed} actual 1328 | * @param {Mixed} expected 1329 | * @param {String} message 1330 | * @param {String} operator 1331 | * @api public 1332 | */ 1333 | 1334 | assert.fail = function (actual, expected, message, operator) { 1335 | throw new chai.AssertionError({ 1336 | actual: actual 1337 | , expected: expected 1338 | , message: message 1339 | , operator: operator 1340 | , stackStartFunction: assert.fail 1341 | }); 1342 | }; 1343 | 1344 | /** 1345 | * ### .ok(object, [message]) 1346 | * 1347 | * Asserts that `object` is truthy. 1348 | * 1349 | * assert.ok('everything', 'everything is ok'); 1350 | * assert.ok(false, 'this will fail'); 1351 | * 1352 | * @name ok 1353 | * @param {Mixed} object to test 1354 | * @param {String} message 1355 | * @api public 1356 | */ 1357 | 1358 | assert.ok = function (val, msg) { 1359 | new Assertion(val, msg).is.ok; 1360 | }; 1361 | 1362 | /** 1363 | * ### .equal(actual, expected, [message]) 1364 | * 1365 | * Asserts non-strict equality (`==`) of `actual` and `expected`. 1366 | * 1367 | * assert.equal(3, '3', '== coerces values to strings'); 1368 | * 1369 | * @name equal 1370 | * @param {Mixed} actual 1371 | * @param {Mixed} expected 1372 | * @param {String} message 1373 | * @api public 1374 | */ 1375 | 1376 | assert.equal = function (act, exp, msg) { 1377 | var test = new Assertion(act, msg); 1378 | 1379 | test.assert( 1380 | exp == flag(test, 'object') 1381 | , 'expected #{this} to equal #{exp}' 1382 | , 'expected #{this} to not equal #{act}' 1383 | , exp 1384 | , act 1385 | ); 1386 | }; 1387 | 1388 | /** 1389 | * ### .notEqual(actual, expected, [message]) 1390 | * 1391 | * Asserts non-strict inequality (`!=`) of `actual` and `expected`. 1392 | * 1393 | * assert.notEqual(3, 4, 'these numbers are not equal'); 1394 | * 1395 | * @name notEqual 1396 | * @param {Mixed} actual 1397 | * @param {Mixed} expected 1398 | * @param {String} message 1399 | * @api public 1400 | */ 1401 | 1402 | assert.notEqual = function (act, exp, msg) { 1403 | var test = new Assertion(act, msg); 1404 | 1405 | test.assert( 1406 | exp != flag(test, 'object') 1407 | , 'expected #{this} to not equal #{exp}' 1408 | , 'expected #{this} to equal #{act}' 1409 | , exp 1410 | , act 1411 | ); 1412 | }; 1413 | 1414 | /** 1415 | * ### .strictEqual(actual, expected, [message]) 1416 | * 1417 | * Asserts strict equality (`===`) of `actual` and `expected`. 1418 | * 1419 | * assert.strictEqual(true, true, 'these booleans are strictly equal'); 1420 | * 1421 | * @name strictEqual 1422 | * @param {Mixed} actual 1423 | * @param {Mixed} expected 1424 | * @param {String} message 1425 | * @api public 1426 | */ 1427 | 1428 | assert.strictEqual = function (act, exp, msg) { 1429 | new Assertion(act, msg).to.equal(exp); 1430 | }; 1431 | 1432 | /** 1433 | * ### .notStrictEqual(actual, expected, [message]) 1434 | * 1435 | * Asserts strict inequality (`!==`) of `actual` and `expected`. 1436 | * 1437 | * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); 1438 | * 1439 | * @name notStrictEqual 1440 | * @param {Mixed} actual 1441 | * @param {Mixed} expected 1442 | * @param {String} message 1443 | * @api public 1444 | */ 1445 | 1446 | assert.notStrictEqual = function (act, exp, msg) { 1447 | new Assertion(act, msg).to.not.equal(exp); 1448 | }; 1449 | 1450 | /** 1451 | * ### .deepEqual(actual, expected, [message]) 1452 | * 1453 | * Asserts that `actual` is deeply equal to `expected`. 1454 | * 1455 | * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); 1456 | * 1457 | * @name deepEqual 1458 | * @param {Mixed} actual 1459 | * @param {Mixed} expected 1460 | * @param {String} message 1461 | * @api public 1462 | */ 1463 | 1464 | assert.deepEqual = function (act, exp, msg) { 1465 | new Assertion(act, msg).to.eql(exp); 1466 | }; 1467 | 1468 | /** 1469 | * ### .notDeepEqual(actual, expected, [message]) 1470 | * 1471 | * Assert that `actual` is not deeply equal to `expected`. 1472 | * 1473 | * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); 1474 | * 1475 | * @name notDeepEqual 1476 | * @param {Mixed} actual 1477 | * @param {Mixed} expected 1478 | * @param {String} message 1479 | * @api public 1480 | */ 1481 | 1482 | assert.notDeepEqual = function (act, exp, msg) { 1483 | new Assertion(act, msg).to.not.eql(exp); 1484 | }; 1485 | 1486 | /** 1487 | * ### .isTrue(value, [message]) 1488 | * 1489 | * Asserts that `value` is true. 1490 | * 1491 | * var teaServed = true; 1492 | * assert.isTrue(teaServed, 'the tea has been served'); 1493 | * 1494 | * @name isTrue 1495 | * @param {Mixed} value 1496 | * @param {String} message 1497 | * @api public 1498 | */ 1499 | 1500 | assert.isTrue = function (val, msg) { 1501 | new Assertion(val, msg).is['true']; 1502 | }; 1503 | 1504 | /** 1505 | * ### .isFalse(value, [message]) 1506 | * 1507 | * Asserts that `value` is false. 1508 | * 1509 | * var teaServed = false; 1510 | * assert.isFalse(teaServed, 'no tea yet? hmm...'); 1511 | * 1512 | * @name isFalse 1513 | * @param {Mixed} value 1514 | * @param {String} message 1515 | * @api public 1516 | */ 1517 | 1518 | assert.isFalse = function (val, msg) { 1519 | new Assertion(val, msg).is['false']; 1520 | }; 1521 | 1522 | /** 1523 | * ### .isNull(value, [message]) 1524 | * 1525 | * Asserts that `value` is null. 1526 | * 1527 | * assert.isNull(err, 'there was no error'); 1528 | * 1529 | * @name isNull 1530 | * @param {Mixed} value 1531 | * @param {String} message 1532 | * @api public 1533 | */ 1534 | 1535 | assert.isNull = function (val, msg) { 1536 | new Assertion(val, msg).to.equal(null); 1537 | }; 1538 | 1539 | /** 1540 | * ### .isNotNull(value, [message]) 1541 | * 1542 | * Asserts that `value` is not null. 1543 | * 1544 | * var tea = 'tasty chai'; 1545 | * assert.isNotNull(tea, 'great, time for tea!'); 1546 | * 1547 | * @name isNotNull 1548 | * @param {Mixed} value 1549 | * @param {String} message 1550 | * @api public 1551 | */ 1552 | 1553 | assert.isNotNull = function (val, msg) { 1554 | new Assertion(val, msg).to.not.equal(null); 1555 | }; 1556 | 1557 | /** 1558 | * ### .isUndefined(value, [message]) 1559 | * 1560 | * Asserts that `value` is `undefined`. 1561 | * 1562 | * var tea; 1563 | * assert.isUndefined(tea, 'no tea defined'); 1564 | * 1565 | * @name isUndefined 1566 | * @param {Mixed} value 1567 | * @param {String} message 1568 | * @api public 1569 | */ 1570 | 1571 | assert.isUndefined = function (val, msg) { 1572 | new Assertion(val, msg).to.equal(undefined); 1573 | }; 1574 | 1575 | /** 1576 | * ### .isDefined(value, [message]) 1577 | * 1578 | * Asserts that `value` is not `undefined`. 1579 | * 1580 | * var tea = 'cup of chai'; 1581 | * assert.isDefined(tea, 'tea has been defined'); 1582 | * 1583 | * @name isUndefined 1584 | * @param {Mixed} value 1585 | * @param {String} message 1586 | * @api public 1587 | */ 1588 | 1589 | assert.isDefined = function (val, msg) { 1590 | new Assertion(val, msg).to.not.equal(undefined); 1591 | }; 1592 | 1593 | /** 1594 | * ### .isFunction(value, [message]) 1595 | * 1596 | * Asserts that `value` is a function. 1597 | * 1598 | * function serveTea() { return 'cup of tea'; }; 1599 | * assert.isFunction(serveTea, 'great, we can have tea now'); 1600 | * 1601 | * @name isFunction 1602 | * @param {Mixed} value 1603 | * @param {String} message 1604 | * @api public 1605 | */ 1606 | 1607 | assert.isFunction = function (val, msg) { 1608 | new Assertion(val, msg).to.be.a('function'); 1609 | }; 1610 | 1611 | /** 1612 | * ### .isNotFunction(value, [message]) 1613 | * 1614 | * Asserts that `value` is _not_ a function. 1615 | * 1616 | * var serveTea = [ 'heat', 'pour', 'sip' ]; 1617 | * assert.isNotFunction(serveTea, 'great, we have listed the steps'); 1618 | * 1619 | * @name isNotFunction 1620 | * @param {Mixed} value 1621 | * @param {String} message 1622 | * @api public 1623 | */ 1624 | 1625 | assert.isNotFunction = function (val, msg) { 1626 | new Assertion(val, msg).to.not.be.a('function'); 1627 | }; 1628 | 1629 | /** 1630 | * ### .isObject(value, [message]) 1631 | * 1632 | * Asserts that `value` is an object (as revealed by 1633 | * `Object.prototype.toString`). 1634 | * 1635 | * var selection = { name: 'Chai', serve: 'with spices' }; 1636 | * assert.isObject(selection, 'tea selection is an object'); 1637 | * 1638 | * @name isObject 1639 | * @param {Mixed} value 1640 | * @param {String} message 1641 | * @api public 1642 | */ 1643 | 1644 | assert.isObject = function (val, msg) { 1645 | new Assertion(val, msg).to.be.a('object'); 1646 | }; 1647 | 1648 | /** 1649 | * ### .isNotObject(value, [message]) 1650 | * 1651 | * Asserts that `value` is _not_ an object. 1652 | * 1653 | * var selection = 'chai' 1654 | * assert.isObject(selection, 'tea selection is not an object'); 1655 | * assert.isObject(null, 'null is not an object'); 1656 | * 1657 | * @name isNotObject 1658 | * @param {Mixed} value 1659 | * @param {String} message 1660 | * @api public 1661 | */ 1662 | 1663 | assert.isNotObject = function (val, msg) { 1664 | new Assertion(val, msg).to.not.be.a('object'); 1665 | }; 1666 | 1667 | /** 1668 | * ### .isArray(value, [message]) 1669 | * 1670 | * Asserts that `value` is an array. 1671 | * 1672 | * var menu = [ 'green', 'chai', 'oolong' ]; 1673 | * assert.isArray(menu, 'what kind of tea do we want?'); 1674 | * 1675 | * @name isArray 1676 | * @param {Mixed} value 1677 | * @param {String} message 1678 | * @api public 1679 | */ 1680 | 1681 | assert.isArray = function (val, msg) { 1682 | new Assertion(val, msg).to.be.an('array'); 1683 | }; 1684 | 1685 | /** 1686 | * ### .isNotArray(value, [message]) 1687 | * 1688 | * Asserts that `value` is _not_ an array. 1689 | * 1690 | * var menu = 'green|chai|oolong'; 1691 | * assert.isNotArray(menu, 'what kind of tea do we want?'); 1692 | * 1693 | * @name isNotArray 1694 | * @param {Mixed} value 1695 | * @param {String} message 1696 | * @api public 1697 | */ 1698 | 1699 | assert.isNotArray = function (val, msg) { 1700 | new Assertion(val, msg).to.not.be.an('array'); 1701 | }; 1702 | 1703 | /** 1704 | * ### .isString(value, [message]) 1705 | * 1706 | * Asserts that `value` is a string. 1707 | * 1708 | * var teaOrder = 'chai'; 1709 | * assert.isString(teaOrder, 'order placed'); 1710 | * 1711 | * @name isString 1712 | * @param {Mixed} value 1713 | * @param {String} message 1714 | * @api public 1715 | */ 1716 | 1717 | assert.isString = function (val, msg) { 1718 | new Assertion(val, msg).to.be.a('string'); 1719 | }; 1720 | 1721 | /** 1722 | * ### .isNotString(value, [message]) 1723 | * 1724 | * Asserts that `value` is _not_ a string. 1725 | * 1726 | * var teaOrder = 4; 1727 | * assert.isNotString(teaOrder, 'order placed'); 1728 | * 1729 | * @name isNotString 1730 | * @param {Mixed} value 1731 | * @param {String} message 1732 | * @api public 1733 | */ 1734 | 1735 | assert.isNotString = function (val, msg) { 1736 | new Assertion(val, msg).to.not.be.a('string'); 1737 | }; 1738 | 1739 | /** 1740 | * ### .isNumber(value, [message]) 1741 | * 1742 | * Asserts that `value` is a number. 1743 | * 1744 | * var cups = 2; 1745 | * assert.isNumber(cups, 'how many cups'); 1746 | * 1747 | * @name isNumber 1748 | * @param {Number} value 1749 | * @param {String} message 1750 | * @api public 1751 | */ 1752 | 1753 | assert.isNumber = function (val, msg) { 1754 | new Assertion(val, msg).to.be.a('number'); 1755 | }; 1756 | 1757 | /** 1758 | * ### .isNotNumber(value, [message]) 1759 | * 1760 | * Asserts that `value` is _not_ a number. 1761 | * 1762 | * var cups = '2 cups please'; 1763 | * assert.isNotNumber(cups, 'how many cups'); 1764 | * 1765 | * @name isNotNumber 1766 | * @param {Mixed} value 1767 | * @param {String} message 1768 | * @api public 1769 | */ 1770 | 1771 | assert.isNotNumber = function (val, msg) { 1772 | new Assertion(val, msg).to.not.be.a('number'); 1773 | }; 1774 | 1775 | /** 1776 | * ### .isBoolean(value, [message]) 1777 | * 1778 | * Asserts that `value` is a boolean. 1779 | * 1780 | * var teaReady = true 1781 | * , teaServed = false; 1782 | * 1783 | * assert.isBoolean(teaReady, 'is the tea ready'); 1784 | * assert.isBoolean(teaServed, 'has tea been served'); 1785 | * 1786 | * @name isBoolean 1787 | * @param {Mixed} value 1788 | * @param {String} message 1789 | * @api public 1790 | */ 1791 | 1792 | assert.isBoolean = function (val, msg) { 1793 | new Assertion(val, msg).to.be.a('boolean'); 1794 | }; 1795 | 1796 | /** 1797 | * ### .isNotBoolean(value, [message]) 1798 | * 1799 | * Asserts that `value` is _not_ a boolean. 1800 | * 1801 | * var teaReady = 'yep' 1802 | * , teaServed = 'nope'; 1803 | * 1804 | * assert.isNotBoolean(teaReady, 'is the tea ready'); 1805 | * assert.isNotBoolean(teaServed, 'has tea been served'); 1806 | * 1807 | * @name isNotBoolean 1808 | * @param {Mixed} value 1809 | * @param {String} message 1810 | * @api public 1811 | */ 1812 | 1813 | assert.isNotBoolean = function (val, msg) { 1814 | new Assertion(val, msg).to.not.be.a('boolean'); 1815 | }; 1816 | 1817 | /** 1818 | * ### .typeOf(value, name, [message]) 1819 | * 1820 | * Asserts that `value`'s type is `name`, as determined by 1821 | * `Object.prototype.toString`. 1822 | * 1823 | * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); 1824 | * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); 1825 | * assert.typeOf('tea', 'string', 'we have a string'); 1826 | * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); 1827 | * assert.typeOf(null, 'null', 'we have a null'); 1828 | * assert.typeOf(undefined, 'undefined', 'we have an undefined'); 1829 | * 1830 | * @name typeOf 1831 | * @param {Mixed} value 1832 | * @param {String} name 1833 | * @param {String} message 1834 | * @api public 1835 | */ 1836 | 1837 | assert.typeOf = function (val, type, msg) { 1838 | new Assertion(val, msg).to.be.a(type); 1839 | }; 1840 | 1841 | /** 1842 | * ### .notTypeOf(value, name, [message]) 1843 | * 1844 | * Asserts that `value`'s type is _not_ `name`, as determined by 1845 | * `Object.prototype.toString`. 1846 | * 1847 | * assert.notTypeOf('tea', 'number', 'strings are not numbers'); 1848 | * 1849 | * @name notTypeOf 1850 | * @param {Mixed} value 1851 | * @param {String} typeof name 1852 | * @param {String} message 1853 | * @api public 1854 | */ 1855 | 1856 | assert.notTypeOf = function (val, type, msg) { 1857 | new Assertion(val, msg).to.not.be.a(type); 1858 | }; 1859 | 1860 | /** 1861 | * ### .instanceOf(object, constructor, [message]) 1862 | * 1863 | * Asserts that `value` is an instance of `constructor`. 1864 | * 1865 | * var Tea = function (name) { this.name = name; } 1866 | * , chai = new Tea('chai'); 1867 | * 1868 | * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); 1869 | * 1870 | * @name instanceOf 1871 | * @param {Object} object 1872 | * @param {Constructor} constructor 1873 | * @param {String} message 1874 | * @api public 1875 | */ 1876 | 1877 | assert.instanceOf = function (val, type, msg) { 1878 | new Assertion(val, msg).to.be.instanceOf(type); 1879 | }; 1880 | 1881 | /** 1882 | * ### .notInstanceOf(object, constructor, [message]) 1883 | * 1884 | * Asserts `value` is not an instance of `constructor`. 1885 | * 1886 | * var Tea = function (name) { this.name = name; } 1887 | * , chai = new String('chai'); 1888 | * 1889 | * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); 1890 | * 1891 | * @name notInstanceOf 1892 | * @param {Object} object 1893 | * @param {Constructor} constructor 1894 | * @param {String} message 1895 | * @api public 1896 | */ 1897 | 1898 | assert.notInstanceOf = function (val, type, msg) { 1899 | new Assertion(val, msg).to.not.be.instanceOf(type); 1900 | }; 1901 | 1902 | /** 1903 | * ### .include(haystack, needle, [message]) 1904 | * 1905 | * Asserts that `haystack` includes `needle`. Works 1906 | * for strings and arrays. 1907 | * 1908 | * assert.include('foobar', 'bar', 'foobar contains string "bar"'); 1909 | * assert.include([ 1, 2, 3 ], 3, 'array contains value'); 1910 | * 1911 | * @name include 1912 | * @param {Array|String} haystack 1913 | * @param {Mixed} needle 1914 | * @param {String} message 1915 | * @api public 1916 | */ 1917 | 1918 | assert.include = function (exp, inc, msg) { 1919 | var obj = new Assertion(exp, msg); 1920 | 1921 | if (Array.isArray(exp)) { 1922 | obj.to.include(inc); 1923 | } else if ('string' === typeof exp) { 1924 | obj.to.contain.string(inc); 1925 | } 1926 | }; 1927 | 1928 | /** 1929 | * ### .match(value, regexp, [message]) 1930 | * 1931 | * Asserts that `value` matches the regular expression `regexp`. 1932 | * 1933 | * assert.match('foobar', /^foo/, 'regexp matches'); 1934 | * 1935 | * @name match 1936 | * @param {Mixed} value 1937 | * @param {RegExp} regexp 1938 | * @param {String} message 1939 | * @api public 1940 | */ 1941 | 1942 | assert.match = function (exp, re, msg) { 1943 | new Assertion(exp, msg).to.match(re); 1944 | }; 1945 | 1946 | /** 1947 | * ### .notMatch(value, regexp, [message]) 1948 | * 1949 | * Asserts that `value` does not match the regular expression `regexp`. 1950 | * 1951 | * assert.notMatch('foobar', /^foo/, 'regexp does not match'); 1952 | * 1953 | * @name notMatch 1954 | * @param {Mixed} value 1955 | * @param {RegExp} regexp 1956 | * @param {String} message 1957 | * @api public 1958 | */ 1959 | 1960 | assert.notMatch = function (exp, re, msg) { 1961 | new Assertion(exp, msg).to.not.match(re); 1962 | }; 1963 | 1964 | /** 1965 | * ### .property(object, property, [message]) 1966 | * 1967 | * Asserts that `object` has a property named by `property`. 1968 | * 1969 | * assert.property({ tea: { green: 'matcha' }}, 'tea'); 1970 | * 1971 | * @name property 1972 | * @param {Object} object 1973 | * @param {String} property 1974 | * @param {String} message 1975 | * @api public 1976 | */ 1977 | 1978 | assert.property = function (obj, prop, msg) { 1979 | new Assertion(obj, msg).to.have.property(prop); 1980 | }; 1981 | 1982 | /** 1983 | * ### .notProperty(object, property, [message]) 1984 | * 1985 | * Asserts that `object` does _not_ have a property named by `property`. 1986 | * 1987 | * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); 1988 | * 1989 | * @name notProperty 1990 | * @param {Object} object 1991 | * @param {String} property 1992 | * @param {String} message 1993 | * @api public 1994 | */ 1995 | 1996 | assert.notProperty = function (obj, prop, msg) { 1997 | new Assertion(obj, msg).to.not.have.property(prop); 1998 | }; 1999 | 2000 | /** 2001 | * ### .deepProperty(object, property, [message]) 2002 | * 2003 | * Asserts that `object` has a property named by `property`, which can be a 2004 | * string using dot- and bracket-notation for deep reference. 2005 | * 2006 | * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); 2007 | * 2008 | * @name deepProperty 2009 | * @param {Object} object 2010 | * @param {String} property 2011 | * @param {String} message 2012 | * @api public 2013 | */ 2014 | 2015 | assert.deepProperty = function (obj, prop, msg) { 2016 | new Assertion(obj, msg).to.have.deep.property(prop); 2017 | }; 2018 | 2019 | /** 2020 | * ### .notDeepProperty(object, property, [message]) 2021 | * 2022 | * Asserts that `object` does _not_ have a property named by `property`, which 2023 | * can be a string using dot- and bracket-notation for deep reference. 2024 | * 2025 | * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); 2026 | * 2027 | * @name notDeepProperty 2028 | * @param {Object} object 2029 | * @param {String} property 2030 | * @param {String} message 2031 | * @api public 2032 | */ 2033 | 2034 | assert.notDeepProperty = function (obj, prop, msg) { 2035 | new Assertion(obj, msg).to.not.have.deep.property(prop); 2036 | }; 2037 | 2038 | /** 2039 | * ### .propertyVal(object, property, value, [message]) 2040 | * 2041 | * Asserts that `object` has a property named by `property` with value given 2042 | * by `value`. 2043 | * 2044 | * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); 2045 | * 2046 | * @name propertyVal 2047 | * @param {Object} object 2048 | * @param {String} property 2049 | * @param {Mixed} value 2050 | * @param {String} message 2051 | * @api public 2052 | */ 2053 | 2054 | assert.propertyVal = function (obj, prop, val, msg) { 2055 | new Assertion(obj, msg).to.have.property(prop, val); 2056 | }; 2057 | 2058 | /** 2059 | * ### .propertyNotVal(object, property, value, [message]) 2060 | * 2061 | * Asserts that `object` has a property named by `property`, but with a value 2062 | * different from that given by `value`. 2063 | * 2064 | * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); 2065 | * 2066 | * @name propertyNotVal 2067 | * @param {Object} object 2068 | * @param {String} property 2069 | * @param {Mixed} value 2070 | * @param {String} message 2071 | * @api public 2072 | */ 2073 | 2074 | assert.propertyNotVal = function (obj, prop, val, msg) { 2075 | new Assertion(obj, msg).to.not.have.property(prop, val); 2076 | }; 2077 | 2078 | /** 2079 | * ### .deepPropertyVal(object, property, value, [message]) 2080 | * 2081 | * Asserts that `object` has a property named by `property` with value given 2082 | * by `value`. `property` can use dot- and bracket-notation for deep 2083 | * reference. 2084 | * 2085 | * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); 2086 | * 2087 | * @name deepPropertyVal 2088 | * @param {Object} object 2089 | * @param {String} property 2090 | * @param {Mixed} value 2091 | * @param {String} message 2092 | * @api public 2093 | */ 2094 | 2095 | assert.deepPropertyVal = function (obj, prop, val, msg) { 2096 | new Assertion(obj, msg).to.have.deep.property(prop, val); 2097 | }; 2098 | 2099 | /** 2100 | * ### .deepPropertyNotVal(object, property, value, [message]) 2101 | * 2102 | * Asserts that `object` has a property named by `property`, but with a value 2103 | * different from that given by `value`. `property` can use dot- and 2104 | * bracket-notation for deep reference. 2105 | * 2106 | * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); 2107 | * 2108 | * @name deepPropertyNotVal 2109 | * @param {Object} object 2110 | * @param {String} property 2111 | * @param {Mixed} value 2112 | * @param {String} message 2113 | * @api public 2114 | */ 2115 | 2116 | assert.deepPropertyNotVal = function (obj, prop, val, msg) { 2117 | new Assertion(obj, msg).to.not.have.deep.property(prop, val); 2118 | }; 2119 | 2120 | /** 2121 | * ### .lengthOf(object, length, [message]) 2122 | * 2123 | * Asserts that `object` has a `length` property with the expected value. 2124 | * 2125 | * assert.lengthOf([1,2,3], 3, 'array has length of 3'); 2126 | * assert.lengthOf('foobar', 5, 'string has length of 6'); 2127 | * 2128 | * @name lengthOf 2129 | * @param {Mixed} object 2130 | * @param {Number} length 2131 | * @param {String} message 2132 | * @api public 2133 | */ 2134 | 2135 | assert.lengthOf = function (exp, len, msg) { 2136 | new Assertion(exp, msg).to.have.length(len); 2137 | }; 2138 | 2139 | /** 2140 | * ### .throws(function, [constructor/regexp], [message]) 2141 | * 2142 | * Asserts that `function` will throw an error that is an instance of 2143 | * `constructor`, or alternately that it will throw an error with message 2144 | * matching `regexp`. 2145 | * 2146 | * assert.throw(fn, ReferenceError, 'function throws a reference error'); 2147 | * 2148 | * @name throws 2149 | * @alias throw 2150 | * @alias Throw 2151 | * @param {Function} function 2152 | * @param {ErrorConstructor} constructor 2153 | * @param {RegExp} regexp 2154 | * @param {String} message 2155 | * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types 2156 | * @api public 2157 | */ 2158 | 2159 | assert.Throw = function (fn, type, msg) { 2160 | if ('string' === typeof type) { 2161 | msg = type; 2162 | type = null; 2163 | } 2164 | 2165 | new Assertion(fn, msg).to.Throw(type); 2166 | }; 2167 | 2168 | /** 2169 | * ### .doesNotThrow(function, [constructor/regexp], [message]) 2170 | * 2171 | * Asserts that `function` will _not_ throw an error that is an instance of 2172 | * `constructor`, or alternately that it will not throw an error with message 2173 | * matching `regexp`. 2174 | * 2175 | * assert.doesNotThrow(fn, Error, 'function does not throw'); 2176 | * 2177 | * @name doesNotThrow 2178 | * @param {Function} function 2179 | * @param {ErrorConstructor} constructor 2180 | * @param {RegExp} regexp 2181 | * @param {String} message 2182 | * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types 2183 | * @api public 2184 | */ 2185 | 2186 | assert.doesNotThrow = function (fn, type, msg) { 2187 | if ('string' === typeof type) { 2188 | msg = type; 2189 | type = null; 2190 | } 2191 | 2192 | new Assertion(fn, msg).to.not.Throw(type); 2193 | }; 2194 | 2195 | /** 2196 | * ### .operator(val1, operator, val2, [message]) 2197 | * 2198 | * Compares two values using `operator`. 2199 | * 2200 | * assert.operator(1, '<', 2, 'everything is ok'); 2201 | * assert.operator(1, '>', 2, 'this will fail'); 2202 | * 2203 | * @name operator 2204 | * @param {Mixed} val1 2205 | * @param {String} operator 2206 | * @param {Mixed} val2 2207 | * @param {String} message 2208 | * @api public 2209 | */ 2210 | 2211 | assert.operator = function (val, operator, val2, msg) { 2212 | if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { 2213 | throw new Error('Invalid operator "' + operator + '"'); 2214 | } 2215 | var test = new Assertion(eval(val + operator + val2), msg); 2216 | test.assert( 2217 | true === flag(test, 'object') 2218 | , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) 2219 | , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); 2220 | }; 2221 | 2222 | /*! 2223 | * Undocumented / untested 2224 | */ 2225 | 2226 | assert.ifError = function (val, msg) { 2227 | new Assertion(val, msg).to.not.be.ok; 2228 | }; 2229 | 2230 | /*! 2231 | * Aliases. 2232 | */ 2233 | 2234 | (function alias(name, as){ 2235 | assert[as] = assert[name]; 2236 | return alias; 2237 | }) 2238 | ('Throw', 'throw') 2239 | ('Throw', 'throws'); 2240 | }; 2241 | 2242 | }); // module: interface/assert.js 2243 | 2244 | require.register("interface/expect.js", function(module, exports, require){ 2245 | /*! 2246 | * chai 2247 | * Copyright(c) 2011-2012 Jake Luer 2248 | * MIT Licensed 2249 | */ 2250 | 2251 | module.exports = function (chai, util) { 2252 | chai.expect = function (val, message) { 2253 | return new chai.Assertion(val, message); 2254 | }; 2255 | }; 2256 | 2257 | 2258 | }); // module: interface/expect.js 2259 | 2260 | require.register("interface/should.js", function(module, exports, require){ 2261 | /*! 2262 | * chai 2263 | * Copyright(c) 2011-2012 Jake Luer 2264 | * MIT Licensed 2265 | */ 2266 | 2267 | module.exports = function (chai, util) { 2268 | var Assertion = chai.Assertion; 2269 | 2270 | function loadShould () { 2271 | // modify Object.prototype to have `should` 2272 | Object.defineProperty(Object.prototype, 'should', 2273 | { set: function () {} 2274 | , get: function(){ 2275 | if (this instanceof String || this instanceof Number) { 2276 | return new Assertion(this.constructor(this)); 2277 | } else if (this instanceof Boolean) { 2278 | return new Assertion(this == true); 2279 | } 2280 | return new Assertion(this); 2281 | } 2282 | , configurable: true 2283 | }); 2284 | 2285 | var should = {}; 2286 | 2287 | should.equal = function (val1, val2) { 2288 | new Assertion(val1).to.equal(val2); 2289 | }; 2290 | 2291 | should.Throw = function (fn, errt, errs) { 2292 | new Assertion(fn).to.Throw(errt, errs); 2293 | }; 2294 | 2295 | should.exist = function (val) { 2296 | new Assertion(val).to.exist; 2297 | } 2298 | 2299 | // negation 2300 | should.not = {} 2301 | 2302 | should.not.equal = function (val1, val2) { 2303 | new Assertion(val1).to.not.equal(val2); 2304 | }; 2305 | 2306 | should.not.Throw = function (fn, errt, errs) { 2307 | new Assertion(fn).to.not.Throw(errt, errs); 2308 | }; 2309 | 2310 | should.not.exist = function (val) { 2311 | new Assertion(val).to.not.exist; 2312 | } 2313 | 2314 | should['throw'] = should['Throw']; 2315 | should.not['throw'] = should.not['Throw']; 2316 | 2317 | return should; 2318 | }; 2319 | 2320 | chai.should = loadShould; 2321 | chai.Should = loadShould; 2322 | }; 2323 | 2324 | }); // module: interface/should.js 2325 | 2326 | require.register("utils/addChainableMethod.js", function(module, exports, require){ 2327 | /*! 2328 | * Chai - addChainingMethod utility 2329 | * Copyright(c) 2012 Jake Luer 2330 | * MIT Licensed 2331 | */ 2332 | 2333 | /*! 2334 | * Module dependencies 2335 | */ 2336 | 2337 | var transferFlags = require('./transferFlags'); 2338 | 2339 | /** 2340 | * ### addChainableMethod (ctx, name, method, chainingBehavior) 2341 | * 2342 | * Adds a method to an object, such that the method can also be chained. 2343 | * 2344 | * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { 2345 | * var obj = utils.flag(this, 'object'); 2346 | * new chai.Assertion(obj).to.be.equal(str); 2347 | * }); 2348 | * 2349 | * Can also be accessed directly from `chai.Assertion`. 2350 | * 2351 | * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); 2352 | * 2353 | * The result can then be used as both a method assertion, executing both `method` and 2354 | * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. 2355 | * 2356 | * expect(fooStr).to.be.foo('bar'); 2357 | * expect(fooStr).to.be.foo.equal('foo'); 2358 | * 2359 | * @param {Object} ctx object to which the method is added 2360 | * @param {String} name of method to add 2361 | * @param {Function} method function to be used for `name`, when called 2362 | * @param {Function} chainingBehavior function to be called every time the property is accessed 2363 | * @name addChainableMethod 2364 | * @api public 2365 | */ 2366 | 2367 | module.exports = function (ctx, name, method, chainingBehavior) { 2368 | if (typeof chainingBehavior !== 'function') 2369 | chainingBehavior = function () { }; 2370 | 2371 | Object.defineProperty(ctx, name, 2372 | { get: function () { 2373 | chainingBehavior.call(this); 2374 | 2375 | var assert = function () { 2376 | var result = method.apply(this, arguments); 2377 | return result === undefined ? this : result; 2378 | }; 2379 | 2380 | // Re-enumerate every time to better accomodate plugins. 2381 | var asserterNames = Object.getOwnPropertyNames(ctx); 2382 | asserterNames.forEach(function (asserterName) { 2383 | var pd = Object.getOwnPropertyDescriptor(ctx, asserterName) 2384 | , functionProtoPD = Object.getOwnPropertyDescriptor(Function.prototype, asserterName); 2385 | // Avoid trying to overwrite things that we can't, like `length` and `arguments`. 2386 | if (functionProtoPD && !functionProtoPD.configurable) return; 2387 | if (asserterName === 'arguments') return; // @see chaijs/chai/issues/69 2388 | Object.defineProperty(assert, asserterName, pd); 2389 | }); 2390 | 2391 | transferFlags(this, assert); 2392 | return assert; 2393 | } 2394 | , configurable: true 2395 | }); 2396 | }; 2397 | 2398 | }); // module: utils/addChainableMethod.js 2399 | 2400 | require.register("utils/addMethod.js", function(module, exports, require){ 2401 | /*! 2402 | * Chai - addMethod utility 2403 | * Copyright(c) 2012 Jake Luer 2404 | * MIT Licensed 2405 | */ 2406 | 2407 | /** 2408 | * ### addMethod (ctx, name, method) 2409 | * 2410 | * Adds a method to the prototype of an object. 2411 | * 2412 | * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { 2413 | * var obj = utils.flag(this, 'object'); 2414 | * new chai.Assertion(obj).to.be.equal(str); 2415 | * }); 2416 | * 2417 | * Can also be accessed directly from `chai.Assertion`. 2418 | * 2419 | * chai.Assertion.addMethod('foo', fn); 2420 | * 2421 | * Then can be used as any other assertion. 2422 | * 2423 | * expect(fooStr).to.be.foo('bar'); 2424 | * 2425 | * @param {Object} ctx object to which the method is added 2426 | * @param {String} name of method to add 2427 | * @param {Function} method function to be used for name 2428 | * @name addMethod 2429 | * @api public 2430 | */ 2431 | 2432 | module.exports = function (ctx, name, method) { 2433 | ctx[name] = function () { 2434 | var result = method.apply(this, arguments); 2435 | return result === undefined ? this : result; 2436 | }; 2437 | }; 2438 | 2439 | }); // module: utils/addMethod.js 2440 | 2441 | require.register("utils/addProperty.js", function(module, exports, require){ 2442 | /*! 2443 | * Chai - addProperty utility 2444 | * Copyright(c) 2012 Jake Luer 2445 | * MIT Licensed 2446 | */ 2447 | 2448 | /** 2449 | * ### addProperty (ctx, name, getter) 2450 | * 2451 | * Adds a property to the prototype of an object. 2452 | * 2453 | * utils.addProperty(chai.Assertion.prototype, 'foo', function () { 2454 | * var obj = utils.flag(this, 'object'); 2455 | * new chai.Assertion(obj).to.be.instanceof(Foo); 2456 | * }); 2457 | * 2458 | * Can also be accessed directly from `chai.Assertion`. 2459 | * 2460 | * chai.Assertion.addProperty('foo', fn); 2461 | * 2462 | * Then can be used as any other assertion. 2463 | * 2464 | * expect(myFoo).to.be.foo; 2465 | * 2466 | * @param {Object} ctx object to which the property is added 2467 | * @param {String} name of property to add 2468 | * @param {Function} getter function to be used for name 2469 | * @name addProperty 2470 | * @api public 2471 | */ 2472 | 2473 | module.exports = function (ctx, name, getter) { 2474 | Object.defineProperty(ctx, name, 2475 | { get: function () { 2476 | var result = getter.call(this); 2477 | return result === undefined ? this : result; 2478 | } 2479 | , configurable: true 2480 | }); 2481 | }; 2482 | 2483 | }); // module: utils/addProperty.js 2484 | 2485 | require.register("utils/eql.js", function(module, exports, require){ 2486 | // This is directly from Node.js assert 2487 | // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js 2488 | 2489 | 2490 | module.exports = _deepEqual; 2491 | 2492 | // For browser implementation 2493 | if (!Buffer) { 2494 | var Buffer = { 2495 | isBuffer: function () { 2496 | return false; 2497 | } 2498 | }; 2499 | } 2500 | 2501 | function _deepEqual(actual, expected) { 2502 | // 7.1. All identical values are equivalent, as determined by ===. 2503 | if (actual === expected) { 2504 | return true; 2505 | 2506 | } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { 2507 | if (actual.length != expected.length) return false; 2508 | 2509 | for (var i = 0; i < actual.length; i++) { 2510 | if (actual[i] !== expected[i]) return false; 2511 | } 2512 | 2513 | return true; 2514 | 2515 | // 7.2. If the expected value is a Date object, the actual value is 2516 | // equivalent if it is also a Date object that refers to the same time. 2517 | } else if (actual instanceof Date && expected instanceof Date) { 2518 | return actual.getTime() === expected.getTime(); 2519 | 2520 | // 7.3. Other pairs that do not both pass typeof value == 'object', 2521 | // equivalence is determined by ==. 2522 | } else if (typeof actual != 'object' && typeof expected != 'object') { 2523 | return actual === expected; 2524 | 2525 | // 7.4. For all other Object pairs, including Array objects, equivalence is 2526 | // determined by having the same number of owned properties (as verified 2527 | // with Object.prototype.hasOwnProperty.call), the same set of keys 2528 | // (although not necessarily the same order), equivalent values for every 2529 | // corresponding key, and an identical 'prototype' property. Note: this 2530 | // accounts for both named and indexed properties on Arrays. 2531 | } else { 2532 | return objEquiv(actual, expected); 2533 | } 2534 | } 2535 | 2536 | function isUndefinedOrNull(value) { 2537 | return value === null || value === undefined; 2538 | } 2539 | 2540 | function isArguments(object) { 2541 | return Object.prototype.toString.call(object) == '[object Arguments]'; 2542 | } 2543 | 2544 | function objEquiv(a, b) { 2545 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 2546 | return false; 2547 | // an identical 'prototype' property. 2548 | if (a.prototype !== b.prototype) return false; 2549 | //~~~I've managed to break Object.keys through screwy arguments passing. 2550 | // Converting to array solves the problem. 2551 | if (isArguments(a)) { 2552 | if (!isArguments(b)) { 2553 | return false; 2554 | } 2555 | a = pSlice.call(a); 2556 | b = pSlice.call(b); 2557 | return _deepEqual(a, b); 2558 | } 2559 | try { 2560 | var ka = Object.keys(a), 2561 | kb = Object.keys(b), 2562 | key, i; 2563 | } catch (e) {//happens when one is a string literal and the other isn't 2564 | return false; 2565 | } 2566 | // having the same number of owned properties (keys incorporates 2567 | // hasOwnProperty) 2568 | if (ka.length != kb.length) 2569 | return false; 2570 | //the same set of keys (although not necessarily the same order), 2571 | ka.sort(); 2572 | kb.sort(); 2573 | //~~~cheap key test 2574 | for (i = ka.length - 1; i >= 0; i--) { 2575 | if (ka[i] != kb[i]) 2576 | return false; 2577 | } 2578 | //equivalent values for every corresponding key, and 2579 | //~~~possibly expensive deep test 2580 | for (i = ka.length - 1; i >= 0; i--) { 2581 | key = ka[i]; 2582 | if (!_deepEqual(a[key], b[key])) return false; 2583 | } 2584 | return true; 2585 | } 2586 | }); // module: utils/eql.js 2587 | 2588 | require.register("utils/flag.js", function(module, exports, require){ 2589 | /*! 2590 | * Chai - flag utility 2591 | * Copyright(c) 2012 Jake Luer 2592 | * MIT Licensed 2593 | */ 2594 | 2595 | /** 2596 | * ### flag(object ,key, [value]) 2597 | * 2598 | * Get or set a flag value on an object. If a 2599 | * value is provided it will be set, else it will 2600 | * return the currently set value or `undefined` if 2601 | * the value is not set. 2602 | * 2603 | * utils.flag(this, 'foo', 'bar'); // setter 2604 | * utils.flag(this, 'foo'); // getter, returns `bar` 2605 | * 2606 | * @param {Object} object (constructed Assertion 2607 | * @param {String} key 2608 | * @param {Mixed} value (optional) 2609 | * @name flag 2610 | * @api private 2611 | */ 2612 | 2613 | module.exports = function (obj, key, value) { 2614 | var flags = obj.__flags || (obj.__flags = Object.create(null)); 2615 | if (arguments.length === 3) { 2616 | flags[key] = value; 2617 | } else { 2618 | return flags[key]; 2619 | } 2620 | }; 2621 | 2622 | }); // module: utils/flag.js 2623 | 2624 | require.register("utils/getActual.js", function(module, exports, require){ 2625 | /*! 2626 | * Chai - getActual utility 2627 | * Copyright(c) 2012 Jake Luer 2628 | * MIT Licensed 2629 | */ 2630 | 2631 | /** 2632 | * # getActual(object, [actual]) 2633 | * 2634 | * Returns the `actual` value for an Assertion 2635 | * 2636 | * @param {Object} object (constructed Assertion) 2637 | * @param {Arguments} chai.Assertion.prototype.assert arguments 2638 | */ 2639 | 2640 | module.exports = function (obj, args) { 2641 | var actual = args[4]; 2642 | return 'undefined' !== actual ? actual : obj.obj; 2643 | }; 2644 | 2645 | }); // module: utils/getActual.js 2646 | 2647 | require.register("utils/getMessage.js", function(module, exports, require){ 2648 | /*! 2649 | * Chai - message composition utility 2650 | * Copyright(c) 2012 Jake Luer 2651 | * MIT Licensed 2652 | */ 2653 | 2654 | /*! 2655 | * Module dependancies 2656 | */ 2657 | 2658 | var flag = require('./flag') 2659 | , getActual = require('./getActual') 2660 | , inspect = require('./inspect'); 2661 | 2662 | /** 2663 | * # getMessage(object, message, negateMessage) 2664 | * 2665 | * Construct the error message based on flags 2666 | * and template tags. Template tags will return 2667 | * a stringified inspection of the object referenced. 2668 | * 2669 | * Messsage template tags: 2670 | * - `#{this}` current asserted object 2671 | * - `#{act}` actual value 2672 | * - `#{exp}` expected value 2673 | * 2674 | * @param {Object} object (constructed Assertion) 2675 | * @param {Arguments} chai.Assertion.prototype.assert arguments 2676 | */ 2677 | 2678 | module.exports = function (obj, args) { 2679 | var negate = flag(obj, 'negate') 2680 | , val = flag(obj, 'object') 2681 | , expected = args[3] 2682 | , actual = getActual(obj, args) 2683 | , msg = negate ? args[2] : args[1] 2684 | , flagMsg = flag(obj, 'message'); 2685 | 2686 | msg = msg || ''; 2687 | msg = msg 2688 | .replace(/#{this}/g, inspect(val)) 2689 | .replace(/#{act}/g, inspect(actual)) 2690 | .replace(/#{exp}/g, inspect(expected)); 2691 | 2692 | return flagMsg ? flagMsg + ': ' + msg : msg; 2693 | }; 2694 | 2695 | }); // module: utils/getMessage.js 2696 | 2697 | require.register("utils/getName.js", function(module, exports, require){ 2698 | /*! 2699 | * Chai - getName utility 2700 | * Copyright(c) 2012 Jake Luer 2701 | * MIT Licensed 2702 | */ 2703 | 2704 | /** 2705 | * # getName(func) 2706 | * 2707 | * Gets the name of a function, in a cross-browser way. 2708 | * 2709 | * @param {Function} a function (usually a constructor) 2710 | */ 2711 | 2712 | module.exports = function (func) { 2713 | if (func.name) return func.name; 2714 | 2715 | var match = /^\s?function ([^(]*)\(/.exec(func); 2716 | return match && match[1] ? match[1] : ""; 2717 | }; 2718 | 2719 | }); // module: utils/getName.js 2720 | 2721 | require.register("utils/getPathValue.js", function(module, exports, require){ 2722 | /*! 2723 | * Chai - getPathValue utility 2724 | * Copyright(c) 2012 Jake Luer 2725 | * @see https://github.com/logicalparadox/filtr 2726 | * MIT Licensed 2727 | */ 2728 | 2729 | /** 2730 | * ### .getPathValue(path, object) 2731 | * 2732 | * This allows the retrieval of values in an 2733 | * object given a string path. 2734 | * 2735 | * var obj = { 2736 | * prop1: { 2737 | * arr: ['a', 'b', 'c'] 2738 | * , str: 'Hello' 2739 | * } 2740 | * , prop2: { 2741 | * arr: [ { nested: 'Universe' } ] 2742 | * , str: 'Hello again!' 2743 | * } 2744 | * } 2745 | * 2746 | * The following would be the results. 2747 | * 2748 | * getPathValue('prop1.str', obj); // Hello 2749 | * getPathValue('prop1.att[2]', obj); // b 2750 | * getPathValue('prop2.arr[0].nested', obj); // Universe 2751 | * 2752 | * @param {String} path 2753 | * @param {Object} object 2754 | * @returns {Object} value or `undefined` 2755 | * @name getPathValue 2756 | * @api public 2757 | */ 2758 | 2759 | var getPathValue = module.exports = function (path, obj) { 2760 | var parsed = parsePath(path); 2761 | return _getPathValue(parsed, obj); 2762 | }; 2763 | 2764 | /*! 2765 | * ## parsePath(path) 2766 | * 2767 | * Helper function used to parse string object 2768 | * paths. Use in conjunction with `_getPathValue`. 2769 | * 2770 | * var parsed = parsePath('myobject.property.subprop'); 2771 | * 2772 | * ### Paths: 2773 | * 2774 | * * Can be as near infinitely deep and nested 2775 | * * Arrays are also valid using the formal `myobject.document[3].property`. 2776 | * 2777 | * @param {String} path 2778 | * @returns {Object} parsed 2779 | * @api private 2780 | */ 2781 | 2782 | function parsePath (path) { 2783 | var parts = path.split('.').filter(Boolean); 2784 | return parts.map(function (value) { 2785 | var re = /([A-Za-z0-9]+)\[(\d+)\]$/ 2786 | , mArr = re.exec(value) 2787 | , val; 2788 | if (mArr) val = { p: mArr[1], i: parseFloat(mArr[2]) }; 2789 | return val || value; 2790 | }); 2791 | }; 2792 | 2793 | /*! 2794 | * ## _getPathValue(parsed, obj) 2795 | * 2796 | * Helper companion function for `.parsePath` that returns 2797 | * the value located at the parsed address. 2798 | * 2799 | * var value = getPathValue(parsed, obj); 2800 | * 2801 | * @param {Object} parsed definition from `parsePath`. 2802 | * @param {Object} object to search against 2803 | * @returns {Object|Undefined} value 2804 | * @api private 2805 | */ 2806 | 2807 | function _getPathValue (parsed, obj) { 2808 | var tmp = obj 2809 | , res; 2810 | for (var i = 0, l = parsed.length; i < l; i++) { 2811 | var part = parsed[i]; 2812 | if (tmp) { 2813 | if ('object' === typeof part && tmp[part.p]) { 2814 | tmp = tmp[part.p][part.i]; 2815 | } else { 2816 | tmp = tmp[part]; 2817 | } 2818 | if (i == (l - 1)) res = tmp; 2819 | } else { 2820 | res = undefined; 2821 | } 2822 | } 2823 | return res; 2824 | }; 2825 | 2826 | }); // module: utils/getPathValue.js 2827 | 2828 | require.register("utils/index.js", function(module, exports, require){ 2829 | /*! 2830 | * chai 2831 | * Copyright(c) 2011 Jake Luer 2832 | * MIT Licensed 2833 | */ 2834 | 2835 | /*! 2836 | * Main exports 2837 | */ 2838 | 2839 | var exports = module.exports = {}; 2840 | 2841 | /*! 2842 | * test utility 2843 | */ 2844 | 2845 | exports.test = require('./test'); 2846 | 2847 | /*! 2848 | * message utility 2849 | */ 2850 | 2851 | exports.getMessage = require('./getMessage'); 2852 | 2853 | /*! 2854 | * actual utility 2855 | */ 2856 | 2857 | exports.getActual = require('./getActual'); 2858 | 2859 | /*! 2860 | * Inspect util 2861 | */ 2862 | 2863 | exports.inspect = require('./inspect'); 2864 | 2865 | /*! 2866 | * Flag utility 2867 | */ 2868 | 2869 | exports.flag = require('./flag'); 2870 | 2871 | /*! 2872 | * Flag transferring utility 2873 | */ 2874 | 2875 | exports.transferFlags = require('./transferFlags'); 2876 | 2877 | /*! 2878 | * Deep equal utility 2879 | */ 2880 | 2881 | exports.eql = require('./eql'); 2882 | 2883 | /*! 2884 | * Deep path value 2885 | */ 2886 | 2887 | exports.getPathValue = require('./getPathValue'); 2888 | 2889 | /*! 2890 | * Function name 2891 | */ 2892 | 2893 | exports.getName = require('./getName'); 2894 | 2895 | /*! 2896 | * add Property 2897 | */ 2898 | 2899 | exports.addProperty = require('./addProperty'); 2900 | 2901 | /*! 2902 | * add Method 2903 | */ 2904 | 2905 | exports.addMethod = require('./addMethod'); 2906 | 2907 | /*! 2908 | * overwrite Property 2909 | */ 2910 | 2911 | exports.overwriteProperty = require('./overwriteProperty'); 2912 | 2913 | /*! 2914 | * overwrite Method 2915 | */ 2916 | 2917 | exports.overwriteMethod = require('./overwriteMethod'); 2918 | 2919 | /*! 2920 | * Add a chainable method 2921 | */ 2922 | 2923 | exports.addChainableMethod = require('./addChainableMethod'); 2924 | 2925 | 2926 | }); // module: utils/index.js 2927 | 2928 | require.register("utils/inspect.js", function(module, exports, require){ 2929 | // This is (almost) directly from Node.js utils 2930 | // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js 2931 | 2932 | var getName = require('./getName'); 2933 | 2934 | module.exports = inspect; 2935 | 2936 | /** 2937 | * Echos the value of a value. Trys to print the value out 2938 | * in the best way possible given the different types. 2939 | * 2940 | * @param {Object} obj The object to print out. 2941 | * @param {Boolean} showHidden Flag that shows hidden (not enumerable) 2942 | * properties of objects. 2943 | * @param {Number} depth Depth in which to descend in object. Default is 2. 2944 | * @param {Boolean} colors Flag to turn on ANSI escape codes to color the 2945 | * output. Default is false (no coloring). 2946 | */ 2947 | function inspect(obj, showHidden, depth, colors) { 2948 | var ctx = { 2949 | showHidden: showHidden, 2950 | seen: [], 2951 | stylize: function (str) { return str; } 2952 | }; 2953 | return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); 2954 | } 2955 | 2956 | function formatValue(ctx, value, recurseTimes) { 2957 | // Provide a hook for user-specified inspect functions. 2958 | // Check that value is an object with an inspect function on it 2959 | if (value && typeof value.inspect === 'function' && 2960 | // Filter out the util module, it's inspect function is special 2961 | value.inspect !== exports.inspect && 2962 | // Also filter out any prototype objects using the circular check. 2963 | !(value.constructor && value.constructor.prototype === value)) { 2964 | return value.inspect(recurseTimes); 2965 | } 2966 | 2967 | // Primitive types cannot have properties 2968 | var primitive = formatPrimitive(ctx, value); 2969 | if (primitive) { 2970 | return primitive; 2971 | } 2972 | 2973 | // Look up the keys of the object. 2974 | var visibleKeys = Object.keys(value); 2975 | var keys = ctx.showHidden ? Object.getOwnPropertyNames(value) : visibleKeys; 2976 | 2977 | // Some type of object without properties can be shortcutted. 2978 | // In IE, errors have a single `stack` property, or if they are vanilla `Error`, 2979 | // a `stack` plus `description` property; ignore those for consistency. 2980 | if (keys.length === 0 || (isError(value) && ( 2981 | (keys.length === 1 && keys[0] === 'stack') || 2982 | (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') 2983 | ))) { 2984 | if (typeof value === 'function') { 2985 | var name = getName(value); 2986 | var nameSuffix = name ? ': ' + name : ''; 2987 | return ctx.stylize('[Function' + nameSuffix + ']', 'special'); 2988 | } 2989 | if (isRegExp(value)) { 2990 | return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); 2991 | } 2992 | if (isDate(value)) { 2993 | return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); 2994 | } 2995 | if (isError(value)) { 2996 | return formatError(value); 2997 | } 2998 | } 2999 | 3000 | var base = '', array = false, braces = ['{', '}']; 3001 | 3002 | // Make Array say that they are Array 3003 | if (isArray(value)) { 3004 | array = true; 3005 | braces = ['[', ']']; 3006 | } 3007 | 3008 | // Make functions say that they are functions 3009 | if (typeof value === 'function') { 3010 | var n = value.name ? ': ' + value.name : ''; 3011 | base = ' [Function' + n + ']'; 3012 | } 3013 | 3014 | // Make RegExps say that they are RegExps 3015 | if (isRegExp(value)) { 3016 | base = ' ' + RegExp.prototype.toString.call(value); 3017 | } 3018 | 3019 | // Make dates with properties first say the date 3020 | if (isDate(value)) { 3021 | base = ' ' + Date.prototype.toUTCString.call(value); 3022 | } 3023 | 3024 | // Make error with message first say the error 3025 | if (isError(value)) { 3026 | return formatError(value); 3027 | } 3028 | 3029 | if (keys.length === 0 && (!array || value.length == 0)) { 3030 | return braces[0] + base + braces[1]; 3031 | } 3032 | 3033 | if (recurseTimes < 0) { 3034 | if (isRegExp(value)) { 3035 | return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); 3036 | } else { 3037 | return ctx.stylize('[Object]', 'special'); 3038 | } 3039 | } 3040 | 3041 | ctx.seen.push(value); 3042 | 3043 | var output; 3044 | if (array) { 3045 | output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); 3046 | } else { 3047 | output = keys.map(function(key) { 3048 | return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); 3049 | }); 3050 | } 3051 | 3052 | ctx.seen.pop(); 3053 | 3054 | return reduceToSingleString(output, base, braces); 3055 | } 3056 | 3057 | 3058 | function formatPrimitive(ctx, value) { 3059 | switch (typeof value) { 3060 | case 'undefined': 3061 | return ctx.stylize('undefined', 'undefined'); 3062 | 3063 | case 'string': 3064 | var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') 3065 | .replace(/'/g, "\\'") 3066 | .replace(/\\"/g, '"') + '\''; 3067 | return ctx.stylize(simple, 'string'); 3068 | 3069 | case 'number': 3070 | return ctx.stylize('' + value, 'number'); 3071 | 3072 | case 'boolean': 3073 | return ctx.stylize('' + value, 'boolean'); 3074 | } 3075 | // For some reason typeof null is "object", so special case here. 3076 | if (value === null) { 3077 | return ctx.stylize('null', 'null'); 3078 | } 3079 | } 3080 | 3081 | 3082 | function formatError(value) { 3083 | return '[' + Error.prototype.toString.call(value) + ']'; 3084 | } 3085 | 3086 | 3087 | function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { 3088 | var output = []; 3089 | for (var i = 0, l = value.length; i < l; ++i) { 3090 | if (Object.prototype.hasOwnProperty.call(value, String(i))) { 3091 | output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, 3092 | String(i), true)); 3093 | } else { 3094 | output.push(''); 3095 | } 3096 | } 3097 | keys.forEach(function(key) { 3098 | if (!key.match(/^\d+$/)) { 3099 | output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, 3100 | key, true)); 3101 | } 3102 | }); 3103 | return output; 3104 | } 3105 | 3106 | 3107 | function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { 3108 | var name, str; 3109 | if (value.__lookupGetter__) { 3110 | if (value.__lookupGetter__(key)) { 3111 | if (value.__lookupSetter__(key)) { 3112 | str = ctx.stylize('[Getter/Setter]', 'special'); 3113 | } else { 3114 | str = ctx.stylize('[Getter]', 'special'); 3115 | } 3116 | } else { 3117 | if (value.__lookupSetter__(key)) { 3118 | str = ctx.stylize('[Setter]', 'special'); 3119 | } 3120 | } 3121 | } 3122 | if (visibleKeys.indexOf(key) < 0) { 3123 | name = '[' + key + ']'; 3124 | } 3125 | if (!str) { 3126 | if (ctx.seen.indexOf(value[key]) < 0) { 3127 | if (recurseTimes === null) { 3128 | str = formatValue(ctx, value[key], null); 3129 | } else { 3130 | str = formatValue(ctx, value[key], recurseTimes - 1); 3131 | } 3132 | if (str.indexOf('\n') > -1) { 3133 | if (array) { 3134 | str = str.split('\n').map(function(line) { 3135 | return ' ' + line; 3136 | }).join('\n').substr(2); 3137 | } else { 3138 | str = '\n' + str.split('\n').map(function(line) { 3139 | return ' ' + line; 3140 | }).join('\n'); 3141 | } 3142 | } 3143 | } else { 3144 | str = ctx.stylize('[Circular]', 'special'); 3145 | } 3146 | } 3147 | if (typeof name === 'undefined') { 3148 | if (array && key.match(/^\d+$/)) { 3149 | return str; 3150 | } 3151 | name = JSON.stringify('' + key); 3152 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { 3153 | name = name.substr(1, name.length - 2); 3154 | name = ctx.stylize(name, 'name'); 3155 | } else { 3156 | name = name.replace(/'/g, "\\'") 3157 | .replace(/\\"/g, '"') 3158 | .replace(/(^"|"$)/g, "'"); 3159 | name = ctx.stylize(name, 'string'); 3160 | } 3161 | } 3162 | 3163 | return name + ': ' + str; 3164 | } 3165 | 3166 | 3167 | function reduceToSingleString(output, base, braces) { 3168 | var numLinesEst = 0; 3169 | var length = output.reduce(function(prev, cur) { 3170 | numLinesEst++; 3171 | if (cur.indexOf('\n') >= 0) numLinesEst++; 3172 | return prev + cur.length + 1; 3173 | }, 0); 3174 | 3175 | if (length > 60) { 3176 | return braces[0] + 3177 | (base === '' ? '' : base + '\n ') + 3178 | ' ' + 3179 | output.join(',\n ') + 3180 | ' ' + 3181 | braces[1]; 3182 | } 3183 | 3184 | return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; 3185 | } 3186 | 3187 | function isArray(ar) { 3188 | return Array.isArray(ar) || 3189 | (typeof ar === 'object' && objectToString(ar) === '[object Array]'); 3190 | } 3191 | 3192 | function isRegExp(re) { 3193 | return typeof re === 'object' && objectToString(re) === '[object RegExp]'; 3194 | } 3195 | 3196 | function isDate(d) { 3197 | return typeof d === 'object' && objectToString(d) === '[object Date]'; 3198 | } 3199 | 3200 | function isError(e) { 3201 | return typeof e === 'object' && objectToString(e) === '[object Error]'; 3202 | } 3203 | 3204 | function objectToString(o) { 3205 | return Object.prototype.toString.call(o); 3206 | } 3207 | 3208 | }); // module: utils/inspect.js 3209 | 3210 | require.register("utils/overwriteMethod.js", function(module, exports, require){ 3211 | /*! 3212 | * Chai - overwriteMethod utility 3213 | * Copyright(c) 2012 Jake Luer 3214 | * MIT Licensed 3215 | */ 3216 | 3217 | /** 3218 | * ### overwriteMethod (ctx, name, fn) 3219 | * 3220 | * Overwites an already existing method and provides 3221 | * access to previous function. Must return function 3222 | * to be used for name. 3223 | * 3224 | * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { 3225 | * return function (str) { 3226 | * var obj = utils.flag(this, 'object'); 3227 | * if (obj instanceof Foo) { 3228 | * new chai.Assertion(obj.value).to.equal(str); 3229 | * } else { 3230 | * _super.apply(this, arguments); 3231 | * } 3232 | * } 3233 | * }); 3234 | * 3235 | * Can also be accessed directly from `chai.Assertion`. 3236 | * 3237 | * chai.Assertion.overwriteMethod('foo', fn); 3238 | * 3239 | * Then can be used as any other assertion. 3240 | * 3241 | * expect(myFoo).to.equal('bar'); 3242 | * 3243 | * @param {Object} ctx object whose method is to be overwritten 3244 | * @param {String} name of method to overwrite 3245 | * @param {Function} method function that returns a function to be used for name 3246 | * @name overwriteMethod 3247 | * @api public 3248 | */ 3249 | 3250 | module.exports = function (ctx, name, method) { 3251 | var _method = ctx[name] 3252 | , _super = function () { return this; }; 3253 | 3254 | if (_method && 'function' === typeof _method) 3255 | _super = _method; 3256 | 3257 | ctx[name] = function () { 3258 | var result = method(_super).apply(this, arguments); 3259 | return result === undefined ? this : result; 3260 | } 3261 | }; 3262 | 3263 | }); // module: utils/overwriteMethod.js 3264 | 3265 | require.register("utils/overwriteProperty.js", function(module, exports, require){ 3266 | /*! 3267 | * Chai - overwriteProperty utility 3268 | * Copyright(c) 2012 Jake Luer 3269 | * MIT Licensed 3270 | */ 3271 | 3272 | /** 3273 | * ### overwriteProperty (ctx, name, fn) 3274 | * 3275 | * Overwites an already existing property getter and provides 3276 | * access to previous value. Must return function to use as getter. 3277 | * 3278 | * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { 3279 | * return function () { 3280 | * var obj = utils.flag(this, 'object'); 3281 | * if (obj instanceof Foo) { 3282 | * new chai.Assertion(obj.name).to.equal('bar'); 3283 | * } else { 3284 | * _super.call(this); 3285 | * } 3286 | * } 3287 | * }); 3288 | * 3289 | * 3290 | * Can also be accessed directly from `chai.Assertion`. 3291 | * 3292 | * chai.Assertion.overwriteProperty('foo', fn); 3293 | * 3294 | * Then can be used as any other assertion. 3295 | * 3296 | * expect(myFoo).to.be.ok; 3297 | * 3298 | * @param {Object} ctx object whose property is to be overwritten 3299 | * @param {String} name of property to overwrite 3300 | * @param {Function} getter function that returns a getter function to be used for name 3301 | * @name overwriteProperty 3302 | * @api public 3303 | */ 3304 | 3305 | module.exports = function (ctx, name, getter) { 3306 | var _get = Object.getOwnPropertyDescriptor(ctx, name) 3307 | , _super = function () {}; 3308 | 3309 | if (_get && 'function' === typeof _get.get) 3310 | _super = _get.get 3311 | 3312 | Object.defineProperty(ctx, name, 3313 | { get: function () { 3314 | var result = getter(_super).call(this); 3315 | return result === undefined ? this : result; 3316 | } 3317 | , configurable: true 3318 | }); 3319 | }; 3320 | 3321 | }); // module: utils/overwriteProperty.js 3322 | 3323 | require.register("utils/test.js", function(module, exports, require){ 3324 | /*! 3325 | * Chai - test utility 3326 | * Copyright(c) 2012 Jake Luer 3327 | * MIT Licensed 3328 | */ 3329 | 3330 | /*! 3331 | * Module dependancies 3332 | */ 3333 | 3334 | var flag = require('./flag'); 3335 | 3336 | /** 3337 | * # test(object, expression) 3338 | * 3339 | * Test and object for expression. 3340 | * 3341 | * @param {Object} object (constructed Assertion) 3342 | * @param {Arguments} chai.Assertion.prototype.assert arguments 3343 | */ 3344 | 3345 | module.exports = function (obj, args) { 3346 | var negate = flag(obj, 'negate') 3347 | , expr = args[0]; 3348 | return negate ? !expr : expr; 3349 | }; 3350 | 3351 | }); // module: utils/test.js 3352 | 3353 | require.register("utils/transferFlags.js", function(module, exports, require){ 3354 | /*! 3355 | * Chai - transferFlags utility 3356 | * Copyright(c) 2012 Jake Luer 3357 | * MIT Licensed 3358 | */ 3359 | 3360 | /** 3361 | * ### transferFlags(assertion, object, includeAll = true) 3362 | * 3363 | * Transfer all the flags for `assertion` to `object`. If 3364 | * `includeAll` is set to `false`, then the base Chai 3365 | * assertion flags (namely `object`, `ssfi`, and `message`) 3366 | * will not be transferred. 3367 | * 3368 | * 3369 | * var newAssertion = new Assertion(); 3370 | * utils.transferFlags(assertion, newAssertion); 3371 | * 3372 | * var anotherAsseriton = new Assertion(myObj); 3373 | * utils.transferFlags(assertion, anotherAssertion, false); 3374 | * 3375 | * @param {Assertion} assertion the assertion to transfer the flags from 3376 | * @param {Object} object the object to transfer the flags too; usually a new assertion 3377 | * @param {Boolean} includeAll 3378 | * @name getAllFlags 3379 | * @api private 3380 | */ 3381 | 3382 | module.exports = function (assertion, object, includeAll) { 3383 | var flags = assertion.__flags || (assertion.__flags = Object.create(null)); 3384 | 3385 | if (!object.__flags) { 3386 | object.__flags = Object.create(null); 3387 | } 3388 | 3389 | includeAll = arguments.length === 3 ? includeAll : true; 3390 | 3391 | for (var flag in flags) { 3392 | if (includeAll || 3393 | (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { 3394 | object.__flags[flag] = flags[flag]; 3395 | } 3396 | } 3397 | }; 3398 | 3399 | }); // module: utils/transferFlags.js 3400 | 3401 | 3402 | return require('chai'); 3403 | }); --------------------------------------------------------------------------------