From a16fc2c14b3f18ab0c725b992a77f767ad5c4d08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 2 Feb 2011 13:38:31 +0100
Subject: [PATCH 001/303] allow empty "data-remote" attribute, e.g. ')).find('form');
+
+ submit(function() {
+ ok(true, 'form with empty "data-remote" attribute is also allowed');
+ });
+});
+
})();
From 6c996cba7ce0b1834eee35564a3af7c7523179a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 2 Feb 2011 13:40:08 +0100
Subject: [PATCH 002/303] fix remote form not submitting when required values
missing
---
src/rails.js | 4 ++--
test/public/test/call-remote-callbacks.js | 3 +++
test/public/test/settings.js | 5 ++++-
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/rails.js b/src/rails.js
index cbb348b1..4dcb3779 100644
--- a/src/rails.js
+++ b/src/rails.js
@@ -119,8 +119,8 @@
var form = $(this), remote = form.attr('data-remote') != undefined;
if (!allowAction(form)) return false;
- // skip other logic when required values are missing, but don't cancel the event
- if (requiredValuesMissing(form)) return;
+ // skip other logic when required values are missing
+ if (requiredValuesMissing(form)) return !remote;
if (remote) {
handleRemote(form);
diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js
index 69870867..f0c00117 100644
--- a/test/public/test/call-remote-callbacks.js
+++ b/test/public/test/call-remote-callbacks.js
@@ -46,6 +46,9 @@ asyncTest('blank required form input field should abort request', 1, function()
.bind('ajax:beforeSend', function() {
ok(false, 'ajax:beforeSend should not run');
})
+ .bind('iframe:loading', function() {
+ ok(false, 'form should not get submitted');
+ })
.trigger('submit');
setTimeout(function() {
diff --git a/test/public/test/settings.js b/test/public/test/settings.js
index b1126cab..616ebc68 100644
--- a/test/public/test/settings.js
+++ b/test/public/test/settings.js
@@ -27,6 +27,8 @@ App.assert_request_path = function(request_env, path) {
equal(request_env['PATH_INFO'], path, 'request should be sent to right url');
};
+// hijacks normal form submit; lets it submit to an iframe to prevent
+// navigating away from the test suite
$(document).bind('submit', function(e) {
if (!e.isDefaultPrevented()) {
var form = $(e.target), action = form.attr('action'),
@@ -36,5 +38,6 @@ $(document).bind('submit', function(e) {
if (action.indexOf('iframe') < 0) form.attr('action', action + '?iframe=true')
form.attr('target', name);
$('#qunit-fixture').append(iframe);
+ $.event.trigger('iframe:loading', { form: form });
}
-})
+});
From 58e20328f68517e1c7c54628de6ec35ce7bd916c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 2 Feb 2011 13:41:44 +0100
Subject: [PATCH 003/303] change "data-method" test to remove unnecessary
teardown block
---
test/public/test/data-method.js | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/test/public/test/data-method.js b/test/public/test/data-method.js
index 5823bc4b..d7be7f8e 100644
--- a/test/public/test/data-method.js
+++ b/test/public/test/data-method.js
@@ -1,18 +1,16 @@
(function(){
-module('data-method', {
- teardown: function() { $(document).unbind('iframe:loaded') }
-});
+module('data-method');
function submit(fn) {
- $(document).bind('iframe:loaded', function(e, data) {
- fn(data);
- start();
- });
-
$('#qunit-fixture').
append($('', { href: '/echo', 'data-method': 'delete', text: 'destroy!' }))
- .find('a').trigger('click');
+ .find('a')
+ .bind('iframe:loaded', function(e, data) {
+ fn(data);
+ start();
+ })
+ .trigger('click');
}
asyncTest('link with "data-method" set to "delete"', 2, function() {
From 9f1d6623f314e70a734594aad7620cd135d6a212 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 2 Feb 2011 13:43:23 +0100
Subject: [PATCH 004/303] fix jQuery UI note
closes #79
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e3fba57b..e75848cb 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ For automated installation, use the "jquery-rails" generator:
# Gemfile
gem 'jquery-rails', '>= 0.2.6'
-And run this command (add `-ui` if you want jQuery UI):
+And run this command (add `--ui` if you want jQuery UI):
$ bundle install
$ rails generate jquery:install
From 8b8375bbcd1695ba8297e398a68dd743cdcdaea9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 2 Feb 2011 14:16:47 +0100
Subject: [PATCH 005/303] thoroughly test arguments to "ajax:*" event handlers
closes #83
---
test/public/test/call-remote-callbacks.js | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js
index f0c00117..d1ae79fa 100644
--- a/test/public/test/call-remote-callbacks.js
+++ b/test/public/test/call-remote-callbacks.js
@@ -74,19 +74,32 @@ asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation',
});
});
-asyncTest('"ajax:beforeSend", "ajax:success" and "ajax:complete" are triggered', 3, function() {
+asyncTest('"ajax:beforeSend", "ajax:success" and "ajax:complete" are triggered', 8, function() {
submit(function(form) {
- form.bind('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') });
- form.bind('ajax:success', function(arg) { ok(true, 'ajax:success') });
+ form.bind('ajax:beforeSend', function(e, xhr, settings) {
+ equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:beforeSend" should be an XHR object');
+ equal(settings.url, '/echo', 'second argument to "ajax:beforeSend" should be a settings object');
+ });
+ form.bind('ajax:success', function(e, data, status, xhr) {
+ ok(data.REQUEST_METHOD, 'first argument to ajax:success should be a data object');
+ equal(status, 'success', 'second argument to ajax:success should be a status string');
+ equal(typeof xhr.getResponseHeader, 'function', 'third argument to "ajax:success" should be an XHR object');
+ });
+ form.bind('ajax:complete', function(e, xhr, status) {
+ equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:complete" should be an XHR object');
+ equal(status, 'success', 'second argument to ajax:complete should be a status string');
+ });
});
});
-asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on error', 4, function() {
+asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on error', 6, function() {
submit(function(form) {
form.attr('action', '/error');
form.bind('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') });
form.bind('ajax:error', function(e, xhr, status, error) {
- ok(true, 'ajax:error');
+ equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:error" should be an XHR object');
+ equal(status, 'error', 'second argument to ajax:error should be a status string');
+ equal(error, 'Forbidden', 'third argument to ajax:error should be an HTTP status response');
// Opera returns "0" for HTTP code
equal(xhr.status, window.opera ? 0 : 403, 'status code should be 403');
});
From e4fabbaa192c8a350b223d74b2ab68d890de3663 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 9 Feb 2011 20:12:33 +0100
Subject: [PATCH 006/303] jQuery 1.4 doesn't supply a third argument to
"ajax:error"
---
test/public/test/call-remote-callbacks.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js
index d1ae79fa..5dddf2bc 100644
--- a/test/public/test/call-remote-callbacks.js
+++ b/test/public/test/call-remote-callbacks.js
@@ -99,7 +99,8 @@ asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on
form.bind('ajax:error', function(e, xhr, status, error) {
equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:error" should be an XHR object');
equal(status, 'error', 'second argument to ajax:error should be a status string');
- equal(error, 'Forbidden', 'third argument to ajax:error should be an HTTP status response');
+ if (jQuery().jquery.indexOf('1.4') === 0) strictEqual(error, undefined)
+ else equal(error, 'Forbidden', 'third argument to ajax:error should be an HTTP status response');
// Opera returns "0" for HTTP code
equal(xhr.status, window.opera ? 0 : 403, 'status code should be 403');
});
From e9311550fdb3afeb2917bcb1fef39767bf715003 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Wed, 9 Feb 2011 20:15:13 +0100
Subject: [PATCH 007/303] send the "X-CSRF-Token" header with every Ajax
request
More info: http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
---
src/rails.js | 20 ++++++++++++++++++++
test/public/test/call-remote.js | 9 +++++++++
test/public/test/data-method.js | 3 ++-
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/src/rails.js b/src/rails.js
index 4dcb3779..d862490f 100644
--- a/src/rails.js
+++ b/src/rails.js
@@ -6,6 +6,26 @@
*/
(function($) {
+ // Make sure that every Ajax request sends the CSRF token
+ function CSRFProtection(fn) {
+ var token = $('meta[name="csrf-token"]').attr('content');
+ if (token) fn(function(xhr) { xhr.setRequestHeader('X-CSRF-Token', token) });
+ }
+ if ($().jquery == '1.5') { // gruesome hack
+ var factory = $.ajaxSettings.xhr;
+ $.ajaxSettings.xhr = function() {
+ var xhr = factory();
+ CSRFProtection(function(setHeader) {
+ var open = xhr.open;
+ xhr.open = function() { open.apply(this, arguments); setHeader(this) };
+ });
+ return xhr;
+ };
+ }
+ else $(document).ajaxSend(function(e, xhr) {
+ CSRFProtection(function(setHeader) { setHeader(xhr) });
+ });
+
// Triggers an event on an element and returns the event result
function fire(obj, name, data) {
var event = new $.Event(name);
diff --git a/test/public/test/call-remote.js b/test/public/test/call-remote.js
index 90fd0154..8e342aa2 100644
--- a/test/public/test/call-remote.js
+++ b/test/public/test/call-remote.js
@@ -83,4 +83,13 @@ asyncTest('allow empty "data-remote" attribute', 1, function() {
});
});
+asyncTest('sends CSRF token in custom header', 1, function() {
+ build_form({ method: 'post' });
+ $('#qunit-fixture').append('');
+
+ submit(function(e, data, status, xhr) {
+ equal(data.HTTP_X_CSRF_TOKEN, 'cf50faa3fe97702ca1ae', 'X-CSRF-Token header should be sent');
+ });
+});
+
})();
diff --git a/test/public/test/data-method.js b/test/public/test/data-method.js
index d7be7f8e..34c282e0 100644
--- a/test/public/test/data-method.js
+++ b/test/public/test/data-method.js
@@ -13,10 +13,11 @@ function submit(fn) {
.trigger('click');
}
-asyncTest('link with "data-method" set to "delete"', 2, function() {
+asyncTest('link with "data-method" set to "delete"', 3, function() {
submit(function(data) {
equal(data.REQUEST_METHOD, 'DELETE');
strictEqual(data.params.authenticity_token, undefined);
+ strictEqual(data.HTTP_X_CSRF_TOKEN, undefined);
});
});
From 764e4e45600ec48b312b4af4d417088384e6cdce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?=
Date: Thu, 10 Feb 2011 13:36:26 +0100
Subject: [PATCH 008/303] fix some of the tests for IE7
---
test/public/test/call-remote-callbacks.js | 8 ++++----
test/public/test/call-remote.js | 2 +-
test/public/test/settings.js | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js
index 5dddf2bc..b078b8f6 100644
--- a/test/public/test/call-remote-callbacks.js
+++ b/test/public/test/call-remote-callbacks.js
@@ -77,16 +77,16 @@ asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation',
asyncTest('"ajax:beforeSend", "ajax:success" and "ajax:complete" are triggered', 8, function() {
submit(function(form) {
form.bind('ajax:beforeSend', function(e, xhr, settings) {
- equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:beforeSend" should be an XHR object');
+ ok(xhr.setRequestHeader, 'first argument to "ajax:beforeSend" should be an XHR object');
equal(settings.url, '/echo', 'second argument to "ajax:beforeSend" should be a settings object');
});
form.bind('ajax:success', function(e, data, status, xhr) {
ok(data.REQUEST_METHOD, 'first argument to ajax:success should be a data object');
equal(status, 'success', 'second argument to ajax:success should be a status string');
- equal(typeof xhr.getResponseHeader, 'function', 'third argument to "ajax:success" should be an XHR object');
+ ok(xhr.getResponseHeader, 'third argument to "ajax:success" should be an XHR object');
});
form.bind('ajax:complete', function(e, xhr, status) {
- equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:complete" should be an XHR object');
+ ok(xhr.getResponseHeader, 'first argument to "ajax:complete" should be an XHR object');
equal(status, 'success', 'second argument to ajax:complete should be a status string');
});
});
@@ -97,7 +97,7 @@ asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on
form.attr('action', '/error');
form.bind('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') });
form.bind('ajax:error', function(e, xhr, status, error) {
- equal(typeof xhr.getResponseHeader, 'function', 'first argument to "ajax:error" should be an XHR object');
+ ok(xhr.getResponseHeader, 'first argument to "ajax:error" should be an XHR object');
equal(status, 'error', 'second argument to ajax:error should be a status string');
if (jQuery().jquery.indexOf('1.4') === 0) strictEqual(error, undefined)
else equal(error, 'Forbidden', 'third argument to ajax:error should be an HTTP status response');
diff --git a/test/public/test/call-remote.js b/test/public/test/call-remote.js
index 8e342aa2..ce73a260 100644
--- a/test/public/test/call-remote.js
+++ b/test/public/test/call-remote.js
@@ -10,7 +10,7 @@ function build_form(attrs) {
module('call-remote');
function submit(fn) {
- $('form[data-remote]')
+ $('form')
.bind('ajax:success', fn)
.bind('ajax:complete', function() { start() })
.trigger('submit');
diff --git a/test/public/test/settings.js b/test/public/test/settings.js
index 616ebc68..2961dfb7 100644
--- a/test/public/test/settings.js
+++ b/test/public/test/settings.js
@@ -33,7 +33,7 @@ $(document).bind('submit', function(e) {
if (!e.isDefaultPrevented()) {
var form = $(e.target), action = form.attr('action'),
name = 'form-frame' + jQuery.guid++,
- iframe = $('