From 02568801e6e34ee1ccbb825c6a10dfab83d1663e Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Fri, 25 Nov 2016 23:42:29 -0500 Subject: Add UJS tests --- actionview/test/ujs/public/test/.eslintrc.yml | 21 + .../test/ujs/public/test/call-remote-callbacks.js | 469 ++++ actionview/test/ujs/public/test/call-remote.js | 247 +++ actionview/test/ujs/public/test/csrf-refresh.js | 24 + actionview/test/ujs/public/test/csrf-token.js | 27 + actionview/test/ujs/public/test/data-confirm.js | 288 +++ .../test/ujs/public/test/data-disable-with.js | 391 ++++ actionview/test/ujs/public/test/data-disable.js | 321 +++ actionview/test/ujs/public/test/data-method.js | 85 + actionview/test/ujs/public/test/data-remote.js | 400 ++++ actionview/test/ujs/public/test/override.js | 56 + actionview/test/ujs/public/test/settings.js | 116 + .../test/ujs/public/vendor/jquery.metadata.js | 122 ++ actionview/test/ujs/public/vendor/qunit.css | 237 ++ actionview/test/ujs/public/vendor/qunit.js | 2288 ++++++++++++++++++++ 15 files changed, 5092 insertions(+) create mode 100644 actionview/test/ujs/public/test/.eslintrc.yml create mode 100644 actionview/test/ujs/public/test/call-remote-callbacks.js create mode 100644 actionview/test/ujs/public/test/call-remote.js create mode 100644 actionview/test/ujs/public/test/csrf-refresh.js create mode 100644 actionview/test/ujs/public/test/csrf-token.js create mode 100644 actionview/test/ujs/public/test/data-confirm.js create mode 100644 actionview/test/ujs/public/test/data-disable-with.js create mode 100644 actionview/test/ujs/public/test/data-disable.js create mode 100644 actionview/test/ujs/public/test/data-method.js create mode 100644 actionview/test/ujs/public/test/data-remote.js create mode 100644 actionview/test/ujs/public/test/override.js create mode 100644 actionview/test/ujs/public/test/settings.js create mode 100644 actionview/test/ujs/public/vendor/jquery.metadata.js create mode 100644 actionview/test/ujs/public/vendor/qunit.css create mode 100644 actionview/test/ujs/public/vendor/qunit.js (limited to 'actionview/test/ujs/public') diff --git a/actionview/test/ujs/public/test/.eslintrc.yml b/actionview/test/ujs/public/test/.eslintrc.yml new file mode 100644 index 0000000000..06d7dd36ea --- /dev/null +++ b/actionview/test/ujs/public/test/.eslintrc.yml @@ -0,0 +1,21 @@ +env: + browser: true +extends: eslint:recommended +rules: + no-undef: off + no-unused-vars: off + indent: off + linebreak-style: ['error', 'unix'] + quotes: ['error', 'single'] + semi: ['error', 'never'] + no-shadow: ['error'] # Prevent potential errors + no-console: 'off' + # styles + space-before-function-paren: ['error', 'never'] + space-before-blocks: 'error' + brace-style: ['error', '1tbs', { allowSingleLine: true }] + key-spacing: 'error' + array-bracket-spacing: 'error' + comma-spacing: 'error' + comma-dangle: 'off' + eol-last: 'error' diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js new file mode 100644 index 0000000000..082d10bfbd --- /dev/null +++ b/actionview/test/ujs/public/test/call-remote-callbacks.js @@ -0,0 +1,469 @@ +(function() { + +module('call-remote-callbacks', { + setup: function() { + $('#qunit-fixture').append($('
', { + action: '/echo', method: 'get', 'data-remote': 'true' + })) + }, + teardown: function() { + $(document).undelegate('form[data-remote]', 'ajax:beforeSend') + $(document).undelegate('form[data-remote]', 'ajax:before') + $(document).undelegate('form[data-remote]', 'ajax:send') + $(document).undelegate('form[data-remote]', 'ajax:complete') + $(document).undelegate('form[data-remote]', 'ajax:success') + $(document).unbind('ajaxStop') + $(document).unbind('iframe:loading') + } +}) + +function start_after_submit(form) { + form.bindNative('ajax:complete', function() { + ok(true, 'ajax:complete') + start() + }) +} + +function submit(fn) { + var form = $('form') + start_after_submit(form) + + if (fn) fn(form) + form.triggerNative('submit') +} + +function submit_with_button(submit_button) { + var form = $('form') + start_after_submit(form) + + submit_button.triggerNative('click') +} + +asyncTest('modifying form fields with "ajax:before" sends modified data in request', 4, function() { + $('form[data-remote]') + .append($('')) + .append($('')) + .bindNative('ajax:before', function() { + var form = $(this) + form + .append($('', {name: 'other_user_name', value: 'jonathan'})) + .find('input[name="removed_user_name"]').remove() + form + .find('input[name="user_name"]').val('steve') + }) + + submit(function(form) { + form.bindNative('ajax:success', function(e, data, status, xhr) { + equal(data.params.user_name, 'steve', 'modified field value should have been submitted') + equal(data.params.other_user_name, 'jonathan', 'added field value should have been submitted') + equal(data.params.removed_user_name, undefined, 'removed field value should be undefined') + }) + }) +}) + +asyncTest('modifying data("type") with "ajax:before" requests new dataType in request', 2, function() { + $('form[data-remote]').data('type', 'html') + .bindNative('ajax:before', function() { + this.setAttribute('data-type', 'xml') + }) + + submit(function(form) { + form.bindNative('ajax:beforeSend', function(e, xhr, settings) { + equal(settings.dataType, 'xml', 'modified dataType should have been requested') + }) + }) +}) + +asyncTest('setting data("with-credentials",true) with "ajax:before" uses new setting in request', 2, function() { + $('form[data-remote]').data('with-credentials', false) + .bindNative('ajax:before', function() { + this.setAttribute('data-with-credentials', true) + }) + + submit(function(form) { + form.bindNative('ajax:beforeSend', function(e, xhr, settings) { + equal(settings.withCredentials, true, 'setting modified in ajax:before should have forced withCredentials request') + }) + }) +}) + +asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function() { + submit(function(form) { + form.bindNative('ajax:beforeSend', function() { + ok(true, 'aborting request in ajax:beforeSend') + return false + }) + form.unbind('ajax:send').bindNative('ajax:send', function() { + ok(false, 'ajax:send should not run') + }) + form.unbind('ajax:complete').bindNative('ajax:complete', function() { + ok(false, 'ajax:complete should not run') + }) + form.bindNative('ajax:error', function(e, xhr, status, error) { + ok(false, 'ajax:error should not run') + }) + $(document).bindNative('ajaxStop', function() { + start() + }) + }) +}) + +asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 5, function() { + $(document).bind('iframe:loading', function() { + ok(false, 'form should not get submitted') + }) + + var form = $('form[data-remote]') + .append($('')) + .append($('')) + .bindNative('ajax:beforeSend', function() { + ok(false, 'ajax:beforeSend should not run') + }) + .bindNative('ajax:aborted:required', function(e, data) { + data = $(data) + ok(data.length == 2, 'ajax:aborted:required event is passed all blank required inputs (jQuery objects)') + ok(data.first().is('input[name="user_name"]'), 'ajax:aborted:required adds blank required input to data') + ok(data.last().is('textarea[name="user_bio"]'), 'ajax:aborted:required adds blank required textarea to data') + ok(true, 'ajax:aborted:required should run') + }) + .triggerNative('submit') + + setTimeout(function() { + form.find('input[required],textarea[required]').val('Tyler') + form.unbind('ajax:beforeSend') + submit() + }, 13) +}) + +asyncTest('blank required form input for non-remote form should abort normal submission', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .removeAttr('data-remote') + .bindNative('ujs:everythingStopped', function() { + ok(true, 'ujs:everythingStopped should run') + }) + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +asyncTest('form should be submitted with blank required fields if handler is bound to "ajax:aborted:required" event that returns false', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .bindNative('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run') + }) + .bindNative('ajax:aborted:required', function() { + return false + }) + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +asyncTest('disabled fields should not be included in blank required check', 2, function() { + var form = $('form[data-remote]') + .append($('')) + .append($('')) + .bindNative('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run') + }) + .bindNative('ajax:aborted:required', function() { + ok(false, 'ajax:aborted:required should not run') + }) + + submit() +}) + +asyncTest('form should be submitted with blank required fields if it has the "novalidate" attribute', 2, function() { + var form = $('form[data-remote]') + .append($('')) + .attr('novalidate', 'novalidate') + .bindNative('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run') + }) + .bindNative('ajax:aborted:required', function() { + ok(false, 'ajax:aborted:required should not run') + }) + + submit() +}) + +asyncTest('form should be submitted with blank required fields if the button has the "formnovalidate" attribute', 2, function() { + var submit_button = $('') + var form = $('form[data-remote]') + .append($('')) + .append(submit_button) + .bindNative('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run') + }) + .bindNative('ajax:aborted:required', function() { + ok(false, 'ajax:aborted:required should not run') + }) + + submit_with_button(submit_button) +}) + +asyncTest('blank required form input for non-remote form with "novalidate" attribute should not abort normal submission', 1, function() { + $(document).bind('iframe:loading', function() { + ok(true, 'form should get submitted') + }) + + var form = $('form[data-remote]') + .append($('')) + .removeAttr('data-remote') + .attr('novalidate', 'novalidate') + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +asyncTest('unchecked required checkbox should abort form submission', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .removeAttr('data-remote') + .bindNative('ujs:everythingStopped', function() { + ok(true, 'ujs:everythingStopped should run') + }) + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +asyncTest('unchecked required radio should abort form submission', 3, function() { + var form = $('form[data-remote]') + .append($('')) + .append($('')) + .removeAttr('data-remote') + .bindNative('ujs:everythingStopped', function() { + ok(true, 'ujs:everythingStopped should run') + }) + .bindNative('ajax:aborted:required', function(e, data) { + data = $(data) + equal(data.length, 2, 'blankRequiredInputs should include both radios') + ok(data.first().is('input[type=radio][value=1]'), 'blankRequiredInputs[0] should be the first radio') + }) + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +asyncTest('required radio should only require one to be checked', 1, function() { + $(document).bind('iframe:loading', function() { + ok(true, 'form should get submitted') + }) + + var form = $('form[data-remote]') + .append($('')) + .append($('')) + .removeAttr('data-remote') + .bindNative('ujs:everythingStopped', function() { + ok(false, 'ujs:everythingStopped should not run') + }) + .find('#checkme').prop('checked', true) + .end() + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +asyncTest('required radio should only require one to be checked if not all radios are required', 1, function() { + $(document).bind('iframe:loading', function() { + ok(true, 'form should get submitted') + }) + + var form = $('form[data-remote]') + // Check the radio that is not required + .append($('')) + // Check the radio that is not required + .append($('')) + // Only one needs to be required + .append($('')) + .removeAttr('data-remote') + .bindNative('ujs:everythingStopped', function() { + ok(false, 'ujs:everythingStopped should not run') + }) + .find('#checkme').prop('checked', true) + .end() + .triggerNative('submit') + + setTimeout(function() { + start() + }, 13) +}) + +function skipIt() { + // This test cannot work due to the security feature in browsers which makes the value + // attribute of file input fields readonly, so it cannot be set with default value. + // This is what the test would look like though if browsers let us automate this test. + asyncTest('non-blank file form input field should abort remote request, but submit normally', 5, function() { + var form = $('form[data-remote]') + .append($('')) + .bindNative('ajax:beforeSend', function() { + ok(false, 'ajax:beforeSend should not run') + }) + .bind('iframe:loading', function() { + ok(true, 'form should get submitted') + }) + .bindNative('ajax:aborted:file', function(e, data) { + ok(data.length == 1, 'ajax:aborted:file event is passed all non-blank file inputs (jQuery objects)') + ok(data.first().is('input[name="attachment"]'), 'ajax:aborted:file adds non-blank file input to data') + ok(true, 'ajax:aborted:file event should run') + }) + .triggerNative('submit') + + setTimeout(function() { + form.find('input[type="file"]').val('') + form.unbind('ajax:beforeSend') + submit() + }, 13) + }) + + asyncTest('file form input field should not abort remote request if file form input does not have a name attribute', 5, function() { + var form = $('form[data-remote]') + .append($('')) + .bindNative('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run') + }) + .bind('iframe:loading', function() { + ok(true, 'form should get submitted') + }) + .bindNative('ajax:aborted:file', function(e, data) { + ok(false, 'ajax:aborted:file should not run') + }) + .triggerNative('submit') + + setTimeout(function() { + form.find('input[type="file"]').val('') + form.unbind('ajax:beforeSend') + submit() + }, 13) + }) + + asyncTest('blank file input field should abort request entirely if handler bound to "ajax:aborted:file" event that returns false', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .bindNative('ajax:beforeSend', function() { + ok(false, 'ajax:beforeSend should not run') + }) + .bind('iframe:loading', function() { + ok(false, 'form should not get submitted') + }) + .bindNative('ajax:aborted:file', function() { + return false + }) + .triggerNative('submit') + + setTimeout(function() { + form.find('input[type="file"]').val('') + form.unbind('ajax:beforeSend') + submit() + }, 13) + }) +} + +asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation', 1, function() { + $(document).delegate('form[data-remote]', 'ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend observed with event delegation') + return false + }) + + submit(function(form) { + form.unbind('ajax:send').bindNative('ajax:send', function() { + ok(false, 'ajax:send should not run') + }) + form.unbind('ajax:complete').bindNative('ajax:complete', function() { + ok(false, 'ajax:complete should not run') + }) + $(document).bindNative('ajaxStop', function() { + start() + }) + }) +}) + +asyncTest('"ajax:beforeSend", "ajax:send", "ajax:success" and "ajax:complete" are triggered', 9, function() { + submit(function(form) { + form.bindNative('ajax:beforeSend', function(e, xhr, settings) { + 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.bindNative('ajax:send', function(e, xhr) { + ok(xhr.abort, 'first argument to "ajax:send" should be an XHR object') + }) + form.bindNative('ajax:success', function(e, data, status, xhr) { + ok(data.REQUEST_METHOD, 'first argument to ajax:success should be a data object') + equal(status, 'OK', 'second argument to ajax:success should be a status string') + ok(xhr.getResponseHeader, 'third argument to "ajax:success" should be an XHR object') + }) + form.bindNative('ajax:complete', function(e, xhr, status) { + ok(xhr.getResponseHeader, 'first argument to "ajax:complete" should be an XHR object') + equal(status, 'OK', 'second argument to ajax:complete should be a status string') + }) + }) +}) + +if(window.phantom !== undefined) { + asyncTest('"ajax:beforeSend", "ajax:send", "ajax:error" and "ajax:complete" are triggered on error', 7, function() { + submit(function(form) { + form.attr('action', '/error') + form.bindNative('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') }) + form.bindNative('ajax:send', function(arg) { ok(true, 'ajax:send') }) + form.bindNative('ajax:error', function(e, xhr, status, error) { + 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') + // Firefox 8 returns "Forbidden " with trailing space + equal($.trim(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') + }) + }) + }) +} + +// IF THIS TEST IS FAILING, TRY INCREASING THE TIMEOUT AT THE BOTTOM TO > 100 +asyncTest('binding to ajax callbacks via .delegate() triggers handlers properly', 4, function() { + $(document) + .delegate('form[data-remote]', 'ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend handler is triggered') + }) + .delegate('form[data-remote]', 'ajax:send', function() { + ok(true, 'ajax:send handler is triggered') + }) + .delegate('form[data-remote]', 'ajax:complete', function() { + ok(true, 'ajax:complete handler is triggered') + }) + .delegate('form[data-remote]', 'ajax:success', function() { + ok(true, 'ajax:success handler is triggered') + }) + $('form[data-remote]').triggerNative('submit') + + setTimeout(function() { + start() + }, 63) +}) + +asyncTest('binding to ajax:send event to call jquery methods on ajax object', 2, function() { + $('form[data-remote]') + .bindNative('ajax:send', function(e, xhr) { + ok(true, 'event should fire') + equal(typeof(xhr.abort), 'function', 'event should pass jqXHR object') + xhr.abort() + }) + .triggerNative('submit') + + setTimeout(function() { start() }, 35) +}) + +})() diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js new file mode 100644 index 0000000000..dbeb8ad832 --- /dev/null +++ b/actionview/test/ujs/public/test/call-remote.js @@ -0,0 +1,247 @@ +(function() { + +function buildForm(attrs) { + attrs = $.extend({ action: '/echo', 'data-remote': 'true' }, attrs) + + $('#qunit-fixture').append($('', attrs)) + .find('form').append($('')) +} + +module('call-remote') + +function submit(fn) { + $('form') + .bindNative('ajax:success', fn) + .bindNative('ajax:complete', function() { start() }) + .triggerNative('submit') +} + +asyncTest('form method is read from "method" and not from "data-method"', 1, function() { + buildForm({ method: 'post', 'data-method': 'get' }) + + submit(function(e, data, status, xhr) { + App.assertPostRequest(data) + }) +}) + +asyncTest('form method is not read from "data-method" attribute in case of missing "method"', 1, function() { + buildForm({ 'data-method': 'put' }) + + submit(function(e, data, status, xhr) { + App.assertGetRequest(data) + }) +}) + +asyncTest('form method is read from submit button "formmethod" if submit is triggered by that button', 1, function() { + var submitButton = $('') + buildForm({ method: 'post' }) + + $('#qunit-fixture').find('form').append(submitButton) + .bindNative('ajax:success', function(e, data, status, xhr) { + App.assertGetRequest(data) + }) + .bindNative('ajax:complete', function() { start() }) + + submitButton.triggerNative('click') +}) + +asyncTest('form default method is GET', 1, function() { + buildForm() + + submit(function(e, data, status, xhr) { + App.assertGetRequest(data) + }) +}) + +asyncTest('form url is picked up from "action"', 1, function() { + buildForm({ method: 'post' }) + + submit(function(e, data, status, xhr) { + App.assertRequestPath(data, '/echo') + }) +}) + +asyncTest('form url is read from "action" not "href"', 1, function() { + buildForm({ method: 'post', href: '/echo2' }) + + submit(function(e, data, status, xhr) { + App.assertRequestPath(data, '/echo') + }) +}) + +asyncTest('form url is read from submit button "formaction" if submit is triggered by that button', 1, function() { + var submitButton = $('') + buildForm({ method: 'post', href: '/echo2' }) + + $('#qunit-fixture').find('form').append(submitButton) + .bindNative('ajax:success', function(e, data, status, xhr) { + App.assertRequestPath(data, '/echo') + }) + .bindNative('ajax:complete', function() { start() }) + + submitButton.triggerNative('click') +}) + +asyncTest('prefer JS, but accept any format', 1, function() { + buildForm({ method: 'post' }) + + submit(function(e, data, status, xhr) { + var accept = data.HTTP_ACCEPT + ok(accept.match(/text\/javascript.+\*\/\*/), 'Accept: ' + accept) + }) +}) + +asyncTest('JS code should be executed', 1, function() { + buildForm({ method: 'post', 'data-type': 'script' }) + + $('form').append('') + $('form').append('') + + submit() +}) + +asyncTest('XML document should be parsed', 1, function() { + buildForm({ method: 'post', 'data-type': 'html' }) + + $('form').append('') + $('form').append('') + + submit(function(e, data, status, xhr) { + ok(data instanceof Document, 'returned data should be an XML document') + }) +}) + +asyncTest('accept application/json if "data-type" is json', 1, function() { + buildForm({ method: 'post', 'data-type': 'json' }) + + submit(function(e, data, status, xhr) { + equal(data.HTTP_ACCEPT, 'application/json, text/javascript, */*; q=0.01') + }) +}) + +asyncTest('allow empty "data-remote" attribute', 1, function() { + var form = $('#qunit-fixture').append($('')).find('form') + + submit(function() { + ok(true, 'form with empty "data-remote" attribute is also allowed') + }) +}) + +asyncTest('query string in form action should be stripped in a GET request in normal submit', 1, function() { + buildForm({ action: '/echo?param1=abc', 'data-remote': 'false' }) + + $(document).one('iframe:loaded', function(e, data) { + equal(data.params.param1, undefined, '"param1" should not be passed to server') + start() + }) + + $('#qunit-fixture form').triggerNative('submit') +}) + +asyncTest('query string in form action should be stripped in a GET request in ajax submit', 1, function() { + buildForm({ action: '/echo?param1=abc' }) + + submit(function(e, data, status, xhr) { + equal(data.params.param1, undefined, '"param1" should not be passed to server') + }) +}) + +asyncTest('query string in form action should not be stripped in a POST request in normal submit', 1, function() { + buildForm({ action: '/echo?param1=abc', method: 'post', 'data-remote': 'false' }) + + $(document).one('iframe:loaded', function(e, data) { + equal(data.params.param1, 'abc', '"param1" should be passed to server') + start() + }) + + $('#qunit-fixture form').triggerNative('submit') +}) + +asyncTest('query string in form action should not be stripped in a POST request in ajax submit', 1, function() { + buildForm({ action: '/echo?param1=abc', method: 'post' }) + + submit(function(e, data, status, xhr) { + equal(data.params.param1, 'abc', '"param1" should be passed to server') + }) +}) + +asyncTest('allow empty form "action"', 1, function() { + var currentLocation, ajaxLocation + + buildForm({ action: '' }) + + $('#qunit-fixture').find('form') + .bindNative('ajax:beforeSend', function(e, xhr, settings) { + // Get current location (the same way jQuery does) + try { + currentLocation = location.href + } catch(err) { + currentLocation = document.createElement( 'a' ) + currentLocation.href = '' + currentLocation = currentLocation.href + } + currentLocation = currentLocation.replace(/\?.*$/, '') + + // Actual location (strip out settings.data that jQuery serializes and appends) + // HACK: can no longer use settings.data below to see what was appended to URL, as of + // jQuery 1.6.3 (see http://bugs.jquery.com/ticket/10202 and https://github.com/jquery/jquery/pull/544) + ajaxLocation = settings.url.replace('user_name=john', '').replace(/&$/, '').replace(/\?$/, '') + equal(ajaxLocation.match(/^(.*)/)[1], currentLocation, 'URL should be current page by default') + + // Prevent the request from actually getting sent to the current page and + // causing an error. + return false + }) + .triggerNative('submit') + + setTimeout(function() { start() }, 13) +}) + +asyncTest('sends CSRF token in custom header', 1, function() { + buildForm({ 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') + }) +}) + +asyncTest('intelligently guesses crossDomain behavior when target URL has a different protocol and/or hostname', 1, function() { + + // Don't set data-cross-domain here, just set action to be a different domain than localhost + buildForm({ action: 'http://www.alfajango.com' }) + $('#qunit-fixture').append('') + + $('#qunit-fixture').find('form') + .bindNative('ajax:beforeSend', function(evt, req, settings) { + + equal(settings.crossDomain, true, 'crossDomain should be set to true') + + // prevent request from actually getting sent off-domain + return false + }) + .triggerNative('submit') + + setTimeout(function() { start() }, 13) +}) + +asyncTest('intelligently guesses crossDomain behavior when target URL consists of only a path', 1, function() { + + // Don't set data-cross-domain here, just set action to be a different domain than localhost + buildForm({ action: '/just/a/path' }) + $('#qunit-fixture').append('') + + $('#qunit-fixture').find('form') + .bindNative('ajax:beforeSend', function(evt, req, settings) { + + equal(settings.crossDomain, false, 'crossDomain should be set to false') + + // prevent request from actually getting sent off-domain + return false + }) + .triggerNative('submit') + + setTimeout(function() { start() }, 13) +}) + +})() diff --git a/actionview/test/ujs/public/test/csrf-refresh.js b/actionview/test/ujs/public/test/csrf-refresh.js new file mode 100644 index 0000000000..e302042542 --- /dev/null +++ b/actionview/test/ujs/public/test/csrf-refresh.js @@ -0,0 +1,24 @@ +(function() { + +module('csrf-refresh', {}) + +asyncTest('refresh all csrf tokens', 1, function() { + var correctToken = 'cf50faa3fe97702ca1ae' + + var form = $('') + var input = $('').attr({ type: 'hidden', name: 'authenticity_token', id: 'authenticity_token', value: 'foo' }) + input.appendTo(form) + + $('#qunit-fixture') + .append('') + .append('') + .append(form) + + $.rails.refreshCSRFTokens() + currentToken = $('#qunit-fixture #authenticity_token').val() + + start() + equal(currentToken, correctToken) +}) + +})() diff --git a/actionview/test/ujs/public/test/csrf-token.js b/actionview/test/ujs/public/test/csrf-token.js new file mode 100644 index 0000000000..388b40e057 --- /dev/null +++ b/actionview/test/ujs/public/test/csrf-token.js @@ -0,0 +1,27 @@ +(function() { + +module('csrf-token', {}) + +asyncTest('find csrf token', 1, function() { + var correctToken = 'cf50faa3fe97702ca1ae' + + $('#qunit-fixture').append('') + + currentToken = $.rails.csrfToken() + + start() + equal(currentToken, correctToken) +}) + +asyncTest('find csrf param', 1, function() { + var correctParam = 'authenticity_token' + + $('#qunit-fixture').append('') + + currentParam = $.rails.csrfParam() + + start() + equal(currentParam, correctParam) +}) + +})() diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js new file mode 100644 index 0000000000..28190c2250 --- /dev/null +++ b/actionview/test/ujs/public/test/data-confirm.js @@ -0,0 +1,288 @@ +module('data-confirm', { + setup: function() { + $('#qunit-fixture').append($('', { + href: '/echo', + 'data-remote': 'true', + 'data-confirm': 'Are you absolutely sure?', + text: 'my social security number' + })) + + $('#qunit-fixture').append($('') + form.append(button) + + App.checkEnabledState(button, 'Submit') + + form.bindNative('ajax:success', function(e, data) { + setTimeout(function() { + App.checkEnabledState(button, 'Submit') + start() + }, 13) + }) + form.triggerNative('submit') + + App.checkDisabledState(button, 'submitting ...') +}) + +asyncTest('form input[type=submit][data-disable-with] disables', 6, function() { + var form = $('form:not([data-remote])'), input = form.find('input[type=submit]') + + App.checkEnabledState(input, 'Submit') + + $(document).bind('iframe:loaded', function(e, data) { + setTimeout(function() { + App.checkDisabledState(input, 'submitting ...') + start() + }, 30) + }) + form.triggerNative('submit') + + setTimeout(function() { + App.checkDisabledState(input, 'submitting ...') + }, 30) +}) + +test('form input[type=submit][data-disable-with] re-enables when `pageshow` event is triggered', function() { + var form = $('form:not([data-remote])'), input = form.find('input[type=submit]') + + App.checkEnabledState(input, 'Submit') + + // Emulate the disabled state without submitting the form at all, what is the + // state after going back on firefox after submitting a form. + // + // See https://github.com/rails/jquery-ujs/issues/357 + $.rails.disableElement(form[0]) + + App.checkDisabledState(input, 'submitting ...') + + $(window).triggerNative('pageshow') + + App.checkEnabledState(input, 'Submit') +}) + +asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced in ajax callback', 2, function() { + var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html() + + form.bindNative('ajax:success', function() { + form.html(origFormContents) + + setTimeout(function() { + var input = form.find('input[type=submit]') + App.checkEnabledState(input, 'Submit') + start() + }, 30) + }).triggerNative('submit') +}) + +asyncTest('form[data-remote] input[data-disable-with] is replaced with disabled field in ajax callback', 2, function() { + var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'), + newDisabledInput = input.clone().attr('disabled', 'disabled') + + form.bindNative('ajax:success', function() { + input.replaceWith(newDisabledInput) + + setTimeout(function() { + App.checkEnabledState(newDisabledInput, 'Submit') + start() + }, 30) + }).triggerNative('submit') +}) + +asyncTest('form input[type=submit][data-disable-with] using "form" attribute disables', 6, function() { + var form = $('#not_remote'), input = $('input[form=not_remote]') + App.checkEnabledState(input, 'Form Attr Submit') + + $(document).bind('iframe:loaded', function(e, data) { + setTimeout(function() { + App.checkDisabledState(input, 'form attr submitting') + start() + }, 30) + }) + form.triggerNative('submit') + + setTimeout(function() { + App.checkDisabledState(input, 'form attr submitting') + }, 30) + +}) + +asyncTest('form[data-remote] textarea[data-disable-with] attribute', 3, function() { + var form = $('form[data-remote]'), + textarea = $('').appendTo(form) + + form.bindNative('ajax:success', function(e, data) { + setTimeout(function() { + equal(data.params.user_bio, 'born, lived, died.') + start() + }, 13) + }) + form.triggerNative('submit') + + App.checkDisabledState(textarea, 'processing ...') +}) + +asyncTest('a[data-disable-with] disables', 4, function() { + var link = $('a[data-disable-with]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click') + App.checkDisabledState(link, 'clicking...') + start() +}) + +test('a[data-disable-with] re-enables when `pageshow` event is triggered', function() { + var link = $('a[data-disable-with]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click') + App.checkDisabledState(link, 'clicking...') + + $(window).triggerNative('pageshow') + App.checkEnabledState(link, 'Click me') +}) + +asyncTest('a[data-remote][data-disable-with] disables and re-enables', 6, function() { + var link = $('a[data-disable-with]').attr('data-remote', true) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(link, 'clicking...') + }) + .bindNative('ajax:complete', function() { + setTimeout( function() { + App.checkEnabledState(link, 'Click me') + start() + }, 15) + }) + .triggerNative('click') +}) + +asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:before` event is cancelled', 6, function() { + var link = $('a[data-disable-with]').attr('data-remote', true) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:before', function() { + App.checkDisabledState(link, 'clicking...') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(link, 'Click me') + start() + }, 30) +}) + +asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:beforeSend` event is cancelled', 6, function() { + var link = $('a[data-disable-with]').attr('data-remote', true) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(link, 'clicking...') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(link, 'Click me') + start() + }, 30) +}) + +asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:error` event is triggered', 6, function() { + var link = $('a[data-disable-with]').attr('data-remote', true).attr('href', '/error') + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(link, 'clicking...') + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(link, 'Click me') + start() + }, 30) +}) + +asyncTest('form[data-remote] input|button|textarea[data-disable-with] does not disable when `ajax:beforeSend` event is cancelled', 8, function() { + var form = $('form[data-remote]'), + input = form.find('input:text'), + button = $('').appendTo(form), + textarea = $('').appendTo(form), + submit = $('').appendTo(form) + + form + .bindNative('ajax:beforeSend', function() { + return false + }) + .triggerNative('submit') + + App.checkEnabledState(input, 'john') + App.checkEnabledState(button, 'Submit') + App.checkEnabledState(textarea, 'born, lived, died.') + App.checkEnabledState(submit, 'Submit') + + start() +}) + +asyncTest('ctrl-clicking on a link does not disables the link', 6, function() { + var link = $('a[data-disable-with]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { metaKey: true }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { metaKey: true }) + App.checkEnabledState(link, 'Click me') + start() +}) + +asyncTest('button[data-remote][data-disable-with] disables and re-enables', 6, function() { + var button = $('button[data-remote][data-disable-with]') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:send', function() { + App.checkDisabledState(button, 'clicking...') + }) + .bindNative('ajax:complete', function() { + setTimeout( function() { + App.checkEnabledState(button, 'Click me') + start() + }, 15) + }) + .triggerNative('click') +}) + +asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:before` event is cancelled', 6, function() { + var button = $('button[data-remote][data-disable-with]') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:before', function() { + App.checkDisabledState(button, 'clicking...') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(button, 'Click me') + start() + }, 30) +}) + +asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:beforeSend` event is cancelled', 6, function() { + var button = $('button[data-remote][data-disable-with]') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(button, 'clicking...') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(button, 'Click me') + start() + }, 30) +}) + +asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:error` event is triggered', 6, function() { + var button = $('a[data-disable-with]').attr('data-remote', true).attr('href', '/error') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:send', function() { + App.checkDisabledState(button, 'clicking...') + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(button, 'Click me') + start() + }, 30) +}) diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js new file mode 100644 index 0000000000..ccc38cf9ae --- /dev/null +++ b/actionview/test/ujs/public/test/data-disable.js @@ -0,0 +1,321 @@ +module('data-disable', { + setup: function() { + $('#qunit-fixture').append($('', { + action: '/echo', + 'data-remote': 'true', + method: 'post' + })) + .find('form') + .append($('')) + + $('#qunit-fixture').append($('', { + action: '/echo', + method: 'post' + })) + .find('form:last') + // WEEIRDD: the form won't submit to an iframe if the button is name="submit" (??!) + .append($('')) + + $('#qunit-fixture').append($('', { + text: 'Click me', + href: '/echo', + 'data-disable': 'true' + })) + + $('#qunit-fixture').append($('') + form.append(button) + + App.checkEnabledState(button, 'Submit') + + form.bindNative('ajax:success', function(e, data) { + setTimeout(function() { + App.checkEnabledState(button, 'Submit') + start() + }, 13) + }) + form.triggerNative('submit') + + App.checkDisabledState(button, 'Submit') + equal(button.data('ujs:enable-with'), undefined) +}) + +asyncTest('form input[type=submit][data-disable] disables', 6, function() { + var form = $('form:not([data-remote])'), input = form.find('input[type=submit]') + + App.checkEnabledState(input, 'Submit') + + // WEEIRDD: attaching this handler makes the test work in IE7 + $(document).bind('iframe:loading', function(e, f) {}) + + $(document).bind('iframe:loaded', function(e, data) { + setTimeout(function() { + App.checkDisabledState(input, 'Submit') + start() + }, 30) + }) + form.triggerNative('submit') + + setTimeout(function() { + App.checkDisabledState(input, 'Submit') + }, 30) +}) + +asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in ajax callback', 2, function() { + var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html() + + form.bindNative('ajax:success', function() { + form.html(origFormContents) + + setTimeout(function() { + var input = form.find('input[type=submit]') + App.checkEnabledState(input, 'Submit') + start() + }, 30) + }).triggerNative('submit') +}) + +asyncTest('form[data-remote] input[data-disable] is replaced with disabled field in ajax callback', 2, function() { + var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'), + newDisabledInput = input.clone().attr('disabled', 'disabled') + + form.bindNative('ajax:success', function() { + input.replaceWith(newDisabledInput) + + setTimeout(function() { + App.checkEnabledState(newDisabledInput, 'Submit') + start() + }, 30) + }).triggerNative('submit') +}) + +asyncTest('form[data-remote] textarea[data-disable] attribute', 3, function() { + var form = $('form[data-remote]'), + textarea = $('').appendTo(form) + + form.bindNative('ajax:success', function(e, data) { + setTimeout(function() { + equal(data.params.user_bio, 'born, lived, died.') + start() + }, 13) + }) + form.triggerNative('submit') + + App.checkDisabledState(textarea, 'born, lived, died.') +}) + +asyncTest('a[data-disable] disables', 5, function() { + var link = $('a[data-disable]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click') + App.checkDisabledState(link, 'Click me') + equal(link.data('ujs:enable-with'), undefined) + start() +}) + +asyncTest('a[data-remote][data-disable] disables and re-enables', 6, function() { + var link = $('a[data-disable]').attr('data-remote', true) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:send', function() { + App.checkDisabledState(link, 'Click me') + }) + .bindNative('ajax:complete', function() { + setTimeout( function() { + App.checkEnabledState(link, 'Click me') + start() + }, 15) + }) + .triggerNative('click') +}) + +asyncTest('a[data-remote][data-disable] re-enables when `ajax:before` event is cancelled', 6, function() { + var link = $('a[data-disable]').attr('data-remote', true) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:before', function() { + App.checkDisabledState(link, 'Click me') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(link, 'Click me') + start() + }, 30) +}) + +asyncTest('a[data-remote][data-disable] re-enables when `ajax:beforeSend` event is cancelled', 6, function() { + var link = $('a[data-disable]').attr('data-remote', true) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(link, 'Click me') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(link, 'Click me') + start() + }, 30) +}) + +asyncTest('a[data-remote][data-disable] re-enables when `ajax:error` event is triggered', 6, function() { + var link = $('a[data-disable]').attr('data-remote', true).attr('href', '/error') + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:send', function() { + App.checkDisabledState(link, 'Click me') + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(link, 'Click me') + start() + }, 30) +}) + +asyncTest('form[data-remote] input|button|textarea[data-disable] does not disable when `ajax:beforeSend` event is cancelled', 8, function() { + var form = $('form[data-remote]'), + input = form.find('input:text'), + button = $('').appendTo(form), + textarea = $('').appendTo(form), + submit = $('').appendTo(form) + + form + .bindNative('ajax:beforeSend', function() { + return false + }) + .triggerNative('submit') + + App.checkEnabledState(input, 'john') + App.checkEnabledState(button, 'Submit') + App.checkEnabledState(textarea, 'born, lived, died.') + App.checkEnabledState(submit, 'Submit') + + start() +}) + +asyncTest('ctrl-clicking on a link does not disables the link', 6, function() { + var link = $('a[data-disable]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { metaKey: true }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { ctrlKey: true }) + App.checkEnabledState(link, 'Click me') + start() +}) + +asyncTest('button[data-remote][data-disable] disables and re-enables', 6, function() { + var button = $('button[data-remote][data-disable]') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:send', function() { + App.checkDisabledState(button, 'Click me') + }) + .bindNative('ajax:complete', function() { + setTimeout( function() { + App.checkEnabledState(button, 'Click me') + start() + }, 15) + }) + .triggerNative('click') +}) + +asyncTest('button[data-remote][data-disable] re-enables when `ajax:before` event is cancelled', 6, function() { + var button = $('button[data-remote][data-disable]') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:before', function() { + App.checkDisabledState(button, 'Click me') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(button, 'Click me') + start() + }, 30) +}) + +asyncTest('button[data-remote][data-disable] re-enables when `ajax:beforeSend` event is cancelled', 6, function() { + var button = $('button[data-remote][data-disable]') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(button, 'Click me') + return false + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(button, 'Click me') + start() + }, 30) +}) + +asyncTest('button[data-remote][data-disable] re-enables when `ajax:error` event is triggered', 6, function() { + var button = $('a[data-disable]').attr('data-remote', true).attr('href', '/error') + + App.checkEnabledState(button, 'Click me') + + button + .bindNative('ajax:send', function() { + App.checkDisabledState(button, 'Click me') + }) + .triggerNative('click') + + setTimeout(function() { + App.checkEnabledState(button, 'Click me') + start() + }, 30) +}) diff --git a/actionview/test/ujs/public/test/data-method.js b/actionview/test/ujs/public/test/data-method.js new file mode 100644 index 0000000000..47d940c577 --- /dev/null +++ b/actionview/test/ujs/public/test/data-method.js @@ -0,0 +1,85 @@ +(function() { + +module('data-method', { + setup: function() { + $('#qunit-fixture').append($('', { + href: '/echo', 'data-method': 'delete', text: 'destroy!' + })) + }, + teardown: function() { + $(document).unbind('iframe:loaded') + } +}) + +function submit(fn, options) { + $(document).bind('iframe:loaded', function(e, data) { + fn(data) + start() + }) + + $('#qunit-fixture').find('a') + .triggerNative('click') +} + +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) + }) +}) + +asyncTest('click on the child of link with "data-method"', 3, function() { + $(document).bind('iframe:loaded', function(e, data) { + equal(data.REQUEST_METHOD, 'DELETE') + strictEqual(data.params.authenticity_token, undefined) + strictEqual(data.HTTP_X_CSRF_TOKEN, undefined) + start() + }) + $('#qunit-fixture a').html('destroy!').find('strong').triggerNative('click') +}) + +asyncTest('link with "data-method" and CSRF', 1, function() { + $('#qunit-fixture') + .append('') + .append('') + + submit(function(data) { + equal(data.params.authenticity_token, 'cf50faa3fe97702ca1ae') + }) +}) + +asyncTest('link "target" should be carried over to generated form', 1, function() { + $('a[data-method]').attr('target', 'super-special-frame') + submit(function(data) { + equal(data.params._target, 'super-special-frame') + }) +}) + +asyncTest('link with "data-method" and cross origin', 1, function() { + var data = {} + + $('#qunit-fixture') + .append('') + .append('') + + $(document).on('submit', 'form', function(e) { + $(e.currentTarget).serializeArray().map(function(item) { + data[item.name] = item.value + }) + + return false + }) + + var link = $('#qunit-fixture').find('a') + + link.attr('href', 'http://www.alfajango.com') + + link.triggerNative('click') + + start() + + notEqual(data.authenticity_token, 'cf50faa3fe97702ca1ae') +}) + +})() diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js new file mode 100644 index 0000000000..a51aa10417 --- /dev/null +++ b/actionview/test/ujs/public/test/data-remote.js @@ -0,0 +1,400 @@ +module('data-remote', { + setup: function() { + $('#qunit-fixture') + .append($('', { + href: '/echo', + 'data-remote': 'true', + 'data-params': 'data1=value1&data2=value2', + text: 'my address' + })) + .append($('