├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── backbone.queryparams-1.1-shim.js ├── backbone.queryparams.js ├── backbone.queryparams.min.js ├── backbone.queryparams.min.map ├── bower.json ├── lib └── compiler.jar ├── package.json ├── readme.md ├── release-notes.md └── test ├── backbone-1.1 ├── LICENSE ├── backbone.js └── test │ ├── collection.js │ ├── environment.js │ ├── events.js │ ├── index.html │ ├── model.coffee │ ├── model.js │ ├── noconflict.js │ ├── router.js │ ├── sync.js │ └── view.js ├── backbone.query.params-test.js ├── backbone ├── LICENSE ├── backbone.js └── test │ ├── collection.js │ ├── environment.js │ ├── events.js │ ├── index.html │ ├── model.coffee │ ├── model.js │ ├── noconflict.js │ ├── router.js │ ├── sync.js │ ├── test-zepto.html │ ├── vendor │ ├── jquery.js │ ├── json2.js │ ├── qunit.css │ ├── qunit.js │ ├── runner.js │ ├── underscore.js │ └── zepto-0.6.js │ └── view.js ├── test-1.1.html └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | node_modules/ 4 | sauce_connect.log* 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": false, 3 | "esnext": false, 4 | "browser": true, 5 | "bitwise": true, 6 | "curly": true, 7 | "eqeqeq": false, 8 | "forin": true, 9 | "immed": false, 10 | "latedef": false, 11 | "newcap": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonew": false, 15 | "plusplus": false, 16 | "regexp": false, 17 | "undef": true, 18 | "unused": true, 19 | "strict": false, 20 | "trailing": true, 21 | "maxparams": 6, 22 | "asi": false, 23 | "boss": false, 24 | "expr": true, 25 | "laxbreak": true, 26 | "loopfunc": true, 27 | "shadow": true, 28 | "nonstandard": true, 29 | "onevar": false, 30 | "predef": [ 31 | "_", 32 | "$", 33 | "Backbone", 34 | "deepEqual", 35 | "define", 36 | "equal", 37 | "module", 38 | "require", 39 | "strictEqual", 40 | "test" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - npm install -g grunt-cli 6 | script: 7 | - grunt --stack travis 8 | email: 9 | on_failure: change 10 | on_success: never 11 | env: 12 | global: 13 | - secure: u5Hj+68KvcfyFbUE7T1KPkl4AVrKsXDg/oTPavTYAC8wPXxnzXUmar3qDNzuL5EKdD8v0d3Noa4o87D09KUiXRMZU4/5msTOpL960Q6OWxH2ON+ZhUqNFlTG6aUsOVzLRTgyT1k3eol+GSebPaXaMMMja2P6Ub1E2rTpNjC+KGk= 14 | - secure: DvHKYDRyw2DczDFhnP9MUTbvu7KBhNWF2eh8w2s1kLmxnFWeBoW4KqrTzAAzHd52XFLc4MtyQLr+YvbMmf9DCSHTOBWrnVfpiW3G0UzoO07WtVoyEihbk8OrxBpe11aafzYpt2+9UpYh6JnOOwLeR0WYCpzNFmP6GIKKlH3gSKA= 15 | cache: 16 | directories: 17 | - node_modules 18 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var childProcess = require('child_process'); 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | 8 | jshint: { 9 | options: { 10 | jshintrc: '.jshintrc' 11 | }, 12 | files: [ 13 | 'backbone.queryparams.js', 14 | 'test/*.js' 15 | ] 16 | }, 17 | 18 | connect: { 19 | options: { 20 | base: '.', 21 | hostname: '*', 22 | port: 9999 23 | }, 24 | server: {}, 25 | keepalive: { 26 | options: { 27 | keepalive: true 28 | } 29 | } 30 | }, 31 | 'saucelabs-qunit': { 32 | options: { 33 | testname: 'backbone-query-parameters', 34 | build: process.env.TRAVIS_JOB_ID, 35 | detailedError: true, 36 | concurrency: 4 37 | }, 38 | 'backbone-1.0': { 39 | options: { 40 | tags: ['1.0'], 41 | urls: [ 42 | 'http://localhost:9999/test/test.html' 43 | ], 44 | browsers: [ 45 | {browserName: 'chrome'}, 46 | {browserName: 'firefox', platform: 'Linux'}, 47 | {browserName: 'safari'}, 48 | {browserName: 'opera'}, 49 | {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'}, 50 | {browserName: 'internet explorer', version: 8, platform: 'XP'} 51 | ] 52 | } 53 | }, 54 | 'backbone-1.1': { 55 | options: { 56 | tags: ['1.1'], 57 | urls: [ 58 | 'http://localhost:9999/test/test-1.1.html' 59 | ], 60 | browsers: [ 61 | // IE8 backbone core tests fail due to the long running script blocker triggering 62 | {browserName: 'chrome'}, 63 | {browserName: 'firefox'}, 64 | {browserName: 'safari'}, 65 | {browserName: 'opera'}, 66 | {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'}, 67 | {browserName: 'internet explorer', version: 10, platform: 'Windows 8'}, 68 | {browserName: 'internet explorer', version: 9, platform: 'Windows 7'}, 69 | ] 70 | } 71 | } 72 | }, 73 | 74 | closureCompiler: { 75 | options: { 76 | compilerFile: 'lib/compiler.jar', 77 | 78 | checkModified: true, 79 | 80 | compilerOpts: { 81 | compilation_level: 'SIMPLE_OPTIMIZATIONS', 82 | language_in: 'ECMASCRIPT5', 83 | create_source_map: 'backbone.queryparams.min.map' 84 | } 85 | }, 86 | 87 | dist: { 88 | src: 'backbone.queryparams.js', 89 | dest: 'backbone.queryparams.min.js' 90 | } 91 | } 92 | }); 93 | 94 | // Load tasks from npm 95 | grunt.loadNpmTasks('grunt-contrib-connect'); 96 | grunt.loadNpmTasks('grunt-contrib-jshint'); 97 | grunt.loadNpmTasks('grunt-saucelabs'); 98 | grunt.loadNpmTasks('grunt-closure-tools'); 99 | 100 | grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['connect:server', 'saucelabs-qunit:backbone-1.0', 'saucelabs-qunit:backbone-1.1'] : []); 101 | 102 | grunt.registerTask('travis', ['sauce']); 103 | 104 | grunt.registerTask('minify', ['closureCompiler']); 105 | 106 | grunt.registerTask('dev', ['jshint', 'connect:keepalive']); 107 | grunt.registerTask('default', ['jshint', 'minify']); 108 | }; 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Joseph A. Hudson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Joseph A. Hudson nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL JOSEPH A. HUDSON BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /backbone.queryparams-1.1-shim.js: -------------------------------------------------------------------------------- 1 | // 2 | // Works around issue introduced in Backbone 1.1 by https://github.com/jashkenas/backbone/pull/2766 3 | // 4 | // This file is unnecessary under Backbone 1.0 and earlier. 5 | // 6 | // Note that https://github.com/jashkenas/backbone/pull/2890 should hopefully make this irrevelant 7 | // 8 | (function (root, factory) { 9 | if (typeof exports === 'object' && root.require) { 10 | module.exports = factory(require("underscore"), require("backbone")); 11 | } else if (typeof define === "function" && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define(["underscore", "backbone"], function (_, Backbone) { 14 | // Use global variables if the locals are undefined. 15 | return factory(_ || root._, Backbone || root.Backbone); 16 | }); 17 | } else { 18 | // RequireJS isn't being used. Assume underscore and backbone are loaded in 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/backbone-1.1/test/model.coffee: -------------------------------------------------------------------------------- 1 | # Quick Backbone/CoffeeScript tests to make sure that inheritance 2 | # works correctly. 3 | 4 | {ok, equal, deepEqual} = require 'assert' 5 | {Model, Collection, Events} = require '../backbone' 6 | 7 | 8 | # Patch `ok` to store a count of passed tests... 9 | count = 0 10 | oldOk = ok 11 | ok = -> 12 | oldOk arguments... 13 | count++ 14 | 15 | 16 | class Document extends Model 17 | 18 | fullName: -> 19 | @get('name') + ' ' + @get('surname') 20 | 21 | tempest = new Document 22 | id : '1-the-tempest', 23 | title : "The Tempest", 24 | name : "William" 25 | surname : "Shakespeare" 26 | length : 123 27 | 28 | ok tempest.fullName() is "William Shakespeare" 29 | ok tempest.get('length') is 123 30 | 31 | 32 | class ProperDocument extends Document 33 | 34 | fullName: -> 35 | "Mr. " + super 36 | 37 | properTempest = new ProperDocument tempest.attributes 38 | 39 | ok properTempest.fullName() is "Mr. William Shakespeare" 40 | ok properTempest.get('length') is 123 41 | 42 | 43 | console.log "passed #{count} tests" 44 | -------------------------------------------------------------------------------- /test/backbone-1.1/test/noconflict.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | module("Backbone.noConflict"); 4 | 5 | test('noConflict', 2, function() { 6 | var noconflictBackbone = Backbone.noConflict(); 7 | equal(window.Backbone, undefined, 'Returned window.Backbone'); 8 | window.Backbone = noconflictBackbone; 9 | equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone'); 10 | }); 11 | 12 | })(); 13 | -------------------------------------------------------------------------------- /test/backbone-1.1/test/router.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var router = null; 4 | var location = null; 5 | var lastRoute = null; 6 | var lastArgs = []; 7 | 8 | function onRoute(router, route, args) { 9 | lastRoute = route; 10 | lastArgs = args; 11 | } 12 | 13 | var Location = function(href) { 14 | this.replace(href); 15 | }; 16 | 17 | _.extend(Location.prototype, { 18 | 19 | replace: function(href) { 20 | _.extend(this, _.pick($('', {href: href})[0], 21 | 'href', 22 | 'hash', 23 | 'host', 24 | 'search', 25 | 'fragment', 26 | 'pathname', 27 | 'protocol' 28 | )); 29 | // In IE, anchor.pathname does not contain a leading slash though 30 | // window.location.pathname does. 31 | if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname; 32 | }, 33 | 34 | toString: function() { 35 | return this.href; 36 | } 37 | 38 | }); 39 | 40 | module("Backbone.Router", { 41 | 42 | setup: function() { 43 | location = new Location('http://example.com'); 44 | Backbone.history = _.extend(new Backbone.History, {location: location}); 45 | router = new Router({testing: 101}); 46 | Backbone.history.interval = 9; 47 | Backbone.history.start({pushState: false}); 48 | lastRoute = null; 49 | lastArgs = []; 50 | Backbone.history.on('route', onRoute); 51 | }, 52 | 53 | teardown: function() { 54 | Backbone.history.stop(); 55 | Backbone.history.off('route', onRoute); 56 | } 57 | 58 | }); 59 | 60 | var ExternalObject = { 61 | value: 'unset', 62 | 63 | routingFunction: function(value) { 64 | this.value = value; 65 | } 66 | }; 67 | _.bindAll(ExternalObject); 68 | 69 | var Router = Backbone.Router.extend({ 70 | 71 | count: 0, 72 | 73 | routes: { 74 | "noCallback": "noCallback", 75 | "counter": "counter", 76 | "search/:query": "search", 77 | "search/:query/p:page": "search", 78 | "charñ": "charUTF", 79 | "char%C3%B1": "charEscaped", 80 | "contacts": "contacts", 81 | "contacts/new": "newContact", 82 | "contacts/:id": "loadContact", 83 | "route-event/:arg": "routeEvent", 84 | "optional(/:item)": "optionalItem", 85 | "named/optional/(y:z)": "namedOptional", 86 | "splat/*args/end": "splat", 87 | ":repo/compare/*from...*to": "github", 88 | "decode/:named/*splat": "decode", 89 | "*first/complex-*part/*rest": "complex", 90 | ":entity?*args": "query", 91 | "function/:value": ExternalObject.routingFunction, 92 | "*anything": "anything" 93 | }, 94 | 95 | initialize : function(options) { 96 | this.testing = options.testing; 97 | this.route('implicit', 'implicit'); 98 | }, 99 | 100 | counter: function() { 101 | this.count++; 102 | }, 103 | 104 | implicit: function() { 105 | this.count++; 106 | }, 107 | 108 | search: function(query, page) { 109 | this.query = query; 110 | this.page = page; 111 | }, 112 | 113 | charUTF: function() { 114 | this.charType = 'UTF'; 115 | }, 116 | 117 | charEscaped: function() { 118 | this.charType = 'escaped'; 119 | }, 120 | 121 | contacts: function(){ 122 | this.contact = 'index'; 123 | }, 124 | 125 | newContact: function(){ 126 | this.contact = 'new'; 127 | }, 128 | 129 | loadContact: function(){ 130 | this.contact = 'load'; 131 | }, 132 | 133 | optionalItem: function(arg){ 134 | this.arg = arg != void 0 ? arg : null; 135 | }, 136 | 137 | splat: function(args) { 138 | this.args = args; 139 | }, 140 | 141 | github: function(repo, from, to) { 142 | this.repo = repo; 143 | this.from = from; 144 | this.to = to; 145 | }, 146 | 147 | complex: function(first, part, rest) { 148 | this.first = first; 149 | this.part = part; 150 | this.rest = rest; 151 | }, 152 | 153 | query: function(entity, args) { 154 | this.entity = entity; 155 | this.queryArgs = args; 156 | }, 157 | 158 | anything: function(whatever) { 159 | this.anything = whatever; 160 | }, 161 | 162 | namedOptional: function(z) { 163 | this.z = z; 164 | }, 165 | 166 | decode: function(named, path) { 167 | this.named = named; 168 | this.path = path; 169 | }, 170 | 171 | routeEvent: function(arg) { 172 | } 173 | 174 | }); 175 | 176 | test("initialize", 1, function() { 177 | equal(router.testing, 101); 178 | }); 179 | 180 | test("routes (simple)", 4, function() { 181 | location.replace('http://example.com#search/news'); 182 | Backbone.history.checkUrl(); 183 | equal(router.query, 'news'); 184 | equal(router.page, void 0); 185 | equal(lastRoute, 'search'); 186 | equal(lastArgs[0], 'news'); 187 | }); 188 | 189 | test("routes (simple, but unicode)", 4, function() { 190 | location.replace('http://example.com#search/тест'); 191 | Backbone.history.checkUrl(); 192 | equal(router.query, "тест"); 193 | equal(router.page, void 0); 194 | equal(lastRoute, 'search'); 195 | equal(lastArgs[0], "тест"); 196 | }); 197 | 198 | test("routes (two part)", 2, function() { 199 | location.replace('http://example.com#search/nyc/p10'); 200 | Backbone.history.checkUrl(); 201 | equal(router.query, 'nyc'); 202 | equal(router.page, '10'); 203 | }); 204 | 205 | test("routes via navigate", 2, function() { 206 | Backbone.history.navigate('search/manhattan/p20', {trigger: true}); 207 | equal(router.query, 'manhattan'); 208 | equal(router.page, '20'); 209 | }); 210 | 211 | test("routes via navigate for backwards-compatibility", 2, function() { 212 | Backbone.history.navigate('search/manhattan/p20', true); 213 | equal(router.query, 'manhattan'); 214 | equal(router.page, '20'); 215 | }); 216 | 217 | test("reports matched route via nagivate", 1, function() { 218 | ok(Backbone.history.navigate('search/manhattan/p20', true)); 219 | }); 220 | 221 | test("route precedence via navigate", 6, function(){ 222 | // check both 0.9.x and backwards-compatibility options 223 | _.each([ { trigger: true }, true ], function( options ){ 224 | Backbone.history.navigate('contacts', options); 225 | equal(router.contact, 'index'); 226 | Backbone.history.navigate('contacts/new', options); 227 | equal(router.contact, 'new'); 228 | Backbone.history.navigate('contacts/foo', options); 229 | equal(router.contact, 'load'); 230 | }); 231 | }); 232 | 233 | test("loadUrl is not called for identical routes.", 0, function() { 234 | Backbone.history.loadUrl = function(){ ok(false); }; 235 | location.replace('http://example.com#route'); 236 | Backbone.history.navigate('route'); 237 | Backbone.history.navigate('/route'); 238 | Backbone.history.navigate('/route'); 239 | }); 240 | 241 | test("use implicit callback if none provided", 1, function() { 242 | router.count = 0; 243 | router.navigate('implicit', {trigger: true}); 244 | equal(router.count, 1); 245 | }); 246 | 247 | test("routes via navigate with {replace: true}", 1, function() { 248 | location.replace('http://example.com#start_here'); 249 | Backbone.history.checkUrl(); 250 | location.replace = function(href) { 251 | strictEqual(href, new Location('http://example.com#end_here').href); 252 | }; 253 | Backbone.history.navigate('end_here', {replace: true}); 254 | }); 255 | 256 | test("routes (splats)", 1, function() { 257 | location.replace('http://example.com#splat/long-list/of/splatted_99args/end'); 258 | Backbone.history.checkUrl(); 259 | equal(router.args, 'long-list/of/splatted_99args'); 260 | }); 261 | 262 | test("routes (github)", 3, function() { 263 | location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash'); 264 | Backbone.history.checkUrl(); 265 | equal(router.repo, 'backbone'); 266 | equal(router.from, '1.0'); 267 | equal(router.to, 'braddunbar:with/slash'); 268 | }); 269 | 270 | test("routes (optional)", 2, function() { 271 | location.replace('http://example.com#optional'); 272 | Backbone.history.checkUrl(); 273 | ok(!router.arg); 274 | location.replace('http://example.com#optional/thing'); 275 | Backbone.history.checkUrl(); 276 | equal(router.arg, 'thing'); 277 | }); 278 | 279 | test("routes (complex)", 3, function() { 280 | location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); 281 | Backbone.history.checkUrl(); 282 | equal(router.first, 'one/two/three'); 283 | equal(router.part, 'part'); 284 | equal(router.rest, 'four/five/six/seven'); 285 | }); 286 | 287 | test("routes (query)", 5, function() { 288 | location.replace('http://example.com#mandel?a=b&c=d'); 289 | Backbone.history.checkUrl(); 290 | equal(router.entity, 'mandel'); 291 | equal(router.queryArgs, 'a=b&c=d'); 292 | equal(lastRoute, 'query'); 293 | equal(lastArgs[0], 'mandel'); 294 | equal(lastArgs[1], 'a=b&c=d'); 295 | }); 296 | 297 | test("routes (anything)", 1, function() { 298 | location.replace('http://example.com#doesnt-match-a-route'); 299 | Backbone.history.checkUrl(); 300 | equal(router.anything, 'doesnt-match-a-route'); 301 | }); 302 | 303 | test("routes (function)", 3, function() { 304 | router.on('route', function(name) { 305 | ok(name === ''); 306 | }); 307 | equal(ExternalObject.value, 'unset'); 308 | location.replace('http://example.com#function/set'); 309 | Backbone.history.checkUrl(); 310 | equal(ExternalObject.value, 'set'); 311 | }); 312 | 313 | test("Decode named parameters, not splats.", 2, function() { 314 | location.replace('http://example.com#decode/a%2Fb/c%2Fd/e'); 315 | Backbone.history.checkUrl(); 316 | strictEqual(router.named, 'a/b'); 317 | strictEqual(router.path, 'c/d/e'); 318 | }); 319 | 320 | test("fires event when router doesn't have callback on it", 1, function() { 321 | router.on("route:noCallback", function(){ ok(true); }); 322 | location.replace('http://example.com#noCallback'); 323 | Backbone.history.checkUrl(); 324 | }); 325 | 326 | test("#933, #908 - leading slash", 2, function() { 327 | location.replace('http://example.com/root/foo'); 328 | 329 | Backbone.history.stop(); 330 | Backbone.history = _.extend(new Backbone.History, {location: location}); 331 | Backbone.history.start({root: '/root', hashChange: false, silent: true}); 332 | strictEqual(Backbone.history.getFragment(), 'foo'); 333 | 334 | Backbone.history.stop(); 335 | Backbone.history = _.extend(new Backbone.History, {location: location}); 336 | Backbone.history.start({root: '/root/', hashChange: false, silent: true}); 337 | strictEqual(Backbone.history.getFragment(), 'foo'); 338 | }); 339 | 340 | test("#1003 - History is started before navigate is called", 1, function() { 341 | Backbone.history.stop(); 342 | Backbone.history.navigate = function(){ ok(Backbone.History.started); }; 343 | Backbone.history.start(); 344 | // If this is not an old IE navigate will not be called. 345 | if (!Backbone.history.iframe) ok(true); 346 | }); 347 | 348 | test("#967 - Route callback gets passed encoded values.", 3, function() { 349 | var route = 'has%2Fslash/complex-has%23hash/has%20space'; 350 | Backbone.history.navigate(route, {trigger: true}); 351 | strictEqual(router.first, 'has/slash'); 352 | strictEqual(router.part, 'has#hash'); 353 | strictEqual(router.rest, 'has space'); 354 | }); 355 | 356 | test("correctly handles URLs with % (#868)", 3, function() { 357 | location.replace('http://example.com#search/fat%3A1.5%25'); 358 | Backbone.history.checkUrl(); 359 | location.replace('http://example.com#search/fat'); 360 | Backbone.history.checkUrl(); 361 | equal(router.query, 'fat'); 362 | equal(router.page, void 0); 363 | equal(lastRoute, 'search'); 364 | }); 365 | 366 | test("#2666 - Hashes with UTF8 in them.", 2, function() { 367 | Backbone.history.navigate('charñ', {trigger: true}); 368 | equal(router.charType, 'UTF'); 369 | Backbone.history.navigate('char%C3%B1', {trigger: true}); 370 | equal(router.charType, 'escaped'); 371 | }); 372 | 373 | test("#1185 - Use pathname when hashChange is not wanted.", 1, function() { 374 | Backbone.history.stop(); 375 | location.replace('http://example.com/path/name#hash'); 376 | Backbone.history = _.extend(new Backbone.History, {location: location}); 377 | Backbone.history.start({hashChange: false}); 378 | var fragment = Backbone.history.getFragment(); 379 | strictEqual(fragment, location.pathname.replace(/^\//, '')); 380 | }); 381 | 382 | test("#1206 - Strip leading slash before location.assign.", 1, function() { 383 | Backbone.history.stop(); 384 | location.replace('http://example.com/root/'); 385 | Backbone.history = _.extend(new Backbone.History, {location: location}); 386 | Backbone.history.start({hashChange: false, root: '/root/'}); 387 | location.assign = function(pathname) { 388 | strictEqual(pathname, '/root/fragment'); 389 | }; 390 | Backbone.history.navigate('/fragment'); 391 | }); 392 | 393 | test("#1387 - Root fragment without trailing slash.", 1, function() { 394 | Backbone.history.stop(); 395 | location.replace('http://example.com/root'); 396 | Backbone.history = _.extend(new Backbone.History, {location: location}); 397 | Backbone.history.start({hashChange: false, root: '/root/', silent: true}); 398 | strictEqual(Backbone.history.getFragment(), ''); 399 | }); 400 | 401 | test("#1366 - History does not prepend root to fragment.", 2, function() { 402 | Backbone.history.stop(); 403 | location.replace('http://example.com/root/'); 404 | Backbone.history = _.extend(new Backbone.History, { 405 | location: location, 406 | history: { 407 | pushState: function(state, title, url) { 408 | strictEqual(url, '/root/x'); 409 | } 410 | } 411 | }); 412 | Backbone.history.start({ 413 | root: '/root/', 414 | pushState: true, 415 | hashChange: false 416 | }); 417 | Backbone.history.navigate('x'); 418 | strictEqual(Backbone.history.fragment, 'x'); 419 | }); 420 | 421 | test("Normalize root.", 1, function() { 422 | Backbone.history.stop(); 423 | location.replace('http://example.com/root'); 424 | Backbone.history = _.extend(new Backbone.History, { 425 | location: location, 426 | history: { 427 | pushState: function(state, title, url) { 428 | strictEqual(url, '/root/fragment'); 429 | } 430 | } 431 | }); 432 | Backbone.history.start({ 433 | pushState: true, 434 | root: '/root', 435 | hashChange: false 436 | }); 437 | Backbone.history.navigate('fragment'); 438 | }); 439 | 440 | test("Normalize root.", 1, function() { 441 | Backbone.history.stop(); 442 | location.replace('http://example.com/root#fragment'); 443 | Backbone.history = _.extend(new Backbone.History, { 444 | location: location, 445 | history: { 446 | pushState: function(state, title, url) {}, 447 | replaceState: function(state, title, url) { 448 | strictEqual(url, '/root/fragment'); 449 | } 450 | } 451 | }); 452 | Backbone.history.start({ 453 | pushState: true, 454 | root: '/root' 455 | }); 456 | }); 457 | 458 | test("Normalize root.", 1, function() { 459 | Backbone.history.stop(); 460 | location.replace('http://example.com/root'); 461 | Backbone.history = _.extend(new Backbone.History, {location: location}); 462 | Backbone.history.loadUrl = function() { ok(true); }; 463 | Backbone.history.start({ 464 | pushState: true, 465 | root: '/root' 466 | }); 467 | }); 468 | 469 | test("Normalize root - leading slash.", 1, function() { 470 | Backbone.history.stop(); 471 | location.replace('http://example.com/root'); 472 | Backbone.history = _.extend(new Backbone.History, { 473 | location: location, 474 | history: { 475 | pushState: function(){}, 476 | replaceState: function(){} 477 | } 478 | }); 479 | Backbone.history.start({root: 'root'}); 480 | strictEqual(Backbone.history.root, '/root/'); 481 | }); 482 | 483 | test("Transition from hashChange to pushState.", 1, function() { 484 | Backbone.history.stop(); 485 | location.replace('http://example.com/root#x/y'); 486 | Backbone.history = _.extend(new Backbone.History, { 487 | location: location, 488 | history: { 489 | pushState: function(){}, 490 | replaceState: function(state, title, url){ 491 | strictEqual(url, '/root/x/y'); 492 | } 493 | } 494 | }); 495 | Backbone.history.start({ 496 | root: 'root', 497 | pushState: true 498 | }); 499 | }); 500 | 501 | test("#1619: Router: Normalize empty root", 1, function() { 502 | Backbone.history.stop(); 503 | location.replace('http://example.com/'); 504 | Backbone.history = _.extend(new Backbone.History, { 505 | location: location, 506 | history: { 507 | pushState: function(){}, 508 | replaceState: function(){} 509 | } 510 | }); 511 | Backbone.history.start({root: ''}); 512 | strictEqual(Backbone.history.root, '/'); 513 | }); 514 | 515 | test("#1619: Router: nagivate with empty root", 1, function() { 516 | Backbone.history.stop(); 517 | location.replace('http://example.com/'); 518 | Backbone.history = _.extend(new Backbone.History, { 519 | location: location, 520 | history: { 521 | pushState: function(state, title, url) { 522 | strictEqual(url, '/fragment'); 523 | } 524 | } 525 | }); 526 | Backbone.history.start({ 527 | pushState: true, 528 | root: '', 529 | hashChange: false 530 | }); 531 | Backbone.history.navigate('fragment'); 532 | }); 533 | 534 | test("Transition from pushState to hashChange.", 1, function() { 535 | Backbone.history.stop(); 536 | location.replace('http://example.com/root/x/y?a=b'); 537 | location.replace = function(url) { 538 | strictEqual(url, '/root/?a=b#x/y'); 539 | }; 540 | Backbone.history = _.extend(new Backbone.History, { 541 | location: location, 542 | history: { 543 | pushState: null, 544 | replaceState: null 545 | } 546 | }); 547 | Backbone.history.start({ 548 | root: 'root', 549 | pushState: true 550 | }); 551 | }); 552 | 553 | test("#1695 - hashChange to pushState with search.", 1, function() { 554 | Backbone.history.stop(); 555 | location.replace('http://example.com/root?a=b#x/y'); 556 | Backbone.history = _.extend(new Backbone.History, { 557 | location: location, 558 | history: { 559 | pushState: function(){}, 560 | replaceState: function(state, title, url){ 561 | strictEqual(url, '/root/x/y?a=b'); 562 | } 563 | } 564 | }); 565 | Backbone.history.start({ 566 | root: 'root', 567 | pushState: true 568 | }); 569 | }); 570 | 571 | test("#1746 - Router allows empty route.", 1, function() { 572 | var Router = Backbone.Router.extend({ 573 | routes: {'': 'empty'}, 574 | empty: function(){}, 575 | route: function(route){ 576 | strictEqual(route, ''); 577 | } 578 | }); 579 | new Router; 580 | }); 581 | 582 | test("#1794 - Trailing space in fragments.", 1, function() { 583 | var history = new Backbone.History; 584 | strictEqual(history.getFragment('fragment '), 'fragment'); 585 | }); 586 | 587 | test("#1820 - Leading slash and trailing space.", 1, function() { 588 | var history = new Backbone.History; 589 | strictEqual(history.getFragment('/fragment '), 'fragment'); 590 | }); 591 | 592 | test("#1980 - Optional parameters.", 2, function() { 593 | location.replace('http://example.com#named/optional/y'); 594 | Backbone.history.checkUrl(); 595 | strictEqual(router.z, undefined); 596 | location.replace('http://example.com#named/optional/y123'); 597 | Backbone.history.checkUrl(); 598 | strictEqual(router.z, '123'); 599 | }); 600 | 601 | test("#2062 - Trigger 'route' event on router instance.", 2, function() { 602 | router.on('route', function(name, args) { 603 | strictEqual(name, 'routeEvent'); 604 | deepEqual(args, ['x']); 605 | }); 606 | location.replace('http://example.com#route-event/x'); 607 | Backbone.history.checkUrl(); 608 | }); 609 | 610 | test("#2255 - Extend routes by making routes a function.", 1, function() { 611 | var RouterBase = Backbone.Router.extend({ 612 | routes: function() { 613 | return { 614 | home: "root", 615 | index: "index.html" 616 | }; 617 | } 618 | }); 619 | 620 | var RouterExtended = RouterBase.extend({ 621 | routes: function() { 622 | var _super = RouterExtended.__super__.routes; 623 | return _.extend(_super(), 624 | { show: "show", 625 | search: "search" }); 626 | } 627 | }); 628 | 629 | var router = new RouterExtended(); 630 | deepEqual({home: "root", index: "index.html", show: "show", search: "search"}, router.routes); 631 | }); 632 | 633 | test("#2538 - hashChange to pushState only if both requested.", 0, function() { 634 | Backbone.history.stop(); 635 | location.replace('http://example.com/root?a=b#x/y'); 636 | Backbone.history = _.extend(new Backbone.History, { 637 | location: location, 638 | history: { 639 | pushState: function(){}, 640 | replaceState: function(){ ok(false); } 641 | } 642 | }); 643 | Backbone.history.start({ 644 | root: 'root', 645 | pushState: true, 646 | hashChange: false 647 | }); 648 | }); 649 | 650 | test('No hash fallback.', 0, function() { 651 | Backbone.history.stop(); 652 | Backbone.history = _.extend(new Backbone.History, { 653 | location: location, 654 | history: { 655 | pushState: function(){}, 656 | replaceState: function(){} 657 | } 658 | }); 659 | 660 | var Router = Backbone.Router.extend({ 661 | routes: { 662 | hash: function() { ok(false); } 663 | } 664 | }); 665 | var router = new Router; 666 | 667 | location.replace('http://example.com/'); 668 | Backbone.history.start({ 669 | pushState: true, 670 | hashChange: false 671 | }); 672 | location.replace('http://example.com/nomatch#hash'); 673 | Backbone.history.checkUrl(); 674 | }); 675 | 676 | test('#2656 - No trailing slash on root.', 1, function() { 677 | Backbone.history.stop(); 678 | Backbone.history = _.extend(new Backbone.History, { 679 | location: location, 680 | history: { 681 | pushState: function(state, title, url){ 682 | strictEqual(url, '/root'); 683 | } 684 | } 685 | }); 686 | location.replace('http://example.com/root/path'); 687 | Backbone.history.start({pushState: true, root: 'root'}); 688 | Backbone.history.navigate(''); 689 | }); 690 | 691 | test('#2656 - No trailing slash on root.', 1, function() { 692 | Backbone.history.stop(); 693 | Backbone.history = _.extend(new Backbone.History, { 694 | location: location, 695 | history: { 696 | pushState: function(state, title, url) { 697 | strictEqual(url, '/'); 698 | } 699 | } 700 | }); 701 | location.replace('http://example.com/path'); 702 | Backbone.history.start({pushState: true}); 703 | Backbone.history.navigate(''); 704 | }); 705 | 706 | test('#2765 - Fragment matching sans query/hash.', 2, function() { 707 | Backbone.history.stop(); 708 | Backbone.history = _.extend(new Backbone.History, { 709 | location: location, 710 | history: { 711 | pushState: function(state, title, url) { 712 | strictEqual(url, '/path?query#hash'); 713 | } 714 | } 715 | }); 716 | 717 | var Router = Backbone.Router.extend({ 718 | routes: { 719 | path: function() { ok(true); } 720 | } 721 | }); 722 | var router = new Router; 723 | 724 | location.replace('http://example.com/'); 725 | Backbone.history.start({pushState: true}); 726 | Backbone.history.navigate('path?query#hash', true); 727 | }); 728 | 729 | })(); 730 | -------------------------------------------------------------------------------- /test/backbone-1.1/test/sync.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var Library = Backbone.Collection.extend({ 4 | url : function() { return '/library'; } 5 | }); 6 | var library; 7 | 8 | var attrs = { 9 | title : "The Tempest", 10 | author : "Bill Shakespeare", 11 | length : 123 12 | }; 13 | 14 | module("Backbone.sync", { 15 | 16 | setup : function() { 17 | library = new Library; 18 | library.create(attrs, {wait: false}); 19 | }, 20 | 21 | teardown: function() { 22 | Backbone.emulateHTTP = false; 23 | } 24 | 25 | }); 26 | 27 | test("read", 4, function() { 28 | library.fetch(); 29 | equal(this.ajaxSettings.url, '/library'); 30 | equal(this.ajaxSettings.type, 'GET'); 31 | equal(this.ajaxSettings.dataType, 'json'); 32 | ok(_.isEmpty(this.ajaxSettings.data)); 33 | }); 34 | 35 | test("passing data", 3, function() { 36 | library.fetch({data: {a: 'a', one: 1}}); 37 | equal(this.ajaxSettings.url, '/library'); 38 | equal(this.ajaxSettings.data.a, 'a'); 39 | equal(this.ajaxSettings.data.one, 1); 40 | }); 41 | 42 | test("create", 6, function() { 43 | equal(this.ajaxSettings.url, '/library'); 44 | equal(this.ajaxSettings.type, 'POST'); 45 | equal(this.ajaxSettings.dataType, 'json'); 46 | var data = JSON.parse(this.ajaxSettings.data); 47 | equal(data.title, 'The Tempest'); 48 | equal(data.author, 'Bill Shakespeare'); 49 | equal(data.length, 123); 50 | }); 51 | 52 | test("update", 7, function() { 53 | library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); 54 | equal(this.ajaxSettings.url, '/library/1-the-tempest'); 55 | equal(this.ajaxSettings.type, 'PUT'); 56 | equal(this.ajaxSettings.dataType, 'json'); 57 | var data = JSON.parse(this.ajaxSettings.data); 58 | equal(data.id, '1-the-tempest'); 59 | equal(data.title, 'The Tempest'); 60 | equal(data.author, 'William Shakespeare'); 61 | equal(data.length, 123); 62 | }); 63 | 64 | test("update with emulateHTTP and emulateJSON", 7, function() { 65 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { 66 | emulateHTTP: true, 67 | emulateJSON: true 68 | }); 69 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 70 | equal(this.ajaxSettings.type, 'POST'); 71 | equal(this.ajaxSettings.dataType, 'json'); 72 | equal(this.ajaxSettings.data._method, 'PUT'); 73 | var data = JSON.parse(this.ajaxSettings.data.model); 74 | equal(data.id, '2-the-tempest'); 75 | equal(data.author, 'Tim Shakespeare'); 76 | equal(data.length, 123); 77 | }); 78 | 79 | test("update with just emulateHTTP", 6, function() { 80 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { 81 | emulateHTTP: true 82 | }); 83 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 84 | equal(this.ajaxSettings.type, 'POST'); 85 | equal(this.ajaxSettings.contentType, 'application/json'); 86 | var data = JSON.parse(this.ajaxSettings.data); 87 | equal(data.id, '2-the-tempest'); 88 | equal(data.author, 'Tim Shakespeare'); 89 | equal(data.length, 123); 90 | }); 91 | 92 | test("update with just emulateJSON", 6, function() { 93 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { 94 | emulateJSON: true 95 | }); 96 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 97 | equal(this.ajaxSettings.type, 'PUT'); 98 | equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded'); 99 | var data = JSON.parse(this.ajaxSettings.data.model); 100 | equal(data.id, '2-the-tempest'); 101 | equal(data.author, 'Tim Shakespeare'); 102 | equal(data.length, 123); 103 | }); 104 | 105 | test("read model", 3, function() { 106 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); 107 | library.first().fetch(); 108 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 109 | equal(this.ajaxSettings.type, 'GET'); 110 | ok(_.isEmpty(this.ajaxSettings.data)); 111 | }); 112 | 113 | test("destroy", 3, function() { 114 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); 115 | library.first().destroy({wait: true}); 116 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 117 | equal(this.ajaxSettings.type, 'DELETE'); 118 | equal(this.ajaxSettings.data, null); 119 | }); 120 | 121 | test("destroy with emulateHTTP", 3, function() { 122 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); 123 | library.first().destroy({ 124 | emulateHTTP: true, 125 | emulateJSON: true 126 | }); 127 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 128 | equal(this.ajaxSettings.type, 'POST'); 129 | equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}'); 130 | }); 131 | 132 | test("urlError", 2, function() { 133 | var model = new Backbone.Model(); 134 | raises(function() { 135 | model.fetch(); 136 | }); 137 | model.fetch({url: '/one/two'}); 138 | equal(this.ajaxSettings.url, '/one/two'); 139 | }); 140 | 141 | test("#1052 - `options` is optional.", 0, function() { 142 | var model = new Backbone.Model(); 143 | model.url = '/test'; 144 | Backbone.sync('create', model); 145 | }); 146 | 147 | test("Backbone.ajax", 1, function() { 148 | Backbone.ajax = function(settings){ 149 | strictEqual(settings.url, '/test'); 150 | }; 151 | var model = new Backbone.Model(); 152 | model.url = '/test'; 153 | Backbone.sync('create', model); 154 | }); 155 | 156 | test("Call provided error callback on error.", 1, function() { 157 | var model = new Backbone.Model; 158 | model.url = '/test'; 159 | Backbone.sync('read', model, { 160 | error: function() { ok(true); } 161 | }); 162 | this.ajaxSettings.error(); 163 | }); 164 | 165 | test('Use Backbone.emulateHTTP as default.', 2, function() { 166 | var model = new Backbone.Model; 167 | model.url = '/test'; 168 | 169 | Backbone.emulateHTTP = true; 170 | model.sync('create', model); 171 | strictEqual(this.ajaxSettings.emulateHTTP, true); 172 | 173 | Backbone.emulateHTTP = false; 174 | model.sync('create', model); 175 | strictEqual(this.ajaxSettings.emulateHTTP, false); 176 | }); 177 | 178 | test('Use Backbone.emulateJSON as default.', 2, function() { 179 | var model = new Backbone.Model; 180 | model.url = '/test'; 181 | 182 | Backbone.emulateJSON = true; 183 | model.sync('create', model); 184 | strictEqual(this.ajaxSettings.emulateJSON, true); 185 | 186 | Backbone.emulateJSON = false; 187 | model.sync('create', model); 188 | strictEqual(this.ajaxSettings.emulateJSON, false); 189 | }); 190 | 191 | test("#1756 - Call user provided beforeSend function.", 4, function() { 192 | Backbone.emulateHTTP = true; 193 | var model = new Backbone.Model; 194 | model.url = '/test'; 195 | var xhr = { 196 | setRequestHeader: function(header, value) { 197 | strictEqual(header, 'X-HTTP-Method-Override'); 198 | strictEqual(value, 'DELETE'); 199 | } 200 | }; 201 | model.sync('delete', model, { 202 | beforeSend: function(_xhr) { 203 | ok(_xhr === xhr); 204 | return false; 205 | } 206 | }); 207 | strictEqual(this.ajaxSettings.beforeSend(xhr), false); 208 | }); 209 | 210 | })(); 211 | -------------------------------------------------------------------------------- /test/backbone-1.1/test/view.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var view; 4 | 5 | module("Backbone.View", { 6 | 7 | setup: function() { 8 | view = new Backbone.View({ 9 | id : 'test-view', 10 | className : 'test-view', 11 | other : 'non-special-option' 12 | }); 13 | } 14 | 15 | }); 16 | 17 | test("constructor", 3, function() { 18 | equal(view.el.id, 'test-view'); 19 | equal(view.el.className, 'test-view'); 20 | equal(view.el.other, void 0); 21 | }); 22 | 23 | test("jQuery", 1, function() { 24 | var view = new Backbone.View; 25 | view.setElement('

test

'); 26 | strictEqual(view.$('a b').html(), 'test'); 27 | }); 28 | 29 | test("initialize", 1, function() { 30 | var View = Backbone.View.extend({ 31 | initialize: function() { 32 | this.one = 1; 33 | } 34 | }); 35 | 36 | strictEqual(new View().one, 1); 37 | }); 38 | 39 | test("delegateEvents", 6, function() { 40 | var counter1 = 0, counter2 = 0; 41 | 42 | var view = new Backbone.View({el: '

'}); 43 | view.increment = function(){ counter1++; }; 44 | view.$el.on('click', function(){ counter2++; }); 45 | 46 | var events = {'click #test': 'increment'}; 47 | 48 | view.delegateEvents(events); 49 | view.$('#test').trigger('click'); 50 | equal(counter1, 1); 51 | equal(counter2, 1); 52 | 53 | view.$('#test').trigger('click'); 54 | equal(counter1, 2); 55 | equal(counter2, 2); 56 | 57 | view.delegateEvents(events); 58 | view.$('#test').trigger('click'); 59 | equal(counter1, 3); 60 | equal(counter2, 3); 61 | }); 62 | 63 | test("delegateEvents allows functions for callbacks", 3, function() { 64 | var view = new Backbone.View({el: '

'}); 65 | view.counter = 0; 66 | 67 | var events = { 68 | click: function() { 69 | this.counter++; 70 | } 71 | }; 72 | 73 | view.delegateEvents(events); 74 | view.$el.trigger('click'); 75 | equal(view.counter, 1); 76 | 77 | view.$el.trigger('click'); 78 | equal(view.counter, 2); 79 | 80 | view.delegateEvents(events); 81 | view.$el.trigger('click'); 82 | equal(view.counter, 3); 83 | }); 84 | 85 | 86 | test("delegateEvents ignore undefined methods", 0, function() { 87 | var view = new Backbone.View({el: '

'}); 88 | view.delegateEvents({'click': 'undefinedMethod'}); 89 | view.$el.trigger('click'); 90 | }); 91 | 92 | test("undelegateEvents", 6, function() { 93 | var counter1 = 0, counter2 = 0; 94 | 95 | var view = new Backbone.View({el: '

'}); 96 | view.increment = function(){ counter1++; }; 97 | view.$el.on('click', function(){ counter2++; }); 98 | 99 | var events = {'click #test': 'increment'}; 100 | 101 | view.delegateEvents(events); 102 | view.$('#test').trigger('click'); 103 | equal(counter1, 1); 104 | equal(counter2, 1); 105 | 106 | view.undelegateEvents(); 107 | view.$('#test').trigger('click'); 108 | equal(counter1, 1); 109 | equal(counter2, 2); 110 | 111 | view.delegateEvents(events); 112 | view.$('#test').trigger('click'); 113 | equal(counter1, 2); 114 | equal(counter2, 3); 115 | }); 116 | 117 | test("_ensureElement with DOM node el", 1, function() { 118 | var View = Backbone.View.extend({ 119 | el: document.body 120 | }); 121 | 122 | equal(new View().el, document.body); 123 | }); 124 | 125 | test("_ensureElement with string el", 3, function() { 126 | var View = Backbone.View.extend({ 127 | el: "body" 128 | }); 129 | strictEqual(new View().el, document.body); 130 | 131 | View = Backbone.View.extend({ 132 | el: "#testElement > h1" 133 | }); 134 | strictEqual(new View().el, $("#testElement > h1").get(0)); 135 | 136 | View = Backbone.View.extend({ 137 | el: "#nonexistent" 138 | }); 139 | ok(!new View().el); 140 | }); 141 | 142 | test("with className and id functions", 2, function() { 143 | var View = Backbone.View.extend({ 144 | className: function() { 145 | return 'className'; 146 | }, 147 | id: function() { 148 | return 'id'; 149 | } 150 | }); 151 | 152 | strictEqual(new View().el.className, 'className'); 153 | strictEqual(new View().el.id, 'id'); 154 | }); 155 | 156 | test("with attributes", 2, function() { 157 | var View = Backbone.View.extend({ 158 | attributes: { 159 | id: 'id', 160 | 'class': 'class' 161 | } 162 | }); 163 | 164 | strictEqual(new View().el.className, 'class'); 165 | strictEqual(new View().el.id, 'id'); 166 | }); 167 | 168 | test("with attributes as a function", 1, function() { 169 | var View = Backbone.View.extend({ 170 | attributes: function() { 171 | return {'class': 'dynamic'}; 172 | } 173 | }); 174 | 175 | strictEqual(new View().el.className, 'dynamic'); 176 | }); 177 | 178 | test("multiple views per element", 3, function() { 179 | var count = 0; 180 | var $el = $('

'); 181 | 182 | var View = Backbone.View.extend({ 183 | el: $el, 184 | events: { 185 | click: function() { 186 | count++; 187 | } 188 | } 189 | }); 190 | 191 | var view1 = new View; 192 | $el.trigger("click"); 193 | equal(1, count); 194 | 195 | var view2 = new View; 196 | $el.trigger("click"); 197 | equal(3, count); 198 | 199 | view1.delegateEvents(); 200 | $el.trigger("click"); 201 | equal(5, count); 202 | }); 203 | 204 | test("custom events, with namespaces", 2, function() { 205 | var count = 0; 206 | 207 | var View = Backbone.View.extend({ 208 | el: $('body'), 209 | events: function() { 210 | return {"fake$event.namespaced": "run"}; 211 | }, 212 | run: function() { 213 | count++; 214 | } 215 | }); 216 | 217 | var view = new View; 218 | $('body').trigger('fake$event').trigger('fake$event'); 219 | equal(count, 2); 220 | 221 | $('body').unbind('.namespaced'); 222 | $('body').trigger('fake$event'); 223 | equal(count, 2); 224 | }); 225 | 226 | test("#1048 - setElement uses provided object.", 2, function() { 227 | var $el = $('body'); 228 | 229 | var view = new Backbone.View({el: $el}); 230 | ok(view.$el === $el); 231 | 232 | view.setElement($el = $($el)); 233 | ok(view.$el === $el); 234 | }); 235 | 236 | test("#986 - Undelegate before changing element.", 1, function() { 237 | var button1 = $(''); 238 | var button2 = $(''); 239 | 240 | var View = Backbone.View.extend({ 241 | events: { 242 | click: function(e) { 243 | ok(view.el === e.target); 244 | } 245 | } 246 | }); 247 | 248 | var view = new View({el: button1}); 249 | view.setElement(button2); 250 | 251 | button1.trigger('click'); 252 | button2.trigger('click'); 253 | }); 254 | 255 | test("#1172 - Clone attributes object", 2, function() { 256 | var View = Backbone.View.extend({ 257 | attributes: {foo: 'bar'} 258 | }); 259 | 260 | var view1 = new View({id: 'foo'}); 261 | strictEqual(view1.el.id, 'foo'); 262 | 263 | var view2 = new View(); 264 | ok(!view2.el.id); 265 | }); 266 | 267 | test("#1228 - tagName can be provided as a function", 1, function() { 268 | var View = Backbone.View.extend({ 269 | tagName: function() { 270 | return 'p'; 271 | } 272 | }); 273 | 274 | ok(new View().$el.is('p')); 275 | }); 276 | 277 | test("views stopListening", 0, function() { 278 | var View = Backbone.View.extend({ 279 | initialize: function() { 280 | this.listenTo(this.model, 'all x', function(){ ok(false); }, this); 281 | this.listenTo(this.collection, 'all x', function(){ ok(false); }, this); 282 | } 283 | }); 284 | 285 | var view = new View({ 286 | model: new Backbone.Model, 287 | collection: new Backbone.Collection 288 | }); 289 | 290 | view.stopListening(); 291 | view.model.trigger('x'); 292 | view.collection.trigger('x'); 293 | }); 294 | 295 | test("Provide function for el.", 2, function() { 296 | var View = Backbone.View.extend({ 297 | el: function() { 298 | return "

"; 299 | } 300 | }); 301 | 302 | var view = new View; 303 | ok(view.$el.is('p')); 304 | ok(view.$el.has('a')); 305 | }); 306 | 307 | test("events passed in options", 2, function() { 308 | var counter = 0; 309 | 310 | var View = Backbone.View.extend({ 311 | el: '

', 312 | increment: function() { 313 | counter++; 314 | } 315 | }); 316 | 317 | var view = new View({events:{'click #test':'increment'}}); 318 | var view2 = new View({events:function(){ 319 | return {'click #test':'increment'}; 320 | }}); 321 | 322 | view.$('#test').trigger('click'); 323 | view2.$('#test').trigger('click'); 324 | equal(counter, 2); 325 | 326 | view.$('#test').trigger('click'); 327 | view2.$('#test').trigger('click'); 328 | equal(counter, 4); 329 | }); 330 | 331 | })(); 332 | -------------------------------------------------------------------------------- /test/backbone.query.params-test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /*global QUnit */ 3 | 4 | var router = null; 5 | var location = null; 6 | var lastRoute = null; 7 | var lastArgs = []; 8 | 9 | function onRoute(router, route, args) { 10 | lastRoute = route; 11 | lastArgs = args; 12 | } 13 | 14 | var Location = function(href) { 15 | this.replace(href); 16 | }; 17 | 18 | _.extend(Location.prototype, { 19 | 20 | replace: function(href) { 21 | _.extend(this, _.pick($('', {href: href})[0], 22 | 'href', 23 | 'hash', 24 | 'host', 25 | 'search', 26 | 'fragment', 27 | 'pathname', 28 | 'protocol' 29 | )); 30 | // In IE, anchor.pathname does not contain a leading slash though 31 | // window.location.pathname does. 32 | if (!/^\//.test(this.pathname)) { 33 | this.pathname = '/' + this.pathname; 34 | } 35 | }, 36 | 37 | toString: function() { 38 | return this.href; 39 | } 40 | 41 | }); 42 | 43 | var History = function(location) { 44 | this.location = location; 45 | }; 46 | History.prototype.pushState = function(state, title, href) { 47 | this.location.replace(href); 48 | }; 49 | History.prototype.replaceState = function(state, title, href) { 50 | this.location.replace(href); 51 | }; 52 | 53 | var Router = Backbone.Router.extend({ 54 | 55 | count: 0, 56 | 57 | routes: { 58 | "noCallback": "noCallback", 59 | "counter": "counter", 60 | "search/:query": "search", 61 | "search/:query/p:page": "search", 62 | "contacts": "contacts", 63 | "contacts/new": "newContact", 64 | "contacts/:id": "loadContact", 65 | "optional(/:item)": "optionalItem", 66 | "named/optional/(y:z)": "namedOptional", 67 | "splat/*args/end": "splat", 68 | "*first/complex-:part/*rest": "complex", 69 | ":entity?*args": "query", 70 | "*anything": "anything" 71 | }, 72 | 73 | initialize : function(options) { 74 | this.testing = options.testing; 75 | this.route('implicit', 'implicit'); 76 | }, 77 | 78 | counter: function() { 79 | this.count++; 80 | }, 81 | 82 | implicit: function() { 83 | this.count++; 84 | }, 85 | 86 | search : function(query, page, queryParams) { 87 | this.query = query; 88 | this.page = page; 89 | this.queryParams = queryParams; 90 | }, 91 | 92 | contacts: function(){ 93 | this.contact = 'index'; 94 | }, 95 | 96 | newContact: function(){ 97 | this.contact = 'new'; 98 | }, 99 | 100 | loadContact: function(){ 101 | this.contact = 'load'; 102 | }, 103 | 104 | optionalItem: function(arg){ 105 | this.arg = arg != void 0 ? arg : null; 106 | }, 107 | 108 | splat: function(args) { 109 | this.args = args; 110 | }, 111 | 112 | complex: function(first, part, rest) { 113 | this.first = first; 114 | this.part = part; 115 | this.rest = rest; 116 | }, 117 | 118 | query: function(entity, args) { 119 | this.entity = entity; 120 | this.queryArgs = args; 121 | }, 122 | 123 | anything: function(whatever) { 124 | this.anything = whatever; 125 | }, 126 | 127 | namedOptional: function(z) { 128 | this.z = z; 129 | } 130 | 131 | }); 132 | 133 | 134 | function queryParams(wantPushState, hasPushState) { 135 | module("Backbone.QueryParams" + (wantPushState ? " - wantPushState" : "") + (hasPushState ? " - hasPushState" : ""), { 136 | 137 | setup: function() { 138 | location = new Location('http://example.com'); 139 | Backbone.history = _.extend(new Backbone.History(), {location: location, history: hasPushState ? new History(location) : {}}); 140 | router = new Router({testing: 101}); 141 | Backbone.history.interval = 9; 142 | Backbone.history.start({pushState: wantPushState}); 143 | lastRoute = null; 144 | lastArgs = []; 145 | Backbone.history.on('route', onRoute); 146 | }, 147 | 148 | teardown: function() { 149 | Backbone.history.stop(); 150 | Backbone.history.off('route', onRoute); 151 | } 152 | 153 | }); 154 | 155 | test("Route callback gets passed DECODED values.", 3, function() { 156 | var route = 'has%2Fslash/complex-has%23hash/has+space'; 157 | Backbone.history.navigate(route, {trigger: true}); 158 | strictEqual(router.first, 'has/slash'); 159 | strictEqual(router.part, 'has#hash'); 160 | strictEqual(router.rest, 'has space'); 161 | }); 162 | 163 | test("routes (two part - encoded reserved char)", 2, function() { 164 | var route = 'search/nyc/pa%2Fb'; 165 | Backbone.history.navigate(route, {trigger: true}); 166 | Backbone.history.checkUrl(); 167 | equal(router.query, 'nyc'); 168 | equal(router.page, 'a/b'); 169 | }); 170 | 171 | test("routes (two part - query params)", 3, function() { 172 | var route = 'search/nyc/p10?a=b'; 173 | Backbone.history.navigate(route, {trigger: true}); 174 | Backbone.history.checkUrl(); 175 | equal(router.query, 'nyc'); 176 | equal(router.page, '10'); 177 | equal(router.queryParams.a, 'b'); 178 | }); 179 | 180 | test("routes (two part - query params - hash and list - location)", 24, function() { 181 | var route = 'search/nyc/p10?a=b&a2=x&a2=y&a3=x&a3=y&a3=z&&a4=x=y=z&b.c=d&b.d=e&b.e.f=g&array1=|a&array2=a|b&array3=|c|d&array4=|e%7C'; 182 | Backbone.history.navigate(route, {trigger: true}); 183 | Backbone.history.checkUrl(); 184 | equal(router.query, 'nyc'); 185 | equal(router.page, '10'); 186 | equal(router.queryParams.a, 'b'); 187 | equal(router.queryParams.a2.length, 2); 188 | equal(router.queryParams.a2[0], 'x'); 189 | equal(router.queryParams.a2[1], 'y'); 190 | equal(router.queryParams.a3.length, 3); 191 | equal(router.queryParams.a3[0], 'x'); 192 | equal(router.queryParams.a3[1], 'y'); 193 | equal(router.queryParams.a3[2], 'z'); 194 | equal(router.queryParams.a4, 'x=y=z'); 195 | equal(router.queryParams.b.c, 'd'); 196 | equal(router.queryParams.b.d, 'e'); 197 | equal(router.queryParams.b.e.f, 'g'); 198 | equal(router.queryParams.array1.length, 1); 199 | equal(router.queryParams.array1[0], 'a'); 200 | equal(router.queryParams.array2.length, 2); 201 | equal(router.queryParams.array2[0], 'a'); 202 | equal(router.queryParams.array2[1], 'b'); 203 | equal(router.queryParams.array3.length, 2); 204 | equal(router.queryParams.array3[0], 'c'); 205 | equal(router.queryParams.array3[1], 'd'); 206 | equal(router.queryParams.array4.length, 1); 207 | equal(router.queryParams.array4[0], 'e|'); 208 | }); 209 | 210 | test("routes (two part - query params)", 3, function() { 211 | var route = 'search/nyc/p10?a=b'; 212 | Backbone.history.navigate(route, {trigger: true}); 213 | Backbone.history.checkUrl(); 214 | equal(router.query, 'nyc'); 215 | equal(router.page, '10'); 216 | equal(router.queryParams.a, 'b'); 217 | }); 218 | 219 | test("routes (two part - query params - hash and list - navigate)", 21, function() { 220 | var route = router.toFragment('search/nyc/p10', { 221 | a:'l', b:{c: 'n', d:'m', e:{f: 'o'}}, g:'', array1:['p'], array2:['q', 'r'], array3:['s','t','|'], array4:[0, 5, 6, 8, 9] 222 | }); 223 | Backbone.history.navigate(route, {trigger: true}); 224 | Backbone.history.checkUrl(); 225 | equal(router.query, 'nyc'); 226 | equal(router.page, '10'); 227 | equal(router.queryParams.a, 'l'); 228 | equal(router.queryParams.b.c, 'n'); 229 | equal(router.queryParams.b.d, 'm'); 230 | equal(router.queryParams.b.e.f, 'o'); 231 | equal(router.queryParams.g, ''); 232 | equal(router.queryParams.array1.length, 1); 233 | equal(router.queryParams.array1[0], 'p'); 234 | equal(router.queryParams.array2.length, 2); 235 | equal(router.queryParams.array2[0], 'q'); 236 | equal(router.queryParams.array2[1], 'r'); 237 | equal(router.queryParams.array3.length, 3); 238 | equal(router.queryParams.array3[0], 's'); 239 | equal(router.queryParams.array3[1], 't'); 240 | equal(router.queryParams.array3[2], '|'); 241 | equal(router.queryParams.array4[0], 0); 242 | equal(router.queryParams.array4[1], 5); 243 | equal(router.queryParams.array4[2], 6); 244 | equal(router.queryParams.array4[3], 8); 245 | equal(router.queryParams.array4[4], 9); 246 | }); 247 | 248 | test("routes (decoding with 2 repeated values)", 4, function() { 249 | var route = 'search/nyc/p10?f.foo.bar=foo%20%2B%20bar&f.foo.bar=hello%20qux'; 250 | Backbone.history.navigate(route, {trigger: true}); 251 | Backbone.history.checkUrl(); 252 | equal(router.query, 'nyc'); 253 | equal(router.page, '10'); 254 | equal(router.queryParams.f.foo.bar[0], 'foo + bar'); 255 | equal(router.queryParams.f.foo.bar[1], 'hello qux'); 256 | }); 257 | 258 | test("routes (decoding with 3 repeated values)", 5, function() { 259 | var route = 'search/nyc/p10?f.foo.bar=foo%20%2B%20bar&f.foo.bar=hello%20qux&f.foo.bar=baz%20baz'; 260 | Backbone.history.navigate(route, {trigger: true}); 261 | Backbone.history.checkUrl(); 262 | equal(router.query, 'nyc'); 263 | equal(router.page, '10'); 264 | equal(router.queryParams.f.foo.bar[0], 'foo + bar'); 265 | equal(router.queryParams.f.foo.bar[1], 'hello qux'); 266 | equal(router.queryParams.f.foo.bar[2], 'baz baz'); 267 | }); 268 | 269 | test("routes (with array[] structure)", 9, function() { 270 | var route = 'search/nyc/p10?a=b&b[]=x&b[]=y&b[]=z&c=y&d[]=z'; 271 | Backbone.history.navigate(route, {trigger: true}); 272 | Backbone.history.checkUrl(); 273 | equal(router.query, 'nyc'); 274 | equal(router.page, '10'); 275 | equal(router.queryParams.a, 'b'); 276 | equal(router.queryParams.b.length, 3); 277 | equal(router.queryParams.b[0], 'x'); 278 | equal(router.queryParams.b[1], 'y'); 279 | equal(router.queryParams.b[2], 'z'); 280 | equal(router.queryParams.c, 'y'); 281 | equal(router.queryParams.d, 'z'); 282 | }); 283 | 284 | test("routes (optional)", 2, function() { 285 | Backbone.history.navigate('optional', {trigger: true}); 286 | Backbone.history.checkUrl(); 287 | ok(!router.arg); 288 | 289 | Backbone.history.navigate('optional/42', {trigger: true}); 290 | Backbone.history.checkUrl(); 291 | equal(router.arg, 42); 292 | }); 293 | 294 | test("routes (optional) with named parameters", 2, function() { 295 | Backbone.Router.namedParameters = true; 296 | Backbone.history.navigate('optional', {trigger: true}); 297 | Backbone.history.checkUrl(); 298 | equal(typeof router.arg, 'object'); 299 | 300 | Backbone.history.navigate('optional/42', {trigger: true}); 301 | Backbone.history.checkUrl(); 302 | equal(router.arg.item, 42); 303 | Backbone.Router.namedParameters = false; 304 | }); 305 | 306 | test("named parameters (defined statically)", 3, function() { 307 | Backbone.Router.namedParameters = true; 308 | var route = 'search/nyc/p10?a=b'; 309 | Backbone.history.navigate(route, {trigger: true}); 310 | Backbone.history.checkUrl(); 311 | // only 1 param in this case populated with query parameters and route vars keyd with their associated name 312 | var data = router.query; 313 | equal(data.query, 'nyc'); 314 | equal(data.page, '10'); 315 | equal(data.a, 'b'); 316 | Backbone.Router.namedParameters = false; 317 | }); 318 | 319 | test("named parameters (defined on router instance)", 3, function() { 320 | var Router = Backbone.Router.extend({ 321 | namedParameters: true, 322 | routes: { 323 | "search2/:query/p:page": "search", 324 | }, 325 | search : function(query, page, queryParams) { 326 | this.query = query; 327 | this.page = page; 328 | this.queryParams = queryParams; 329 | } 330 | }); 331 | var router = new Router(); 332 | var route = 'search2/nyc/p10?a=b'; 333 | Backbone.history.navigate(route, {trigger: true}); 334 | Backbone.history.checkUrl(); 335 | // only 1 param in this case populated with query parameters and route vars keyd with their associated name 336 | var data = router.query; 337 | equal(data.query, 'nyc'); 338 | equal(data.page, '10'); 339 | equal(data.a, 'b'); 340 | }); 341 | 342 | test("getQueryParameters", 2, function() { 343 | new Backbone.Router(); 344 | deepEqual(Backbone.history.getQueryParameters('?cmpid'), {cmpid: ""}); 345 | deepEqual(Backbone.history.getQueryParameters('?cmpid='), {cmpid: ""}); 346 | }); 347 | 348 | test("url parameters decoded", 2, function(){ 349 | var route = 'search/nyc/p10?foo=bar%20%3A+baz&qux=foo', 350 | params = Backbone.history.getQueryParameters(route); 351 | equal(params.foo, 'bar : baz'); 352 | equal(params.qux, 'foo'); 353 | }); 354 | 355 | test("querystring space decoding", 2, function(){ 356 | var route = '?search=text+%3D%20%22foo+bar%22', 357 | expectedParams = {search: 'text = "foo bar"'}; 358 | 359 | deepEqual(Backbone.history.getQueryParameters(route), expectedParams); 360 | 361 | var Router = Backbone.Router.extend({ 362 | namedParameters: true, 363 | routes: { 364 | "": "search", 365 | }, 366 | search : function(queryParams) { 367 | this.queryParams = queryParams; 368 | } 369 | }); 370 | var router = new Router(); 371 | Backbone.history.navigate(route, {trigger: true}); 372 | deepEqual(router.queryParams, expectedParams); 373 | }); 374 | 375 | test("complex wildcard", function() { 376 | var regex = Backbone.Router.prototype._routeToRegExp('search/*dest'), 377 | match = regex.exec('search/foo?bar'); 378 | equal(match[1], 'foo'); 379 | equal(match[2], '?bar'); 380 | 381 | match = regex.exec('search/foo'); 382 | equal(match[1], 'foo'); 383 | ok(!match[2]); 384 | }); 385 | } 386 | 387 | // Sauce qunit reporter 388 | var log = []; 389 | 390 | QUnit.done(function (test_results) { 391 | var tests = []; 392 | for(var i = 0, len = log.length; i < len; i++) { 393 | var details = log[i]; 394 | tests.push({ 395 | name: details.name, 396 | result: details.result, 397 | expected: details.expected, 398 | actual: details.actual, 399 | source: details.source 400 | }); 401 | } 402 | test_results.tests = tests; 403 | 404 | window.global_test_results = test_results; 405 | }); 406 | QUnit.testStart(function(testDetails){ 407 | QUnit.log(function(details){ 408 | if (!details.result) { 409 | details.name = testDetails.name; 410 | log.push(details); 411 | } 412 | }); 413 | }); 414 | 415 | queryParams(true, true); 416 | queryParams(true, false); 417 | queryParams(false); 418 | }); 419 | -------------------------------------------------------------------------------- /test/backbone/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2012 Jeremy Ashkenas, DocumentCloud 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/backbone/test/environment.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var Environment = this.Environment = function(){}; 4 | 5 | _.extend(Environment.prototype, { 6 | 7 | ajax: Backbone.ajax, 8 | 9 | sync: Backbone.sync, 10 | 11 | emulateHTTP: Backbone.emulateHTTP, 12 | 13 | emulateJSON: Backbone.emulateJSON, 14 | 15 | setup: function() { 16 | var env = this; 17 | 18 | // Capture ajax settings for comparison. 19 | Backbone.ajax = function(settings) { 20 | env.ajaxSettings = settings; 21 | }; 22 | 23 | // Capture the arguments to Backbone.sync for comparison. 24 | Backbone.sync = function(method, model, options) { 25 | env.syncArgs = { 26 | method: method, 27 | model: model, 28 | options: options 29 | }; 30 | env.sync.apply(this, arguments); 31 | }; 32 | }, 33 | 34 | teardown: function() { 35 | this.syncArgs = null; 36 | this.ajaxSettings = null; 37 | Backbone.sync = this.sync; 38 | Backbone.ajax = this.ajax; 39 | Backbone.emulateHTTP = this.emulateHTTP; 40 | Backbone.emulateJSON = this.emulateJSON; 41 | } 42 | 43 | }); 44 | 45 | })(); 46 | -------------------------------------------------------------------------------- /test/backbone/test/events.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Backbone.Events"); 4 | 5 | test("on and trigger", 2, function() { 6 | var obj = { counter: 0 }; 7 | _.extend(obj,Backbone.Events); 8 | obj.on('event', function() { obj.counter += 1; }); 9 | obj.trigger('event'); 10 | equal(obj.counter,1,'counter should be incremented.'); 11 | obj.trigger('event'); 12 | obj.trigger('event'); 13 | obj.trigger('event'); 14 | obj.trigger('event'); 15 | equal(obj.counter, 5, 'counter should be incremented five times.'); 16 | }); 17 | 18 | test("binding and triggering multiple events", 4, function() { 19 | var obj = { counter: 0 }; 20 | _.extend(obj, Backbone.Events); 21 | 22 | obj.on('a b c', function() { obj.counter += 1; }); 23 | 24 | obj.trigger('a'); 25 | equal(obj.counter, 1); 26 | 27 | obj.trigger('a b'); 28 | equal(obj.counter, 3); 29 | 30 | obj.trigger('c'); 31 | equal(obj.counter, 4); 32 | 33 | obj.off('a c'); 34 | obj.trigger('a b c'); 35 | equal(obj.counter, 5); 36 | }); 37 | 38 | test("binding and triggering with event maps", function() { 39 | var obj = { counter: 0 }; 40 | _.extend(obj, Backbone.Events); 41 | 42 | var increment = function() { 43 | this.counter += 1; 44 | }; 45 | 46 | obj.on({ 47 | a: increment, 48 | b: increment, 49 | c: increment 50 | }, obj); 51 | 52 | obj.trigger('a'); 53 | equal(obj.counter, 1); 54 | 55 | obj.trigger('a b'); 56 | equal(obj.counter, 3); 57 | 58 | obj.trigger('c'); 59 | equal(obj.counter, 4); 60 | 61 | obj.off({ 62 | a: increment, 63 | c: increment 64 | }, obj); 65 | obj.trigger('a b c'); 66 | equal(obj.counter, 5); 67 | }); 68 | 69 | test("listenTo and stopListening", 1, function() { 70 | var a = _.extend({}, Backbone.Events); 71 | var b = _.extend({}, Backbone.Events); 72 | a.listenTo(b, 'all', function(){ ok(true); }); 73 | b.trigger('anything'); 74 | a.listenTo(b, 'all', function(){ ok(false); }); 75 | a.stopListening(); 76 | b.trigger('anything'); 77 | }); 78 | 79 | test("listenTo and stopListening with event maps", 4, function() { 80 | var a = _.extend({}, Backbone.Events); 81 | var b = _.extend({}, Backbone.Events); 82 | var cb = function(){ ok(true); }; 83 | a.listenTo(b, {event: cb}); 84 | b.trigger('event'); 85 | a.listenTo(b, {event2: cb}); 86 | b.on('event2', cb); 87 | a.stopListening(b, {event2: cb}); 88 | b.trigger('event event2'); 89 | a.stopListening(); 90 | b.trigger('event event2'); 91 | }); 92 | 93 | test("stopListening with omitted args", 2, function () { 94 | var a = _.extend({}, Backbone.Events); 95 | var b = _.extend({}, Backbone.Events); 96 | var cb = function () { ok(true); }; 97 | a.listenTo(b, 'event', cb); 98 | b.on('event', cb); 99 | a.listenTo(b, 'event2', cb); 100 | a.stopListening(null, {event: cb}); 101 | b.trigger('event event2'); 102 | b.off(); 103 | a.listenTo(b, 'event event2', cb); 104 | a.stopListening(null, 'event'); 105 | a.stopListening(); 106 | b.trigger('event2'); 107 | }); 108 | 109 | test("listenToOnce and stopListening", 1, function() { 110 | var a = _.extend({}, Backbone.Events); 111 | var b = _.extend({}, Backbone.Events); 112 | a.listenToOnce(b, 'all', function() { ok(true); }); 113 | b.trigger('anything'); 114 | b.trigger('anything'); 115 | a.listenToOnce(b, 'all', function() { ok(false); }); 116 | a.stopListening(); 117 | b.trigger('anything'); 118 | }); 119 | 120 | test("listenTo, listenToOnce and stopListening", 1, function() { 121 | var a = _.extend({}, Backbone.Events); 122 | var b = _.extend({}, Backbone.Events); 123 | a.listenToOnce(b, 'all', function() { ok(true); }); 124 | b.trigger('anything'); 125 | b.trigger('anything'); 126 | a.listenTo(b, 'all', function() { ok(false); }); 127 | a.stopListening(); 128 | b.trigger('anything'); 129 | }); 130 | 131 | test("listenTo and stopListening with event maps", 1, function() { 132 | var a = _.extend({}, Backbone.Events); 133 | var b = _.extend({}, Backbone.Events); 134 | a.listenTo(b, {change: function(){ ok(true); }}); 135 | b.trigger('change'); 136 | a.listenTo(b, {change: function(){ ok(false); }}); 137 | a.stopListening(); 138 | b.trigger('change'); 139 | }); 140 | 141 | test("listenTo yourself", 1, function(){ 142 | var e = _.extend({}, Backbone.Events); 143 | e.listenTo(e, "foo", function(){ ok(true); }); 144 | e.trigger("foo"); 145 | }); 146 | 147 | test("listenTo yourself cleans yourself up with stopListening", 1, function(){ 148 | var e = _.extend({}, Backbone.Events); 149 | e.listenTo(e, "foo", function(){ ok(true); }); 150 | e.trigger("foo"); 151 | e.stopListening(); 152 | e.trigger("foo"); 153 | }); 154 | 155 | test("listenTo with empty callback doesn't throw an error", 1, function(){ 156 | var e = _.extend({}, Backbone.Events); 157 | e.listenTo(e, "foo", null); 158 | e.trigger("foo"); 159 | ok(true); 160 | }); 161 | 162 | test("trigger all for each event", 3, function() { 163 | var a, b, obj = { counter: 0 }; 164 | _.extend(obj, Backbone.Events); 165 | obj.on('all', function(event) { 166 | obj.counter++; 167 | if (event == 'a') a = true; 168 | if (event == 'b') b = true; 169 | }) 170 | .trigger('a b'); 171 | ok(a); 172 | ok(b); 173 | equal(obj.counter, 2); 174 | }); 175 | 176 | test("on, then unbind all functions", 1, function() { 177 | var obj = { counter: 0 }; 178 | _.extend(obj,Backbone.Events); 179 | var callback = function() { obj.counter += 1; }; 180 | obj.on('event', callback); 181 | obj.trigger('event'); 182 | obj.off('event'); 183 | obj.trigger('event'); 184 | equal(obj.counter, 1, 'counter should have only been incremented once.'); 185 | }); 186 | 187 | test("bind two callbacks, unbind only one", 2, function() { 188 | var obj = { counterA: 0, counterB: 0 }; 189 | _.extend(obj,Backbone.Events); 190 | var callback = function() { obj.counterA += 1; }; 191 | obj.on('event', callback); 192 | obj.on('event', function() { obj.counterB += 1; }); 193 | obj.trigger('event'); 194 | obj.off('event', callback); 195 | obj.trigger('event'); 196 | equal(obj.counterA, 1, 'counterA should have only been incremented once.'); 197 | equal(obj.counterB, 2, 'counterB should have been incremented twice.'); 198 | }); 199 | 200 | test("unbind a callback in the midst of it firing", 1, function() { 201 | var obj = {counter: 0}; 202 | _.extend(obj, Backbone.Events); 203 | var callback = function() { 204 | obj.counter += 1; 205 | obj.off('event', callback); 206 | }; 207 | obj.on('event', callback); 208 | obj.trigger('event'); 209 | obj.trigger('event'); 210 | obj.trigger('event'); 211 | equal(obj.counter, 1, 'the callback should have been unbound.'); 212 | }); 213 | 214 | test("two binds that unbind themeselves", 2, function() { 215 | var obj = { counterA: 0, counterB: 0 }; 216 | _.extend(obj,Backbone.Events); 217 | var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); }; 218 | var incrB = function(){ obj.counterB += 1; obj.off('event', incrB); }; 219 | obj.on('event', incrA); 220 | obj.on('event', incrB); 221 | obj.trigger('event'); 222 | obj.trigger('event'); 223 | obj.trigger('event'); 224 | equal(obj.counterA, 1, 'counterA should have only been incremented once.'); 225 | equal(obj.counterB, 1, 'counterB should have only been incremented once.'); 226 | }); 227 | 228 | test("bind a callback with a supplied context", 1, function () { 229 | var TestClass = function () { 230 | return this; 231 | }; 232 | TestClass.prototype.assertTrue = function () { 233 | ok(true, '`this` was bound to the callback'); 234 | }; 235 | 236 | var obj = _.extend({},Backbone.Events); 237 | obj.on('event', function () { this.assertTrue(); }, (new TestClass)); 238 | obj.trigger('event'); 239 | }); 240 | 241 | test("nested trigger with unbind", 1, function () { 242 | var obj = { counter: 0 }; 243 | _.extend(obj, Backbone.Events); 244 | var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); }; 245 | var incr2 = function(){ obj.counter += 1; }; 246 | obj.on('event', incr1); 247 | obj.on('event', incr2); 248 | obj.trigger('event'); 249 | equal(obj.counter, 3, 'counter should have been incremented three times'); 250 | }); 251 | 252 | test("callback list is not altered during trigger", 2, function () { 253 | var counter = 0, obj = _.extend({}, Backbone.Events); 254 | var incr = function(){ counter++; }; 255 | obj.on('event', function(){ obj.on('event', incr).on('all', incr); }) 256 | .trigger('event'); 257 | equal(counter, 0, 'bind does not alter callback list'); 258 | obj.off() 259 | .on('event', function(){ obj.off('event', incr).off('all', incr); }) 260 | .on('event', incr) 261 | .on('all', incr) 262 | .trigger('event'); 263 | equal(counter, 2, 'unbind does not alter callback list'); 264 | }); 265 | 266 | test("#1282 - 'all' callback list is retrieved after each event.", 1, function() { 267 | var counter = 0; 268 | var obj = _.extend({}, Backbone.Events); 269 | var incr = function(){ counter++; }; 270 | obj.on('x', function() { 271 | obj.on('y', incr).on('all', incr); 272 | }) 273 | .trigger('x y'); 274 | strictEqual(counter, 2); 275 | }); 276 | 277 | test("if no callback is provided, `on` is a noop", 0, function() { 278 | _.extend({}, Backbone.Events).on('test').trigger('test'); 279 | }); 280 | 281 | test("if callback is truthy but not a function, `on` should throw an error just like jQuery", 1, function() { 282 | var view = _.extend({}, Backbone.Events).on('test', 'noop'); 283 | throws(function() { 284 | view.trigger('test'); 285 | }); 286 | }); 287 | 288 | test("remove all events for a specific context", 4, function() { 289 | var obj = _.extend({}, Backbone.Events); 290 | obj.on('x y all', function() { ok(true); }); 291 | obj.on('x y all', function() { ok(false); }, obj); 292 | obj.off(null, null, obj); 293 | obj.trigger('x y'); 294 | }); 295 | 296 | test("remove all events for a specific callback", 4, function() { 297 | var obj = _.extend({}, Backbone.Events); 298 | var success = function() { ok(true); }; 299 | var fail = function() { ok(false); }; 300 | obj.on('x y all', success); 301 | obj.on('x y all', fail); 302 | obj.off(null, fail); 303 | obj.trigger('x y'); 304 | }); 305 | 306 | test("#1310 - off does not skip consecutive events", 0, function() { 307 | var obj = _.extend({}, Backbone.Events); 308 | obj.on('event', function() { ok(false); }, obj); 309 | obj.on('event', function() { ok(false); }, obj); 310 | obj.off(null, null, obj); 311 | obj.trigger('event'); 312 | }); 313 | 314 | test("once", 2, function() { 315 | // Same as the previous test, but we use once rather than having to explicitly unbind 316 | var obj = { counterA: 0, counterB: 0 }; 317 | _.extend(obj, Backbone.Events); 318 | var incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; 319 | var incrB = function(){ obj.counterB += 1; }; 320 | obj.once('event', incrA); 321 | obj.once('event', incrB); 322 | obj.trigger('event'); 323 | equal(obj.counterA, 1, 'counterA should have only been incremented once.'); 324 | equal(obj.counterB, 1, 'counterB should have only been incremented once.'); 325 | }); 326 | 327 | test("once variant one", 3, function() { 328 | var f = function(){ ok(true); }; 329 | 330 | var a = _.extend({}, Backbone.Events).once('event', f); 331 | var b = _.extend({}, Backbone.Events).on('event', f); 332 | 333 | a.trigger('event'); 334 | 335 | b.trigger('event'); 336 | b.trigger('event'); 337 | }); 338 | 339 | test("once variant two", 3, function() { 340 | var f = function(){ ok(true); }; 341 | var obj = _.extend({}, Backbone.Events); 342 | 343 | obj 344 | .once('event', f) 345 | .on('event', f) 346 | .trigger('event') 347 | .trigger('event'); 348 | }); 349 | 350 | test("once with off", 0, function() { 351 | var f = function(){ ok(true); }; 352 | var obj = _.extend({}, Backbone.Events); 353 | 354 | obj.once('event', f); 355 | obj.off('event', f); 356 | obj.trigger('event'); 357 | }); 358 | 359 | test("once with event maps", function() { 360 | var obj = { counter: 0 }; 361 | _.extend(obj, Backbone.Events); 362 | 363 | var increment = function() { 364 | this.counter += 1; 365 | }; 366 | 367 | obj.once({ 368 | a: increment, 369 | b: increment, 370 | c: increment 371 | }, obj); 372 | 373 | obj.trigger('a'); 374 | equal(obj.counter, 1); 375 | 376 | obj.trigger('a b'); 377 | equal(obj.counter, 2); 378 | 379 | obj.trigger('c'); 380 | equal(obj.counter, 3); 381 | 382 | obj.trigger('a b c'); 383 | equal(obj.counter, 3); 384 | }); 385 | 386 | test("once with off only by context", 0, function() { 387 | var context = {}; 388 | var obj = _.extend({}, Backbone.Events); 389 | obj.once('event', function(){ ok(false); }, context); 390 | obj.off(null, null, context); 391 | obj.trigger('event'); 392 | }); 393 | 394 | test("Backbone object inherits Events", function() { 395 | ok(Backbone.on === Backbone.Events.on); 396 | }); 397 | 398 | asyncTest("once with asynchronous events", 1, function() { 399 | var func = _.debounce(function() { ok(true); start(); }, 50); 400 | var obj = _.extend({}, Backbone.Events).once('async', func); 401 | 402 | obj.trigger('async'); 403 | obj.trigger('async'); 404 | }); 405 | 406 | test("once with multiple events.", 2, function() { 407 | var obj = _.extend({}, Backbone.Events); 408 | obj.once('x y', function() { ok(true); }); 409 | obj.trigger('x y'); 410 | }); 411 | 412 | test("Off during iteration with once.", 2, function() { 413 | var obj = _.extend({}, Backbone.Events); 414 | var f = function(){ this.off('event', f); }; 415 | obj.on('event', f); 416 | obj.once('event', function(){}); 417 | obj.on('event', function(){ ok(true); }); 418 | 419 | obj.trigger('event'); 420 | obj.trigger('event'); 421 | }); 422 | 423 | test("`once` on `all` should work as expected", 1, function() { 424 | Backbone.once('all', function() { 425 | ok(true); 426 | Backbone.trigger('all'); 427 | }); 428 | Backbone.trigger('all'); 429 | }); 430 | 431 | test("once without a callback is a noop", 0, function() { 432 | _.extend({}, Backbone.Events).once('event').trigger('event'); 433 | }); 434 | 435 | test("event functions are chainable", function() { 436 | var obj = _.extend({}, Backbone.Events); 437 | var obj2 = _.extend({}, Backbone.Events); 438 | var fn = function() {}; 439 | equal(obj, obj.trigger('noeventssetyet')); 440 | equal(obj, obj.off('noeventssetyet')); 441 | equal(obj, obj.stopListening('noeventssetyet')); 442 | equal(obj, obj.on('a', fn)); 443 | equal(obj, obj.once('c', fn)); 444 | equal(obj, obj.trigger('a')); 445 | equal(obj, obj.listenTo(obj2, 'a', fn)); 446 | equal(obj, obj.listenToOnce(obj2, 'b', fn)); 447 | equal(obj, obj.off('a c')); 448 | equal(obj, obj.stopListening(obj2, 'a')); 449 | equal(obj, obj.stopListening()); 450 | }); 451 | 452 | }); 453 | -------------------------------------------------------------------------------- /test/backbone/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |

Test

26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /test/backbone/test/model.coffee: -------------------------------------------------------------------------------- 1 | # Quick Backbone/CoffeeScript tests to make sure that inheritance 2 | # works correctly. 3 | 4 | {ok, equal, deepEqual} = require 'assert' 5 | {Model, Collection, Events} = require '../backbone' 6 | 7 | 8 | # Patch `ok` to store a count of passed tests... 9 | count = 0 10 | oldOk = ok 11 | ok = -> 12 | oldOk arguments... 13 | count++ 14 | 15 | 16 | class Document extends Model 17 | 18 | fullName: -> 19 | @get('name') + ' ' + @get('surname') 20 | 21 | tempest = new Document 22 | id : '1-the-tempest', 23 | title : "The Tempest", 24 | name : "William" 25 | surname : "Shakespeare" 26 | length : 123 27 | 28 | ok tempest.fullName() is "William Shakespeare" 29 | ok tempest.get('length') is 123 30 | 31 | 32 | class ProperDocument extends Document 33 | 34 | fullName: -> 35 | "Mr. " + super 36 | 37 | properTempest = new ProperDocument tempest.attributes 38 | 39 | ok properTempest.fullName() is "Mr. William Shakespeare" 40 | ok properTempest.get('length') is 123 41 | 42 | 43 | console.log "passed #{count} tests" 44 | -------------------------------------------------------------------------------- /test/backbone/test/noconflict.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Backbone.noConflict"); 4 | 5 | test('noConflict', 2, function() { 6 | var noconflictBackbone = Backbone.noConflict(); 7 | equal(window.Backbone, undefined, 'Returned window.Backbone'); 8 | window.Backbone = noconflictBackbone; 9 | equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone'); 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/backbone/test/router.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var router = null; 4 | var location = null; 5 | var lastRoute = null; 6 | var lastArgs = []; 7 | 8 | function onRoute(router, route, args) { 9 | lastRoute = route; 10 | lastArgs = args; 11 | } 12 | 13 | var Location = function(href) { 14 | this.replace(href); 15 | }; 16 | 17 | _.extend(Location.prototype, { 18 | 19 | replace: function(href) { 20 | _.extend(this, _.pick($('', {href: href})[0], 21 | 'href', 22 | 'hash', 23 | 'host', 24 | 'search', 25 | 'fragment', 26 | 'pathname', 27 | 'protocol' 28 | )); 29 | // In IE, anchor.pathname does not contain a leading slash though 30 | // window.location.pathname does. 31 | if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname; 32 | }, 33 | 34 | toString: function() { 35 | return this.href; 36 | } 37 | 38 | }); 39 | 40 | module("Backbone.Router", { 41 | 42 | setup: function() { 43 | location = new Location('http://example.com'); 44 | Backbone.history = _.extend(new Backbone.History, {location: location}); 45 | router = new Router({testing: 101}); 46 | Backbone.history.interval = 9; 47 | Backbone.history.start({pushState: false}); 48 | lastRoute = null; 49 | lastArgs = []; 50 | Backbone.history.on('route', onRoute); 51 | }, 52 | 53 | teardown: function() { 54 | Backbone.history.stop(); 55 | Backbone.history.off('route', onRoute); 56 | } 57 | 58 | }); 59 | 60 | var ExternalObject = { 61 | value: 'unset', 62 | 63 | routingFunction: function(value) { 64 | this.value = value; 65 | } 66 | }; 67 | _.bindAll(ExternalObject); 68 | 69 | var Router = Backbone.Router.extend({ 70 | 71 | count: 0, 72 | 73 | routes: { 74 | "noCallback": "noCallback", 75 | "counter": "counter", 76 | "search/:query": "search", 77 | "search/:query/p:page": "search", 78 | "contacts": "contacts", 79 | "contacts/new": "newContact", 80 | "contacts/:id": "loadContact", 81 | "route-event/:arg": "routeEvent", 82 | "optional(/:item)": "optionalItem", 83 | "named/optional/(y:z)": "namedOptional", 84 | "splat/*args/end": "splat", 85 | ":repo/compare/*from...*to": "github", 86 | "decode/:named/*splat": "decode", 87 | "*first/complex-*part/*rest": "complex", 88 | ":entity?*args": "query", 89 | "function/:value": ExternalObject.routingFunction, 90 | "*anything": "anything" 91 | }, 92 | 93 | initialize : function(options) { 94 | this.testing = options.testing; 95 | this.route('implicit', 'implicit'); 96 | this.route(/regex\/(\d+)([\?]{1}.*)?/, 'regex'); 97 | }, 98 | 99 | counter: function() { 100 | this.count++; 101 | }, 102 | 103 | implicit: function() { 104 | this.count++; 105 | }, 106 | 107 | regex: function(id) { 108 | this.count++; 109 | }, 110 | 111 | search : function(query, page) { 112 | this.query = query; 113 | this.page = page; 114 | }, 115 | 116 | contacts: function(){ 117 | this.contact = 'index'; 118 | }, 119 | 120 | newContact: function(){ 121 | this.contact = 'new'; 122 | }, 123 | 124 | loadContact: function(){ 125 | this.contact = 'load'; 126 | }, 127 | 128 | optionalItem: function(arg){ 129 | this.arg = arg != void 0 ? arg : null; 130 | }, 131 | 132 | splat: function(args) { 133 | this.args = args; 134 | }, 135 | 136 | github: function(repo, from, to) { 137 | this.repo = repo; 138 | this.from = from; 139 | this.to = to; 140 | }, 141 | 142 | complex: function(first, part, rest) { 143 | this.first = first; 144 | this.part = part; 145 | this.rest = rest; 146 | }, 147 | 148 | query: function(entity, args) { 149 | this.entity = entity; 150 | this.queryArgs = args; 151 | }, 152 | 153 | anything: function(whatever) { 154 | this.anything = whatever; 155 | }, 156 | 157 | namedOptional: function(z) { 158 | this.z = z; 159 | }, 160 | 161 | decode: function(named, path) { 162 | this.named = named; 163 | this.path = path; 164 | }, 165 | 166 | routeEvent: function(arg) { 167 | } 168 | 169 | }); 170 | 171 | test("initialize", 1, function() { 172 | equal(router.testing, 101); 173 | }); 174 | 175 | test("routes (simple)", 4, function() { 176 | location.replace('http://example.com#search/news'); 177 | Backbone.history.checkUrl(); 178 | equal(router.query, 'news'); 179 | equal(router.page, void 0); 180 | equal(lastRoute, 'search'); 181 | equal(lastArgs[0], 'news'); 182 | }); 183 | 184 | test("routes (simple, but unicode)", 4, function() { 185 | location.replace('http://example.com#search/тест'); 186 | Backbone.history.checkUrl(); 187 | equal(router.query, "тест"); 188 | equal(router.page, void 0); 189 | equal(lastRoute, 'search'); 190 | equal(lastArgs[0], "тест"); 191 | }); 192 | 193 | test("routes (two part)", 2, function() { 194 | location.replace('http://example.com#search/nyc/p10'); 195 | Backbone.history.checkUrl(); 196 | equal(router.query, 'nyc'); 197 | equal(router.page, '10'); 198 | }); 199 | 200 | test("routes via navigate", 2, function() { 201 | Backbone.history.navigate('search/manhattan/p20', {trigger: true}); 202 | equal(router.query, 'manhattan'); 203 | equal(router.page, '20'); 204 | }); 205 | 206 | test("routes via navigate for backwards-compatibility", 2, function() { 207 | Backbone.history.navigate('search/manhattan/p20', true); 208 | equal(router.query, 'manhattan'); 209 | equal(router.page, '20'); 210 | }); 211 | 212 | test("reports matched route via nagivate", 1, function() { 213 | ok(Backbone.history.navigate('search/manhattan/p20', true)); 214 | }); 215 | 216 | test("route precedence via navigate", 6, function(){ 217 | // check both 0.9.x and backwards-compatibility options 218 | _.each([ { trigger: true }, true ], function( options ){ 219 | Backbone.history.navigate('contacts', options); 220 | equal(router.contact, 'index'); 221 | Backbone.history.navigate('contacts/new', options); 222 | equal(router.contact, 'new'); 223 | Backbone.history.navigate('contacts/foo', options); 224 | equal(router.contact, 'load'); 225 | }); 226 | }); 227 | 228 | test("loadUrl is not called for identical routes.", 0, function() { 229 | Backbone.history.loadUrl = function(){ ok(false); }; 230 | location.replace('http://example.com#route'); 231 | Backbone.history.navigate('route'); 232 | Backbone.history.navigate('/route'); 233 | Backbone.history.navigate('/route'); 234 | }); 235 | 236 | test("use implicit callback if none provided", 1, function() { 237 | router.count = 0; 238 | router.navigate('implicit', {trigger: true}); 239 | equal(router.count, 1); 240 | }); 241 | 242 | test("routes via navigate with {replace: true}", 1, function() { 243 | location.replace('http://example.com#start_here'); 244 | Backbone.history.checkUrl(); 245 | location.replace = function(href) { 246 | strictEqual(href, new Location('http://example.com#end_here').href); 247 | }; 248 | Backbone.history.navigate('end_here', {replace: true}); 249 | }); 250 | 251 | test("routes (regex)", 1, function() { 252 | router.count = 0; 253 | router.navigate('regex/123', {trigger: true}); 254 | equal(router.count, 1); 255 | }); 256 | 257 | test("routes (splats)", 1, function() { 258 | location.replace('http://example.com#splat/long-list/of/splatted_99args/end'); 259 | Backbone.history.checkUrl(); 260 | equal(router.args, 'long-list/of/splatted_99args'); 261 | }); 262 | 263 | test("routes (github)", 3, function() { 264 | location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash'); 265 | Backbone.history.checkUrl(); 266 | equal(router.repo, 'backbone'); 267 | equal(router.from, '1.0'); 268 | equal(router.to, 'braddunbar:with/slash'); 269 | }); 270 | 271 | test("routes (optional)", 2, function() { 272 | location.replace('http://example.com#optional'); 273 | Backbone.history.checkUrl(); 274 | ok(!router.arg); 275 | location.replace('http://example.com#optional/thing'); 276 | Backbone.history.checkUrl(); 277 | equal(router.arg, 'thing'); 278 | }); 279 | 280 | test("routes (complex)", 3, function() { 281 | location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); 282 | Backbone.history.checkUrl(); 283 | equal(router.first, 'one/two/three'); 284 | equal(router.part, 'part'); 285 | equal(router.rest, 'four/five/six/seven'); 286 | }); 287 | 288 | test("routes (query)", 5, function() { 289 | location.replace('http://example.com#mandel?a=b&c=d'); 290 | Backbone.history.checkUrl(); 291 | equal(router.entity, 'mandel'); 292 | equal(router.queryArgs, 'a=b&c=d'); 293 | equal(lastRoute, 'query'); 294 | equal(lastArgs[0], 'mandel'); 295 | equal(lastArgs[1], 'a=b&c=d'); 296 | }); 297 | 298 | test("routes (anything)", 1, function() { 299 | location.replace('http://example.com#doesnt-match-a-route'); 300 | Backbone.history.checkUrl(); 301 | equal(router.anything, 'doesnt-match-a-route'); 302 | }); 303 | 304 | test("routes (function)", 3, function() { 305 | router.on('route', function(name) { 306 | ok(name === ''); 307 | }); 308 | equal(ExternalObject.value, 'unset'); 309 | location.replace('http://example.com#function/set'); 310 | Backbone.history.checkUrl(); 311 | equal(ExternalObject.value, 'set'); 312 | }); 313 | 314 | test("Decode named parameters, not splats.", 2, function() { 315 | location.replace('http://example.com#decode/a%2Fb/c%2Fd/e'); 316 | Backbone.history.checkUrl(); 317 | strictEqual(router.named, 'a/b'); 318 | strictEqual(router.path, 'c/d/e'); 319 | }); 320 | 321 | test("fires event when router doesn't have callback on it", 1, function() { 322 | router.on("route:noCallback", function(){ ok(true); }); 323 | location.replace('http://example.com#noCallback'); 324 | Backbone.history.checkUrl(); 325 | }); 326 | 327 | test("#933, #908 - leading slash", 2, function() { 328 | location.replace('http://example.com/root/foo'); 329 | 330 | Backbone.history.stop(); 331 | Backbone.history = _.extend(new Backbone.History, {location: location}); 332 | Backbone.history.start({root: '/root', hashChange: false, silent: true}); 333 | strictEqual(Backbone.history.getFragment(), 'foo'); 334 | 335 | Backbone.history.stop(); 336 | Backbone.history = _.extend(new Backbone.History, {location: location}); 337 | Backbone.history.start({root: '/root/', hashChange: false, silent: true}); 338 | strictEqual(Backbone.history.getFragment(), 'foo'); 339 | }); 340 | 341 | test("#1003 - History is started before navigate is called", 1, function() { 342 | Backbone.history.stop(); 343 | Backbone.history.navigate = function(){ ok(Backbone.History.started); }; 344 | Backbone.history.start(); 345 | // If this is not an old IE navigate will not be called. 346 | if (!Backbone.history.iframe) ok(true); 347 | }); 348 | 349 | test("#967 - Route callback gets passed encoded values.", 3, function() { 350 | var route = 'has%2Fslash/complex-has%23hash/has%20space'; 351 | Backbone.history.navigate(route, {trigger: true}); 352 | strictEqual(router.first, 'has/slash'); 353 | strictEqual(router.part, 'has#hash'); 354 | strictEqual(router.rest, 'has space'); 355 | }); 356 | 357 | test("correctly handles URLs with % (#868)", 3, function() { 358 | location.replace('http://example.com#search/fat%3A1.5%25'); 359 | Backbone.history.checkUrl(); 360 | location.replace('http://example.com#search/fat'); 361 | Backbone.history.checkUrl(); 362 | equal(router.query, 'fat'); 363 | equal(router.page, void 0); 364 | equal(lastRoute, 'search'); 365 | }); 366 | 367 | test("#1185 - Use pathname when hashChange is not wanted.", 1, function() { 368 | Backbone.history.stop(); 369 | location.replace('http://example.com/path/name#hash'); 370 | Backbone.history = _.extend(new Backbone.History, {location: location}); 371 | Backbone.history.start({hashChange: false}); 372 | var fragment = Backbone.history.getFragment(); 373 | strictEqual(fragment, location.pathname.replace(/^\//, '')); 374 | }); 375 | 376 | test("#1206 - Strip leading slash before location.assign.", 1, function() { 377 | Backbone.history.stop(); 378 | location.replace('http://example.com/root/'); 379 | Backbone.history = _.extend(new Backbone.History, {location: location}); 380 | Backbone.history.start({hashChange: false, root: '/root/'}); 381 | location.assign = function(pathname) { 382 | strictEqual(pathname, '/root/fragment'); 383 | }; 384 | Backbone.history.navigate('/fragment'); 385 | }); 386 | 387 | test("#1387 - Root fragment without trailing slash.", 1, function() { 388 | Backbone.history.stop(); 389 | location.replace('http://example.com/root'); 390 | Backbone.history = _.extend(new Backbone.History, {location: location}); 391 | Backbone.history.start({hashChange: false, root: '/root/', silent: true}); 392 | strictEqual(Backbone.history.getFragment(), ''); 393 | }); 394 | 395 | test("#1366 - History does not prepend root to fragment.", 2, function() { 396 | Backbone.history.stop(); 397 | location.replace('http://example.com/root/'); 398 | Backbone.history = _.extend(new Backbone.History, { 399 | location: location, 400 | history: { 401 | pushState: function(state, title, url) { 402 | strictEqual(url, '/root/x'); 403 | } 404 | } 405 | }); 406 | Backbone.history.start({ 407 | root: '/root/', 408 | pushState: true, 409 | hashChange: false 410 | }); 411 | Backbone.history.navigate('x'); 412 | strictEqual(Backbone.history.fragment, 'x'); 413 | }); 414 | 415 | test("Normalize root.", 1, function() { 416 | Backbone.history.stop(); 417 | location.replace('http://example.com/root'); 418 | Backbone.history = _.extend(new Backbone.History, { 419 | location: location, 420 | history: { 421 | pushState: function(state, title, url) { 422 | strictEqual(url, '/root/fragment'); 423 | } 424 | } 425 | }); 426 | Backbone.history.start({ 427 | pushState: true, 428 | root: '/root', 429 | hashChange: false 430 | }); 431 | Backbone.history.navigate('fragment'); 432 | }); 433 | 434 | test("Normalize root.", 1, function() { 435 | Backbone.history.stop(); 436 | location.replace('http://example.com/root#fragment'); 437 | Backbone.history = _.extend(new Backbone.History, { 438 | location: location, 439 | history: { 440 | pushState: function(state, title, url) {}, 441 | replaceState: function(state, title, url) { 442 | strictEqual(url, '/root/fragment'); 443 | } 444 | } 445 | }); 446 | Backbone.history.start({ 447 | pushState: true, 448 | root: '/root' 449 | }); 450 | }); 451 | 452 | test("Normalize root.", 1, function() { 453 | Backbone.history.stop(); 454 | location.replace('http://example.com/root'); 455 | Backbone.history = _.extend(new Backbone.History, {location: location}); 456 | Backbone.history.loadUrl = function() { ok(true); }; 457 | Backbone.history.start({ 458 | pushState: true, 459 | root: '/root' 460 | }); 461 | }); 462 | 463 | test("Normalize root - leading slash.", 1, function() { 464 | Backbone.history.stop(); 465 | location.replace('http://example.com/root'); 466 | Backbone.history = _.extend(new Backbone.History, { 467 | location: location, 468 | history: { 469 | pushState: function(){}, 470 | replaceState: function(){} 471 | } 472 | }); 473 | Backbone.history.start({root: 'root'}); 474 | strictEqual(Backbone.history.root, '/root/'); 475 | }); 476 | 477 | test("Transition from hashChange to pushState.", 1, function() { 478 | Backbone.history.stop(); 479 | location.replace('http://example.com/root#x/y'); 480 | Backbone.history = _.extend(new Backbone.History, { 481 | location: location, 482 | history: { 483 | pushState: function(){}, 484 | replaceState: function(state, title, url){ 485 | strictEqual(url, '/root/x/y'); 486 | } 487 | } 488 | }); 489 | Backbone.history.start({ 490 | root: 'root', 491 | pushState: true 492 | }); 493 | }); 494 | 495 | test("#1619: Router: Normalize empty root", 1, function() { 496 | Backbone.history.stop(); 497 | location.replace('http://example.com/'); 498 | Backbone.history = _.extend(new Backbone.History, { 499 | location: location, 500 | history: { 501 | pushState: function(){}, 502 | replaceState: function(){} 503 | } 504 | }); 505 | Backbone.history.start({root: ''}); 506 | strictEqual(Backbone.history.root, '/'); 507 | }); 508 | 509 | test("#1619: Router: nagivate with empty root", 1, function() { 510 | Backbone.history.stop(); 511 | location.replace('http://example.com/'); 512 | Backbone.history = _.extend(new Backbone.History, { 513 | location: location, 514 | history: { 515 | pushState: function(state, title, url) { 516 | strictEqual(url, '/fragment'); 517 | } 518 | } 519 | }); 520 | Backbone.history.start({ 521 | pushState: true, 522 | root: '', 523 | hashChange: false 524 | }); 525 | Backbone.history.navigate('fragment'); 526 | }); 527 | 528 | test("Transition from pushState to hashChange.", 1, function() { 529 | Backbone.history.stop(); 530 | location.replace('http://example.com/root/x/y?a=b'); 531 | location.replace = function(url) { 532 | strictEqual(url, '/root/?a=b#x/y'); 533 | }; 534 | Backbone.history = _.extend(new Backbone.History, { 535 | location: location, 536 | history: { 537 | pushState: null, 538 | replaceState: null 539 | } 540 | }); 541 | Backbone.history.start({ 542 | root: 'root', 543 | pushState: true 544 | }); 545 | }); 546 | 547 | test("#1695 - hashChange to pushState with search.", 1, function() { 548 | Backbone.history.stop(); 549 | location.replace('http://example.com/root?a=b#x/y'); 550 | Backbone.history = _.extend(new Backbone.History, { 551 | location: location, 552 | history: { 553 | pushState: function(){}, 554 | replaceState: function(state, title, url){ 555 | strictEqual(url, '/root/x/y?a=b'); 556 | } 557 | } 558 | }); 559 | Backbone.history.start({ 560 | root: 'root', 561 | pushState: true 562 | }); 563 | }); 564 | 565 | test("#1746 - Router allows empty route.", 1, function() { 566 | var Router = Backbone.Router.extend({ 567 | routes: {'': 'empty'}, 568 | empty: function(){}, 569 | route: function(route){ 570 | strictEqual(route, ''); 571 | } 572 | }); 573 | new Router; 574 | }); 575 | 576 | test("#1794 - Trailing space in fragments.", 1, function() { 577 | var history = new Backbone.History; 578 | strictEqual(history.getFragment('fragment '), 'fragment'); 579 | }); 580 | 581 | test("#1820 - Leading slash and trailing space.", 1, function() { 582 | var history = new Backbone.History; 583 | strictEqual(history.getFragment('/fragment '), 'fragment'); 584 | }); 585 | 586 | test("#1980 - Optional parameters.", 2, function() { 587 | location.replace('http://example.com#named/optional/y'); 588 | Backbone.history.checkUrl(); 589 | strictEqual(router.z, undefined); 590 | location.replace('http://example.com#named/optional/y123'); 591 | Backbone.history.checkUrl(); 592 | strictEqual(router.z, '123'); 593 | }); 594 | 595 | test("#2062 - Trigger 'route' event on router instance.", 2, function() { 596 | router.on('route', function(name, args) { 597 | strictEqual(name, 'routeEvent'); 598 | deepEqual(args, ['x']); 599 | }); 600 | location.replace('http://example.com#route-event/x'); 601 | Backbone.history.checkUrl(); 602 | }); 603 | 604 | test("#2255 - Extend routes by making routes a function.", 1, function() { 605 | var RouterBase = Backbone.Router.extend({ 606 | routes: function() { 607 | return { 608 | home: "root", 609 | index: "index.html" 610 | }; 611 | } 612 | }); 613 | 614 | var RouterExtended = RouterBase.extend({ 615 | routes: function() { 616 | var _super = RouterExtended.__super__.routes; 617 | return _.extend(_super(), 618 | { show: "show", 619 | search: "search" }); 620 | } 621 | }); 622 | 623 | var router = new RouterExtended(); 624 | deepEqual({home: "root", index: "index.html", show: "show", search: "search"}, router.routes); 625 | }); 626 | 627 | }); 628 | -------------------------------------------------------------------------------- /test/backbone/test/sync.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var Library = Backbone.Collection.extend({ 4 | url : function() { return '/library'; } 5 | }); 6 | var library; 7 | 8 | var attrs = { 9 | title : "The Tempest", 10 | author : "Bill Shakespeare", 11 | length : 123 12 | }; 13 | 14 | module("Backbone.sync", _.extend(new Environment, { 15 | 16 | setup : function() { 17 | Environment.prototype.setup.apply(this, arguments); 18 | library = new Library; 19 | library.create(attrs, {wait: false}); 20 | }, 21 | 22 | teardown: function() { 23 | Environment.prototype.teardown.apply(this, arguments); 24 | Backbone.emulateHTTP = false; 25 | } 26 | 27 | })); 28 | 29 | test("read", 4, function() { 30 | library.fetch(); 31 | equal(this.ajaxSettings.url, '/library'); 32 | equal(this.ajaxSettings.type, 'GET'); 33 | equal(this.ajaxSettings.dataType, 'json'); 34 | ok(_.isEmpty(this.ajaxSettings.data)); 35 | }); 36 | 37 | test("passing data", 3, function() { 38 | library.fetch({data: {a: 'a', one: 1}}); 39 | equal(this.ajaxSettings.url, '/library'); 40 | equal(this.ajaxSettings.data.a, 'a'); 41 | equal(this.ajaxSettings.data.one, 1); 42 | }); 43 | 44 | test("create", 6, function() { 45 | equal(this.ajaxSettings.url, '/library'); 46 | equal(this.ajaxSettings.type, 'POST'); 47 | equal(this.ajaxSettings.dataType, 'json'); 48 | var data = JSON.parse(this.ajaxSettings.data); 49 | equal(data.title, 'The Tempest'); 50 | equal(data.author, 'Bill Shakespeare'); 51 | equal(data.length, 123); 52 | }); 53 | 54 | test("update", 7, function() { 55 | library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); 56 | equal(this.ajaxSettings.url, '/library/1-the-tempest'); 57 | equal(this.ajaxSettings.type, 'PUT'); 58 | equal(this.ajaxSettings.dataType, 'json'); 59 | var data = JSON.parse(this.ajaxSettings.data); 60 | equal(data.id, '1-the-tempest'); 61 | equal(data.title, 'The Tempest'); 62 | equal(data.author, 'William Shakespeare'); 63 | equal(data.length, 123); 64 | }); 65 | 66 | test("update with emulateHTTP and emulateJSON", 7, function() { 67 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { 68 | emulateHTTP: true, 69 | emulateJSON: true 70 | }); 71 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 72 | equal(this.ajaxSettings.type, 'POST'); 73 | equal(this.ajaxSettings.dataType, 'json'); 74 | equal(this.ajaxSettings.data._method, 'PUT'); 75 | var data = JSON.parse(this.ajaxSettings.data.model); 76 | equal(data.id, '2-the-tempest'); 77 | equal(data.author, 'Tim Shakespeare'); 78 | equal(data.length, 123); 79 | }); 80 | 81 | test("update with just emulateHTTP", 6, function() { 82 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { 83 | emulateHTTP: true 84 | }); 85 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 86 | equal(this.ajaxSettings.type, 'POST'); 87 | equal(this.ajaxSettings.contentType, 'application/json'); 88 | var data = JSON.parse(this.ajaxSettings.data); 89 | equal(data.id, '2-the-tempest'); 90 | equal(data.author, 'Tim Shakespeare'); 91 | equal(data.length, 123); 92 | }); 93 | 94 | test("update with just emulateJSON", 6, function() { 95 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { 96 | emulateJSON: true 97 | }); 98 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 99 | equal(this.ajaxSettings.type, 'PUT'); 100 | equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded'); 101 | var data = JSON.parse(this.ajaxSettings.data.model); 102 | equal(data.id, '2-the-tempest'); 103 | equal(data.author, 'Tim Shakespeare'); 104 | equal(data.length, 123); 105 | }); 106 | 107 | test("read model", 3, function() { 108 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); 109 | library.first().fetch(); 110 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 111 | equal(this.ajaxSettings.type, 'GET'); 112 | ok(_.isEmpty(this.ajaxSettings.data)); 113 | }); 114 | 115 | test("destroy", 3, function() { 116 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); 117 | library.first().destroy({wait: true}); 118 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 119 | equal(this.ajaxSettings.type, 'DELETE'); 120 | equal(this.ajaxSettings.data, null); 121 | }); 122 | 123 | test("destroy with emulateHTTP", 3, function() { 124 | library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); 125 | library.first().destroy({ 126 | emulateHTTP: true, 127 | emulateJSON: true 128 | }); 129 | equal(this.ajaxSettings.url, '/library/2-the-tempest'); 130 | equal(this.ajaxSettings.type, 'POST'); 131 | equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}'); 132 | }); 133 | 134 | test("urlError", 2, function() { 135 | var model = new Backbone.Model(); 136 | raises(function() { 137 | model.fetch(); 138 | }); 139 | model.fetch({url: '/one/two'}); 140 | equal(this.ajaxSettings.url, '/one/two'); 141 | }); 142 | 143 | test("#1052 - `options` is optional.", 0, function() { 144 | var model = new Backbone.Model(); 145 | model.url = '/test'; 146 | Backbone.sync('create', model); 147 | }); 148 | 149 | test("Backbone.ajax", 1, function() { 150 | Backbone.ajax = function(settings){ 151 | strictEqual(settings.url, '/test'); 152 | }; 153 | var model = new Backbone.Model(); 154 | model.url = '/test'; 155 | Backbone.sync('create', model); 156 | }); 157 | 158 | test("Call provided error callback on error.", 1, function() { 159 | var model = new Backbone.Model; 160 | model.url = '/test'; 161 | Backbone.sync('read', model, { 162 | error: function() { ok(true); } 163 | }); 164 | this.ajaxSettings.error(); 165 | }); 166 | 167 | test('Use Backbone.emulateHTTP as default.', 2, function() { 168 | var model = new Backbone.Model; 169 | model.url = '/test'; 170 | 171 | Backbone.emulateHTTP = true; 172 | model.sync('create', model); 173 | strictEqual(this.ajaxSettings.emulateHTTP, true); 174 | 175 | Backbone.emulateHTTP = false; 176 | model.sync('create', model); 177 | strictEqual(this.ajaxSettings.emulateHTTP, false); 178 | }); 179 | 180 | test('Use Backbone.emulateJSON as default.', 2, function() { 181 | var model = new Backbone.Model; 182 | model.url = '/test'; 183 | 184 | Backbone.emulateJSON = true; 185 | model.sync('create', model); 186 | strictEqual(this.ajaxSettings.emulateJSON, true); 187 | 188 | Backbone.emulateJSON = false; 189 | model.sync('create', model); 190 | strictEqual(this.ajaxSettings.emulateJSON, false); 191 | }); 192 | 193 | test("#1756 - Call user provided beforeSend function.", 4, function() { 194 | Backbone.emulateHTTP = true; 195 | var model = new Backbone.Model; 196 | model.url = '/test'; 197 | var xhr = { 198 | setRequestHeader: function(header, value) { 199 | strictEqual(header, 'X-HTTP-Method-Override'); 200 | strictEqual(value, 'DELETE'); 201 | } 202 | }; 203 | model.sync('delete', model, { 204 | beforeSend: function(_xhr) { 205 | ok(_xhr === xhr); 206 | return false; 207 | } 208 | }); 209 | strictEqual(this.ajaxSettings.beforeSend(xhr), false); 210 | }); 211 | 212 | }); 213 | -------------------------------------------------------------------------------- /test/backbone/test/test-zepto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backbone Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

Backbone Test Suite

23 |

24 |

25 |
    26 |

    27 |

    Backbone Speed Suite

    28 |
    29 | 30 | 31 | -------------------------------------------------------------------------------- /test/backbone/test/vendor/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2009-09-29 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | cx.lastIndex = 0; 437 | if (cx.test(text)) { 438 | text = text.replace(cx, function (a) { 439 | return '\\u' + 440 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 441 | }); 442 | } 443 | 444 | // In the second stage, we run the text against regular expressions that look 445 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 446 | // because they can cause invocation, and '=' because it can cause mutation. 447 | // But just to be safe, we want to reject all unexpected forms. 448 | 449 | // We split the second stage into 4 regexp operations in order to work around 450 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 451 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 452 | // replace all simple value tokens with ']' characters. Third, we delete all 453 | // open brackets that follow a colon or comma or that begin the text. Finally, 454 | // we look to see that the remaining characters are only whitespace or ']' or 455 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 456 | 457 | if (/^[\],:{}\s]*$/. 458 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 459 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 460 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 461 | 462 | // In the third stage we use the eval function to compile the text into a 463 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 464 | // in JavaScript: it can begin a block or an object literal. We wrap the text 465 | // in parens to eliminate the ambiguity. 466 | 467 | j = eval('(' + text + ')'); 468 | 469 | // In the optional fourth stage, we recursively walk the new structure, passing 470 | // each name/value pair to a reviver function for possible transformation. 471 | 472 | return typeof reviver === 'function' ? 473 | walk({'': j}, '') : j; 474 | } 475 | 476 | // If the text is not JSON parseable, then a SyntaxError is thrown. 477 | 478 | throw new SyntaxError('JSON.parse'); 479 | }; 480 | } 481 | }()); -------------------------------------------------------------------------------- /test/backbone/test/vendor/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /test/backbone/test/vendor/runner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QtWebKit-powered headless test runner using PhantomJS 3 | * 4 | * PhantomJS binaries: http://phantomjs.org/download.html 5 | * Requires PhantomJS 1.6+ (1.7+ recommended) 6 | * 7 | * Run with: 8 | * phantomjs runner.js [url-of-your-qunit-testsuite] 9 | * 10 | * e.g. 11 | * phantomjs runner.js http://localhost/qunit/test/index.html 12 | */ 13 | 14 | /*jshint latedef:false */ 15 | /*global phantom:false, require:false, console:false, window:false, QUnit:false */ 16 | 17 | (function() { 18 | 'use strict'; 19 | 20 | var args = require('system').args; 21 | 22 | // arg[0]: scriptName, args[1...]: arguments 23 | if (args.length !== 2) { 24 | console.error('Usage:\n phantomjs runner.js [url-of-your-qunit-testsuite]'); 25 | phantom.exit(1); 26 | } 27 | 28 | var url = args[1], 29 | page = require('webpage').create(); 30 | 31 | // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`) 32 | page.onConsoleMessage = function(msg) { 33 | console.log(msg); 34 | }; 35 | 36 | page.onInitialized = function() { 37 | page.evaluate(addLogging); 38 | }; 39 | 40 | page.onCallback = function(message) { 41 | var result, 42 | failed; 43 | 44 | if (message) { 45 | if (message.name === 'QUnit.done') { 46 | result = message.data; 47 | failed = !result || result.failed; 48 | 49 | phantom.exit(failed ? 1 : 0); 50 | } 51 | } 52 | }; 53 | 54 | page.open(url, function(status) { 55 | if (status !== 'success') { 56 | console.error('Unable to access network: ' + status); 57 | phantom.exit(1); 58 | } else { 59 | // Cannot do this verification with the 'DOMContentLoaded' handler because it 60 | // will be too late to attach it if a page does not have any script tags. 61 | var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); }); 62 | if (qunitMissing) { 63 | console.error('The `QUnit` object is not present on this page.'); 64 | phantom.exit(1); 65 | } 66 | 67 | // Do nothing... the callback mechanism will handle everything! 68 | } 69 | }); 70 | 71 | function addLogging() { 72 | window.document.addEventListener('DOMContentLoaded', function() { 73 | var current_test_assertions = []; 74 | 75 | QUnit.log(function(details) { 76 | var response; 77 | 78 | // Ignore passing assertions 79 | if (details.result) { 80 | return; 81 | } 82 | 83 | response = details.message || ''; 84 | 85 | if (typeof details.expected !== 'undefined') { 86 | if (response) { 87 | response += ', '; 88 | } 89 | 90 | response += 'expected: ' + details.expected + ', but was: ' + details.actual; 91 | if (details.source) { 92 | response += "\n" + details.source; 93 | } 94 | } 95 | 96 | current_test_assertions.push('Failed assertion: ' + response); 97 | }); 98 | 99 | QUnit.testDone(function(result) { 100 | var i, 101 | len, 102 | name = result.module + ': ' + result.name; 103 | 104 | if (result.failed) { 105 | console.log('Test failed: ' + name); 106 | 107 | for (i = 0, len = current_test_assertions.length; i < len; i++) { 108 | console.log(' ' + current_test_assertions[i]); 109 | } 110 | } 111 | 112 | current_test_assertions.length = 0; 113 | }); 114 | 115 | QUnit.done(function(result) { 116 | console.log('Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.'); 117 | 118 | if (typeof window.callPhantom === 'function') { 119 | window.callPhantom({ 120 | 'name': 'QUnit.done', 121 | 'data': result 122 | }); 123 | } 124 | }); 125 | }, false); 126 | } 127 | })(); 128 | -------------------------------------------------------------------------------- /test/backbone/test/view.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var view; 4 | 5 | module("Backbone.View", { 6 | 7 | setup: function() { 8 | view = new Backbone.View({ 9 | id : 'test-view', 10 | className : 'test-view', 11 | other : 'non-special-option' 12 | }); 13 | } 14 | 15 | }); 16 | 17 | test("constructor", 3, function() { 18 | equal(view.el.id, 'test-view'); 19 | equal(view.el.className, 'test-view'); 20 | equal(view.el.other, void 0); 21 | }); 22 | 23 | test("jQuery", 1, function() { 24 | var view = new Backbone.View; 25 | view.setElement('

    test

    '); 26 | strictEqual(view.$('a b').html(), 'test'); 27 | }); 28 | 29 | test("initialize", 1, function() { 30 | var View = Backbone.View.extend({ 31 | initialize: function() { 32 | this.one = 1; 33 | } 34 | }); 35 | 36 | strictEqual(new View().one, 1); 37 | }); 38 | 39 | test("delegateEvents", 6, function() { 40 | var counter1 = 0, counter2 = 0; 41 | 42 | var view = new Backbone.View({el: '

    '}); 43 | view.increment = function(){ counter1++; }; 44 | view.$el.on('click', function(){ counter2++; }); 45 | 46 | var events = {'click #test': 'increment'}; 47 | 48 | view.delegateEvents(events); 49 | view.$('#test').trigger('click'); 50 | equal(counter1, 1); 51 | equal(counter2, 1); 52 | 53 | view.$('#test').trigger('click'); 54 | equal(counter1, 2); 55 | equal(counter2, 2); 56 | 57 | view.delegateEvents(events); 58 | view.$('#test').trigger('click'); 59 | equal(counter1, 3); 60 | equal(counter2, 3); 61 | }); 62 | 63 | test("delegateEvents allows functions for callbacks", 3, function() { 64 | var view = new Backbone.View({el: '

    '}); 65 | view.counter = 0; 66 | 67 | var events = { 68 | click: function() { 69 | this.counter++; 70 | } 71 | }; 72 | 73 | view.delegateEvents(events); 74 | view.$el.trigger('click'); 75 | equal(view.counter, 1); 76 | 77 | view.$el.trigger('click'); 78 | equal(view.counter, 2); 79 | 80 | view.delegateEvents(events); 81 | view.$el.trigger('click'); 82 | equal(view.counter, 3); 83 | }); 84 | 85 | 86 | test("delegateEvents ignore undefined methods", 0, function() { 87 | var view = new Backbone.View({el: '

    '}); 88 | view.delegateEvents({'click': 'undefinedMethod'}); 89 | view.$el.trigger('click'); 90 | }); 91 | 92 | test("undelegateEvents", 6, function() { 93 | var counter1 = 0, counter2 = 0; 94 | 95 | var view = new Backbone.View({el: '

    '}); 96 | view.increment = function(){ counter1++; }; 97 | view.$el.on('click', function(){ counter2++; }); 98 | 99 | var events = {'click #test': 'increment'}; 100 | 101 | view.delegateEvents(events); 102 | view.$('#test').trigger('click'); 103 | equal(counter1, 1); 104 | equal(counter2, 1); 105 | 106 | view.undelegateEvents(); 107 | view.$('#test').trigger('click'); 108 | equal(counter1, 1); 109 | equal(counter2, 2); 110 | 111 | view.delegateEvents(events); 112 | view.$('#test').trigger('click'); 113 | equal(counter1, 2); 114 | equal(counter2, 3); 115 | }); 116 | 117 | test("_ensureElement with DOM node el", 1, function() { 118 | var View = Backbone.View.extend({ 119 | el: document.body 120 | }); 121 | 122 | equal(new View().el, document.body); 123 | }); 124 | 125 | test("_ensureElement with string el", 3, function() { 126 | var View = Backbone.View.extend({ 127 | el: "body" 128 | }); 129 | strictEqual(new View().el, document.body); 130 | 131 | View = Backbone.View.extend({ 132 | el: "#testElement > h1" 133 | }); 134 | strictEqual(new View().el, $("#testElement > h1").get(0)); 135 | 136 | View = Backbone.View.extend({ 137 | el: "#nonexistent" 138 | }); 139 | ok(!new View().el); 140 | }); 141 | 142 | test("with className and id functions", 2, function() { 143 | var View = Backbone.View.extend({ 144 | className: function() { 145 | return 'className'; 146 | }, 147 | id: function() { 148 | return 'id'; 149 | } 150 | }); 151 | 152 | strictEqual(new View().el.className, 'className'); 153 | strictEqual(new View().el.id, 'id'); 154 | }); 155 | 156 | test("with attributes", 2, function() { 157 | var View = Backbone.View.extend({ 158 | attributes: { 159 | id: 'id', 160 | 'class': 'class' 161 | } 162 | }); 163 | 164 | strictEqual(new View().el.className, 'class'); 165 | strictEqual(new View().el.id, 'id'); 166 | }); 167 | 168 | test("with attributes as a function", 1, function() { 169 | var View = Backbone.View.extend({ 170 | attributes: function() { 171 | return {'class': 'dynamic'}; 172 | } 173 | }); 174 | 175 | strictEqual(new View().el.className, 'dynamic'); 176 | }); 177 | 178 | test("multiple views per element", 3, function() { 179 | var count = 0; 180 | var $el = $('

    '); 181 | 182 | var View = Backbone.View.extend({ 183 | el: $el, 184 | events: { 185 | click: function() { 186 | count++; 187 | } 188 | } 189 | }); 190 | 191 | var view1 = new View; 192 | $el.trigger("click"); 193 | equal(1, count); 194 | 195 | var view2 = new View; 196 | $el.trigger("click"); 197 | equal(3, count); 198 | 199 | view1.delegateEvents(); 200 | $el.trigger("click"); 201 | equal(5, count); 202 | }); 203 | 204 | test("custom events, with namespaces", 2, function() { 205 | var count = 0; 206 | 207 | var View = Backbone.View.extend({ 208 | el: $('body'), 209 | events: function() { 210 | return {"fake$event.namespaced": "run"}; 211 | }, 212 | run: function() { 213 | count++; 214 | } 215 | }); 216 | 217 | var view = new View; 218 | $('body').trigger('fake$event').trigger('fake$event'); 219 | equal(count, 2); 220 | 221 | $('body').unbind('.namespaced'); 222 | $('body').trigger('fake$event'); 223 | equal(count, 2); 224 | }); 225 | 226 | test("#1048 - setElement uses provided object.", 2, function() { 227 | var $el = $('body'); 228 | 229 | var view = new Backbone.View({el: $el}); 230 | ok(view.$el === $el); 231 | 232 | view.setElement($el = $($el)); 233 | ok(view.$el === $el); 234 | }); 235 | 236 | test("#986 - Undelegate before changing element.", 1, function() { 237 | var button1 = $(''); 238 | var button2 = $(''); 239 | 240 | var View = Backbone.View.extend({ 241 | events: { 242 | click: function(e) { 243 | ok(view.el === e.target); 244 | } 245 | } 246 | }); 247 | 248 | var view = new View({el: button1}); 249 | view.setElement(button2); 250 | 251 | button1.trigger('click'); 252 | button2.trigger('click'); 253 | }); 254 | 255 | test("#1172 - Clone attributes object", 2, function() { 256 | var View = Backbone.View.extend({ 257 | attributes: {foo: 'bar'} 258 | }); 259 | 260 | var view1 = new View({id: 'foo'}); 261 | strictEqual(view1.el.id, 'foo'); 262 | 263 | var view2 = new View(); 264 | ok(!view2.el.id); 265 | }); 266 | 267 | test("#1228 - tagName can be provided as a function", 1, function() { 268 | var View = Backbone.View.extend({ 269 | tagName: function() { 270 | return 'p'; 271 | } 272 | }); 273 | 274 | ok(new View().$el.is('p')); 275 | }); 276 | 277 | test("views stopListening", 0, function() { 278 | var View = Backbone.View.extend({ 279 | initialize: function() { 280 | this.listenTo(this.model, 'all x', function(){ ok(false); }, this); 281 | this.listenTo(this.collection, 'all x', function(){ ok(false); }, this); 282 | } 283 | }); 284 | 285 | var view = new View({ 286 | model: new Backbone.Model, 287 | collection: new Backbone.Collection 288 | }); 289 | 290 | view.stopListening(); 291 | view.model.trigger('x'); 292 | view.collection.trigger('x'); 293 | }); 294 | 295 | test("Provide function for el.", 1, function() { 296 | var View = Backbone.View.extend({ 297 | el: function() { 298 | return "

    "; 299 | } 300 | }); 301 | 302 | var view = new View; 303 | ok(view.$el.is('p:has(a)')); 304 | }); 305 | 306 | test("events passed in options", 2, function() { 307 | var counter = 0; 308 | 309 | var View = Backbone.View.extend({ 310 | el: '

    ', 311 | increment: function() { 312 | counter++; 313 | } 314 | }); 315 | 316 | var view = new View({events:{'click #test':'increment'}}); 317 | var view2 = new View({events:function(){ 318 | return {'click #test':'increment'}; 319 | }}); 320 | 321 | view.$('#test').trigger('click'); 322 | view2.$('#test').trigger('click'); 323 | equal(counter, 2); 324 | 325 | view.$('#test').trigger('click'); 326 | view2.$('#test').trigger('click'); 327 | equal(counter, 4); 328 | }); 329 | 330 | }); 331 | -------------------------------------------------------------------------------- /test/test-1.1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    26 |
    27 |
    28 |

    Test

    29 |
    30 |
    31 | 32 | 33 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    25 |
    26 |
    27 |

    Test

    28 |
    29 |
    30 | 31 | 32 | --------------------------------------------------------------------------------