TESTS: Use Pretender in test mode for more flexible server responses
This commit is contained in:
parent
fe6235b40e
commit
d3cc85c784
|
@ -13,6 +13,7 @@
|
||||||
"module",
|
"module",
|
||||||
"moduleFor",
|
"moduleFor",
|
||||||
"moduleForComponent",
|
"moduleForComponent",
|
||||||
|
"Pretender",
|
||||||
"sandbox",
|
"sandbox",
|
||||||
"integration",
|
"integration",
|
||||||
"controllerFor",
|
"controllerFor",
|
||||||
|
|
|
@ -11,10 +11,12 @@ export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||||
needs: ['modal', 'createAccount'],
|
needs: ['modal', 'createAccount'],
|
||||||
authenticate: null,
|
authenticate: null,
|
||||||
loggingIn: false,
|
loggingIn: false,
|
||||||
|
loggedIn: false,
|
||||||
|
|
||||||
resetForm: function() {
|
resetForm: function() {
|
||||||
this.set('authenticate', null);
|
this.set('authenticate', null);
|
||||||
this.set('loggingIn', false);
|
this.set('loggingIn', false);
|
||||||
|
this.set('loggedIn', false);
|
||||||
},
|
},
|
||||||
|
|
||||||
site: function() {
|
site: function() {
|
||||||
|
@ -32,9 +34,7 @@ export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||||
return this.get('loggingIn') ? I18n.t('login.logging_in') : I18n.t('login.title');
|
return this.get('loggingIn') ? I18n.t('login.logging_in') : I18n.t('login.title');
|
||||||
}.property('loggingIn'),
|
}.property('loggingIn'),
|
||||||
|
|
||||||
loginDisabled: function() {
|
loginDisabled: Em.computed.or('loggingIn', 'loggedIn'),
|
||||||
return this.get('loggingIn');
|
|
||||||
}.property('loggingIn'),
|
|
||||||
|
|
||||||
showSignupLink: function() {
|
showSignupLink: function() {
|
||||||
return !Discourse.SiteSettings.invite_only &&
|
return !Discourse.SiteSettings.invite_only &&
|
||||||
|
@ -68,6 +68,7 @@ export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||||
}
|
}
|
||||||
self.flash(result.error, 'error');
|
self.flash(result.error, 'error');
|
||||||
} else {
|
} else {
|
||||||
|
self.set('loggedIn', true);
|
||||||
// Trigger the browser's password manager using the hidden static login form:
|
// Trigger the browser's password manager using the hidden static login form:
|
||||||
var $hidden_login_form = $('#hidden-login-form');
|
var $hidden_login_form = $('#hidden-login-form');
|
||||||
$hidden_login_form.find('input[name=username]').val(self.get('loginName'));
|
$hidden_login_form.find('input[name=username]').val(self.get('loginName'));
|
||||||
|
@ -76,7 +77,7 @@ export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||||
$hidden_login_form.submit();
|
$hidden_login_form.submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, function() {
|
}, function(e) {
|
||||||
// Failed to login
|
// Failed to login
|
||||||
if (self.blank('loginName') || self.blank('loginPassword')) {
|
if (self.blank('loginName') || self.blank('loginPassword')) {
|
||||||
self.flash(I18n.t('login.blank_username_or_password'), 'error');
|
self.flash(I18n.t('login.blank_username_or_password'), 'error');
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<td class="label"><label for='new-account-username'>{{i18n user.username.title}}</label></td>
|
<td class="label"><label for='new-account-username'>{{i18n user.username.title}}</label></td>
|
||||||
<td>
|
<td>
|
||||||
{{input value=accountUsername id="new-account-username" maxlength=Discourse.SiteSettings.max_username_length}}
|
{{input value=accountUsername id="new-account-username" maxlength=Discourse.SiteSettings.max_username_length}}
|
||||||
{{input-tip validation=usernameValidation}}
|
{{input-tip validation=usernameValidation id="username-validation"}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="instructions">
|
<tr class="instructions">
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
/*jshint maxlen:10000000 */
|
|
||||||
Discourse.URL_FIXTURES["/session"] = [ { error: "Incorrect username, email or password" } ];
|
|
|
@ -1,3 +1,3 @@
|
||||||
/*jshint maxlen:10000000 */
|
/*jshint maxlen:10000000 */
|
||||||
Discourse.SiteSettingsOriginal = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","traditional_markdown_linebreaks":false,"top_menu":"latest|new|unread|read|starred|categories","post_menu":"like|edit|flag|delete|share|bookmark|admin|reply","share_links":"twitter|facebook|google+|email","track_external_right_clicks":false,"must_approve_users":false,"ga_tracking_code":"UA-33736483-2","ga_domain_name":"","enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"min_post_length":20,"max_post_length":16000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_search_term_length":3,"flush_timings_secs":5,"suppress_reply_directly_below":true,"email_domains_blacklist":"mailinator.com","email_domains_whitelist":null,"version_checks":true,"min_title_similar_length":10,"min_body_similar_length":15,"category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","max_upload_size_kb":1024,"category_featured_topics":6,"favicon_url":"/assets/favicon.ico","dynamic_favicon":false,"uncategorized_name":"uncategorized","uncategorized_color":"AB9364","uncategorized_text_color":"FFFFFF","invite_only":false,"login_required":false,"min_password_length":8,"enable_local_logins":true,"enable_google_logins":false,"enable_google_oauth2_logins":false,"enable_yahoo_logins":false,"enable_twitter_logins":false,"enable_facebook_logins":false,"enable_cas_logins":false,"enable_github_logins":false,"educate_until_posts":2,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"min_private_message_post_length":5,"faq_url":"","tos_url":"","privacy_policy_url":"","authorized_extensions":".jpg|.jpeg|.png|.gif|.txt","relative_date_duration":14,"delete_removed_posts_after":24,"delete_user_max_post_age":7, "default_code_lang": "lang-auto", "suppress_uncategorized_badge": true, "min_username_length": 3, "max_username_length": 20};
|
Discourse.SiteSettingsOriginal = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","mobile_logo_url":"","favicon_url":"//meta.discourse.org/uploads/default/2499/79d53726406d87af.ico","allow_user_locale":false,"suggested_topics":7,"track_external_right_clicks":false,"ga_universal_tracking_code":"","ga_universal_domain_name":"auto","ga_tracking_code":"UA-33736483-2","ga_domain_name":"","top_menu":"latest|new|unread|starred|categories|top","post_menu":"like|share|flag|edit|bookmark|delete|admin|reply","post_menu_hidden_items":"edit|delete|admin","share_links":"twitter|facebook|google+|email","category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","enable_mobile_theme":true,"relative_date_duration":14,"category_featured_topics":4,"fixed_category_positions":false,"show_subcategory_list":false,"posts_per_page":20,"enable_badges":true,"invite_only":false,"login_required":false,"must_approve_users":false,"enable_local_logins":true,"allow_new_registrations":true,"enable_google_logins":true,"enable_google_oauth2_logins":false,"enable_yahoo_logins":true,"enable_twitter_logins":true,"enable_facebook_logins":true,"enable_github_logins":true,"enable_sso":false,"min_username_length":3,"max_username_length":20,"min_password_length":8,"enable_names":true,"invites_shown":30,"delete_user_max_post_age":60,"delete_all_posts_max":15,"min_post_length":20,"min_private_message_post_length":10,"max_post_length":32000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_title_similar_length":10,"min_body_similar_length":15,"edit_history_visible_to_public":true,"delete_removed_posts_after":24,"traditional_markdown_linebreaks":false,"suppress_reply_directly_below":true,"suppress_reply_directly_above":true,"newuser_max_images":0,"newuser_max_attachments":0,"display_name_on_posts":true,"short_progress_text_threshold":10000,"default_code_lang":"lang-auto","autohighlight_all_code":false,"email_in":false,"max_image_size_kb":3072,"max_attachment_size_kb":1024,"authorized_extensions":".jpg|.jpeg|.png|.gif|.svg|.txt|.ico|.yml","max_image_width":690,"max_image_height":500,"allow_profile_backgrounds":true,"allow_uploaded_avatars":true,"allow_animated_avatars":false,"basic_requires_read_posts":30,"enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"flush_timings_secs":5,"tos_url":"","privacy_policy_url":"","tos_accept_required":false,"faq_url":"","allow_restore":false,"maximum_backups":7,"version_checks":true,"suppress_uncategorized_badge":true,"min_search_term_length":3,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"global_notice":"","show_create_topics_notice":true,"available_locales":"cs|da|de|en|es|fr|he|id|it|ja|ko|nb_NO|nl|pl_PL|pt|pt_BR|ru|sv|uk|zh_CN|zh_TW"};
|
||||||
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
|
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* global console */
|
||||||
|
|
||||||
|
function parsePostData(query) {
|
||||||
|
var result = {};
|
||||||
|
query.split("&").forEach(function(part) {
|
||||||
|
var item = part.split("=");
|
||||||
|
result[item[0]] = decodeURIComponent(item[1]);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function json(code, obj) {
|
||||||
|
if (typeof code === "object") {
|
||||||
|
obj = code;
|
||||||
|
code = 200;
|
||||||
|
}
|
||||||
|
return [code, {"Content-Type": "application/json"}, JSON.stringify(obj)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
var server = new Pretender(function() {
|
||||||
|
this.post('/session', function(request) {
|
||||||
|
var data = parsePostData(request.requestBody);
|
||||||
|
|
||||||
|
if (data.password === 'correct') {
|
||||||
|
return json({username: 'eviltrout'});
|
||||||
|
}
|
||||||
|
return json(400, {error: 'invalid login'});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.get('/users/hp.json', function() {
|
||||||
|
return json({"value":"32faff1b1ef1ac3","challenge":"61a3de0ccf086fb9604b76e884d75801"});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.get('/users/check_username', function(request) {
|
||||||
|
if (request.queryParams.username === 'taken') {
|
||||||
|
return json({available: false, suggestion: 'nottaken'});
|
||||||
|
}
|
||||||
|
return json({available: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.post('/users', function(request) {
|
||||||
|
return json({success: true});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.unhandledRequest = function(verb, path) {
|
||||||
|
console.error('Unhandled request in test environment: ' + path + ' (' + verb + ')');
|
||||||
|
};
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
integration("Header (Anonymous)");
|
integration("Header (Anonymous)");
|
||||||
|
|
||||||
test("header", function() {
|
test("header", function() {
|
||||||
expect(14);
|
|
||||||
|
|
||||||
visit("/");
|
visit("/");
|
||||||
andThen(function() {
|
andThen(function() {
|
||||||
ok(exists("header"), "is rendered");
|
ok(exists("header"), "is rendered");
|
||||||
|
|
|
@ -5,8 +5,6 @@ integration("Header (Staff)", {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("header", function() {
|
test("header", function() {
|
||||||
expect(6);
|
|
||||||
|
|
||||||
visit("/");
|
visit("/");
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
|
@ -30,5 +28,4 @@ test("header", function() {
|
||||||
ok(exists("#user-dropdown:visible"), "is lazily rendered after user opens it");
|
ok(exists("#user-dropdown:visible"), "is lazily rendered after user opens it");
|
||||||
ok(exists("#user-dropdown .user-dropdown-links"), "has showing / hiding user-dropdown links correctly bound");
|
ok(exists("#user-dropdown .user-dropdown-links"), "has showing / hiding user-dropdown links correctly bound");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,61 @@
|
||||||
integration("Signing In");
|
integration("Signing In");
|
||||||
|
|
||||||
test("sign in with incorrect credentials", function() {
|
test("sign in", function() {
|
||||||
visit("/");
|
visit("/");
|
||||||
click("header .login-button");
|
click("header .login-button");
|
||||||
andThen(function() {
|
andThen(function() {
|
||||||
ok(exists('.login-modal'), "it shows the login modal");
|
ok(exists('.login-modal'), "it shows the login modal");
|
||||||
});
|
});
|
||||||
fillIn('#login-account-name', 'eviltrout');
|
|
||||||
fillIn('#login-account-password', 'where da plankton at?');
|
|
||||||
|
|
||||||
// The fixture is set to invalid login
|
// Test invalid password first
|
||||||
|
fillIn('#login-account-name', 'eviltrout');
|
||||||
|
fillIn('#login-account-password', 'incorrect');
|
||||||
click('.modal-footer .btn-primary');
|
click('.modal-footer .btn-primary');
|
||||||
andThen(function() {
|
andThen(function() {
|
||||||
// ok(exists('#modal-alert:visible', 'it displays the login error'));
|
ok(exists('#modal-alert:visible', 'it displays the login error'));
|
||||||
|
not(exists('.modal-footer .btn-primary:disabled'), "enables the login button");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use the correct password
|
||||||
|
fillIn('#login-account-password', 'correct');
|
||||||
|
click('.modal-footer .btn-primary');
|
||||||
|
andThen(function() {
|
||||||
|
ok(exists('.modal-footer .btn-primary:disabled'), "disables the login button");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("create account", function() {
|
||||||
|
visit("/");
|
||||||
|
click("header .login-button");
|
||||||
|
click('#new-account-link');
|
||||||
|
|
||||||
|
andThen(function() {
|
||||||
|
ok(exists('.create-account'), "it shows the create account modal");
|
||||||
|
ok(exists('.modal-footer .btn-primary:disabled'), 'create account is disabled at first');
|
||||||
|
});
|
||||||
|
|
||||||
|
fillIn('#new-account-name', 'Dr. Good Tuna');
|
||||||
|
fillIn('#new-account-password', 'cool password bro');
|
||||||
|
|
||||||
|
// Check username
|
||||||
|
fillIn('#new-account-email', 'good.tuna@test.com');
|
||||||
|
fillIn('#new-account-username', 'taken');
|
||||||
|
andThen(function() {
|
||||||
|
ok(exists('#username-validation.bad'), 'the username validation is bad');
|
||||||
|
ok(exists('.modal-footer .btn-primary:disabled'), 'create account is still disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
fillIn('#new-account-username', 'goodtuna');
|
||||||
|
andThen(function() {
|
||||||
|
ok(exists('#username-validation.good'), 'the username validation is good');
|
||||||
|
not(exists('.modal-footer .btn-primary:disabled'), 'create account is enabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
click('.modal-footer .btn-primary');
|
||||||
|
andThen(function() {
|
||||||
|
not(exists('.modal-body'), 'it hides the body when finished');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
/*jshint maxlen:250 */
|
/*jshint maxlen:250 */
|
||||||
/*global document, sinon, console, QUnit, Logster */
|
/*global document, sinon, QUnit, Logster */
|
||||||
|
|
||||||
//= require env
|
//= require env
|
||||||
|
|
||||||
//= require ../../app/assets/javascripts/preload_store.js
|
//= require ../../app/assets/javascripts/preload_store
|
||||||
|
|
||||||
// probe framework first
|
// probe framework first
|
||||||
//= require ../../app/assets/javascripts/discourse/lib/probes.js
|
//= require ../../app/assets/javascripts/discourse/lib/probes
|
||||||
|
|
||||||
// Externals we need to load first
|
// Externals we need to load first
|
||||||
//= require development/jquery-2.1.1.js
|
//= require development/jquery-2.1.1
|
||||||
//= require jquery.ui.widget.js
|
//= require jquery.ui.widget
|
||||||
//= require handlebars.js
|
//= require handlebars
|
||||||
//= require development/ember.js
|
//= require development/ember
|
||||||
//= require message-bus.js
|
//= require message-bus
|
||||||
//= require ember-qunit.js
|
//= require ember-qunit
|
||||||
|
//= require fake_xml_http_request
|
||||||
|
//= require route-recognizer
|
||||||
|
//= require pretender
|
||||||
|
|
||||||
//= require ../../app/assets/javascripts/locales/i18n
|
//= require ../../app/assets/javascripts/locales/i18n
|
||||||
//= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers
|
//= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers
|
||||||
|
@ -60,16 +63,6 @@ sinon.config = {
|
||||||
|
|
||||||
window.assetPath = function() { return null; };
|
window.assetPath = function() { return null; };
|
||||||
|
|
||||||
var oldAjax = $.ajax;
|
|
||||||
$.ajax = function() {
|
|
||||||
try {
|
|
||||||
this.undef();
|
|
||||||
} catch(e) {
|
|
||||||
console.error("Discourse.Ajax called in test environment (" + arguments[0] + ")\n caller: " + e.stack.split("\n").slice(2).join("\n"));
|
|
||||||
}
|
|
||||||
return oldAjax.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stop the message bus so we don't get ajax calls
|
// Stop the message bus so we don't get ajax calls
|
||||||
Discourse.MessageBus.stop();
|
Discourse.MessageBus.stop();
|
||||||
|
|
||||||
|
@ -92,8 +85,13 @@ if (window.Logster) {
|
||||||
window.Logster = { enabled: false };
|
window.Logster = { enabled: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
var origDebounce = Ember.run.debounce;
|
var origDebounce = Ember.run.debounce,
|
||||||
|
createPretendServer = require('helpers/create-pretender', null, null, false).default,
|
||||||
|
server;
|
||||||
|
|
||||||
QUnit.testStart(function(ctx) {
|
QUnit.testStart(function(ctx) {
|
||||||
|
server = createPretendServer();
|
||||||
|
|
||||||
// Allow our tests to change site settings and have them reset before the next test
|
// Allow our tests to change site settings and have them reset before the next test
|
||||||
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
|
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
|
||||||
Discourse.BaseUri = "/";
|
Discourse.BaseUri = "/";
|
||||||
|
@ -118,6 +116,8 @@ QUnit.testDone(function() {
|
||||||
|
|
||||||
// Destroy any modals
|
// Destroy any modals
|
||||||
$('.modal-backdrop').remove();
|
$('.modal-backdrop').remove();
|
||||||
|
|
||||||
|
server.shutdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load ES6 tests
|
// Load ES6 tests
|
||||||
|
|
|
@ -0,0 +1,480 @@
|
||||||
|
(function(undefined){
|
||||||
|
/**
|
||||||
|
* Minimal Event interface implementation
|
||||||
|
*
|
||||||
|
* Original implementation by Sven Fuchs: https://gist.github.com/995028
|
||||||
|
* Modifications and tests by Christian Johansen.
|
||||||
|
*
|
||||||
|
* @author Sven Fuchs (svenfuchs@artweb-design.de)
|
||||||
|
* @author Christian Johansen (christian@cjohansen.no)
|
||||||
|
* @license BSD
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011 Sven Fuchs, Christian Johansen
|
||||||
|
*/
|
||||||
|
|
||||||
|
var _Event = function Event(type, bubbles, cancelable, target) {
|
||||||
|
this.type = type;
|
||||||
|
this.bubbles = bubbles;
|
||||||
|
this.cancelable = cancelable;
|
||||||
|
this.target = target;
|
||||||
|
};
|
||||||
|
|
||||||
|
_Event.prototype = {
|
||||||
|
stopPropagation: function () {},
|
||||||
|
preventDefault: function () {
|
||||||
|
this.defaultPrevented = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Used to set the statusText property of an xhr object
|
||||||
|
*/
|
||||||
|
var httpStatusCodes = {
|
||||||
|
100: "Continue",
|
||||||
|
101: "Switching Protocols",
|
||||||
|
200: "OK",
|
||||||
|
201: "Created",
|
||||||
|
202: "Accepted",
|
||||||
|
203: "Non-Authoritative Information",
|
||||||
|
204: "No Content",
|
||||||
|
205: "Reset Content",
|
||||||
|
206: "Partial Content",
|
||||||
|
300: "Multiple Choice",
|
||||||
|
301: "Moved Permanently",
|
||||||
|
302: "Found",
|
||||||
|
303: "See Other",
|
||||||
|
304: "Not Modified",
|
||||||
|
305: "Use Proxy",
|
||||||
|
307: "Temporary Redirect",
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
402: "Payment Required",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
406: "Not Acceptable",
|
||||||
|
407: "Proxy Authentication Required",
|
||||||
|
408: "Request Timeout",
|
||||||
|
409: "Conflict",
|
||||||
|
410: "Gone",
|
||||||
|
411: "Length Required",
|
||||||
|
412: "Precondition Failed",
|
||||||
|
413: "Request Entity Too Large",
|
||||||
|
414: "Request-URI Too Long",
|
||||||
|
415: "Unsupported Media Type",
|
||||||
|
416: "Requested Range Not Satisfiable",
|
||||||
|
417: "Expectation Failed",
|
||||||
|
422: "Unprocessable Entity",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
502: "Bad Gateway",
|
||||||
|
503: "Service Unavailable",
|
||||||
|
504: "Gateway Timeout",
|
||||||
|
505: "HTTP Version Not Supported"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cross-browser XML parsing. Used to turn
|
||||||
|
XML responses into Document objects
|
||||||
|
Borrowed from JSpec
|
||||||
|
*/
|
||||||
|
function parseXML(text) {
|
||||||
|
var xmlDoc;
|
||||||
|
|
||||||
|
if (typeof DOMParser != "undefined") {
|
||||||
|
var parser = new DOMParser();
|
||||||
|
xmlDoc = parser.parseFromString(text, "text/xml");
|
||||||
|
} else {
|
||||||
|
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
|
||||||
|
xmlDoc.async = "false";
|
||||||
|
xmlDoc.loadXML(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Without mocking, the native XMLHttpRequest object will throw
|
||||||
|
an error when attempting to set these headers. We match this behavior.
|
||||||
|
*/
|
||||||
|
var unsafeHeaders = {
|
||||||
|
"Accept-Charset": true,
|
||||||
|
"Accept-Encoding": true,
|
||||||
|
"Connection": true,
|
||||||
|
"Content-Length": true,
|
||||||
|
"Cookie": true,
|
||||||
|
"Cookie2": true,
|
||||||
|
"Content-Transfer-Encoding": true,
|
||||||
|
"Date": true,
|
||||||
|
"Expect": true,
|
||||||
|
"Host": true,
|
||||||
|
"Keep-Alive": true,
|
||||||
|
"Referer": true,
|
||||||
|
"TE": true,
|
||||||
|
"Trailer": true,
|
||||||
|
"Transfer-Encoding": true,
|
||||||
|
"Upgrade": true,
|
||||||
|
"User-Agent": true,
|
||||||
|
"Via": true
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Adds an "event" onto the fake xhr object
|
||||||
|
that just calls the same-named method. This is
|
||||||
|
in case a library adds callbacks for these events.
|
||||||
|
*/
|
||||||
|
function _addEventListener(eventName, xhr){
|
||||||
|
xhr.addEventListener(eventName, function (event) {
|
||||||
|
var listener = xhr["on" + eventName];
|
||||||
|
|
||||||
|
if (listener && typeof listener == "function") {
|
||||||
|
listener(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constructor for a fake window.XMLHttpRequest
|
||||||
|
*/
|
||||||
|
function FakeXMLHttpRequest() {
|
||||||
|
this.readyState = FakeXMLHttpRequest.UNSENT;
|
||||||
|
this.requestHeaders = {};
|
||||||
|
this.requestBody = null;
|
||||||
|
this.status = 0;
|
||||||
|
this.statusText = "";
|
||||||
|
|
||||||
|
this._eventListeners = {};
|
||||||
|
var events = ["loadstart", "load", "abort", "loadend"];
|
||||||
|
for (var i = events.length - 1; i >= 0; i--) {
|
||||||
|
_addEventListener(events[i], this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// These status codes are available on the native XMLHttpRequest
|
||||||
|
// object, so we match that here in case a library is relying on them.
|
||||||
|
FakeXMLHttpRequest.UNSENT = 0;
|
||||||
|
FakeXMLHttpRequest.OPENED = 1;
|
||||||
|
FakeXMLHttpRequest.HEADERS_RECEIVED = 2;
|
||||||
|
FakeXMLHttpRequest.LOADING = 3;
|
||||||
|
FakeXMLHttpRequest.DONE = 4;
|
||||||
|
|
||||||
|
FakeXMLHttpRequest.prototype = {
|
||||||
|
UNSENT: 0,
|
||||||
|
OPENED: 1,
|
||||||
|
HEADERS_RECEIVED: 2,
|
||||||
|
LOADING: 3,
|
||||||
|
DONE: 4,
|
||||||
|
async: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's open function
|
||||||
|
*/
|
||||||
|
open: function open(method, url, async, username, password) {
|
||||||
|
this.method = method;
|
||||||
|
this.url = url;
|
||||||
|
this.async = typeof async == "boolean" ? async : true;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.responseText = null;
|
||||||
|
this.responseXML = null;
|
||||||
|
this.requestHeaders = {};
|
||||||
|
this.sendFlag = false;
|
||||||
|
this._readyStateChange(FakeXMLHttpRequest.OPENED);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's addEventListener function
|
||||||
|
*/
|
||||||
|
addEventListener: function addEventListener(event, listener) {
|
||||||
|
this._eventListeners[event] = this._eventListeners[event] || [];
|
||||||
|
this._eventListeners[event].push(listener);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's removeEventListener function
|
||||||
|
*/
|
||||||
|
removeEventListener: function removeEventListener(event, listener) {
|
||||||
|
var listeners = this._eventListeners[event] || [];
|
||||||
|
|
||||||
|
for (var i = 0, l = listeners.length; i < l; ++i) {
|
||||||
|
if (listeners[i] == listener) {
|
||||||
|
return listeners.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's dispatchEvent function
|
||||||
|
*/
|
||||||
|
dispatchEvent: function dispatchEvent(event) {
|
||||||
|
var type = event.type;
|
||||||
|
var listeners = this._eventListeners[type] || [];
|
||||||
|
|
||||||
|
for (var i = 0; i < listeners.length; i++) {
|
||||||
|
if (typeof listeners[i] == "function") {
|
||||||
|
listeners[i].call(this, event);
|
||||||
|
} else {
|
||||||
|
listeners[i].handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!event.defaultPrevented;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's setRequestHeader function
|
||||||
|
*/
|
||||||
|
setRequestHeader: function setRequestHeader(header, value) {
|
||||||
|
verifyState(this);
|
||||||
|
|
||||||
|
if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
|
||||||
|
throw new Error("Refused to set unsafe header \"" + header + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.requestHeaders[header]) {
|
||||||
|
this.requestHeaders[header] += "," + value;
|
||||||
|
} else {
|
||||||
|
this.requestHeaders[header] = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's send function
|
||||||
|
*/
|
||||||
|
send: function send(data) {
|
||||||
|
verifyState(this);
|
||||||
|
|
||||||
|
if (!/^(get|head)$/i.test(this.method)) {
|
||||||
|
if (this.requestHeaders["Content-Type"]) {
|
||||||
|
var value = this.requestHeaders["Content-Type"].split(";");
|
||||||
|
this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";
|
||||||
|
} else {
|
||||||
|
this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestBody = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorFlag = false;
|
||||||
|
this.sendFlag = this.async;
|
||||||
|
this._readyStateChange(FakeXMLHttpRequest.OPENED);
|
||||||
|
|
||||||
|
if (typeof this.onSend == "function") {
|
||||||
|
this.onSend(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatchEvent(new _Event("loadstart", false, false, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's abort function
|
||||||
|
*/
|
||||||
|
abort: function abort() {
|
||||||
|
this.aborted = true;
|
||||||
|
this.responseText = null;
|
||||||
|
this.errorFlag = true;
|
||||||
|
this.requestHeaders = {};
|
||||||
|
|
||||||
|
if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) {
|
||||||
|
this._readyStateChange(FakeXMLHttpRequest.DONE);
|
||||||
|
this.sendFlag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.readyState = FakeXMLHttpRequest.UNSENT;
|
||||||
|
|
||||||
|
this.dispatchEvent(new _Event("abort", false, false, this));
|
||||||
|
if (typeof this.onerror === "function") {
|
||||||
|
this.onerror();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's getResponseHeader function
|
||||||
|
*/
|
||||||
|
getResponseHeader: function getResponseHeader(header) {
|
||||||
|
if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^Set-Cookie2?$/i.test(header)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = header.toLowerCase();
|
||||||
|
|
||||||
|
for (var h in this.responseHeaders) {
|
||||||
|
if (h.toLowerCase() == header) {
|
||||||
|
return this.responseHeaders[h];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function
|
||||||
|
*/
|
||||||
|
getAllResponseHeaders: function getAllResponseHeaders() {
|
||||||
|
if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers = "";
|
||||||
|
|
||||||
|
for (var header in this.responseHeaders) {
|
||||||
|
if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) {
|
||||||
|
headers += header + ": " + this.responseHeaders[header] + "\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Places a FakeXMLHttpRequest object into the passed
|
||||||
|
state.
|
||||||
|
*/
|
||||||
|
_readyStateChange: function _readyStateChange(state) {
|
||||||
|
this.readyState = state;
|
||||||
|
|
||||||
|
if (typeof this.onreadystatechange == "function") {
|
||||||
|
this.onreadystatechange();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatchEvent(new _Event("readystatechange"));
|
||||||
|
|
||||||
|
if (this.readyState == FakeXMLHttpRequest.DONE) {
|
||||||
|
this.dispatchEvent(new _Event("load", false, false, this));
|
||||||
|
this.dispatchEvent(new _Event("loadend", false, false, this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sets the FakeXMLHttpRequest object's response headers and
|
||||||
|
places the object into readyState 2
|
||||||
|
*/
|
||||||
|
_setResponseHeaders: function _setResponseHeaders(headers) {
|
||||||
|
this.responseHeaders = {};
|
||||||
|
|
||||||
|
for (var header in headers) {
|
||||||
|
if (headers.hasOwnProperty(header)) {
|
||||||
|
this.responseHeaders[header] = headers[header];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.async) {
|
||||||
|
this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
|
||||||
|
} else {
|
||||||
|
this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sets the FakeXMLHttpRequest object's response body and
|
||||||
|
if body text is XML, sets responseXML to parsed document
|
||||||
|
object
|
||||||
|
*/
|
||||||
|
_setResponseBody: function _setResponseBody(body) {
|
||||||
|
verifyRequestSent(this);
|
||||||
|
verifyHeadersReceived(this);
|
||||||
|
verifyResponseBodyType(body);
|
||||||
|
|
||||||
|
var chunkSize = this.chunkSize || 10;
|
||||||
|
var index = 0;
|
||||||
|
this.responseText = "";
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (this.async) {
|
||||||
|
this._readyStateChange(FakeXMLHttpRequest.LOADING);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.responseText += body.substring(index, index + chunkSize);
|
||||||
|
index += chunkSize;
|
||||||
|
} while (index < body.length);
|
||||||
|
|
||||||
|
var type = this.getResponseHeader("Content-Type");
|
||||||
|
|
||||||
|
if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
|
||||||
|
try {
|
||||||
|
this.responseXML = parseXML(this.responseText);
|
||||||
|
} catch (e) {
|
||||||
|
// Unable to parse XML - no biggie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.async) {
|
||||||
|
this._readyStateChange(FakeXMLHttpRequest.DONE);
|
||||||
|
} else {
|
||||||
|
this.readyState = FakeXMLHttpRequest.DONE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Forces a response on to the FakeXMLHttpRequest object.
|
||||||
|
|
||||||
|
This is the public API for faking responses. This function
|
||||||
|
takes a number status, headers object, and string body:
|
||||||
|
|
||||||
|
```
|
||||||
|
xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.")
|
||||||
|
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
respond: function respond(status, headers, body) {
|
||||||
|
this._setResponseHeaders(headers || {});
|
||||||
|
this.status = typeof status == "number" ? status : 200;
|
||||||
|
this.statusText = httpStatusCodes[this.status];
|
||||||
|
this._setResponseBody(body || "");
|
||||||
|
if (typeof this.onload === "function"){
|
||||||
|
this.onload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function verifyState(xhr) {
|
||||||
|
if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
|
||||||
|
throw new Error("INVALID_STATE_ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.sendFlag) {
|
||||||
|
throw new Error("INVALID_STATE_ERR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function verifyRequestSent(xhr) {
|
||||||
|
if (xhr.readyState == FakeXMLHttpRequest.DONE) {
|
||||||
|
throw new Error("Request done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyHeadersReceived(xhr) {
|
||||||
|
if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
|
||||||
|
throw new Error("No headers received");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyResponseBodyType(body) {
|
||||||
|
if (typeof body != "string") {
|
||||||
|
var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
|
||||||
|
body + ", which is not a string.");
|
||||||
|
error.name = "InvalidBodyException";
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = FakeXMLHttpRequest;
|
||||||
|
} else if (typeof define === 'function' && define.amd) {
|
||||||
|
define(function() { return FakeXMLHttpRequest; });
|
||||||
|
} else if (typeof window !== 'undefined') {
|
||||||
|
window.FakeXMLHttpRequest = FakeXMLHttpRequest;
|
||||||
|
} else if (this) {
|
||||||
|
this.FakeXMLHttpRequest = FakeXMLHttpRequest;
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,128 @@
|
||||||
|
(function(window){
|
||||||
|
|
||||||
|
var isNode = typeof process !== 'undefined' && process.toString() === '[object process]';
|
||||||
|
var RouteRecognizer = isNode ? require('route-recognizer')['default'] : window.RouteRecognizer;
|
||||||
|
var FakeXMLHttpRequest = isNode ? require('./bower_components/FakeXMLHttpRequest/fake_xml_http_request') : window.FakeXMLHttpRequest;
|
||||||
|
|
||||||
|
function Pretender(maps){
|
||||||
|
maps = maps || function(){};
|
||||||
|
// Herein we keep track of RouteRecognizer instances
|
||||||
|
// keyed by HTTP method. Feel free to add more as needed.
|
||||||
|
this.registry = {
|
||||||
|
GET: new RouteRecognizer(),
|
||||||
|
PUT: new RouteRecognizer(),
|
||||||
|
POST: new RouteRecognizer(),
|
||||||
|
DELETE: new RouteRecognizer(),
|
||||||
|
PATCH: new RouteRecognizer(),
|
||||||
|
HEAD: new RouteRecognizer()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handlers = [];
|
||||||
|
this.handledRequests = [];
|
||||||
|
this.unhandledRequests = [];
|
||||||
|
|
||||||
|
// reference the native XMLHttpRequest object so
|
||||||
|
// it can be restored later
|
||||||
|
this._nativeXMLHttpRequest = window.XMLHttpRequest;
|
||||||
|
|
||||||
|
// capture xhr requests, channeling them into
|
||||||
|
// the route map.
|
||||||
|
window.XMLHttpRequest = interceptor(this);
|
||||||
|
|
||||||
|
// trigger the route map DSL.
|
||||||
|
maps.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function interceptor(pretender) {
|
||||||
|
function FakeRequest(){
|
||||||
|
// super()
|
||||||
|
FakeXMLHttpRequest.call(this);
|
||||||
|
}
|
||||||
|
// extend
|
||||||
|
var proto = new FakeXMLHttpRequest();
|
||||||
|
proto.send = function send(){
|
||||||
|
FakeXMLHttpRequest.prototype.send.apply(this, arguments);
|
||||||
|
pretender.handleRequest(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
FakeRequest.prototype = proto;
|
||||||
|
return FakeRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verbify(verb){
|
||||||
|
return function(path, handler){
|
||||||
|
this.register(verb, path, handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Pretender.prototype = {
|
||||||
|
get: verbify('GET'),
|
||||||
|
post: verbify('POST'),
|
||||||
|
put: verbify('PUT'),
|
||||||
|
'delete': verbify('DELETE'),
|
||||||
|
patch: verbify('PATCH'),
|
||||||
|
head: verbify('HEAD'),
|
||||||
|
register: function register(verb, path, handler){
|
||||||
|
handler.numberOfCalls = 0;
|
||||||
|
this.handlers.push(handler);
|
||||||
|
|
||||||
|
var registry = this.registry[verb];
|
||||||
|
registry.add([{path: path, handler: handler}]);
|
||||||
|
},
|
||||||
|
handleRequest: function handleRequest(request){
|
||||||
|
var verb = request.method.toUpperCase();
|
||||||
|
var path = request.url;
|
||||||
|
var handler = this._handlerFor(verb, path, request);
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
handler.handler.numberOfCalls++;
|
||||||
|
this.handledRequests.push(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var statusHeadersAndBody = handler.handler(request),
|
||||||
|
status = statusHeadersAndBody[0],
|
||||||
|
headers = statusHeadersAndBody[1],
|
||||||
|
body = this.prepareBody(statusHeadersAndBody[2]);
|
||||||
|
request.respond(status, headers, body);
|
||||||
|
this.handledRequest(verb, path, request);
|
||||||
|
} catch (error) {
|
||||||
|
this.erroredRequest(verb, path, request, error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.unhandledRequests.push(request);
|
||||||
|
this.unhandledRequest(verb, path, request);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prepareBody: function(body){ return body; },
|
||||||
|
handledRequest: function(verb, path, request){/* no-op */},
|
||||||
|
unhandledRequest: function(verb, path, request) {
|
||||||
|
throw new Error("Pretender intercepted "+verb+" "+path+" but no handler was defined for this type of request");
|
||||||
|
},
|
||||||
|
erroredRequest: function(verb, path, request, error){
|
||||||
|
error.message = "Pretender intercepted "+verb+" "+path+" but encountered an error: " + error.message;
|
||||||
|
throw error;
|
||||||
|
},
|
||||||
|
_handlerFor: function(verb, path, request){
|
||||||
|
var registry = this.registry[verb];
|
||||||
|
var matches = registry.recognize(path);
|
||||||
|
|
||||||
|
var match = matches ? matches[0] : null;
|
||||||
|
if (match) {
|
||||||
|
request.params = match.params;
|
||||||
|
request.queryParams = matches.queryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
},
|
||||||
|
shutdown: function shutdown(){
|
||||||
|
window.XMLHttpRequest = this._nativeXMLHttpRequest;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNode) {
|
||||||
|
module.exports = Pretender;
|
||||||
|
} else {
|
||||||
|
window.Pretender = Pretender;
|
||||||
|
}
|
||||||
|
|
||||||
|
})(window);
|
|
@ -0,0 +1,778 @@
|
||||||
|
(function(global) {
|
||||||
|
var define, requireModule, require, requirejs;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var _isArray;
|
||||||
|
if (!Array.isArray) {
|
||||||
|
_isArray = function (x) {
|
||||||
|
return Object.prototype.toString.call(x) === "[object Array]";
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
_isArray = Array.isArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registry = {}, seen = {};
|
||||||
|
var FAILED = false;
|
||||||
|
|
||||||
|
var uuid = 0;
|
||||||
|
|
||||||
|
function tryFinally(tryable, finalizer) {
|
||||||
|
try {
|
||||||
|
return tryable();
|
||||||
|
} finally {
|
||||||
|
finalizer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Module(name, deps, callback, exports) {
|
||||||
|
var defaultDeps = ['require', 'exports', 'module'];
|
||||||
|
|
||||||
|
this.id = uuid++;
|
||||||
|
this.name = name;
|
||||||
|
this.deps = !deps.length && callback.length ? defaultDeps : deps;
|
||||||
|
this.exports = exports || { };
|
||||||
|
this.callback = callback;
|
||||||
|
this.state = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
define = function(name, deps, callback) {
|
||||||
|
if (!_isArray(deps)) {
|
||||||
|
callback = deps;
|
||||||
|
deps = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
registry[name] = new Module(name, deps, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
define.amd = {};
|
||||||
|
|
||||||
|
function reify(mod, name, seen) {
|
||||||
|
var deps = mod.deps;
|
||||||
|
var length = deps.length;
|
||||||
|
var reified = new Array(length);
|
||||||
|
var dep;
|
||||||
|
// TODO: new Module
|
||||||
|
// TODO: seen refactor
|
||||||
|
var module = { };
|
||||||
|
|
||||||
|
for (var i = 0, l = length; i < l; i++) {
|
||||||
|
dep = deps[i];
|
||||||
|
if (dep === 'exports') {
|
||||||
|
module.exports = reified[i] = seen;
|
||||||
|
} else if (dep === 'require') {
|
||||||
|
reified[i] = require;
|
||||||
|
} else if (dep === 'module') {
|
||||||
|
mod.exports = seen;
|
||||||
|
module = reified[i] = mod;
|
||||||
|
} else {
|
||||||
|
reified[i] = require(resolve(dep, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deps: reified,
|
||||||
|
module: module
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
requirejs = require = requireModule = function(name) {
|
||||||
|
var mod = registry[name];
|
||||||
|
if (!mod) {
|
||||||
|
throw new Error('Could not find module ' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod.state !== FAILED &&
|
||||||
|
seen.hasOwnProperty(name)) {
|
||||||
|
return seen[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
var reified;
|
||||||
|
var module;
|
||||||
|
var loaded = false;
|
||||||
|
|
||||||
|
seen[name] = { }; // placeholder for run-time cycles
|
||||||
|
|
||||||
|
tryFinally(function() {
|
||||||
|
reified = reify(mod, name, seen[name]);
|
||||||
|
module = mod.callback.apply(this, reified.deps);
|
||||||
|
loaded = true;
|
||||||
|
}, function() {
|
||||||
|
if (!loaded) {
|
||||||
|
mod.state = FAILED;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (module === undefined && reified.module.exports) {
|
||||||
|
return (seen[name] = reified.module.exports);
|
||||||
|
} else {
|
||||||
|
return (seen[name] = module);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolve(child, name) {
|
||||||
|
if (child.charAt(0) !== '.') { return child; }
|
||||||
|
|
||||||
|
var parts = child.split('/');
|
||||||
|
var nameParts = name.split('/');
|
||||||
|
var parentBase = nameParts.slice(0, -1);
|
||||||
|
|
||||||
|
for (var i = 0, l = parts.length; i < l; i++) {
|
||||||
|
var part = parts[i];
|
||||||
|
|
||||||
|
if (part === '..') { parentBase.pop(); }
|
||||||
|
else if (part === '.') { continue; }
|
||||||
|
else { parentBase.push(part); }
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentBase.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
requirejs.entries = requirejs._eak_seen = registry;
|
||||||
|
requirejs.clear = function(){
|
||||||
|
requirejs.entries = requirejs._eak_seen = registry = {};
|
||||||
|
seen = state = {};
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
define("route-recognizer",
|
||||||
|
["route-recognizer/dsl","exports"],
|
||||||
|
function(__dependency1__, __exports__) {
|
||||||
|
"use strict";
|
||||||
|
var map = __dependency1__["default"];
|
||||||
|
|
||||||
|
var specials = [
|
||||||
|
'/', '.', '*', '+', '?', '|',
|
||||||
|
'(', ')', '[', ']', '{', '}', '\\'
|
||||||
|
];
|
||||||
|
|
||||||
|
var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
|
||||||
|
|
||||||
|
function isArray(test) {
|
||||||
|
return Object.prototype.toString.call(test) === "[object Array]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Segment represents a segment in the original route description.
|
||||||
|
// Each Segment type provides an `eachChar` and `regex` method.
|
||||||
|
//
|
||||||
|
// The `eachChar` method invokes the callback with one or more character
|
||||||
|
// specifications. A character specification consumes one or more input
|
||||||
|
// characters.
|
||||||
|
//
|
||||||
|
// The `regex` method returns a regex fragment for the segment. If the
|
||||||
|
// segment is a dynamic of star segment, the regex fragment also includes
|
||||||
|
// a capture.
|
||||||
|
//
|
||||||
|
// A character specification contains:
|
||||||
|
//
|
||||||
|
// * `validChars`: a String with a list of all valid characters, or
|
||||||
|
// * `invalidChars`: a String with a list of all invalid characters
|
||||||
|
// * `repeat`: true if the character specification can repeat
|
||||||
|
|
||||||
|
function StaticSegment(string) { this.string = string; }
|
||||||
|
StaticSegment.prototype = {
|
||||||
|
eachChar: function(callback) {
|
||||||
|
var string = this.string, ch;
|
||||||
|
|
||||||
|
for (var i=0, l=string.length; i<l; i++) {
|
||||||
|
ch = string.charAt(i);
|
||||||
|
callback({ validChars: ch });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
regex: function() {
|
||||||
|
return this.string.replace(escapeRegex, '\\$1');
|
||||||
|
},
|
||||||
|
|
||||||
|
generate: function() {
|
||||||
|
return this.string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function DynamicSegment(name) { this.name = name; }
|
||||||
|
DynamicSegment.prototype = {
|
||||||
|
eachChar: function(callback) {
|
||||||
|
callback({ invalidChars: "/", repeat: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
regex: function() {
|
||||||
|
return "([^/]+)";
|
||||||
|
},
|
||||||
|
|
||||||
|
generate: function(params) {
|
||||||
|
return params[this.name];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function StarSegment(name) { this.name = name; }
|
||||||
|
StarSegment.prototype = {
|
||||||
|
eachChar: function(callback) {
|
||||||
|
callback({ invalidChars: "", repeat: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
regex: function() {
|
||||||
|
return "(.+)";
|
||||||
|
},
|
||||||
|
|
||||||
|
generate: function(params) {
|
||||||
|
return params[this.name];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function EpsilonSegment() {}
|
||||||
|
EpsilonSegment.prototype = {
|
||||||
|
eachChar: function() {},
|
||||||
|
regex: function() { return ""; },
|
||||||
|
generate: function() { return ""; }
|
||||||
|
};
|
||||||
|
|
||||||
|
function parse(route, names, types) {
|
||||||
|
// normalize route as not starting with a "/". Recognition will
|
||||||
|
// also normalize.
|
||||||
|
if (route.charAt(0) === "/") { route = route.substr(1); }
|
||||||
|
|
||||||
|
var segments = route.split("/"), results = [];
|
||||||
|
|
||||||
|
for (var i=0, l=segments.length; i<l; i++) {
|
||||||
|
var segment = segments[i], match;
|
||||||
|
|
||||||
|
if (match = segment.match(/^:([^\/]+)$/)) {
|
||||||
|
results.push(new DynamicSegment(match[1]));
|
||||||
|
names.push(match[1]);
|
||||||
|
types.dynamics++;
|
||||||
|
} else if (match = segment.match(/^\*([^\/]+)$/)) {
|
||||||
|
results.push(new StarSegment(match[1]));
|
||||||
|
names.push(match[1]);
|
||||||
|
types.stars++;
|
||||||
|
} else if(segment === "") {
|
||||||
|
results.push(new EpsilonSegment());
|
||||||
|
} else {
|
||||||
|
results.push(new StaticSegment(segment));
|
||||||
|
types.statics++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A State has a character specification and (`charSpec`) and a list of possible
|
||||||
|
// subsequent states (`nextStates`).
|
||||||
|
//
|
||||||
|
// If a State is an accepting state, it will also have several additional
|
||||||
|
// properties:
|
||||||
|
//
|
||||||
|
// * `regex`: A regular expression that is used to extract parameters from paths
|
||||||
|
// that reached this accepting state.
|
||||||
|
// * `handlers`: Information on how to convert the list of captures into calls
|
||||||
|
// to registered handlers with the specified parameters
|
||||||
|
// * `types`: How many static, dynamic or star segments in this route. Used to
|
||||||
|
// decide which route to use if multiple registered routes match a path.
|
||||||
|
//
|
||||||
|
// Currently, State is implemented naively by looping over `nextStates` and
|
||||||
|
// comparing a character specification against a character. A more efficient
|
||||||
|
// implementation would use a hash of keys pointing at one or more next states.
|
||||||
|
|
||||||
|
function State(charSpec) {
|
||||||
|
this.charSpec = charSpec;
|
||||||
|
this.nextStates = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
State.prototype = {
|
||||||
|
get: function(charSpec) {
|
||||||
|
var nextStates = this.nextStates;
|
||||||
|
|
||||||
|
for (var i=0, l=nextStates.length; i<l; i++) {
|
||||||
|
var child = nextStates[i];
|
||||||
|
|
||||||
|
var isEqual = child.charSpec.validChars === charSpec.validChars;
|
||||||
|
isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars;
|
||||||
|
|
||||||
|
if (isEqual) { return child; }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
put: function(charSpec) {
|
||||||
|
var state;
|
||||||
|
|
||||||
|
// If the character specification already exists in a child of the current
|
||||||
|
// state, just return that state.
|
||||||
|
if (state = this.get(charSpec)) { return state; }
|
||||||
|
|
||||||
|
// Make a new state for the character spec
|
||||||
|
state = new State(charSpec);
|
||||||
|
|
||||||
|
// Insert the new state as a child of the current state
|
||||||
|
this.nextStates.push(state);
|
||||||
|
|
||||||
|
// If this character specification repeats, insert the new state as a child
|
||||||
|
// of itself. Note that this will not trigger an infinite loop because each
|
||||||
|
// transition during recognition consumes a character.
|
||||||
|
if (charSpec.repeat) {
|
||||||
|
state.nextStates.push(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the new state
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Find a list of child states matching the next character
|
||||||
|
match: function(ch) {
|
||||||
|
// DEBUG "Processing `" + ch + "`:"
|
||||||
|
var nextStates = this.nextStates,
|
||||||
|
child, charSpec, chars;
|
||||||
|
|
||||||
|
// DEBUG " " + debugState(this)
|
||||||
|
var returned = [];
|
||||||
|
|
||||||
|
for (var i=0, l=nextStates.length; i<l; i++) {
|
||||||
|
child = nextStates[i];
|
||||||
|
|
||||||
|
charSpec = child.charSpec;
|
||||||
|
|
||||||
|
if (typeof (chars = charSpec.validChars) !== 'undefined') {
|
||||||
|
if (chars.indexOf(ch) !== -1) { returned.push(child); }
|
||||||
|
} else if (typeof (chars = charSpec.invalidChars) !== 'undefined') {
|
||||||
|
if (chars.indexOf(ch) === -1) { returned.push(child); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IF DEBUG
|
||||||
|
, debug: function() {
|
||||||
|
var charSpec = this.charSpec,
|
||||||
|
debug = "[",
|
||||||
|
chars = charSpec.validChars || charSpec.invalidChars;
|
||||||
|
|
||||||
|
if (charSpec.invalidChars) { debug += "^"; }
|
||||||
|
debug += chars;
|
||||||
|
debug += "]";
|
||||||
|
|
||||||
|
if (charSpec.repeat) { debug += "+"; }
|
||||||
|
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
END IF **/
|
||||||
|
};
|
||||||
|
|
||||||
|
/** IF DEBUG
|
||||||
|
function debug(log) {
|
||||||
|
console.log(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
function debugState(state) {
|
||||||
|
return state.nextStates.map(function(n) {
|
||||||
|
if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; }
|
||||||
|
return "( " + n.debug() + " <then> " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )";
|
||||||
|
}).join(", ")
|
||||||
|
}
|
||||||
|
END IF **/
|
||||||
|
|
||||||
|
// This is a somewhat naive strategy, but should work in a lot of cases
|
||||||
|
// A better strategy would properly resolve /posts/:id/new and /posts/edit/:id.
|
||||||
|
//
|
||||||
|
// This strategy generally prefers more static and less dynamic matching.
|
||||||
|
// Specifically, it
|
||||||
|
//
|
||||||
|
// * prefers fewer stars to more, then
|
||||||
|
// * prefers using stars for less of the match to more, then
|
||||||
|
// * prefers fewer dynamic segments to more, then
|
||||||
|
// * prefers more static segments to more
|
||||||
|
function sortSolutions(states) {
|
||||||
|
return states.sort(function(a, b) {
|
||||||
|
if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; }
|
||||||
|
|
||||||
|
if (a.types.stars) {
|
||||||
|
if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; }
|
||||||
|
if (a.types.dynamics !== b.types.dynamics) { return b.types.dynamics - a.types.dynamics; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; }
|
||||||
|
if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; }
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recognizeChar(states, ch) {
|
||||||
|
var nextStates = [];
|
||||||
|
|
||||||
|
for (var i=0, l=states.length; i<l; i++) {
|
||||||
|
var state = states[i];
|
||||||
|
|
||||||
|
nextStates = nextStates.concat(state.match(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oCreate = Object.create || function(proto) {
|
||||||
|
function F() {}
|
||||||
|
F.prototype = proto;
|
||||||
|
return new F();
|
||||||
|
};
|
||||||
|
|
||||||
|
function RecognizeResults(queryParams) {
|
||||||
|
this.queryParams = queryParams || {};
|
||||||
|
}
|
||||||
|
RecognizeResults.prototype = oCreate({
|
||||||
|
splice: Array.prototype.splice,
|
||||||
|
slice: Array.prototype.slice,
|
||||||
|
push: Array.prototype.push,
|
||||||
|
length: 0,
|
||||||
|
queryParams: null
|
||||||
|
});
|
||||||
|
|
||||||
|
function findHandler(state, path, queryParams) {
|
||||||
|
var handlers = state.handlers, regex = state.regex;
|
||||||
|
var captures = path.match(regex), currentCapture = 1;
|
||||||
|
var result = new RecognizeResults(queryParams);
|
||||||
|
|
||||||
|
for (var i=0, l=handlers.length; i<l; i++) {
|
||||||
|
var handler = handlers[i], names = handler.names, params = {};
|
||||||
|
|
||||||
|
for (var j=0, m=names.length; j<m; j++) {
|
||||||
|
params[names[j]] = captures[currentCapture++];
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSegment(currentState, segment) {
|
||||||
|
segment.eachChar(function(ch) {
|
||||||
|
var state;
|
||||||
|
|
||||||
|
currentState = currentState.put(ch);
|
||||||
|
});
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main interface
|
||||||
|
|
||||||
|
var RouteRecognizer = function() {
|
||||||
|
this.rootState = new State();
|
||||||
|
this.names = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
RouteRecognizer.prototype = {
|
||||||
|
add: function(routes, options) {
|
||||||
|
var currentState = this.rootState, regex = "^",
|
||||||
|
types = { statics: 0, dynamics: 0, stars: 0 },
|
||||||
|
handlers = [], allSegments = [], name;
|
||||||
|
|
||||||
|
var isEmpty = true;
|
||||||
|
|
||||||
|
for (var i=0, l=routes.length; i<l; i++) {
|
||||||
|
var route = routes[i], names = [];
|
||||||
|
|
||||||
|
var segments = parse(route.path, names, types);
|
||||||
|
|
||||||
|
allSegments = allSegments.concat(segments);
|
||||||
|
|
||||||
|
for (var j=0, m=segments.length; j<m; j++) {
|
||||||
|
var segment = segments[j];
|
||||||
|
|
||||||
|
if (segment instanceof EpsilonSegment) { continue; }
|
||||||
|
|
||||||
|
isEmpty = false;
|
||||||
|
|
||||||
|
// Add a "/" for the new segment
|
||||||
|
currentState = currentState.put({ validChars: "/" });
|
||||||
|
regex += "/";
|
||||||
|
|
||||||
|
// Add a representation of the segment to the NFA and regex
|
||||||
|
currentState = addSegment(currentState, segment);
|
||||||
|
regex += segment.regex();
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = { handler: route.handler, names: names };
|
||||||
|
handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
currentState = currentState.put({ validChars: "/" });
|
||||||
|
regex += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
currentState.handlers = handlers;
|
||||||
|
currentState.regex = new RegExp(regex + "$");
|
||||||
|
currentState.types = types;
|
||||||
|
|
||||||
|
if (name = options && options.as) {
|
||||||
|
this.names[name] = {
|
||||||
|
segments: allSegments,
|
||||||
|
handlers: handlers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handlersFor: function(name) {
|
||||||
|
var route = this.names[name], result = [];
|
||||||
|
if (!route) { throw new Error("There is no route named " + name); }
|
||||||
|
|
||||||
|
for (var i=0, l=route.handlers.length; i<l; i++) {
|
||||||
|
result.push(route.handlers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
hasRoute: function(name) {
|
||||||
|
return !!this.names[name];
|
||||||
|
},
|
||||||
|
|
||||||
|
generate: function(name, params) {
|
||||||
|
var route = this.names[name], output = "";
|
||||||
|
if (!route) { throw new Error("There is no route named " + name); }
|
||||||
|
|
||||||
|
var segments = route.segments;
|
||||||
|
|
||||||
|
for (var i=0, l=segments.length; i<l; i++) {
|
||||||
|
var segment = segments[i];
|
||||||
|
|
||||||
|
if (segment instanceof EpsilonSegment) { continue; }
|
||||||
|
|
||||||
|
output += "/";
|
||||||
|
output += segment.generate(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.charAt(0) !== '/') { output = '/' + output; }
|
||||||
|
|
||||||
|
if (params && params.queryParams) {
|
||||||
|
output += this.generateQueryString(params.queryParams, route.handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
|
||||||
|
generateQueryString: function(params, handlers) {
|
||||||
|
var pairs = [];
|
||||||
|
var keys = [];
|
||||||
|
for(var key in params) {
|
||||||
|
if (params.hasOwnProperty(key)) {
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys.sort();
|
||||||
|
for (var i = 0, len = keys.length; i < len; i++) {
|
||||||
|
key = keys[i];
|
||||||
|
var value = params[key];
|
||||||
|
if (value == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var pair = encodeURIComponent(key);
|
||||||
|
if (isArray(value)) {
|
||||||
|
for (var j = 0, l = value.length; j < l; j++) {
|
||||||
|
var arrayPair = key + '[]' + '=' + encodeURIComponent(value[j]);
|
||||||
|
pairs.push(arrayPair);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pair += "=" + encodeURIComponent(value);
|
||||||
|
pairs.push(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pairs.length === 0) { return ''; }
|
||||||
|
|
||||||
|
return "?" + pairs.join("&");
|
||||||
|
},
|
||||||
|
|
||||||
|
parseQueryString: function(queryString) {
|
||||||
|
var pairs = queryString.split("&"), queryParams = {};
|
||||||
|
for(var i=0; i < pairs.length; i++) {
|
||||||
|
var pair = pairs[i].split('='),
|
||||||
|
key = decodeURIComponent(pair[0]),
|
||||||
|
keyLength = key.length,
|
||||||
|
isArray = false,
|
||||||
|
value;
|
||||||
|
if (pair.length === 1) {
|
||||||
|
value = 'true';
|
||||||
|
} else {
|
||||||
|
//Handle arrays
|
||||||
|
if (keyLength > 2 && key.slice(keyLength -2) === '[]') {
|
||||||
|
isArray = true;
|
||||||
|
key = key.slice(0, keyLength - 2);
|
||||||
|
if(!queryParams[key]) {
|
||||||
|
queryParams[key] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = pair[1] ? decodeURIComponent(pair[1]) : '';
|
||||||
|
}
|
||||||
|
if (isArray) {
|
||||||
|
queryParams[key].push(value);
|
||||||
|
} else {
|
||||||
|
queryParams[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryParams;
|
||||||
|
},
|
||||||
|
|
||||||
|
recognize: function(path) {
|
||||||
|
var states = [ this.rootState ],
|
||||||
|
pathLen, i, l, queryStart, queryParams = {},
|
||||||
|
isSlashDropped = false;
|
||||||
|
|
||||||
|
path = decodeURI(path);
|
||||||
|
|
||||||
|
queryStart = path.indexOf('?');
|
||||||
|
if (queryStart !== -1) {
|
||||||
|
var queryString = path.substr(queryStart + 1, path.length);
|
||||||
|
path = path.substr(0, queryStart);
|
||||||
|
queryParams = this.parseQueryString(queryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG GROUP path
|
||||||
|
|
||||||
|
if (path.charAt(0) !== "/") { path = "/" + path; }
|
||||||
|
|
||||||
|
pathLen = path.length;
|
||||||
|
if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
|
||||||
|
path = path.substr(0, pathLen - 1);
|
||||||
|
isSlashDropped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0, l=path.length; i<l; i++) {
|
||||||
|
states = recognizeChar(states, path.charAt(i));
|
||||||
|
if (!states.length) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// END DEBUG GROUP
|
||||||
|
|
||||||
|
var solutions = [];
|
||||||
|
for (i=0, l=states.length; i<l; i++) {
|
||||||
|
if (states[i].handlers) { solutions.push(states[i]); }
|
||||||
|
}
|
||||||
|
|
||||||
|
states = sortSolutions(solutions);
|
||||||
|
|
||||||
|
var state = solutions[0];
|
||||||
|
|
||||||
|
if (state && state.handlers) {
|
||||||
|
// if a trailing slash was dropped and a star segment is the last segment
|
||||||
|
// specified, put the trailing slash back
|
||||||
|
if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") {
|
||||||
|
path = path + "/";
|
||||||
|
}
|
||||||
|
return findHandler(state, path, queryParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RouteRecognizer.prototype.map = map;
|
||||||
|
|
||||||
|
__exports__["default"] = RouteRecognizer;
|
||||||
|
});
|
||||||
|
define("route-recognizer/dsl",
|
||||||
|
["exports"],
|
||||||
|
function(__exports__) {
|
||||||
|
"use strict";
|
||||||
|
function Target(path, matcher, delegate) {
|
||||||
|
this.path = path;
|
||||||
|
this.matcher = matcher;
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Target.prototype = {
|
||||||
|
to: function(target, callback) {
|
||||||
|
var delegate = this.delegate;
|
||||||
|
|
||||||
|
if (delegate && delegate.willAddRoute) {
|
||||||
|
target = delegate.willAddRoute(this.matcher.target, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matcher.add(this.path, target);
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); }
|
||||||
|
this.matcher.addChild(this.path, target, callback, this.delegate);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function Matcher(target) {
|
||||||
|
this.routes = {};
|
||||||
|
this.children = {};
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher.prototype = {
|
||||||
|
add: function(path, handler) {
|
||||||
|
this.routes[path] = handler;
|
||||||
|
},
|
||||||
|
|
||||||
|
addChild: function(path, target, callback, delegate) {
|
||||||
|
var matcher = new Matcher(target);
|
||||||
|
this.children[path] = matcher;
|
||||||
|
|
||||||
|
var match = generateMatch(path, matcher, delegate);
|
||||||
|
|
||||||
|
if (delegate && delegate.contextEntered) {
|
||||||
|
delegate.contextEntered(target, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(match);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateMatch(startingPath, matcher, delegate) {
|
||||||
|
return function(path, nestedCallback) {
|
||||||
|
var fullPath = startingPath + path;
|
||||||
|
|
||||||
|
if (nestedCallback) {
|
||||||
|
nestedCallback(generateMatch(fullPath, matcher, delegate));
|
||||||
|
} else {
|
||||||
|
return new Target(startingPath + path, matcher, delegate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRoute(routeArray, path, handler) {
|
||||||
|
var len = 0;
|
||||||
|
for (var i=0, l=routeArray.length; i<l; i++) {
|
||||||
|
len += routeArray[i].path.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path.substr(len);
|
||||||
|
var route = { path: path, handler: handler };
|
||||||
|
routeArray.push(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachRoute(baseRoute, matcher, callback, binding) {
|
||||||
|
var routes = matcher.routes;
|
||||||
|
|
||||||
|
for (var path in routes) {
|
||||||
|
if (routes.hasOwnProperty(path)) {
|
||||||
|
var routeArray = baseRoute.slice();
|
||||||
|
addRoute(routeArray, path, routes[path]);
|
||||||
|
|
||||||
|
if (matcher.children[path]) {
|
||||||
|
eachRoute(routeArray, matcher.children[path], callback, binding);
|
||||||
|
} else {
|
||||||
|
callback.call(binding, routeArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__exports__["default"] = function(callback, addRouteCallback) {
|
||||||
|
var matcher = new Matcher();
|
||||||
|
|
||||||
|
callback(generateMatch("", matcher, this.delegate));
|
||||||
|
|
||||||
|
eachRoute([], matcher, function(route) {
|
||||||
|
if (addRouteCallback) { addRouteCallback(this, route); }
|
||||||
|
else { this.add(route); }
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
global.RouteRecognizer = require("route-recognizer")["default"];
|
||||||
|
})(window);
|
Loading…
Reference in New Issue