From 8397eb24daab954e220003fa4cb1e784de6a471a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 Jan 2024 16:49:36 +0100 Subject: [PATCH] Remove rollup and test machinery for rails-ujs (#50535) This leaves only the final compiled targets in place. --- .github/workflows/lint.yml | 4 +- .github/workflows/rails-new-docker.yml | 2 +- RELEASING_RAILS.md | 4 +- actionview/.eslintrc | 23 - actionview/.gitignore | 2 - actionview/RUNNING_UJS_TESTS.rdoc | 9 - actionview/Rakefile | 48 -- actionview/app/javascript/MIT-LICENSE | 20 - actionview/app/javascript/README.md | 61 -- .../javascript/rails-ujs/features/confirm.js | 33 - .../javascript/rails-ujs/features/disable.js | 128 ---- .../javascript/rails-ujs/features/method.js | 43 -- .../javascript/rails-ujs/features/remote.js | 109 ---- actionview/app/javascript/rails-ujs/index.js | 164 ----- .../app/javascript/rails-ujs/utils/ajax.js | 119 ---- .../javascript/rails-ujs/utils/constants.js | 45 -- .../app/javascript/rails-ujs/utils/csp.js | 11 - .../app/javascript/rails-ujs/utils/csrf.js | 30 - .../app/javascript/rails-ujs/utils/dom.js | 52 -- .../app/javascript/rails-ujs/utils/event.js | 82 --- .../app/javascript/rails-ujs/utils/form.js | 43 -- actionview/karma.conf.js | 66 -- .../lib/action_view/helpers/csrf_helper.rb | 2 +- .../action_view/helpers/form_tag_helper.rb | 39 -- .../lib/action_view/helpers/url_helper.rb | 62 -- actionview/package.json | 49 -- actionview/rollup.config.js | 60 -- actionview/rollup.config.test.js | 23 - actionview/test/javascript_package_test.rb | 16 - actionview/test/ujs/config.ru | 6 - actionview/test/ujs/public/test/.eslintrc.yml | 21 - actionview/test/ujs/public/test/call-ajax.js | 27 - .../ujs/public/test/call-remote-callbacks.js | 266 -------- .../test/ujs/public/test/call-remote.js | 354 ----------- .../test/ujs/public/test/csrf-refresh.js | 21 - actionview/test/ujs/public/test/csrf-token.js | 23 - .../test/ujs/public/test/data-confirm.js | 372 ----------- .../test/ujs/public/test/data-disable-with.js | 490 -------------- .../test/ujs/public/test/data-disable.js | 387 ----------- .../test/ujs/public/test/data-method.js | 108 ---- .../test/ujs/public/test/data-remote.js | 601 ------------------ actionview/test/ujs/public/test/override.js | 52 -- actionview/test/ujs/public/test/settings.js | 130 ---- actionview/test/ujs/server.rb | 68 -- actionview/test/ujs/src/attach-bindings.js | 11 - actionview/test/ujs/src/test.js | 17 - guides/source/action_mailer_basics.md | 4 - .../source/contributing_to_ruby_on_rails.md | 4 +- package.json | 3 +- railties/test/isolation/assets/package.json | 3 +- tasks/release.rb | 6 +- yarn.lock | 47 +- 52 files changed, 14 insertions(+), 4356 deletions(-) delete mode 100644 actionview/.eslintrc delete mode 100644 actionview/RUNNING_UJS_TESTS.rdoc delete mode 100644 actionview/app/javascript/MIT-LICENSE delete mode 100644 actionview/app/javascript/README.md delete mode 100644 actionview/app/javascript/rails-ujs/features/confirm.js delete mode 100644 actionview/app/javascript/rails-ujs/features/disable.js delete mode 100644 actionview/app/javascript/rails-ujs/features/method.js delete mode 100644 actionview/app/javascript/rails-ujs/features/remote.js delete mode 100644 actionview/app/javascript/rails-ujs/index.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/ajax.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/constants.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/csp.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/csrf.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/dom.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/event.js delete mode 100644 actionview/app/javascript/rails-ujs/utils/form.js delete mode 100644 actionview/karma.conf.js delete mode 100644 actionview/package.json delete mode 100644 actionview/rollup.config.js delete mode 100644 actionview/rollup.config.test.js delete mode 100644 actionview/test/javascript_package_test.rb delete mode 100644 actionview/test/ujs/config.ru delete mode 100644 actionview/test/ujs/public/test/.eslintrc.yml delete mode 100644 actionview/test/ujs/public/test/call-ajax.js delete mode 100644 actionview/test/ujs/public/test/call-remote-callbacks.js delete mode 100644 actionview/test/ujs/public/test/call-remote.js delete mode 100644 actionview/test/ujs/public/test/csrf-refresh.js delete mode 100644 actionview/test/ujs/public/test/csrf-token.js delete mode 100644 actionview/test/ujs/public/test/data-confirm.js delete mode 100644 actionview/test/ujs/public/test/data-disable-with.js delete mode 100644 actionview/test/ujs/public/test/data-disable.js delete mode 100644 actionview/test/ujs/public/test/data-method.js delete mode 100644 actionview/test/ujs/public/test/data-remote.js delete mode 100644 actionview/test/ujs/public/test/override.js delete mode 100644 actionview/test/ujs/public/test/settings.js delete mode 100644 actionview/test/ujs/server.rb delete mode 100644 actionview/test/ujs/src/attach-bindings.js delete mode 100644 actionview/test/ujs/src/test.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b01d005cc1928..30e0f82761385 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: lint: runs-on: ubuntu-latest env: - BUNDLE_WITHOUT: db:job:cable:storage:ujs + BUNDLE_WITHOUT: db:job:cable:storage steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: python -m pip install --upgrade pip pip install codespell==2.1.0 - name: Check spelling with codespell - run: codespell --ignore-words=codespell.txt --skip="./vendor/bundle,./actionview/test/ujs/public/vendor/qunit.js,./actiontext/app/assets/javascripts/trix.js,./yarn.lock" || exit 1 + run: codespell --ignore-words=codespell.txt --skip="./vendor/bundle,./actiontext/app/assets/javascripts/trix.js,./yarn.lock" || exit 1 - run: tools/railspect changelogs . - run: tools/railspect configuration . diff --git a/.github/workflows/rails-new-docker.yml b/.github/workflows/rails-new-docker.yml index 9fe4b1762164b..882945177a48f 100644 --- a/.github/workflows/rails-new-docker.yml +++ b/.github/workflows/rails-new-docker.yml @@ -8,7 +8,7 @@ permissions: env: APP_NAME: devrails APP_PATH: dev/devrails - BUNDLE_WITHOUT: db:job:cable:storage:ujs + BUNDLE_WITHOUT: db:job:cable:storage jobs: rails-new-docker: diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md index 0eb280385de6d..f7d73c0a2bc83 100644 --- a/RELEASING_RAILS.md +++ b/RELEASING_RAILS.md @@ -82,8 +82,8 @@ for setup instructions. IMPORTANT: Several gems have JavaScript components that are released as npm packages, so you must have Node.js installed, have an npm account (npmjs.com), -and be a package owner for `@rails/actioncable`, `@rails/actiontext`, -`@rails/activestorage`, and `@rails/ujs`. You can check this by making sure your +and be a package owner for `@rails/actioncable`, `@rails/actiontext`, and +`@rails/activestorage`. You can check this by making sure your npm user (`npm whoami`) is listed as an owner (`npm owner ls `) of each package. Do not release until you're set up with npm! diff --git a/actionview/.eslintrc b/actionview/.eslintrc deleted file mode 100644 index a37f9bc991045..0000000000000 --- a/actionview/.eslintrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "eslint:recommended", - "globals": { - "__esm": "readonly" - }, - "rules": { - "semi": ["error", "never"], - "quotes": ["error", "double"], - "no-unused-vars": ["error", { "vars": "all", "args": "none" }] - }, - "plugins": [ - "import" - ], - "env": { - "browser": true, - "es6": true, - "jquery": true - }, - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - } -} diff --git a/actionview/.gitignore b/actionview/.gitignore index b23fae5721207..cc8f8ca4d931f 100644 --- a/actionview/.gitignore +++ b/actionview/.gitignore @@ -1,6 +1,4 @@ /lib/assets/compiled/ -/test/ujs/compiled/ /log/ /test/fixtures/public/absolute/ -/test/ujs/log/ /tmp/ diff --git a/actionview/RUNNING_UJS_TESTS.rdoc b/actionview/RUNNING_UJS_TESTS.rdoc deleted file mode 100644 index c463014464947..0000000000000 --- a/actionview/RUNNING_UJS_TESTS.rdoc +++ /dev/null @@ -1,9 +0,0 @@ -== Running UJS tests - -Run the tests in headless mode by running: - - rake test:ujs - -To run the tests in a browser, start the Rails UJS server by running: - - rake ujs:server diff --git a/actionview/Rakefile b/actionview/Rakefile index 5cefc90f254bb..6aa58d5fa662e 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -30,46 +30,6 @@ namespace :test do t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end - desc "Run tests for rails-ujs" - task :ujs do - system("npm run lint") - exit $?.exitstatus unless $?.success? - - begin - listen_host = "localhost" - listen_port = "4567" - - FileUtils.mkdir_p("log") - pid = File.open("log/test.log", "w") do |f| - spawn(*%W(rackup test/ujs/config.ru -o #{listen_host} -p #{listen_port} -s puma), out: f, err: f, pgroup: true) - end - - start_time = Time.now - - loop do - break if system("lsof -i :4567", 1 => File::NULL) - - if Time.now - start_time > 5 - puts "Failed to start puma after 5 seconds" - puts - puts File.read("log/test.log") - exit 1 - end - - sleep 0.2 - end - # Decode the obfuscate environment variables - decoded_environment_variables = Hash[*Base64.decode64(ENV.fetch("ENCODED", "")).split(/[ =]/)] - system(decoded_environment_variables, "npm", "test") - status = $?.exitstatus - ensure - Process.kill("KILL", -pid) if pid - FileUtils.rm_rf("log") - end - - exit status - end - namespace :integration do # Active Record Integration Tests Rake::TestTask.new(:active_record) do |t| @@ -91,14 +51,6 @@ namespace :test do end end -namespace :ujs do - desc "Starts the test server" - task :server do - spawn("bundle", "exec", "rackup", "test/ujs/config.ru", "-p", "4567", "-s", "puma") - system("npm", "test", "--", "--no-single-run", "--browsers", "Chrome") - end -end - task :lines do load File.expand_path("../tools/line_statistics", __dir__) files = FileList["lib/**/*.rb"] diff --git a/actionview/app/javascript/MIT-LICENSE b/actionview/app/javascript/MIT-LICENSE deleted file mode 100644 index 9255cf1748691..0000000000000 --- a/actionview/app/javascript/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) Rails Core team - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/actionview/app/javascript/README.md b/actionview/app/javascript/README.md deleted file mode 100644 index 0bec0e2bae0b9..0000000000000 --- a/actionview/app/javascript/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Ruby on Rails unobtrusive scripting adapter - -This unobtrusive scripting support file is developed for the Ruby on Rails framework, but is not strictly tied to any specific backend. You can drop this into any application to: - -- force confirmation dialogs for various actions; -- make non-GET requests from hyperlinks; -- make forms or hyperlinks submit data asynchronously with Ajax; -- have submit buttons become automatically disabled on form submit to prevent double-clicking. - -These features are achieved by adding certain [`data` attributes][data] to your HTML markup. Documentation about the various supported `data` attributes is [available here][ujsdocs]. In Rails, they are added by the framework's template helpers. - -## Optional prerequisites - -Note that the `data` attributes this library adds are a feature of HTML5. If you're not targeting HTML5, these attributes may make your HTML to fail [validation][validator]. However, this shouldn't create any issues for web browsers or other user agents. - -## Installation - -### Bun - bun add @rails/ujs - -### npm - - npm install @rails/ujs --save - -### Yarn - - yarn add @rails/ujs - -Ensure that `.yarnclean` does not include `assets` if you use [yarn autoclean](https://yarnpkg.com/lang/en/docs/cli/autoclean/). - -## Usage - -### Asset pipeline - -In a conventional Rails application that uses the asset pipeline, require `rails-ujs` in your `application.js` manifest: - -```javascript -//= require rails-ujs -``` - -### ES2015+ - -If you're using a JavaScript bundler, add the following to your main JS file: - -```javascript -import Rails from "@rails/ujs" -Rails.start() -``` - -## How to run tests - -Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser. - -## License - -rails-ujs is released under the [MIT License](MIT-LICENSE). - -[data]: https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes" -[validator]: https://validator.w3.org/ -[csrf]: https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html -[ujsdocs]: https://github.com/rails/jquery-ujs/wiki diff --git a/actionview/app/javascript/rails-ujs/features/confirm.js b/actionview/app/javascript/rails-ujs/features/confirm.js deleted file mode 100644 index 33ef4cd99cafb..0000000000000 --- a/actionview/app/javascript/rails-ujs/features/confirm.js +++ /dev/null @@ -1,33 +0,0 @@ -import { fire, stopEverything } from "../utils/event" - -const handleConfirmWithRails = (rails) => function(e) { - if (!allowAction(this, rails)) { stopEverything(e) } -} - -// Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm -const confirm = (message, element) => window.confirm(message) - -// For 'data-confirm' attribute: -// - Fires `confirm` event -// - Shows the confirmation dialog -// - Fires the `confirm:complete` event -// -// Returns `true` if no function stops the chain and user chose yes `false` otherwise. -// Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. -// Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function -// return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. -var allowAction = function(element, rails) { - let callback - const message = element.getAttribute("data-confirm") - if (!message) { return true } - - let answer = false - if (fire(element, "confirm")) { - try { answer = rails.confirm(message, element) } catch(error) { /* do nothing */ } - callback = fire(element, "confirm:complete", [answer]) - } - - return answer && callback -} - -export { handleConfirmWithRails, confirm } diff --git a/actionview/app/javascript/rails-ujs/features/disable.js b/actionview/app/javascript/rails-ujs/features/disable.js deleted file mode 100644 index 3bd1e2279144d..0000000000000 --- a/actionview/app/javascript/rails-ujs/features/disable.js +++ /dev/null @@ -1,128 +0,0 @@ -import { - linkDisableSelector, - buttonDisableSelector, - formDisableSelector, - formEnableSelector, - formSubmitSelector -} from "../utils/constants" -import { matches, getData, setData } from "../utils/dom" -import { stopEverything } from "../utils/event" -import { formElements } from "../utils/form" -import { isContentEditable } from "../utils/dom" - -const handleDisabledElement = function(e) { - const element = this - if (element.disabled) { stopEverything(e) } -} - -// Unified function to enable an element (link, button and form) -const enableElement = (e) => { - let element - if (e instanceof Event) { - if (isXhrRedirect(e)) { return } - element = e.target - } else { - element = e - } - - if (isContentEditable(element)) { - return - } - - if (matches(element, linkDisableSelector)) { - return enableLinkElement(element) - } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { - return enableFormElement(element) - } else if (matches(element, formSubmitSelector)) { - return enableFormElements(element) - } -} - -// Unified function to disable an element (link, button and form) -const disableElement = (e) => { - const element = e instanceof Event ? e.target : e - - if (isContentEditable(element)) { - return - } - - if (matches(element, linkDisableSelector)) { - return disableLinkElement(element) - } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { - return disableFormElement(element) - } else if (matches(element, formSubmitSelector)) { - return disableFormElements(element) - } -} - -// Replace element's HTML with the 'data-disable-with' after storing original html -// and prevent clicking on it -var disableLinkElement = function(element) { - if (getData(element, "ujs:disabled")) { return } - const replacement = element.getAttribute("data-disable-with") - if (replacement != null) { - setData(element, "ujs:enable-with", element.innerHTML) // store enabled state - element.innerHTML = replacement - } - element.addEventListener("click", stopEverything) // prevent further clicking - return setData(element, "ujs:disabled", true) -} - -// Restore element to its original state which was disabled by 'disableLinkElement' above -var enableLinkElement = function(element) { - const originalText = getData(element, "ujs:enable-with") - if (originalText != null) { - element.innerHTML = originalText // set to old enabled state - setData(element, "ujs:enable-with", null) // clean up cache - } - element.removeEventListener("click", stopEverything) // enable element - return setData(element, "ujs:disabled", null) -} - -// Disables form elements: -// - Caches element value in 'ujs:enable-with' data store -// - Replaces element text with value of 'data-disable-with' attribute -// - Sets disabled property to true -var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement) - -var disableFormElement = function(element) { - if (getData(element, "ujs:disabled")) { return } - const replacement = element.getAttribute("data-disable-with") - if (replacement != null) { - if (matches(element, "button")) { - setData(element, "ujs:enable-with", element.innerHTML) - element.innerHTML = replacement - } else { - setData(element, "ujs:enable-with", element.value) - element.value = replacement - } - } - element.disabled = true - return setData(element, "ujs:disabled", true) -} - -// Re-enables disabled form elements: -// - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) -// - Sets disabled property to false -var enableFormElements = form => formElements(form, formEnableSelector).forEach(element => enableFormElement(element)) - -var enableFormElement = function(element) { - const originalText = getData(element, "ujs:enable-with") - if (originalText != null) { - if (matches(element, "button")) { - element.innerHTML = originalText - } else { - element.value = originalText - } - setData(element, "ujs:enable-with", null) // clean up cache - } - element.disabled = false - return setData(element, "ujs:disabled", null) -} - -var isXhrRedirect = function(event) { - const xhr = event.detail ? event.detail[0] : undefined - return xhr && xhr.getResponseHeader("X-Xhr-Redirect") -} - -export { handleDisabledElement, enableElement, disableElement } diff --git a/actionview/app/javascript/rails-ujs/features/method.js b/actionview/app/javascript/rails-ujs/features/method.js deleted file mode 100644 index bf56b75571242..0000000000000 --- a/actionview/app/javascript/rails-ujs/features/method.js +++ /dev/null @@ -1,43 +0,0 @@ -import { isCrossDomain } from "../utils/ajax" -import * as csrf from "../utils/csrf" -import { stopEverything } from "../utils/event" -import { isContentEditable } from "../utils/dom" - -// Handles "data-method" on links such as: -// Delete -const handleMethodWithRails = (rails) => function(e) { - const link = this - const method = link.getAttribute("data-method") - if (!method) { return } - - if (isContentEditable(this)) { - return - } - - const href = rails.href(link) - const csrfToken = csrf.csrfToken() - const csrfParam = csrf.csrfParam() - const form = document.createElement("form") - let formContent = `` - - if (csrfParam && csrfToken && !isCrossDomain(href)) { - formContent += `` - } - - // Must trigger submit by click on a button, else "submit" event handler won't work! - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit - formContent += "" - - form.method = "post" - form.action = href - form.target = link.target - form.innerHTML = formContent - form.style.display = "none" - - document.body.appendChild(form) - form.querySelector("[type=\"submit\"]").click() - - stopEverything(e) -} - -export { handleMethodWithRails } diff --git a/actionview/app/javascript/rails-ujs/features/remote.js b/actionview/app/javascript/rails-ujs/features/remote.js deleted file mode 100644 index 709462477dfc7..0000000000000 --- a/actionview/app/javascript/rails-ujs/features/remote.js +++ /dev/null @@ -1,109 +0,0 @@ -import { formSubmitSelector, buttonClickSelector, inputChangeSelector } from "../utils/constants" -import { ajax, isCrossDomain } from "../utils/ajax" -import { matches, getData, setData } from "../utils/dom" -import { fire, stopEverything } from "../utils/event" -import { serializeElement } from "../utils/form" -import { isContentEditable } from "../utils/dom" - -// Checks "data-remote" if true to handle the request through a XHR request. -const isRemote = function(element) { - const value = element.getAttribute("data-remote") - return (value != null) && (value !== "false") -} - -// Submits "remote" forms and links with ajax -const handleRemoteWithRails = (rails) => function(e) { - let data, method, url - const element = this - - if (!isRemote(element)) { return true } - if (!fire(element, "ajax:before")) { - fire(element, "ajax:stopped") - return false - } - - if (isContentEditable(element)) { - fire(element, "ajax:stopped") - return false - } - - const withCredentials = element.getAttribute("data-with-credentials") - const dataType = element.getAttribute("data-type") || "script" - - if (matches(element, formSubmitSelector)) { - // memoized value from clicked submit button - const button = getData(element, "ujs:submit-button") - method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get" - url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href - - // strip query string if it's a GET request - if (method.toUpperCase() === "GET") { url = url.replace(/\?.*$/, "") } - - if (element.enctype === "multipart/form-data") { - data = new FormData(element) - if (button != null) { data.append(button.name, button.value) } - } else { - data = serializeElement(element, button) - } - - setData(element, "ujs:submit-button", null) - setData(element, "ujs:submit-button-formmethod", null) - setData(element, "ujs:submit-button-formaction", null) - } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { - method = element.getAttribute("data-method") - url = element.getAttribute("data-url") - data = serializeElement(element, element.getAttribute("data-params")) - } else { - method = element.getAttribute("data-method") - url = rails.href(element) - data = element.getAttribute("data-params") - } - - ajax({ - type: method || "GET", - url, - data, - dataType, - // stopping the "ajax:beforeSend" event will cancel the ajax request - beforeSend(xhr, options) { - if (fire(element, "ajax:beforeSend", [xhr, options])) { - return fire(element, "ajax:send", [xhr]) - } else { - fire(element, "ajax:stopped") - return false - } - }, - success(...args) { return fire(element, "ajax:success", args) }, - error(...args) { return fire(element, "ajax:error", args) }, - complete(...args) { return fire(element, "ajax:complete", args) }, - crossDomain: isCrossDomain(url), - withCredentials: (withCredentials != null) && (withCredentials !== "false") - }) - stopEverything(e) -} - -const formSubmitButtonClick = function(e) { - const button = this - const { - form - } = button - if (!form) { return } - // Register the pressed submit button - if (button.name) { setData(form, "ujs:submit-button", {name: button.name, value: button.value}) } - // Save attributes from button - setData(form, "ujs:formnovalidate-button", button.formNoValidate) - setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")) - return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")) -} - -const preventInsignificantClick = function(e) { - const link = this - const method = (link.getAttribute("data-method") || "GET").toUpperCase() - const data = link.getAttribute("data-params") - const metaClick = e.metaKey || e.ctrlKey - const insignificantMetaClick = metaClick && (method === "GET") && !data - const nonPrimaryMouseClick = (e.button != null) && (e.button !== 0) - if (nonPrimaryMouseClick || insignificantMetaClick) { e.stopImmediatePropagation() } -} - -export { handleRemoteWithRails, formSubmitButtonClick, preventInsignificantClick } diff --git a/actionview/app/javascript/rails-ujs/index.js b/actionview/app/javascript/rails-ujs/index.js deleted file mode 100644 index 67c1eb3a7b2f5..0000000000000 --- a/actionview/app/javascript/rails-ujs/index.js +++ /dev/null @@ -1,164 +0,0 @@ -import { - linkClickSelector, - buttonClickSelector, - inputChangeSelector, - formSubmitSelector, - formInputClickSelector, - formDisableSelector, - formEnableSelector, - fileInputSelector, - linkDisableSelector, - buttonDisableSelector -} from "./utils/constants" - -import { ajax, href, isCrossDomain } from "./utils/ajax" -import { cspNonce, loadCSPNonce } from "./utils/csp" -import { csrfToken, csrfParam, CSRFProtection, refreshCSRFTokens } from "./utils/csrf" -import { matches, getData, setData, $ } from "./utils/dom" -import { fire, stopEverything, delegate } from "./utils/event" -import { serializeElement, formElements } from "./utils/form" - -import { confirm, handleConfirmWithRails } from "./features/confirm" -import { handleDisabledElement, enableElement, disableElement } from "./features/disable" -import { handleMethodWithRails } from "./features/method" -import { handleRemoteWithRails, formSubmitButtonClick, preventInsignificantClick } from "./features/remote" - -const Rails = { - $, - ajax, - buttonClickSelector, - buttonDisableSelector, - confirm, - cspNonce, - csrfToken, - csrfParam, - CSRFProtection, - delegate, - disableElement, - enableElement, - fileInputSelector, - fire, - formElements, - formEnableSelector, - formDisableSelector, - formInputClickSelector, - formSubmitButtonClick, - formSubmitSelector, - getData, - handleDisabledElement, - href, - inputChangeSelector, - isCrossDomain, - linkClickSelector, - linkDisableSelector, - loadCSPNonce, - matches, - preventInsignificantClick, - refreshCSRFTokens, - serializeElement, - setData, - stopEverything -} - -// needs to be able to call Rails.confirm in case its overridden -const handleConfirm = handleConfirmWithRails(Rails) -Rails.handleConfirm = handleConfirm - -// needs to be able to call Rails.href in case its overridden -const handleMethod = handleMethodWithRails(Rails) -Rails.handleMethod = handleMethod - -// needs to be able to call Rails.href in case its overridden -const handleRemote = handleRemoteWithRails(Rails) -Rails.handleRemote = handleRemote - -const start = function() { - // Cut down on the number of issues from people inadvertently including - // rails-ujs twice by detecting and raising an error when it happens. - if (window._rails_loaded) { throw new Error("rails-ujs has already been loaded!") } - - // This event works the same as the load event, except that it fires every - // time the page is loaded. - // See https://github.com/rails/jquery-ujs/issues/357 - // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching - window.addEventListener("pageshow", function() { - $(formEnableSelector).forEach(function(el) { - if (getData(el, "ujs:disabled")) { - enableElement(el) - } - }) - $(linkDisableSelector).forEach(function(el) { - if (getData(el, "ujs:disabled")) { - enableElement(el) - } - }) - }) - - delegate(document, linkDisableSelector, "ajax:complete", enableElement) - delegate(document, linkDisableSelector, "ajax:stopped", enableElement) - delegate(document, buttonDisableSelector, "ajax:complete", enableElement) - delegate(document, buttonDisableSelector, "ajax:stopped", enableElement) - - delegate(document, linkClickSelector, "click", preventInsignificantClick) - delegate(document, linkClickSelector, "click", handleDisabledElement) - delegate(document, linkClickSelector, "click", handleConfirm) - delegate(document, linkClickSelector, "click", disableElement) - delegate(document, linkClickSelector, "click", handleRemote) - delegate(document, linkClickSelector, "click", handleMethod) - - delegate(document, buttonClickSelector, "click", preventInsignificantClick) - delegate(document, buttonClickSelector, "click", handleDisabledElement) - delegate(document, buttonClickSelector, "click", handleConfirm) - delegate(document, buttonClickSelector, "click", disableElement) - delegate(document, buttonClickSelector, "click", handleRemote) - - delegate(document, inputChangeSelector, "change", handleDisabledElement) - delegate(document, inputChangeSelector, "change", handleConfirm) - delegate(document, inputChangeSelector, "change", handleRemote) - - delegate(document, formSubmitSelector, "submit", handleDisabledElement) - delegate(document, formSubmitSelector, "submit", handleConfirm) - delegate(document, formSubmitSelector, "submit", handleRemote) - // Normal mode submit - // Slight timeout so that the submit button gets properly serialized - delegate(document, formSubmitSelector, "submit", e => setTimeout((() => disableElement(e)), 13)) - delegate(document, formSubmitSelector, "ajax:send", disableElement) - delegate(document, formSubmitSelector, "ajax:complete", enableElement) - - delegate(document, formInputClickSelector, "click", preventInsignificantClick) - delegate(document, formInputClickSelector, "click", handleDisabledElement) - delegate(document, formInputClickSelector, "click", handleConfirm) - delegate(document, formInputClickSelector, "click", formSubmitButtonClick) - - document.addEventListener("DOMContentLoaded", refreshCSRFTokens) - document.addEventListener("DOMContentLoaded", loadCSPNonce) - return window._rails_loaded = true -} -Rails.start = start - -// For backward compatibility -if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { - if (jQuery.rails) { throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.") } - jQuery.rails = Rails - jQuery.ajaxPrefilter(function(options, originalOptions, xhr) { - if (!options.crossDomain) { return CSRFProtection(xhr) } - }) -} - -// This block is to maintain backwards compatibility with the existing -// difference between what happens in a bundler and what happens using a -// sprockets compiler. In the sprockets case, Rails.start() is called -// automatically, but it is not in the ESModule case. -if (__esm == false && typeof exports !== "object" && typeof module === "undefined") { - // The coffeescript bundle would set this at the very top. The Rollup bundle - // doesn't set this until the entire bundle has finished running, so we need - // to make sure its set before firing the rails:attachBindings event for - // backwards compatibility. - window.Rails = Rails - - if (fire(document, "rails:attachBindings")) { - start() - } -} - -export default Rails diff --git a/actionview/app/javascript/rails-ujs/utils/ajax.js b/actionview/app/javascript/rails-ujs/utils/ajax.js deleted file mode 100644 index 2c2125e0e86dd..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/ajax.js +++ /dev/null @@ -1,119 +0,0 @@ -import { cspNonce } from "./csp" -import { CSRFProtection } from "./csrf" - -const AcceptHeaders = { - "*": "*/*", - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript", - script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" -} - -const ajax = (options) => { - options = prepareOptions(options) - var xhr = createXHR(options, function() { - const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")) - if (Math.floor(xhr.status / 100) === 2) { - if (typeof options.success === "function") { - options.success(response, xhr.statusText, xhr) - } - } else { - if (typeof options.error === "function") { - options.error(response, xhr.statusText, xhr) - } - } - return (typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined) - }) - - if (options.beforeSend && !options.beforeSend(xhr, options)) { - return false - } - - if (xhr.readyState === XMLHttpRequest.OPENED) { - return xhr.send(options.data) - } -} - -var prepareOptions = function(options) { - options.url = options.url || location.href - options.type = options.type.toUpperCase() - // append data to url if it's a GET request - if ((options.type === "GET") && options.data) { - if (options.url.indexOf("?") < 0) { - options.url += "?" + options.data - } else { - options.url += "&" + options.data - } - } - // Use "*" as default dataType - if (!(options.dataType in AcceptHeaders)) { options.dataType = "*" } - options.accept = AcceptHeaders[options.dataType] - if (options.dataType !== "*") { options.accept += ", */*; q=0.01" } - return options -} - -var createXHR = function(options, done) { - const xhr = new XMLHttpRequest() - // Open and set up xhr - xhr.open(options.type, options.url, true) - xhr.setRequestHeader("Accept", options.accept) - // Set Content-Type only when sending a string - // Sending FormData will automatically set Content-Type to multipart/form-data - if (typeof options.data === "string") { - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - } - if (!options.crossDomain) { - xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") - // Add X-CSRF-Token - CSRFProtection(xhr) - } - xhr.withCredentials = !!options.withCredentials - xhr.onreadystatechange = function() { - if (xhr.readyState === XMLHttpRequest.DONE) { return done(xhr) } - } - return xhr -} - -var processResponse = function(response, type) { - if ((typeof response === "string") && (typeof type === "string")) { - if (type.match(/\bjson\b/)) { - try { response = JSON.parse(response) } catch (error) { /* do nothing */ } - } else if (type.match(/\b(?:java|ecma)script\b/)) { - const script = document.createElement("script") - script.setAttribute("nonce", cspNonce()) - script.text = response - document.head.appendChild(script).parentNode.removeChild(script) - } else if (type.match(/\b(xml|html|svg)\b/)) { - const parser = new DOMParser() - type = type.replace(/;.+/, "") // remove something like ';charset=utf-8' - try { response = parser.parseFromString(response, type) } catch (error1) { /* do nothing */ } - } - } - return response -} - -// Default way to get an element's href. May be overridden at Rails.href. -const href = element => element.href - -// Determines if the request is a cross domain request. -const isCrossDomain = function(url) { - const originAnchor = document.createElement("a") - originAnchor.href = location.href - const urlAnchor = document.createElement("a") - try { - urlAnchor.href = url - // If URL protocol is false or is a string containing a single colon - // *and* host are false, assume it is not a cross-domain request - // (should only be the case for IE7 and IE compatibility mode). - // Otherwise, evaluate protocol and host of the URL against the origin - // protocol and host. - return !(((!urlAnchor.protocol || (urlAnchor.protocol === ":")) && !urlAnchor.host) || - ((originAnchor.protocol + "//" + originAnchor.host) === (urlAnchor.protocol + "//" + urlAnchor.host))) - } catch (e) { - // If there is an error parsing the URL, assume it is crossDomain. - return true - } -} - -export { ajax, href, isCrossDomain } diff --git a/actionview/app/javascript/rails-ujs/utils/constants.js b/actionview/app/javascript/rails-ujs/utils/constants.js deleted file mode 100644 index 1973d28eb44c8..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/constants.js +++ /dev/null @@ -1,45 +0,0 @@ -// Link elements bound by rails-ujs -const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]" - -// Button elements bound by rails-ujs -const buttonClickSelector = { - selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", - exclude: "form button" -} - -// Select elements bound by rails-ujs -const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]" - -// Form elements bound by rails-ujs -const formSubmitSelector = "form:not([data-turbo=true])" - -// Form input elements bound by rails-ujs -const formInputClickSelector = "form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])" - -// Form input elements disabled during form submission -const formDisableSelector = "input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled" - -// Form input elements re-enabled after form submission -const formEnableSelector = "input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled" - -// Form file input elements -const fileInputSelector = "input[name][type=file]:not([disabled])" - -// Link onClick disable selector with possible re-enable after remote submission -const linkDisableSelector = "a[data-disable-with], a[data-disable]" - -// Button onClick disable selector with possible re-enable after remote submission -const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]" - -export { - linkClickSelector, - buttonClickSelector, - inputChangeSelector, - formSubmitSelector, - formInputClickSelector, - formDisableSelector, - formEnableSelector, - fileInputSelector, - linkDisableSelector, - buttonDisableSelector -} diff --git a/actionview/app/javascript/rails-ujs/utils/csp.js b/actionview/app/javascript/rails-ujs/utils/csp.js deleted file mode 100644 index ab71796c7b228..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/csp.js +++ /dev/null @@ -1,11 +0,0 @@ -let nonce = null - -const loadCSPNonce = () => { - const metaTag = document.querySelector("meta[name=csp-nonce]") - return nonce = metaTag && metaTag.content -} - -// Returns the Content-Security-Policy nonce for inline scripts. -const cspNonce = () => nonce || loadCSPNonce() - -export { cspNonce, loadCSPNonce } diff --git a/actionview/app/javascript/rails-ujs/utils/csrf.js b/actionview/app/javascript/rails-ujs/utils/csrf.js deleted file mode 100644 index da7a69d5a828f..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/csrf.js +++ /dev/null @@ -1,30 +0,0 @@ -import { $ } from "./dom" - -// Up-to-date Cross-Site Request Forgery token -const csrfToken = () => { - const meta = document.querySelector("meta[name=csrf-token]") - return meta && meta.content -} - -// URL param that must contain the CSRF token -const csrfParam = () => { - const meta = document.querySelector("meta[name=csrf-param]") - return meta && meta.content -} - -// Make sure that every Ajax request sends the CSRF token -const CSRFProtection = (xhr) => { - const token = csrfToken() - if (token) { return xhr.setRequestHeader("X-CSRF-Token", token) } -} - -// Make sure that all forms have actual up-to-date tokens (cached forms contain old ones) -const refreshCSRFTokens = () => { - const token = csrfToken() - const param = csrfParam() - if (token && param) { - return $("form input[name=\"" + param + "\"]").forEach(input => input.value = token) - } -} - -export { csrfToken, csrfParam, CSRFProtection, refreshCSRFTokens } diff --git a/actionview/app/javascript/rails-ujs/utils/dom.js b/actionview/app/javascript/rails-ujs/utils/dom.js deleted file mode 100644 index 38eaca9b73da5..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/dom.js +++ /dev/null @@ -1,52 +0,0 @@ -const m = Element.prototype.matches || - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - Element.prototype.webkitMatchesSelector - -// Checks if the given native dom element matches the selector -// element:: -// native DOM element -// selector:: -// CSS selector string or -// a JavaScript object with `selector` and `exclude` properties -// Examples: "form", { selector: "form", exclude: "form[data-remote='true']"} -const matches = function(element, selector) { - if (selector.exclude) { - return m.call(element, selector.selector) && !m.call(element, selector.exclude) - } else { - return m.call(element, selector) - } -} - -// get and set data on a given element using "expando properties" -// See: https://developer.mozilla.org/en-US/docs/Glossary/Expando -const EXPANDO = "_ujsData" - -const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined - -const setData = function(element, key, value) { - if (!element[EXPANDO]) { element[EXPANDO] = {} } - return element[EXPANDO][key] = value -} - -// a wrapper for document.querySelectorAll -// returns an Array -const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)) - -const isContentEditable = function(element) { - var isEditable = false - do { - if(element.isContentEditable) { - isEditable = true - break - } - - element = element.parentElement - } while(element) - - return isEditable -} - -export { matches, getData, setData, $, isContentEditable } diff --git a/actionview/app/javascript/rails-ujs/utils/event.js b/actionview/app/javascript/rails-ujs/utils/event.js deleted file mode 100644 index be526b8aff6ac..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/event.js +++ /dev/null @@ -1,82 +0,0 @@ -import { matches } from "./dom" - -let preventDefault - -// Polyfill for CustomEvent in IE9+ -// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill -let { - CustomEvent -} = window - -if (typeof CustomEvent !== "function") { - CustomEvent = function(event, params) { - const evt = document.createEvent("CustomEvent") - evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) - return evt - } - - CustomEvent.prototype = window.Event.prototype; - - // Fix setting `defaultPrevented` when `preventDefault()` is called - // http://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events - ({ preventDefault } = CustomEvent.prototype) - CustomEvent.prototype.preventDefault = function() { - const result = preventDefault.call(this) - if (this.cancelable && !this.defaultPrevented) { - Object.defineProperty(this, "defaultPrevented", {get() { return true }}) - } - return result - } -} - -// Triggers a custom event on an element and returns false if the event result is false -// obj:: -// a native DOM element -// name:: -// string that corresponds to the event you want to trigger -// e.g. 'click', 'submit' -// data:: -// data you want to pass when you dispatch an event -const fire = (obj, name, data) => { - const event = new CustomEvent( - name, { - bubbles: true, - cancelable: true, - detail: data - } - ) - obj.dispatchEvent(event) - return !event.defaultPrevented -} - -// Helper function, needed to provide consistent behavior in IE -const stopEverything = (e) => { - fire(e.target, "ujs:everythingStopped") - e.preventDefault() - e.stopPropagation() - e.stopImmediatePropagation() -} - -// Delegates events -// to a specified parent `element`, which fires event `handler` -// for the specified `selector` when an event of `eventType` is triggered -// element:: -// parent element that will listen for events e.g. document -// selector:: -// CSS selector; or an object that has `selector` and `exclude` properties (see: Rails.matches) -// eventType:: -// string representing the event e.g. 'submit', 'click' -// handler:: -// the event handler to be called -const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, function(e) { - let { - target - } = e - while (!!(target instanceof Element) && !matches(target, selector)) { target = target.parentNode } - if (target instanceof Element && (handler.call(target, e) === false)) { - e.preventDefault() - e.stopPropagation() - } -}) - -export { fire, stopEverything, delegate } diff --git a/actionview/app/javascript/rails-ujs/utils/form.js b/actionview/app/javascript/rails-ujs/utils/form.js deleted file mode 100644 index 0c11a9908a9d6..0000000000000 --- a/actionview/app/javascript/rails-ujs/utils/form.js +++ /dev/null @@ -1,43 +0,0 @@ -import { matches } from "./dom" - -const toArray = e => Array.prototype.slice.call(e) - -const serializeElement = (element, additionalParam) => { - let inputs = [element] - if (matches(element, "form")) { inputs = toArray(element.elements) } - const params = [] - - inputs.forEach(function(input) { - if (!input.name || input.disabled) { return } - if (matches(input, "fieldset[disabled] *")) { return } - if (matches(input, "select")) { - toArray(input.options).forEach(function(option) { - if (option.selected) { params.push({name: input.name, value: option.value}) } - }) - } else if (input.checked || (["radio", "checkbox", "submit"].indexOf(input.type) === -1)) { - params.push({name: input.name, value: input.value}) - } - }) - - if (additionalParam) { params.push(additionalParam) } - - return params.map(function(param) { - if (param.name) { - return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}` - } else { - return param - }}).join("&") -} - -// Helper function that returns form elements that match the specified CSS selector -// If form is actually a "form" element this will return associated elements outside the from that have -// the HTML form attribute set -const formElements = (form, selector) => { - if (matches(form, "form")) { - return toArray(form.elements).filter(el => matches(el, selector)) - } else { - return toArray(form.querySelectorAll(selector)) - } -} - -export { serializeElement, formElements } diff --git a/actionview/karma.conf.js b/actionview/karma.conf.js deleted file mode 100644 index a95ce5be608ce..0000000000000 --- a/actionview/karma.conf.js +++ /dev/null @@ -1,66 +0,0 @@ -// Karma configuration for running the UJS tests - -const config = { - browsers: ["ChromeHeadless"], - frameworks: ["qunit"], - files: [ - "test/ujs/compiled/test.js", - ], - - client: { - clearContext: false, - qunit: { - showUI: true - } - }, - - singleRun: true, - autoWatch: false, - - captureTimeout: 180000, - browserDisconnectTimeout: 180000, - browserDisconnectTolerance: 3, - browserNoActivityTimeout: 300000, - proxies: { - '/echo': 'http://localhost:4567/echo', - '/error': 'http://localhost:4567/error' - } -} - -if (process.env.CI) { - config.customLaunchers = { - sl_chrome: sauce("chrome", "latest", "Windows 10") - } - - config.browsers = Object.keys(config.customLaunchers) - config.reporters = ["dots", "saucelabs"] - - config.sauceLabs = { - testName: "Rails UJS", - retryLimit: 3, - build: buildId(), - } - - function sauce(browserName, version, platform) { - const options = { - base: "SauceLabs", - browserName: browserName.toString(), - version: version.toString(), - } - if (platform) { - options.platform = platform.toString() - } - return options - } - - function buildId() { - const { BUILDKITE_JOB_ID } = process.env - return BUILDKITE_JOB_ID - ? `Buildkite ${BUILDKITE_JOB_ID}` - : "" - } -} - -module.exports = function(karmaConfig) { - karmaConfig.set(config) -} diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index 41f0c1f6fa4e7..19626468d77bb 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -17,7 +17,7 @@ module CsrfHelper # You don't need to use these tags for regular forms as they generate their own hidden fields. # # For Ajax requests other than GETs, extract the "csrf-token" from the meta-tag and send as the - # +X-CSRF-Token+ HTTP header. If you are using rails-ujs, this happens automatically. + # +X-CSRF-Token+ HTTP header. # def csrf_meta_tags if defined?(protect_against_forgery?) && protect_against_forgery? diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index f41179cc9b4d8..0177140ab96ce 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -522,25 +522,6 @@ def radio_button_tag(name, value, *args) # submit_tag "Edit", class: "edit_button" # # => # - # ==== Deprecated: \Rails UJS attributes - # - # Prior to \Rails 7, \Rails shipped with the JavaScript library called @rails/ujs on by default. Following \Rails 7, - # this library is no longer on by default. This library integrated with the following options: - # - # * confirm: 'question?' - If present the unobtrusive JavaScript - # drivers will provide a prompt with the question specified. If the user accepts, - # the form is processed normally, otherwise no action is taken. - # * :disable_with - Value of this parameter will be used as the value for a - # disabled version of the submit button when the form is submitted. This feature is - # provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag - # pass :data => { disable_with: false } Defaults to value attribute. - # - # submit_tag "Complete sale", data: { disable_with: "Submitting..." } - # # => - # - # submit_tag "Save", data: { confirm: "Are you sure?" } - # # => - # def submit_tag(value = "Save changes", options = {}) options = options.deep_stringify_keys tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options) @@ -582,26 +563,6 @@ def submit_tag(value = "Save changes", options = {}) # # Ask me! # # # - # ==== Deprecated: \Rails UJS attributes - # - # Prior to \Rails 7, \Rails shipped with a JavaScript library called @rails/ujs on by default. Following \Rails 7, - # this library is no longer on by default. This library integrated with the following options: - # - # * confirm: 'question?' - If present, the - # unobtrusive JavaScript drivers will provide a prompt with - # the question specified. If the user accepts, the form is - # processed normally, otherwise no action is taken. - # * :disable_with - Value of this parameter will be - # used as the value for a disabled version of the submit - # button when the form is submitted. This feature is provided - # by the unobtrusive JavaScript driver. - # - # button_tag "Save", data: { confirm: "Are you sure?" } - # # => - # - # button_tag "Checkout", data: { disable_with: "Please wait..." } - # # => - # def button_tag(content_or_options = nil, options = nil, &block) if content_or_options.is_a? Hash options = content_or_options diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 29d46a7708975..b080890d22a00 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -195,42 +195,6 @@ def _filtered_referrer # :nodoc: # link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" } # # => Visit Other Site # - # ==== Deprecated: \Rails UJS Attributes - # - # Prior to \Rails 7, \Rails shipped with a JavaScript library called @rails/ujs on by default. Following \Rails 7, - # this library is no longer on by default. This library integrated with the following options: - # - # * method: symbol of HTTP verb - This modifier will dynamically - # create an HTML form and immediately submit the form for processing using - # the HTTP verb specified. Useful for having links perform a POST operation - # in dangerous actions like deleting a record (which search bots can follow - # while spidering your site). Supported verbs are :post, :delete, :patch, and :put. - # Note that if the user has JavaScript disabled, the request will fall back - # to using GET. If href: '#' is used and the user has JavaScript - # disabled clicking the link will have no effect. If you are relying on the - # POST behavior, you should check for it in your controller's action by using - # the request object's methods for post?, delete?, patch?, or put?. - # * remote: true - This will allow @rails/ujs - # to make an Ajax request to the URL in question instead of following - # the link. - # - # @rails/ujs also integrated with the following +:data+ options: - # - # * confirm: "question?" - This will allow @rails/ujs - # to prompt with the question specified (in this case, the - # resulting text would be question?). If the user accepts, the - # link is processed normally, otherwise no action is taken. - # * :disable_with - Value of this parameter will be used as the - # name for a disabled version of the link. - # - # ===== \Rails UJS Examples - # - # link_to "Remove Profile", profile_path(@profile), method: :delete - # # => Remove Profile - # - # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } - # # => Visit Other Site - # def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options, name = options, name, block if block_given? options ||= {} @@ -328,32 +292,6 @@ def link_to(name = nil, options = nil, html_options = nil, &block) # # # # " # - # ==== Deprecated: \Rails UJS Attributes - # - # Prior to \Rails 7, \Rails shipped with a JavaScript library called @rails/ujs on by default. Following \Rails 7, - # this library is no longer on by default. This library integrated with the following options: - # - # * :remote - If set to true, will allow @rails/ujs to control the - # submit behavior. By default this behavior is an Ajax submit. - # - # @rails/ujs also integrated with the following +:data+ options: - # - # * confirm: "question?" - This will allow @rails/ujs - # to prompt with the question specified (in this case, the - # resulting text would be question?). If the user accepts, the - # button is processed normally, otherwise no action is taken. - # * :disable_with - Value of this parameter will be - # used as the value for a disabled version of the submit - # button when the form is submitted. - # - # ===== \Rails UJS Examples - # - # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %> - # # => "
- # # - # # - # #
" - # def button_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? html_options ||= {} diff --git a/actionview/package.json b/actionview/package.json deleted file mode 100644 index f1ea2e9308c22..0000000000000 --- a/actionview/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@rails/ujs", - "version": "7.2.0-alpha", - "description": "Ruby on Rails unobtrusive scripting adapter", - "main": "app/assets/javascripts/rails-ujs.js", - "module": "app/assets/javascripts/rails-ujs.esm.js", - "files": [ - "app/assets/javascripts/*.js" - ], - "directories": { - "test": "test" - }, - "scripts": { - "build": "rollup --config rollup.config.js", - "pretest": "rollup --config rollup.config.test.js", - "test": "karma start", - "lint": "eslint app/javascript && eslint test/ujs/public/test" - }, - "repository": { - "type": "git", - "url": "rails/rails" - }, - "contributors": [ - "Stephen St. Martin", - "Steve Schwartz", - "Dangyi Liu", - "All contributors" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/rails/rails/issues" - }, - "homepage": "https://rubyonrails.org/", - "devDependencies": { - "@rollup/plugin-commonjs": "^19.0.1", - "@rollup/plugin-node-resolve": "^11.0.1", - "@rollup/plugin-replace": "^5.0.4", - "eslint": "^4.19.1", - "eslint-plugin-import": "^2.23.4", - "jquery": "^2.2.0", - "karma": "^3.1.1", - "karma-chrome-launcher": "^2.2.0", - "karma-qunit": "^2.1.0", - "karma-sauce-launcher": "^1.2.0", - "qunit": "^2.8.0", - "rollup": "^2.53.3", - "rollup-plugin-terser": "^7.0.2" - } -} diff --git a/actionview/rollup.config.js b/actionview/rollup.config.js deleted file mode 100644 index 36ae8d73fdacd..0000000000000 --- a/actionview/rollup.config.js +++ /dev/null @@ -1,60 +0,0 @@ -import { terser } from "rollup-plugin-terser" -import replace from "@rollup/plugin-replace" - -const banner = ` -/* -Unobtrusive JavaScript -https://github.com/rails/rails/blob/main/actionview/app/javascript -Released under the MIT license - */ -` - -const terserOptions = { - mangle: false, - compress: false, - format: { - beautify: true, - indent_level: 2, - comments: function (node, comment) { - if (comment.type == "comment2") { - // multiline comment - return comment.value.includes("Released under the MIT license") - } - } - } -} - -export default [ - { - input: "app/javascript/rails-ujs/index.js", - output: { - file: "app/assets/javascripts/rails-ujs.js", - format: "umd", - name: "Rails", - banner, - }, - plugins: [ - replace({ - preventAssignment: true, - values: { __esm: false }, - }), - terser(terserOptions), - ] - }, - - { - input: "app/javascript/rails-ujs/index.js", - output: { - file: "app/assets/javascripts/rails-ujs.esm.js", - format: "es", - banner, - }, - plugins: [ - replace({ - preventAssignment: true, - values: { __esm: true }, - }), - terser(terserOptions), - ] - } -] diff --git a/actionview/rollup.config.test.js b/actionview/rollup.config.test.js deleted file mode 100644 index b2d2f2e76c774..0000000000000 --- a/actionview/rollup.config.test.js +++ /dev/null @@ -1,23 +0,0 @@ -// Rollup configuration for compiling the UJS tests - -import commonjs from "@rollup/plugin-commonjs" -import replace from "@rollup/plugin-replace" -import resolve from "@rollup/plugin-node-resolve" - -export default { - input: "test/ujs/src/test.js", - - output: { - file: "test/ujs/compiled/test.js", - format: "iife" - }, - - plugins: [ - replace({ - preventAssignment: true, - values: { __esm: false }, // false because the tests expects start() to be called automatically - }), - resolve(), - commonjs() - ] -} diff --git a/actionview/test/javascript_package_test.rb b/actionview/test/javascript_package_test.rb deleted file mode 100644 index 03bb3232a7202..0000000000000 --- a/actionview/test/javascript_package_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class JavascriptPackageTest < ActiveSupport::TestCase - def test_compiled_code_is_in_sync_with_source_code - compiled_files = %w[ - app/assets/javascripts/rails-ujs.js - app/assets/javascripts/rails-ujs.esm.js - ].map do |file| - Pathname(file).expand_path("#{__dir__}/..") - end - - assert_no_changes -> { compiled_files.map(&:read) } do - system "yarn build", exception: true - end - end -end diff --git a/actionview/test/ujs/config.ru b/actionview/test/ujs/config.ru deleted file mode 100644 index 7cd3a16acb0a9..0000000000000 --- a/actionview/test/ujs/config.ru +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -$LOAD_PATH.unshift __dir__ -require "server" - -run UJS::Server diff --git a/actionview/test/ujs/public/test/.eslintrc.yml b/actionview/test/ujs/public/test/.eslintrc.yml deleted file mode 100644 index 06d7dd36ea8ff..0000000000000 --- a/actionview/test/ujs/public/test/.eslintrc.yml +++ /dev/null @@ -1,21 +0,0 @@ -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-ajax.js b/actionview/test/ujs/public/test/call-ajax.js deleted file mode 100644 index a5c070b2c26cf..0000000000000 --- a/actionview/test/ujs/public/test/call-ajax.js +++ /dev/null @@ -1,27 +0,0 @@ -import $ from 'jquery' -import Rails from '../../../../app/javascript/rails-ujs/index' - -QUnit.module('call-ajax', { - beforeEach: function() { - $('#qunit-fixture') - .append($('', { href: '#' })) - } -}) - -QUnit.test('call ajax without "ajax:beforeSend"', function(assert) { - const done = assert.async() - - var link = $('#qunit-fixture a') - link.bindNative('click', function() { - Rails.ajax({ - type: 'get', - url: '/', - success: function() { - assert.ok(true, 'calling request in ajax:success') - done() - } - }) - }) - - link.triggerNative('click') -}) diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js deleted file mode 100644 index d26b6ca6fdcb8..0000000000000 --- a/actionview/test/ujs/public/test/call-remote-callbacks.js +++ /dev/null @@ -1,266 +0,0 @@ -import $ from 'jquery' - -QUnit.module('call-remote-callbacks', { - beforeEach: function() { - $('#qunit-fixture').append($('
', { - action: '/echo', method: 'get', 'data-remote': 'true' - })) - }, - afterEach: 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('iframe:loading') - } -}) - -function submit(fn) { - var form = $('#qunit-fixture form') - - if (fn) fn(form) - form.triggerNative('submit') -} - -QUnit.test('modifying form fields with "ajax:before" sends modified data in request', function(assert) { - const done = assert.async() - - $('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) { - assert.equal(data.params.user_name, 'steve', 'modified field value should have been submitted') - assert.equal(data.params.other_user_name, 'jonathan', 'added field value should have been submitted') - assert.equal(data.params.removed_user_name, undefined, 'removed field value should be undefined') - done() - }) - }) -}) - -QUnit.test('modifying data("type") with "ajax:before" requests new dataType in request', function(assert) { - $('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) { - assert.equal(settings.dataType, 'xml', 'modified dataType should have been requested') - }) - }) -}) - -QUnit.test('setting data("with-credentials",true) with "ajax:before" uses new setting in request', function(assert) { - $('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) { - assert.equal(settings.withCredentials, true, 'setting modified in ajax:before should have forced withCredentials request') - }) - }) -}) - -QUnit.test('stopping the "ajax:beforeSend" event aborts the request', function(assert) { - const done = assert.async() - - submit(function(form) { - form.bindNative('ajax:beforeSend', function(e) { - assert.ok(true, 'aborting request in ajax:beforeSend') - e.preventDefault() - }) - form.unbind('ajax:send').bindNative('ajax:send', function() { - assert.ok(false, 'ajax:send should not run') - }) - form.bindNative('ajax:error', function(e, response, status, xhr) { - assert.ok(false, 'ajax:error should not run') - }) - form.bindNative('ajax:complete', function() { - assert.ok(false, 'ajax:complete should not run') - }) - }) - - setTimeout(function() { done() }, 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. - QUnit.test('non-blank file form input field should abort remote request, but submit normally', function(assert) { - 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) - }) - - QUnit.test('file form input field should not abort remote request if file form input does not have a name attribute', function(assert) { - 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) - }) - - QUnit.test('blank file input field should abort request entirely if handler bound to "ajax:aborted:file" event that returns false', function(assert) { - 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(e) { - e.preventDefault() - }) - .triggerNative('submit') - - setTimeout(function() { - form.find('input[type="file"]').val('') - form.unbind('ajax:beforeSend') - submit() - }, 13) - }) -} - -QUnit.test('"ajax:beforeSend" can be observed and stopped with event delegation', function(assert) { - const done = assert.async() - - $(document).delegate('form[data-remote]', 'ajax:beforeSend', function(e) { - assert.ok(true, 'ajax:beforeSend observed with event delegation') - e.preventDefault() - }) - - submit(function(form) { - form.unbind('ajax:send').bindNative('ajax:send', function() { - assert.ok(false, 'ajax:send should not run') - }) - form.bindNative('ajax:complete', function() { - assert.ok(false, 'ajax:complete should not run') - }) - }) - - setTimeout(function() { done() }, 13) -}) - -QUnit.test('"ajax:beforeSend", "ajax:send", "ajax:success" and "ajax:complete" are triggered', function(assert) { - const done = assert.async(4) - - submit(function(form) { - form.bindNative('ajax:beforeSend', function(e, xhr, settings) { - assert.ok(xhr.setRequestHeader, 'first argument to "ajax:beforeSend" should be an XHR object') - assert.equal(settings.url, '/echo', 'second argument to "ajax:beforeSend" should be a settings object') - done() - }) - form.bindNative('ajax:send', function(e, xhr) { - assert.ok(xhr.abort, 'first argument to "ajax:send" should be an XHR object') - done() - }) - form.bindNative('ajax:success', function(e, data, status, xhr) { - assert.ok(data.REQUEST_METHOD, 'first argument to ajax:success should be a data object') - assert.equal(status, 'OK', 'second argument to ajax:success should be a status string') - assert.ok(xhr.getResponseHeader, 'third argument to "ajax:success" should be an XHR object') - done() - }) - form.bindNative('ajax:complete', function(e, xhr, status) { - assert.ok(xhr.getResponseHeader, 'first argument to "ajax:complete" should be an XHR object') - assert.equal(status, 'OK', 'second argument to ajax:complete should be a status string') - done() - }) - }) -}) - -QUnit.test('"ajax:beforeSend", "ajax:send", "ajax:error" and "ajax:complete" are triggered on error', function(assert) { - const done = assert.async(4) - - submit(function(form) { - form.attr('action', '/error') - form.bindNative('ajax:beforeSend', function(arg) { - assert.ok(true, 'ajax:beforeSend') - done() - }) - form.bindNative('ajax:send', function(arg) { - assert.ok(true, 'ajax:send') - done() - }) - form.bindNative('ajax:error', function(e, response, status, xhr) { - assert.equal(response, '', 'first argument to ajax:error should be an HTTP status response') - assert.equal(status, 'Forbidden', 'second argument to ajax:error should be a status string') - assert.ok(xhr.getResponseHeader, 'third argument to "ajax:error" should be an XHR object') - // Opera returns "0" for HTTP code - assert.equal(xhr.status, window.opera ? 0 : 403, 'status code should be 403') - done() - }) - form.bindNative('ajax:complete', function(e, xhr, status) { - assert.ok(xhr.getResponseHeader, 'first argument to "ajax:complete" should be an XHR object') - assert.equal(status, 'Forbidden', 'second argument to ajax:complete should be a status string') - done() - }) - }) -}) - -QUnit.test('binding to ajax callbacks via .delegate() triggers handlers properly', function(assert) { - const done = assert.async(4) - - $(document) - .delegate('form[data-remote]', 'ajax:beforeSend', function() { - assert.ok(true, 'ajax:beforeSend handler is triggered') - done() - }) - .delegate('form[data-remote]', 'ajax:send', function() { - assert.ok(true, 'ajax:send handler is triggered') - done() - }) - .delegate('form[data-remote]', 'ajax:success', function() { - assert.ok(true, 'ajax:success handler is triggered') - done() - }) - .delegate('form[data-remote]', 'ajax:complete', function() { - assert.ok(true, 'ajax:complete handler is triggered') - done() - }) - $('form[data-remote]').triggerNative('submit') -}) diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js deleted file mode 100644 index 07d2665703923..0000000000000 --- a/actionview/test/ujs/public/test/call-remote.js +++ /dev/null @@ -1,354 +0,0 @@ -import $ from 'jquery' - -function buildForm(attrs) { - attrs = $.extend({ action: '/echo', 'data-remote': 'true' }, attrs) - - $('#qunit-fixture').append($('', attrs)) - .find('form').append($('')) -} - -QUnit.module('call-remote') - -function submit(fn) { - $('#qunit-fixture form') - .bindNative('ajax:success', fn) - .triggerNative('submit') -} - -QUnit.test('form method is read from "method" and not from "data-method"', function(assert) { - const done = assert.async() - - buildForm({ method: 'post', 'data-method': 'get' }) - - submit(function(e, data, status, xhr) { - assert.postRequest(data) - done() - }) -}) - -QUnit.test('form method is not read from "data-method" attribute in case of missing "method"', function(assert) { - const done = assert.async() - - buildForm({ 'data-method': 'put' }) - - submit(function(e, data, status, xhr) { - assert.getRequest(data) - done() - }) -}) - -QUnit.test('form method is read from submit button "formmethod" if submit is triggered by that button', function(assert) { - const done = assert.async() - - var submitButton = $('') - buildForm({ method: 'post' }) - - $('#qunit-fixture').find('form').append(submitButton) - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.getRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - - submitButton.triggerNative('click') -}) - -QUnit.test('form default method is GET', function(assert) { - const done = assert.async() - - buildForm() - - submit(function(e, data, status, xhr) { - assert.getRequest(data) - done() - }) -}) - -QUnit.test('form URL is picked up from "action"', function(assert) { - const done = assert.async() - - buildForm({ method: 'post' }) - - submit(function(e, data, status, xhr) { - assert.requestPath(data, '/echo') - done() - }) -}) - -QUnit.test('form URL is read from "action" not "href"', function(assert) { - const done = assert.async() - - buildForm({ method: 'post', href: '/echo2' }) - - submit(function(e, data, status, xhr) { - assert.requestPath(data, '/echo') - done() - }) -}) - -QUnit.test('form URL is read from submit button "formaction" if submit is triggered by that button', function(assert) { - const done = assert.async() - - var submitButton = $('') - buildForm({ method: 'post', href: '/echo2' }) - - $('#qunit-fixture').find('form').append(submitButton) - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.requestPath(data, '/echo') - }) - .bindNative('ajax:complete', function() { done() }) - - submitButton.triggerNative('click') -}) - -QUnit.test('prefer JS, but accept any format', function(assert) { - const done = assert.async() - - buildForm({ method: 'post' }) - - submit(function(e, data, status, xhr) { - var accept = data.HTTP_ACCEPT - assert.ok(accept.match(/text\/javascript.+\*\/\*/), 'Accept: ' + accept) - done() - }) -}) - -QUnit.test('JS code should be executed', function(assert) { - const done = assert.async() - - buildForm({ method: 'post', 'data-type': 'script' }) - - window.callback = function() { - assert.ok(true, 'remote code should be run') - window.callback = null - - done() - } - - $('form').append('') - $('form').append('') - submit() -}) - -QUnit.test('ecmascript code should be executed', function(assert) { - const done = assert.async() - - window.callback = function() { - assert.ok(true, 'remote code should be run') - window.callback = null - - done() - } - - buildForm({ method: 'post', 'data-type': 'script' }) - - $('form').append('') - $('form').append('') - - submit() -}) - -QUnit.test('execution of JS code does not modify current DOM', function(assert) { - const done = assert.async() - - var docLength, newDocLength - function getDocLength() { - return document.documentElement.outerHTML.length - } - - buildForm({ method: 'post', 'data-type': 'script' }) - - $('form').append('') - $('form').append('') - - docLength = getDocLength() - - submit(function() { - newDocLength = getDocLength() - assert.ok(docLength === newDocLength, 'executed JS should not present in the document') - done() - }) -}) - -QUnit.test('HTML document should be parsed', function(assert) { - const done = assert.async() - - buildForm({ method: 'post', 'data-type': 'html' }) - - $('form').append('') - $('form').append('') - - submit(function(e, data, status, xhr) { - assert.ok(data instanceof HTMLDocument, 'returned data should be an HTML document') - done() - }) -}) - -QUnit.test('XML document should be parsed', function(assert) { - const done = assert.async() - - buildForm({ method: 'post', 'data-type': 'html' }) - - $('form').append('') - $('form').append('') - - submit(function(e, data, status, xhr) { - assert.ok(data instanceof Document, 'returned data should be an XML document') - done() - }) -}) - -QUnit.test('accept application/json if "data-type" is json', function(assert) { - const done = assert.async() - - buildForm({ method: 'post', 'data-type': 'json' }) - - submit(function(e, data, status, xhr) { - assert.equal(data.HTTP_ACCEPT, 'application/json, text/javascript, */*; q=0.01') - done() - }) -}) - -QUnit.test('allow empty "data-remote" attribute', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture').append($('')).find('form') - - submit(function() { - assert.ok(true, 'form with empty "data-remote" attribute is also allowed') - done() - }) -}) - -QUnit.test('query string in form action should be stripped in a GET request in normal submit', function(assert) { - const done = assert.async() - - buildForm({ action: '/echo?param1=abc', 'data-remote': 'false' }) - - $(document).one('iframe:loaded', function(e, data) { - assert.equal(data.params.param1, undefined, '"param1" should not be passed to server') - done() - }) - - $('#qunit-fixture form').triggerNative('submit') -}) - -QUnit.test('query string in form action should be stripped in a GET request in ajax submit', function(assert) { - const done = assert.async() - - buildForm({ action: '/echo?param1=abc' }) - - submit(function(e, data, status, xhr) { - assert.equal(data.params.param1, undefined, '"param1" should not be passed to server') - done() - }) -}) - -QUnit.test('query string in form action should not be stripped in a POST request in normal submit', function(assert) { - const done = assert.async() - - buildForm({ action: '/echo?param1=abc', method: 'post', 'data-remote': 'false' }) - - $(document).one('iframe:loaded', function(e, data) { - assert.equal(data.params.param1, 'abc', '"param1" should be passed to server') - done() - }) - - $('#qunit-fixture form').triggerNative('submit') -}) - -QUnit.test('query string in form action should not be stripped in a POST request in ajax submit', function(assert) { - const done = assert.async() - - buildForm({ action: '/echo?param1=abc', method: 'post' }) - - submit(function(e, data, status, xhr) { - assert.equal(data.params.param1, 'abc', '"param1" should be passed to server') - done() - }) -}) - -QUnit.test('allow empty form "action"', function(assert) { - const done = assert.async() - - var currentLocation, ajaxLocation - - buildForm({ action: '' }) - - $('#qunit-fixture').find('form') - .bindNative('ajax:beforeSend', function(evt, 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 https://bugs.jquery.com/ticket/10202 and https://github.com/jquery/jquery/pull/544) - ajaxLocation = settings.url.replace('user_name=john', '').replace(/&$/, '').replace(/\?$/, '') - assert.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. - evt.preventDefault() - }) - .triggerNative('submit') - - setTimeout(function() { done() }, 13) -}) - -QUnit.test('sends CSRF token in custom header', function(assert) { - const done = assert.async() - - buildForm({ method: 'post' }) - $('#qunit-fixture').append('') - - submit(function(e, data, status, xhr) { - assert.equal(data.HTTP_X_CSRF_TOKEN, 'cf50faa3fe97702ca1ae', 'X-CSRF-Token header should be sent') - done() - }) -}) - -QUnit.test('intelligently guesses crossDomain behavior when target URL has a different protocol and/or hostname', function(assert) { - const done = assert.async() - - // 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) { - - assert.equal(settings.crossDomain, true, 'crossDomain should be set to true') - - // prevent request from actually getting sent off-domain - evt.preventDefault() - }) - .triggerNative('submit') - - setTimeout(function() { done() }, 13) -}) - -QUnit.test('intelligently guesses crossDomain behavior when target URL consists of only a path', function(assert) { - const done = assert.async() - - // 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) { - - assert.equal(settings.crossDomain, false, 'crossDomain should be set to false') - - // prevent request from actually getting sent off-domain - evt.preventDefault() - }) - .triggerNative('submit') - - setTimeout(function() { done() }, 13) -}) diff --git a/actionview/test/ujs/public/test/csrf-refresh.js b/actionview/test/ujs/public/test/csrf-refresh.js deleted file mode 100644 index 66352a76100e1..0000000000000 --- a/actionview/test/ujs/public/test/csrf-refresh.js +++ /dev/null @@ -1,21 +0,0 @@ -import $ from 'jquery' - -QUnit.module('csrf-refresh', {}) - -QUnit.test('refresh all csrf tokens', function(assert) { - 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() - var currentToken = $('#qunit-fixture #authenticity_token').val() - - assert.equal(currentToken, correctToken) -}) diff --git a/actionview/test/ujs/public/test/csrf-token.js b/actionview/test/ujs/public/test/csrf-token.js deleted file mode 100644 index d12067202a24e..0000000000000 --- a/actionview/test/ujs/public/test/csrf-token.js +++ /dev/null @@ -1,23 +0,0 @@ -import $ from 'jquery' - -QUnit.module('csrf-token', {}) - -QUnit.test('find csrf token', function(assert) { - var correctToken = 'cf50faa3fe97702ca1ae' - - $('#qunit-fixture').append('') - - var currentToken = $.rails.csrfToken() - - assert.equal(currentToken, correctToken) -}) - -QUnit.test('find csrf param', function(assert) { - var correctParam = 'authenticity_token' - - $('#qunit-fixture').append('') - - var currentParam = $.rails.csrfParam() - - assert.equal(currentParam, correctParam) -}) diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js deleted file mode 100644 index 7ff22f65300c4..0000000000000 --- a/actionview/test/ujs/public/test/data-confirm.js +++ /dev/null @@ -1,372 +0,0 @@ -import $ from 'jquery' - -QUnit.module('data-confirm', { - beforeEach: 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) - - assert.enabledState(button, 'Submit') - - form.bindNative('ajax:success', function(e, data) { - setTimeout(function() { - assert.enabledState(button, 'Submit') - done() - }, 13) - }) - form.triggerNative('submit') - - assert.disabledState(button, 'submitting ...') -}) - -QUnit.test('a[data-remote][data-disable-with] within a form disables and re-enables', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture form:not([data-remote])'), - link = $('Click me') - form.append(link) - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:beforeSend', function() { - assert.disabledState(link, 'clicking...') - }) - .bindNative('ajax:complete', function() { - setTimeout( function() { - assert.enabledState(link, 'Click me') - link.remove() - done() - }, 15) - }) - .triggerNative('click') -}) - -QUnit.test('form input[type=submit][data-disable-with] disables', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture form:not([data-remote])'), input = form.find('input[type=submit]') - - assert.enabledState(input, 'Submit') - - $(document).bind('iframe:loaded', function(e, data) { - setTimeout(function() { - assert.disabledState(input, 'submitting ...') - done() - }, 30) - }) - form.triggerNative('submit') - - setTimeout(function() { - assert.disabledState(input, 'submitting ...') - }, 30) -}) - -QUnit.test('form input[type=submit][data-disable-with] re-enables when `pageshow` event is triggered', function(assert) { - var form = $('#qunit-fixture form:not([data-remote])'), input = form.find('input[type=submit]') - - assert.enabledState(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]) - - assert.disabledState(input, 'submitting ...') - - $(window).triggerNative('pageshow') - - assert.enabledState(input, 'Submit') -}) - -QUnit.test('form[data-remote] input[type=submit][data-disable-with] is replaced in ajax callback', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture 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]') - assert.enabledState(input, 'Submit') - done() - }, 30) - }).triggerNative('submit') -}) - -QUnit.test('form[data-remote] input[data-disable-with] is replaced with disabled field in ajax callback', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture 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() { - assert.enabledState(newDisabledInput, 'Submit') - done() - }, 30) - }).triggerNative('submit') -}) - -QUnit.test('form input[type=submit][data-disable-with] using "form" attribute disables', function(assert) { - var form = $('#not_remote'), input = $('input[form=not_remote]') - assert.enabledState(input, 'Form Attr Submit') - const done = assert.async() - - $(document).bind('iframe:loaded', function(e, data) { - setTimeout(function() { - assert.disabledState(input, 'form attr submitting') - done() - }, 30) - }) - form.triggerNative('submit') - - setTimeout(function() { - assert.disabledState(input, 'form attr submitting') - }, 30) - -}) - -QUnit.test('form[data-remote] textarea[data-disable-with] attribute', function(assert) { - const done = assert.async() - - var form = $('form[data-remote]'), - textarea = $('').appendTo(form) - - form.bindNative('ajax:success', function(e, data) { - setTimeout(function() { - assert.equal(data.params.user_bio, 'born, lived, died.') - done() - }, 13) - }) - form.triggerNative('submit') - - assert.disabledState(textarea, 'processing ...') -}) - -QUnit.test('a[data-disable-with] disables', function(assert) { - const done = assert.async() - - var link = $('a[data-disable-with]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click') - assert.disabledState(link, 'clicking...') - done() -}) - -QUnit.test('a[data-disable-with] re-enables when `pageshow` event is triggered', function(assert) { - var link = $('a[data-disable-with]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click') - assert.disabledState(link, 'clicking...') - - $(window).triggerNative('pageshow') - assert.enabledState(link, 'Click me') -}) - -QUnit.test('a[data-remote][data-disable-with] disables and re-enables', function(assert) { - const done = assert.async() - - var link = $('a[data-disable-with]').attr('data-remote', true) - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:beforeSend', function() { - assert.disabledState(link, 'clicking...') - }) - .bindNative('ajax:complete', function() { - setTimeout( function() { - assert.enabledState(link, 'Click me') - done() - }, 15) - }) - .triggerNative('click') -}) - -QUnit.test('a[data-remote][data-disable-with] re-enables when `ajax:before` event is cancelled', function(assert) { - const done = assert.async() - - var link = $('a[data-disable-with]').attr('data-remote', true) - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:before', function(e) { - assert.disabledState(link, 'clicking...') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) -}) - -QUnit.test('a[data-remote][data-disable-with] re-enables when `ajax:beforeSend` event is cancelled', function(assert) { - const done = assert.async() - - var link = $('a[data-disable-with]').attr('data-remote', true) - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:beforeSend', function(e) { - assert.disabledState(link, 'clicking...') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) -}) - -QUnit.test('a[data-remote][data-disable-with] re-enables when `ajax:error` event is triggered', function(assert) { - const done = assert.async() - - var link = $('a[data-disable-with]').attr('data-remote', true).attr('href', '/error') - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:beforeSend', function() { - assert.disabledState(link, 'clicking...') - }) - .bindNative('ajax:complete', function() { - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) - }) - .triggerNative('click') -}) - -QUnit.test('form[data-remote] input|button|textarea[data-disable-with] does not disable when `ajax:beforeSend` event is cancelled', function(assert) { - var form = $('form[data-remote]'), - input = form.find('input:text'), - button = $('').appendTo(form), - textarea = $('').appendTo(form), - submit = $('').appendTo(form) - - form - .bindNative('ajax:beforeSend', function(e) { - e.preventDefault() - e.stopPropagation() - }) - .triggerNative('submit') - - assert.enabledState(input, 'john') - assert.enabledState(button, 'Submit') - assert.enabledState(textarea, 'born, lived, died.') - assert.enabledState(submit, 'Submit') -}) - -QUnit.test('ctrl-clicking on a link does not disable the link', function(assert) { - var link = $('a[data-disable-with]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { metaKey: true }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { metaKey: true }) - assert.enabledState(link, 'Click me') -}) - -QUnit.test('right/mouse-wheel-clicking on a link does not disable the link', function(assert) { - var link = $('a[data-disable-with]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 1 }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 1 }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 2 }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 2 }) - assert.enabledState(link, 'Click me') -}) - -QUnit.test('button[data-remote][data-disable-with] disables and re-enables', function(assert) { - const done = assert.async() - - var button = $('button[data-remote][data-disable-with]') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:send', function() { - assert.disabledState(button, 'clicking...') - }) - .bindNative('ajax:complete', function() { - setTimeout( function() { - assert.enabledState(button, 'Click me') - done() - }, 15) - }) - .triggerNative('click') -}) - -QUnit.test('button[data-remote][data-disable-with] re-enables when `ajax:before` event is cancelled', function(assert) { - const done = assert.async() - - var button = $('button[data-remote][data-disable-with]') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:before', function(e) { - assert.disabledState(button, 'clicking...') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) -}) - -QUnit.test('button[data-remote][data-disable-with] re-enables when `ajax:beforeSend` event is cancelled', function(assert) { - const done = assert.async() - - var button = $('button[data-remote][data-disable-with]') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:beforeSend', function(e) { - assert.disabledState(button, 'clicking...') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) -}) - -QUnit.test('button[data-remote][data-disable-with] re-enables when `ajax:error` event is triggered', function(assert) { - const done = assert.async() - - var button = $('a[data-disable-with]').attr('data-remote', true).attr('href', '/error') - - assert.enabledState(button, 'Click me') - button - .bindNative('ajax:send', function() { - assert.disabledState(button, 'clicking...') - }) - .bindNative('ajax:complete', function() { - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) - }) - .triggerNative('click') -}) - -QUnit.test('form button with "data-disable-with" attribute and contenteditable is not modified', function(assert) { - const done = assert.async() - var form = $('form[data-remote]'), button = $('') - - var contenteditable_div = $('#qunit-fixture').find('div') - form.append(button) - contenteditable_div.append(form) - - assert.enabledState(button, 'Submit') - - setTimeout(function() { - assert.enabledState(button, 'Submit') - done() - }, 13) - form.triggerNative('submit') - - assert.enabledState(button, 'Submit') -}) diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js deleted file mode 100644 index b4094d2610172..0000000000000 --- a/actionview/test/ujs/public/test/data-disable.js +++ /dev/null @@ -1,387 +0,0 @@ -import $ from 'jquery' - -QUnit.module('data-disable', { - beforeEach: 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) - - assert.enabledState(button, 'Submit') - - form.bindNative('ajax:success', function(e, data) { - setTimeout(function() { - assert.enabledState(button, 'Submit') - done() - }, 13) - }) - form.triggerNative('submit') - - assert.disabledState(button, 'Submit') - assert.equal(button.data('ujs:enable-with'), undefined) -}) - -QUnit.test('form input[type=submit][data-disable] disables', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture form:not([data-remote])'), input = form.find('input[type=submit]') - - assert.enabledState(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() { - assert.disabledState(input, 'Submit') - done() - }, 30) - }) - form.triggerNative('submit') - - setTimeout(function() { - assert.disabledState(input, 'Submit') - }, 30) -}) - -QUnit.test('form[data-remote] input[type=submit][data-disable] is replaced in ajax callback', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture 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]') - assert.enabledState(input, 'Submit') - done() - }, 30) - }).triggerNative('submit') -}) - -QUnit.test('form[data-remote] input[data-disable] is replaced with disabled field in ajax callback', function(assert) { - const done = assert.async() - - var form = $('#qunit-fixture 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() { - assert.enabledState(newDisabledInput, 'Submit') - done() - }, 30) - }).triggerNative('submit') -}) - -QUnit.test('form[data-remote] textarea[data-disable] attribute', function(assert) { - const done = assert.async() - - var form = $('form[data-remote]'), - textarea = $('').appendTo(form) - - form.bindNative('ajax:success', function(e, data) { - setTimeout(function() { - assert.equal(data.params.user_bio, 'born, lived, died.') - done() - }, 13) - }) - form.triggerNative('submit') - - assert.disabledState(textarea, 'born, lived, died.') -}) - -QUnit.test('a[data-disable] disables', function(assert) { - var link = $('a[data-disable]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click') - assert.disabledState(link, 'Click me') - assert.equal(link.data('ujs:enable-with'), undefined) -}) - -QUnit.test('a[data-remote][data-disable] disables and re-enables', function(assert) { - const done = assert.async() - - var link = $('a[data-disable]').attr('data-remote', true) - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:send', function() { - assert.disabledState(link, 'Click me') - }) - .bindNative('ajax:complete', function() { - setTimeout( function() { - assert.enabledState(link, 'Click me') - done() - }, 15) - }) - .triggerNative('click') -}) - -QUnit.test('a[data-remote][data-disable] re-enables when `ajax:before` event is cancelled', function(assert) { - const done = assert.async() - - var link = $('a[data-disable]').attr('data-remote', true) - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:before', function(e) { - assert.disabledState(link, 'Click me') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) -}) - -QUnit.test('a[data-remote][data-disable] re-enables when `ajax:beforeSend` event is cancelled', function(assert) { - const done = assert.async() - - var link = $('a[data-disable]').attr('data-remote', true) - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:beforeSend', function(e) { - assert.disabledState(link, 'Click me') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) -}) - -QUnit.test('a[data-remote][data-disable] re-enables when `ajax:error` event is triggered', function(assert) { - const done = assert.async() - - var link = $('a[data-disable]').attr('data-remote', true).attr('href', '/error') - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:send', function() { - assert.disabledState(link, 'Click me') - }) - .bindNative('ajax:complete', function() { - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) - }) - .triggerNative('click') -}) - -QUnit.test('form[data-remote] input|button|textarea[data-disable] does not disable when `ajax:beforeSend` event is cancelled', function(assert) { - var form = $('form[data-remote]'), - input = form.find('input:text'), - button = $('').appendTo(form), - textarea = $('').appendTo(form), - submit = $('').appendTo(form) - - form - .bindNative('ajax:beforeSend', function(e) { - e.preventDefault() - e.stopPropagation() - }) - .triggerNative('submit') - - assert.enabledState(input, 'john') - assert.enabledState(button, 'Submit') - assert.enabledState(textarea, 'born, lived, died.') - assert.enabledState(submit, 'Submit') -}) - -QUnit.test('ctrl-clicking on a link does not disables the link', function(assert) { - var link = $('a[data-disable]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { metaKey: true }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { ctrlKey: true }) - assert.enabledState(link, 'Click me') -}) - -QUnit.test('right/mouse-wheel-clicking on a link does not disable the link', function(assert) { - var link = $('a[data-disable]') - - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 1 }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 1 }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 2 }) - assert.enabledState(link, 'Click me') - - link.triggerNative('click', { button: 2 }) - assert.enabledState(link, 'Click me') -}) - -QUnit.test('button[data-remote][data-disable] disables and re-enables', function(assert) { - const done = assert.async() - - var button = $('button[data-remote][data-disable]') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:send', function() { - assert.disabledState(button, 'Click me') - }) - .bindNative('ajax:complete', function() { - setTimeout( function() { - assert.enabledState(button, 'Click me') - done() - }, 15) - }) - .triggerNative('click') -}) - -QUnit.test('button[data-remote][data-disable] re-enables when `ajax:before` event is cancelled', function(assert) { - const done = assert.async() - - var button = $('button[data-remote][data-disable]') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:before', function(e) { - assert.disabledState(button, 'Click me') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) -}) - -QUnit.test('button[data-remote][data-disable] re-enables when `ajax:beforeSend` event is cancelled', function(assert) { - const done = assert.async() - - var button = $('button[data-remote][data-disable]') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:beforeSend', function(e) { - assert.disabledState(button, 'Click me') - e.preventDefault() - }) - .triggerNative('click') - - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) -}) - -QUnit.test('button[data-remote][data-disable] re-enables when `ajax:error` event is triggered', function(assert) { - const done = assert.async() - - var button = $('a[data-disable]').attr('data-remote', true).attr('href', '/error') - - assert.enabledState(button, 'Click me') - - button - .bindNative('ajax:send', function() { - assert.disabledState(button, 'Click me') - }) - .bindNative('ajax:complete', function() { - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) - }) - .triggerNative('click') -}) - -QUnit.test('do not enable elements for XHR redirects', function(assert) { - const done = assert.async() - - var link = $('a[data-disable]').attr('data-remote', true).attr('href', '/echo?with_xhr_redirect=true') - - assert.enabledState(link, 'Click me') - - link - .bindNative('ajax:send', function() { - assert.disabledState(link, 'Click me') - }) - .triggerNative('click') - - setTimeout(function() { - assert.disabledState(link, 'Click me') - done() - }, 30) -}) diff --git a/actionview/test/ujs/public/test/data-method.js b/actionview/test/ujs/public/test/data-method.js deleted file mode 100644 index f63c10a6fef80..0000000000000 --- a/actionview/test/ujs/public/test/data-method.js +++ /dev/null @@ -1,108 +0,0 @@ -import $ from 'jquery' - -QUnit.module('data-method', { - beforeEach: function() { - $('#qunit-fixture').append($('', { - href: '/echo', 'data-method': 'delete', text: 'destroy!' - })) - - $('#qunit-fixture').append($('
', { - id: 'edit-div', 'contenteditable': 'true' - })) - }, - afterEach: function() { - $(document).unbind('iframe:loaded') - } -}) - -function submit(fn, options) { - $(document).bind('iframe:loaded', function(e, data) { - fn(data) - }) - - $('#qunit-fixture').find('a') - .triggerNative('click') -} - -QUnit.test('link with "data-method" set to "delete"', function(assert) { - const done = assert.async() - - submit(function(data) { - assert.equal(data.REQUEST_METHOD, 'DELETE') - assert.strictEqual(data.params.authenticity_token, undefined) - assert.strictEqual(data.HTTP_X_CSRF_TOKEN, undefined) - done() - }) -}) - -QUnit.test('click on the child of link with "data-method"', function(assert) { - const done = assert.async() - - $(document).bind('iframe:loaded', function(e, data) { - assert.equal(data.REQUEST_METHOD, 'DELETE') - assert.strictEqual(data.params.authenticity_token, undefined) - assert.strictEqual(data.HTTP_X_CSRF_TOKEN, undefined) - done() - }) - $('#qunit-fixture a').html('destroy!').find('strong').triggerNative('click') -}) - -QUnit.test('link with "data-method" and CSRF', function(assert) { - const done = assert.async() - - $('#qunit-fixture') - .append('') - .append('') - - submit(function(data) { - assert.equal(data.params.authenticity_token, 'cf50faa3fe97702ca1ae') - done() - }) -}) - -QUnit.test('link "target" should be carried over to generated form', function(assert) { - const done = assert.async() - - $('a[data-method]').attr('target', 'super-special-frame') - submit(function(data) { - assert.equal(data.params._target, 'super-special-frame') - done() - }) -}) - -QUnit.test('link with "data-method" and cross origin', function(assert) { - 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') - - assert.notEqual(data.authenticity_token, 'cf50faa3fe97702ca1ae') -}) - -QUnit.test('do not interact with contenteditable elements', function(assert) { - var contenteditable_div = $('#qunit-fixture').find('div') - contenteditable_div.append('') - - var link = $('#edit-div').find('a') - link.triggerNative('click') - - var collection = document.getElementsByTagName('form') - for (const item of collection) { - assert.notEqual(item.action, 'http://www.shouldnevershowindocument.com/') - } -}) diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js deleted file mode 100644 index 6f867d41fe1e4..0000000000000 --- a/actionview/test/ujs/public/test/data-remote.js +++ /dev/null @@ -1,601 +0,0 @@ -import $ from 'jquery' - -function buildSelect(attrs) { - attrs = $.extend({ - 'name': 'user_data', 'data-remote': 'true', 'data-url': '/echo', 'data-params': 'data1=value1' - }, attrs) - - $('#qunit-fixture').append( - $('')) - - $('#qunit-fixture').append($('
', { - id: 'edit-div', 'contenteditable': 'true' - })) - } -}) - -QUnit.test('ctrl-clicking on a link does not fire ajaxyness', function(assert) { - const done = assert.async() - assert.expect(0) - var link = $('a[data-remote]') - // Ideally, we'd set up an iframe to intercept normal link clicks - // and add a test to make sure the iframe:loaded event is triggered. - // However, jquery doesn't actually cause a native `click` event and - // follow links using `trigger('click')`, it only fires bindings. - link - .removeAttr('data-params') - .bindNative('ajax:beforeSend', function() { - assert.ok(false, 'ajax should not be triggered') - }) - - link.triggerNative('click', { metaKey: true }) - link.triggerNative('click', { ctrlKey: true }) - - setTimeout(function() { - done() - }, 13) -}) - -QUnit.test('right/mouse-wheel-clicking on a link does not fire ajaxyness', function(assert) { - const done = assert.async() - assert.expect(0) - - var link = $('a[data-remote]') - - // Ideally, we'd set up an iframe to intercept normal link clicks - // and add a test to make sure the iframe:loaded event is triggered. - // However, jquery doesn't actually cause a native `click` event and - // follow links using `trigger('click')`, it only fires bindings. - link - .removeAttr('data-params') - .bindNative('ajax:beforeSend', function() { - assert.ok(false, 'ajax should not be triggered') - }) - - link.triggerNative('click', { button: 1 }) - link.triggerNative('click', { button: 2 }) - - setTimeout(function() { - done() - }, 13) -}) - -QUnit.test('clicking on a link via a non-mouse Event (such as from js) works', function(assert) { - var link = $('a[data-remote]') - - const done = assert.async() - - link - .removeAttr('data-params') - .bindNative('ajax:beforeSend', function() { - assert.ok(true, 'ajax should be triggered') - }) - - Rails.fire(link[0], 'click') - - setTimeout(function() { done() }, 13) -}) - -QUnit.test('ctrl-clicking on a link still fires ajax for non-GET links and for links with "data-params"', function(assert) { - var link = $('a[data-remote]') - - const done = assert.async() - - link - .removeAttr('data-params') - .attr('data-method', 'POST') - .bindNative('ajax:beforeSend', function() { - assert.ok(true, 'ajax should be triggered') - }) - .triggerNative('click', { metaKey: true }) - - link - .removeAttr('data-method') - .attr('data-params', 'name=steve') - .triggerNative('click', { metaKey: true }) - - setTimeout(function() { done() }, 13) -}) - -QUnit.test('clicking on a link with data-remote attribute', function(assert) { - const done = assert.async() - - $('a[data-remote]') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.requestPath(data, '/echo') - assert.equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') - assert.equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value') - assert.getRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - .triggerNative('click') -}) - -QUnit.test('clicking on a link with both query string in href and data-params', function(assert) { - const done = assert.async() - - $('a[data-remote]') - .attr('href', '/echo?data3=value3') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.getRequest(data) - assert.equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') - assert.equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value') - assert.equal(data.params.data3, 'value3', 'query string in URL should be passed to server with right value') - }) - .bindNative('ajax:complete', function() { done() }) - .triggerNative('click') -}) - -QUnit.test('clicking on a link with both query string in href and data-params with POST method', function(assert) { - const done = assert.async() - - $('a[data-remote]') - .attr('href', '/echo?data3=value3') - .attr('data-method', 'post') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.postRequest(data) - assert.equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') - assert.equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value') - assert.equal(data.params.data3, 'value3', 'query string in URL should be passed to server with right value') - }) - .bindNative('ajax:complete', function() { done() }) - .triggerNative('click') -}) - -QUnit.test('clicking on a link with disabled attribute', function(assert) { - const done = assert.async() - assert.expect(0) - - $('#qunit-fixture a[disabled]') - .bindNative('ajax:before', function(e, data, status, xhr) { - assert.callbackNotInvoked('ajax:success') - }) - .triggerNative('click') - - setTimeout(function() { - done() - }, 13) -}) - -QUnit.test('clicking on a button with data-remote attribute', function(assert) { - const done = assert.async() - - $('button[data-remote]') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.requestPath(data, '/echo') - assert.equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') - assert.equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value') - assert.getRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - .triggerNative('click') -}) - -QUnit.test('right/mouse-wheel-clicking on a button with data-remote attribute does not fire ajaxyness', function(assert) { - const done = assert.async() - assert.expect(0) - - var button = $('button[data-remote]') - - // Ideally, we'd set up an iframe to intercept normal link clicks - // and add a test to make sure the iframe:loaded event is triggered. - // However, jquery doesn't actually cause a native `click` event and - // follow links using `trigger('click')`, it only fires bindings. - button - .removeAttr('data-params') - .bindNative('ajax:beforeSend', function() { - assert.ok(false, 'ajax should not be triggered') - }) - - button.triggerNative('click', { button: 1 }) - button.triggerNative('click', { button: 2 }) - - setTimeout(function() { - done() - }, 13) -}) - -QUnit.test('changing a select option with data-remote attribute', function(assert) { - const done = assert.async() - - buildSelect() - - $('select[data-remote]') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.requestPath(data, '/echo') - assert.equal(data.params.user_data, 'optionValue2', 'ajax arguments should have key term with right value') - assert.equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') - assert.getRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - .val('optionValue2') - .triggerNative('change') -}) - -QUnit.test('submitting form with data-remote attribute', function(assert) { - const done = assert.async() - - $('form[data-remote]') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.requestPath(data, '/echo') - assert.equal(data.params.user_name, 'john', 'ajax arguments should have key user_name with right value') - assert.postRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - .triggerNative('submit') -}) - -QUnit.test('submitting form with data-remote attribute should include inputs in a fieldset only once', function(assert) { - const done = assert.async() - - $('form[data-remote]') - .append('
') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.equal(data.params.items.length, 1, 'ajax arguments should only have the item once') - assert.postRequest(data) - }) - .bindNative('ajax:complete', function() { - $('form[data-remote], fieldset').remove() - done() - }) - .triggerNative('submit') -}) - -QUnit.test('submitting form with data-remote attribute submits input with matching [form] attribute', function(assert) { - const done = assert.async() - - $('#qunit-fixture') - .append($('')) - .append($('')) - - $('form[data-remote]') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.requestPath(data, '/echo') - assert.equal(data.params.user_name, 'john', 'ajax arguments should have key user_name with right value') - assert.equal(data.params.user_data, 'value1', 'ajax arguments should have key user_data with right value') - assert.equal(data.params.user_email, undefined, 'ajax arguments should not have disabled field') - assert.postRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - .triggerNative('submit') -}) - -QUnit.test('submitting form with data-remote attribute by clicking button with matching [form] attribute', function(assert) { - const done = assert.async() - - $('form[data-remote]') - .bindNative('ajax:success', function(e, data, status, xhr) { - assert.callbackInvoked('ajax:success') - assert.requestPath(data, '/echo') - assert.equal(data.params.user_name, 'john', 'ajax arguments should have key user_name with right value') - assert.equal(data.params.user_data, 'value2', 'ajax arguments should have key user_data with right value') - assert.postRequest(data) - }) - .bindNative('ajax:complete', function() { done() }) - - $('