Remove rollup and test machinery for rails-ujs (#50535)
This leaves only the final compiled targets in place.
This commit is contained in:
parent
66c174557a
commit
8397eb24da
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -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 .
|
||||
|
2
.github/workflows/rails-new-docker.yml
vendored
2
.github/workflows/rails-new-docker.yml
vendored
@ -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:
|
||||
|
@ -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 <pkg>`) of each
|
||||
package. Do not release until you're set up with npm!
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
2
actionview/.gitignore
vendored
2
actionview/.gitignore
vendored
@ -1,6 +1,4 @@
|
||||
/lib/assets/compiled/
|
||||
/test/ujs/compiled/
|
||||
/log/
|
||||
/test/fixtures/public/absolute/
|
||||
/test/ujs/log/
|
||||
/tmp/
|
||||
|
@ -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
|
@ -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"]
|
||||
|
@ -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.
|
@ -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
|
@ -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 }
|
@ -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 }
|
@ -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:
|
||||
// <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
|
||||
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 = `<input name='_method' value='${method}' type='hidden' />`
|
||||
|
||||
if (csrfParam && csrfToken && !isCrossDomain(href)) {
|
||||
formContent += `<input name='${csrfParam}' value='${csrfToken}' type='hidden' />`
|
||||
}
|
||||
|
||||
// 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 += "<input type=\"submit\" />"
|
||||
|
||||
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 }
|
@ -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 }
|
@ -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
|
@ -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 }
|
@ -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
|
||||
}
|
@ -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 }
|
@ -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 }
|
@ -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 }
|
@ -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 }
|
@ -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 }
|
@ -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)
|
||||
}
|
@ -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?
|
||||
|
@ -522,25 +522,6 @@ def radio_button_tag(name, value, *args)
|
||||
# submit_tag "Edit", class: "edit_button"
|
||||
# # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" />
|
||||
#
|
||||
# ==== 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:
|
||||
#
|
||||
# * <tt>confirm: 'question?'</tt> - 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.
|
||||
# * <tt>:disable_with</tt> - 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 <tt>:data => { disable_with: false }</tt> Defaults to value attribute.
|
||||
#
|
||||
# submit_tag "Complete sale", data: { disable_with: "Submitting..." }
|
||||
# # => <input name="commit" data-disable-with="Submitting..." type="submit" value="Complete sale" />
|
||||
#
|
||||
# submit_tag "Save", data: { confirm: "Are you sure?" }
|
||||
# # => <input name='commit' type='submit' value='Save' data-disable-with="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 = {})
|
||||
# # <strong>Ask me!</strong>
|
||||
# # </button>
|
||||
#
|
||||
# ==== 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:
|
||||
#
|
||||
# * <tt>confirm: 'question?'</tt> - 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.
|
||||
# * <tt>:disable_with</tt> - 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 name="button" type="submit" data-confirm="Are you sure?">Save</button>
|
||||
#
|
||||
# button_tag "Checkout", data: { disable_with: "Please wait..." }
|
||||
# # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
|
||||
#
|
||||
def button_tag(content_or_options = nil, options = nil, &block)
|
||||
if content_or_options.is_a? Hash
|
||||
options = content_or_options
|
||||
|
@ -195,42 +195,6 @@ def _filtered_referrer # :nodoc:
|
||||
# link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" }
|
||||
# # => <a href="https://rubyonrails.org/" data-turbo-confirm="Are you sure?">Visit Other Site</a>
|
||||
#
|
||||
# ==== Deprecated: \Rails UJS Attributes
|
||||
#
|
||||
# Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
|
||||
# this library is no longer on by default. This library integrated with the following options:
|
||||
#
|
||||
# * <tt>method: symbol of HTTP verb</tt> - 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 <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
|
||||
# Note that if the user has JavaScript disabled, the request will fall back
|
||||
# to using GET. If <tt>href: '#'</tt> 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 <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
|
||||
# * <tt>remote: true</tt> - This will allow <tt>@rails/ujs</tt>
|
||||
# to make an Ajax request to the URL in question instead of following
|
||||
# the link.
|
||||
#
|
||||
# <tt>@rails/ujs</tt> also integrated with the following +:data+ options:
|
||||
#
|
||||
# * <tt>confirm: "question?"</tt> - This will allow <tt>@rails/ujs</tt>
|
||||
# to prompt with the question specified (in this case, the
|
||||
# resulting text would be <tt>question?</tt>). If the user accepts, the
|
||||
# link is processed normally, otherwise no action is taken.
|
||||
# * <tt>:disable_with</tt> - 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
|
||||
# # => <a href="/profiles/1" rel="nofollow" data-method="delete">Remove Profile</a>
|
||||
#
|
||||
# link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
|
||||
# # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
|
||||
#
|
||||
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)
|
||||
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
|
||||
# # </form>"
|
||||
#
|
||||
# ==== Deprecated: \Rails UJS Attributes
|
||||
#
|
||||
# Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
|
||||
# this library is no longer on by default. This library integrated with the following options:
|
||||
#
|
||||
# * <tt>:remote</tt> - If set to true, will allow <tt>@rails/ujs</tt> to control the
|
||||
# submit behavior. By default this behavior is an Ajax submit.
|
||||
#
|
||||
# <tt>@rails/ujs</tt> also integrated with the following +:data+ options:
|
||||
#
|
||||
# * <tt>confirm: "question?"</tt> - This will allow <tt>@rails/ujs</tt>
|
||||
# to prompt with the question specified (in this case, the
|
||||
# resulting text would be <tt>question?</tt>). If the user accepts, the
|
||||
# button is processed normally, otherwise no action is taken.
|
||||
# * <tt>:disable_with</tt> - 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" } %>
|
||||
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
|
||||
# # <button type="submit">Create</button>
|
||||
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
|
||||
# # </form>"
|
||||
#
|
||||
def button_to(name = nil, options = nil, html_options = nil, &block)
|
||||
html_options, options = options, name if block_given?
|
||||
html_options ||= {}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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),
|
||||
]
|
||||
}
|
||||
]
|
@ -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()
|
||||
]
|
||||
}
|
@ -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
|
@ -1,6 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.unshift __dir__
|
||||
require "server"
|
||||
|
||||
run UJS::Server
|
@ -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'
|
@ -1,27 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import Rails from '../../../../app/javascript/rails-ujs/index'
|
||||
|
||||
QUnit.module('call-ajax', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture')
|
||||
.append($('<a />', { 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')
|
||||
})
|
@ -1,266 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('call-remote-callbacks', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture').append($('<form />', {
|
||||
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($('<input type="text" name="user_name" value="john">'))
|
||||
.append($('<input type="text" name="removed_user_name" value="john">'))
|
||||
.bindNative('ajax:before', function() {
|
||||
var form = $(this)
|
||||
form
|
||||
.append($('<input />', {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($('<input type="file" name="attachment" value="default.png">'))
|
||||
.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($('<input type="file" value="default.png">'))
|
||||
.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($('<input type="file" name="attachment" value="default.png">'))
|
||||
.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')
|
||||
})
|
@ -1,354 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
function buildForm(attrs) {
|
||||
attrs = $.extend({ action: '/echo', 'data-remote': 'true' }, attrs)
|
||||
|
||||
$('#qunit-fixture').append($('<form />', attrs))
|
||||
.find('form').append($('<input type="text" name="user_name" value="john">'))
|
||||
}
|
||||
|
||||
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 = $('<input type="submit" formmethod="get">')
|
||||
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 = $('<input type="submit" formaction="/echo">')
|
||||
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('<input type="text" name="content_type" value="text/javascript">')
|
||||
$('form').append('<input type="text" name="content" value="window.callback()">')
|
||||
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('<input type="text" name="content_type" value="application/ecmascript">')
|
||||
$('form').append('<input type="text" name="content" value="window.callback()">')
|
||||
|
||||
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('<input type="text" name="content_type" value="text/javascript">')
|
||||
$('form').append('<input type="text" name="content" value="\'remote code should be run\'">')
|
||||
|
||||
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('<input type="text" name="content_type" value="text/html">')
|
||||
$('form').append('<input type="text" name="content" value="<p>hello</p>">')
|
||||
|
||||
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('<input type="text" name="content_type" value="application/xml">')
|
||||
$('form').append('<input type="text" name="content" value="<p>hello</p>">')
|
||||
|
||||
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($('<form action="/echo" data-remote />')).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('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />')
|
||||
|
||||
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('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />')
|
||||
|
||||
$('#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('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />')
|
||||
|
||||
$('#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)
|
||||
})
|
@ -1,21 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('csrf-refresh', {})
|
||||
|
||||
QUnit.test('refresh all csrf tokens', function(assert) {
|
||||
var correctToken = 'cf50faa3fe97702ca1ae'
|
||||
|
||||
var form = $('<form />')
|
||||
var input = $('<input>').attr({ type: 'hidden', name: 'authenticity_token', id: 'authenticity_token', value: 'foo' })
|
||||
input.appendTo(form)
|
||||
|
||||
$('#qunit-fixture')
|
||||
.append('<meta name="csrf-param" content="authenticity_token"/>')
|
||||
.append('<meta name="csrf-token" content="' + correctToken + '"/>')
|
||||
.append(form)
|
||||
|
||||
$.rails.refreshCSRFTokens()
|
||||
var currentToken = $('#qunit-fixture #authenticity_token').val()
|
||||
|
||||
assert.equal(currentToken, correctToken)
|
||||
})
|
@ -1,23 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('csrf-token', {})
|
||||
|
||||
QUnit.test('find csrf token', function(assert) {
|
||||
var correctToken = 'cf50faa3fe97702ca1ae'
|
||||
|
||||
$('#qunit-fixture').append('<meta name="csrf-token" content="' + correctToken + '"/>')
|
||||
|
||||
var currentToken = $.rails.csrfToken()
|
||||
|
||||
assert.equal(currentToken, correctToken)
|
||||
})
|
||||
|
||||
QUnit.test('find csrf param', function(assert) {
|
||||
var correctParam = 'authenticity_token'
|
||||
|
||||
$('#qunit-fixture').append('<meta name="csrf-param" content="' + correctParam + '"/>')
|
||||
|
||||
var currentParam = $.rails.csrfParam()
|
||||
|
||||
assert.equal(currentParam, correctParam)
|
||||
})
|
@ -1,372 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('data-confirm', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture').append($('<a />', {
|
||||
href: '/echo',
|
||||
'data-remote': 'true',
|
||||
'data-confirm': 'Are you absolutely sure?',
|
||||
text: 'my social security number'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<button />', {
|
||||
'data-url': '/echo',
|
||||
'data-remote': 'true',
|
||||
'data-confirm': 'Are you absolutely sure?',
|
||||
text: 'Click me'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<form />', {
|
||||
id: 'confirm',
|
||||
action: '/echo',
|
||||
'data-remote': 'true'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<input />', {
|
||||
type: 'submit',
|
||||
form: 'confirm',
|
||||
'data-confirm': 'Are you absolutely sure?'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<button />', {
|
||||
type: 'submit',
|
||||
form: 'confirm',
|
||||
disabled: 'disabled',
|
||||
'data-confirm': 'Are you absolutely sure?'
|
||||
}))
|
||||
|
||||
this.windowConfirm = window.confirm
|
||||
},
|
||||
afterEach: function() {
|
||||
window.confirm = this.windowConfirm
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a link with data-confirm attribute. Confirm yes.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-confirm:
|
||||
window.confirm = function(msg) { message = msg; return true }
|
||||
|
||||
$('a[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == true, 'confirm:complete passes in confirm answer (true)')
|
||||
})
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.callbackInvoked('ajax:success')
|
||||
assert.requestPath(data, '/echo')
|
||||
assert.getRequest(data)
|
||||
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
})
|
||||
.triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a button with data-confirm attribute. Confirm yes.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-confirm:
|
||||
window.confirm = function(msg) { message = msg; return true }
|
||||
|
||||
$('button[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == true, 'confirm:complete passes in confirm answer (true)')
|
||||
})
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.callbackInvoked('ajax:success')
|
||||
assert.requestPath(data, '/echo')
|
||||
assert.getRequest(data)
|
||||
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
})
|
||||
.triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a link with data-confirm attribute. Confirm No.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-decline:
|
||||
window.confirm = function(msg) { message = msg; return false }
|
||||
|
||||
$('a[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == false, 'confirm:complete passes in confirm answer (false)')
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function(e, data, status, xhr) {
|
||||
assert.callbackNotInvoked('ajax:beforeSend')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a button with data-confirm attribute. Confirm No.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-decline:
|
||||
window.confirm = function(msg) { message = msg; return false }
|
||||
|
||||
$('button[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == false, 'confirm:complete passes in confirm answer (false)')
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function(e, data, status, xhr) {
|
||||
assert.callbackNotInvoked('ajax:beforeSend')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a button with data-confirm attribute. Confirm error.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-decline:
|
||||
window.confirm = function(msg) { message = msg; throw 'some random error' }
|
||||
|
||||
$('button[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == false, 'confirm:complete passes in confirm answer (false)')
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function(e, data, status, xhr) {
|
||||
assert.callbackNotInvoked('ajax:beforeSend')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a submit button with form and data-confirm attributes. Confirm No.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-decline:
|
||||
window.confirm = function(msg) { message = msg; return false }
|
||||
|
||||
$('#qunit-fixture input[type=submit][form]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == false, 'confirm:complete passes in confirm answer (false)')
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function(e, data, status, xhr) {
|
||||
assert.callbackNotInvoked('ajax:beforeSend')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('binding to confirm event of a link and returning false', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
// redefine confirm function so we can make sure it's not called
|
||||
window.confirm = function(msg) {
|
||||
assert.ok(false, 'confirm dialog should not be called')
|
||||
}
|
||||
|
||||
$('a[data-confirm]')
|
||||
.bindNative('confirm', function(e) {
|
||||
assert.callbackInvoked('confirm')
|
||||
e.preventDefault()
|
||||
})
|
||||
.bindNative('confirm:complete', function() {
|
||||
assert.callbackNotInvoked('confirm:complete')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('binding to confirm event of a button and returning false', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
// redefine confirm function so we can make sure it's not called
|
||||
window.confirm = function(msg) {
|
||||
assert.ok(false, 'confirm dialog should not be called')
|
||||
}
|
||||
|
||||
$('button[data-confirm]')
|
||||
.bindNative('confirm', function(e) {
|
||||
assert.callbackInvoked('confirm')
|
||||
e.preventDefault()
|
||||
})
|
||||
.bindNative('confirm:complete', function() {
|
||||
assert.callbackNotInvoked('confirm:complete')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('binding to confirm:complete event of a link and returning false', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
// auto-confirm:
|
||||
window.confirm = function(msg) {
|
||||
assert.ok(true, 'confirm dialog should be called')
|
||||
return true
|
||||
}
|
||||
|
||||
$('a[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
e.preventDefault()
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.callbackNotInvoked('ajax:beforeSend')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('binding to confirm:complete event of a button and returning false', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
// auto-confirm:
|
||||
window.confirm = function(msg) {
|
||||
assert.ok(true, 'confirm dialog should be called')
|
||||
return true
|
||||
}
|
||||
|
||||
$('button[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
e.preventDefault()
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.callbackNotInvoked('ajax:beforeSend')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('a button inside a form only confirms once', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var confirmations = 0
|
||||
window.confirm = function(msg) {
|
||||
confirmations++
|
||||
return true
|
||||
}
|
||||
|
||||
$('#qunit-fixture').append($('<form />').append($('<button />', {
|
||||
'data-remote': 'true',
|
||||
'data-confirm': 'Are you absolutely sure?',
|
||||
text: 'Click me'
|
||||
})))
|
||||
|
||||
$('#qunit-fixture form > button[data-confirm]').triggerNative('click')
|
||||
|
||||
assert.ok(confirmations === 1, 'confirmation counter should be 1, but it was ' + confirmations)
|
||||
done()
|
||||
})
|
||||
|
||||
QUnit.test('clicking on the children of a link should also trigger a confirm', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-confirm:
|
||||
window.confirm = function(msg) { message = msg; return true }
|
||||
|
||||
$('a[data-confirm]')
|
||||
.html('<strong>Click me</strong>')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == true, 'confirm:complete passes in confirm answer (true)')
|
||||
})
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.callbackInvoked('ajax:success')
|
||||
assert.requestPath(data, '/echo')
|
||||
assert.getRequest(data)
|
||||
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
done()
|
||||
})
|
||||
.find('strong')
|
||||
.triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('clicking on the children of a disabled button should not trigger a confirm.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message
|
||||
// auto-decline:
|
||||
window.confirm = function(msg) { message = msg; return false }
|
||||
|
||||
$('button[data-confirm][disabled]')
|
||||
.html('<strong>Click me</strong>')
|
||||
.bindNative('confirm', function() {
|
||||
assert.callbackNotInvoked('confirm')
|
||||
})
|
||||
.find('strong')
|
||||
.bindNative('click', function() {
|
||||
assert.callbackInvoked('click')
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() {
|
||||
done()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a link with data-confirm attribute with custom confirm handler. Confirm yes.', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var message, element
|
||||
// redefine confirm function so we can make sure it's not called
|
||||
window.confirm = function(msg) {
|
||||
assert.ok(false, 'confirm dialog should not be called')
|
||||
}
|
||||
// custom auto-confirm:
|
||||
Rails.confirm = function(msg, elem) { message = msg; element = elem; return true }
|
||||
|
||||
$('a[data-confirm]')
|
||||
.bindNative('confirm:complete', function(e, data) {
|
||||
assert.callbackInvoked('confirm:complete')
|
||||
assert.ok(data == true, 'confirm:complete passes in confirm answer (true)')
|
||||
})
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.callbackInvoked('ajax:success')
|
||||
assert.requestPath(data, '/echo')
|
||||
assert.getRequest(data)
|
||||
|
||||
assert.equal(message, 'Are you absolutely sure?')
|
||||
assert.equal(element, $('a[data-confirm]').get(0))
|
||||
done()
|
||||
})
|
||||
.triggerNative('click')
|
||||
})
|
@ -1,490 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('data-disable-with', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture').append($('<form />', {
|
||||
action: '/echo',
|
||||
'data-remote': 'true',
|
||||
method: 'post'
|
||||
}))
|
||||
.find('form')
|
||||
.append($('<input type="text" data-disable-with="processing ..." name="user_name" value="john" />'))
|
||||
|
||||
$('#qunit-fixture').append($('<form />', {
|
||||
action: '/echo',
|
||||
method: 'post',
|
||||
id: 'not_remote'
|
||||
}))
|
||||
.find('form:last')
|
||||
// WEEIRDD: the form won't submit to an iframe if the button is name="submit" (??!)
|
||||
.append($('<input type="submit" data-disable-with="submitting ..." name="submit2" value="Submit" />'))
|
||||
|
||||
$('#qunit-fixture').append($('<a />', {
|
||||
text: 'Click me',
|
||||
href: '/echo',
|
||||
'data-disable-with': 'clicking...'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<input />', {
|
||||
type: 'submit',
|
||||
form: 'not_remote',
|
||||
'data-disable-with': 'form attr submitting',
|
||||
name: 'submit3',
|
||||
value: 'Form Attr Submit'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<button />', {
|
||||
text: 'Click me',
|
||||
'data-remote': true,
|
||||
'data-url': '/echo',
|
||||
'data-disable-with': 'clicking...'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<div />', {
|
||||
id: 'edit-div', 'contenteditable': 'true'
|
||||
}))
|
||||
},
|
||||
afterEach: function() {
|
||||
$(document).unbind('iframe:loaded')
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('form input field with "data-disable-with" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]'), input = form.find('input[type=text]')
|
||||
|
||||
assert.enabledState(input, 'john')
|
||||
|
||||
form.bindNative('ajax:success', function(e, data) {
|
||||
setTimeout(function() {
|
||||
assert.enabledState(input, 'john')
|
||||
assert.equal(data.params.user_name, 'john')
|
||||
done()
|
||||
}, 13)
|
||||
})
|
||||
form.triggerNative('submit')
|
||||
|
||||
assert.disabledState(input, 'processing ...')
|
||||
})
|
||||
|
||||
QUnit.test('blank form input field with "data-disable-with" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]'), input = form.find('input[type=text]')
|
||||
|
||||
input.val('')
|
||||
assert.enabledState(input, '')
|
||||
|
||||
form.bindNative('ajax:success', function(e, data) {
|
||||
setTimeout(function() {
|
||||
assert.enabledState(input, '')
|
||||
assert.equal(data.params.user_name, '')
|
||||
done()
|
||||
}, 13)
|
||||
})
|
||||
form.triggerNative('submit')
|
||||
|
||||
assert.disabledState(input, 'processing ...')
|
||||
})
|
||||
|
||||
QUnit.test('form button with "data-disable-with" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]'), button = $('<button data-disable-with="submitting ..." name="submit2">Submit</button>')
|
||||
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 = $('<a data-remote="true" data-disable-with="clicking...">Click me</a>')
|
||||
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 = $('<textarea data-disable-with="processing ..." name="user_bio">born, lived, died.</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 = $('<button data-disable-with="submitting ..." name="submit2">Submit</button>').appendTo(form),
|
||||
textarea = $('<textarea data-disable-with="processing ..." name="user_bio">born, lived, died.</textarea>').appendTo(form),
|
||||
submit = $('<input type="submit" data-disable-with="submitting ..." name="submit2" value="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 = $('<button data-disable-with="submitting ..." name="submit2">Submit</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')
|
||||
})
|
@ -1,387 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('data-disable', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture').append($('<form />', {
|
||||
action: '/echo',
|
||||
'data-remote': 'true',
|
||||
method: 'post'
|
||||
}))
|
||||
.find('form')
|
||||
.append($('<input type="text" data-disable name="user_name" value="john" />'))
|
||||
|
||||
$('#qunit-fixture').append($('<form />', {
|
||||
action: '/echo',
|
||||
method: 'post'
|
||||
}))
|
||||
.find('form:last')
|
||||
// WEEIRDD: the form won't submit to an iframe if the button is name="submit" (??!)
|
||||
.append($('<input type="submit" data-disable name="submit2" value="Submit" />'))
|
||||
|
||||
$('#qunit-fixture').append($('<a />', {
|
||||
text: 'Click me',
|
||||
href: '/echo',
|
||||
'data-disable': 'true'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<button />', {
|
||||
text: 'Click me',
|
||||
'data-remote': true,
|
||||
'data-url': '/echo',
|
||||
'data-disable': 'true'
|
||||
}))
|
||||
},
|
||||
afterEach: function() {
|
||||
$(document).unbind('iframe:loaded')
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('form input field with "data-disable" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]'), input = form.find('input[type=text]')
|
||||
|
||||
assert.enabledState(input, 'john')
|
||||
|
||||
form.bindNative('ajax:success', function(e, data) {
|
||||
setTimeout(function() {
|
||||
assert.enabledState(input, 'john')
|
||||
assert.equal(data.params.user_name, 'john')
|
||||
done()
|
||||
}, 13)
|
||||
})
|
||||
form.triggerNative('submit')
|
||||
|
||||
assert.disabledState(input, 'john')
|
||||
})
|
||||
|
||||
QUnit.test('form button with "data-disable" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]'), button = $('<button data-disable name="submit2">Submit</button>')
|
||||
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 = $('<textarea data-disable name="user_bio">born, lived, died.</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 = $('<button data-disable="submitting ..." name="submit2">Submit</button>').appendTo(form),
|
||||
textarea = $('<textarea data-disable name="user_bio">born, lived, died.</textarea>').appendTo(form),
|
||||
submit = $('<input type="submit" data-disable="submitting ..." name="submit2" value="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)
|
||||
})
|
@ -1,108 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
QUnit.module('data-method', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture').append($('<a />', {
|
||||
href: '/echo', 'data-method': 'delete', text: 'destroy!'
|
||||
}))
|
||||
|
||||
$('#qunit-fixture').append($('<div />', {
|
||||
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('<strong>destroy!</strong>').find('strong').triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('link with "data-method" and CSRF', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
$('#qunit-fixture')
|
||||
.append('<meta name="csrf-param" content="authenticity_token"/>')
|
||||
.append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae"/>')
|
||||
|
||||
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('<meta name="csrf-param" content="authenticity_token"/>')
|
||||
.append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae"/>')
|
||||
|
||||
$(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('<a href="http://www.shouldnevershowindocument.com" data-method="delete">')
|
||||
|
||||
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/')
|
||||
}
|
||||
})
|
@ -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(
|
||||
$('<select />', attrs)
|
||||
.append($('<option />', {value: 'optionValue1', text: 'option1'}))
|
||||
.append($('<option />', {value: 'optionValue2', text: 'option2'}))
|
||||
)
|
||||
}
|
||||
|
||||
QUnit.module('data-remote', {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture')
|
||||
.append($('<a />', {
|
||||
href: '/echo',
|
||||
'data-remote': 'true',
|
||||
'data-params': 'data1=value1&data2=value2',
|
||||
text: 'my address'
|
||||
}))
|
||||
.append($('<button />', {
|
||||
'data-url': '/echo',
|
||||
'data-remote': 'true',
|
||||
'data-params': 'data1=value1&data2=value2',
|
||||
text: 'my button'
|
||||
}))
|
||||
.append($('<form />', {
|
||||
action: '/echo',
|
||||
'data-remote': 'true',
|
||||
method: 'post',
|
||||
id: 'my-remote-form'
|
||||
}))
|
||||
.append($('<a />', {
|
||||
href: '/echo',
|
||||
'data-remote': 'true',
|
||||
disabled: 'disabled',
|
||||
text: 'Disabled link'
|
||||
}))
|
||||
.find('form').append($('<input type="text" name="user_name" value="john">'))
|
||||
|
||||
$('#qunit-fixture').append($('<div />', {
|
||||
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('<fieldset><input name="items[]" value="Item" /></fieldset>')
|
||||
.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($('<input type="text" name="user_data" value="value1" form="my-remote-form">'))
|
||||
.append($('<input type="text" name="user_email" value="from@example.com" disabled="disabled" form="my-remote-form">'))
|
||||
|
||||
$('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() })
|
||||
|
||||
$('<button />', {
|
||||
type: 'submit',
|
||||
name: 'user_data',
|
||||
value: 'value1',
|
||||
form: 'my-remote-form'
|
||||
})
|
||||
.appendTo($('#qunit-fixture'))
|
||||
|
||||
$('<button />', {
|
||||
type: 'submit',
|
||||
name: 'user_data',
|
||||
value: 'value2',
|
||||
form: 'my-remote-form'
|
||||
})
|
||||
.appendTo($('#qunit-fixture'))
|
||||
.triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('form\'s submit bindings in browsers that don\'t support submit bubbling', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]'), directBindingCalled = false
|
||||
|
||||
assert.ok(!directBindingCalled, 'nothing is called')
|
||||
|
||||
form
|
||||
.append($('<input type="submit" />'))
|
||||
.bindNative('submit', function(event) {
|
||||
assert.ok(event.type == 'submit', 'submit event handlers are called with submit event')
|
||||
assert.ok(true, 'binding handler is called')
|
||||
directBindingCalled = true
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(true, 'form being submitted via ajax')
|
||||
assert.ok(directBindingCalled, 'binding handler already called')
|
||||
})
|
||||
.bindNative('ajax:complete', function() {
|
||||
done()
|
||||
})
|
||||
|
||||
if(!$.support.submitBubbles) {
|
||||
// Must indirectly submit form via click to trigger jQuery's manual submit bubbling in IE
|
||||
form.find('input[type=submit]')
|
||||
.triggerNative('click')
|
||||
} else {
|
||||
form.triggerNative('submit')
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('returning false in form\'s submit bindings in non-submit-bubbling browsers', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var form = $('form[data-remote]')
|
||||
|
||||
form
|
||||
.append($('<input type="submit" />'))
|
||||
.bindNative('submit', function(e) {
|
||||
assert.ok(true, 'binding handler is called')
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'form should not be submitted')
|
||||
})
|
||||
|
||||
if (!$.support.submitBubbles) {
|
||||
// Must indirectly submit form via click to trigger jQuery's manual submit bubbling in IE
|
||||
form.find('input[type=submit]').triggerNative('click')
|
||||
} else {
|
||||
form.triggerNative('submit')
|
||||
}
|
||||
|
||||
setTimeout(function() { done() }, 13)
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a link with falsy "data-remote" attribute does not fire ajaxyness', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
assert.expect(0)
|
||||
$('a[data-remote]')
|
||||
.attr('data-remote', 'false')
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'ajax should not be triggered')
|
||||
})
|
||||
.bindNative('click', function(e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
||||
|
||||
QUnit.test('ctrl-clicking on a link with falsy "data-remote" attribute does not fire ajaxyness even if "data-params" present', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var link = $('a[data-remote]')
|
||||
assert.expect(0)
|
||||
|
||||
link
|
||||
.removeAttr('data-params')
|
||||
.attr('data-remote', 'false')
|
||||
.attr('data-method', 'POST')
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'ajax should not be triggered')
|
||||
})
|
||||
.bindNative('click', function(e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('click', { metaKey: true })
|
||||
|
||||
link
|
||||
.removeAttr('data-method')
|
||||
.attr('data-params', 'name=steve')
|
||||
.triggerNative('click', { metaKey: true })
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
||||
|
||||
QUnit.test('clicking on a button with falsy "data-remote" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
assert.expect(0)
|
||||
|
||||
$('button[data-remote]:first')
|
||||
.attr('data-remote', 'false')
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'ajax should not be triggered')
|
||||
})
|
||||
.bindNative('click', function(e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
||||
|
||||
QUnit.test('submitting a form with falsy "data-remote" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
assert.expect(0)
|
||||
|
||||
$('form[data-remote]:first')
|
||||
.attr('data-remote', 'false')
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'ajax should not be triggered')
|
||||
})
|
||||
.bindNative('submit', function(e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('submit')
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
||||
|
||||
QUnit.test('changing a select option with falsy "data-remote" attribute', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
assert.expect(0)
|
||||
|
||||
buildSelect({'data-remote': 'false'})
|
||||
|
||||
$('select[data-remote=false]:first')
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'ajax should not be triggered')
|
||||
})
|
||||
.val('optionValue2')
|
||||
.triggerNative('change')
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
||||
|
||||
QUnit.test('form should be serialized correctly', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
$('#qunit-fixture form')
|
||||
.append('<textarea name="textarea">textarea</textarea>')
|
||||
.append('<input type="checkbox" name="checkbox[]" value="0" />')
|
||||
.append('<input type="checkbox" checked="checked" name="checkbox[]" value="1" />')
|
||||
.append('<input type="radio" checked="checked" name="radio" value="0" />')
|
||||
.append('<input type="radio" name="radio" value="1" />')
|
||||
.append('<select multiple="multiple" name="select[]">\
|
||||
<option value="1" selected>1</option>\
|
||||
<option value="2" selected>2</option>\
|
||||
<option value="3">3</option>\
|
||||
<option selected>4</option>\
|
||||
</select>')
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.equal(data.params.checkbox.length, 1)
|
||||
assert.equal(data.params.checkbox[0], '1')
|
||||
assert.equal(data.params.radio, '0')
|
||||
assert.equal(data.params.select.length, 3)
|
||||
assert.equal(data.params.select[2], '4')
|
||||
assert.equal(data.params.textarea, 'textarea')
|
||||
|
||||
done()
|
||||
})
|
||||
.triggerNative('submit')
|
||||
})
|
||||
|
||||
QUnit.test('form buttons should only be serialized when clicked', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
$('#qunit-fixture form')
|
||||
.append('<input type="submit" name="submit1" value="submit1" />')
|
||||
.append('<button name="submit2" value="submit2" />')
|
||||
.append('<button name="submit3" value="submit3" />')
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.equal(data.params.submit1, undefined)
|
||||
assert.equal(data.params.submit2, 'submit2')
|
||||
assert.equal(data.params.submit3, undefined)
|
||||
assert.equal(data['rack.request.form_vars'], 'user_name=john&submit2=submit2')
|
||||
|
||||
done()
|
||||
})
|
||||
.find('[name=submit2]').triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('changing a select option without "data-url" attribute still fires ajax request to current location', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
var currentLocation, ajaxLocation
|
||||
|
||||
buildSelect({'data-url': ''})
|
||||
|
||||
$('select[data-remote]')
|
||||
.bindNative('ajax:beforeSend', function(e, xhr, settings) {
|
||||
// Get current location (the same way jQuery does)
|
||||
try {
|
||||
currentLocation = location.href
|
||||
} catch(err) {
|
||||
currentLocation = document.createElement( 'a' )
|
||||
currentLocation.href = ''
|
||||
currentLocation = currentLocation.href
|
||||
}
|
||||
|
||||
ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '')
|
||||
assert.equal(ajaxLocation, currentLocation, 'URL should be current page by default')
|
||||
|
||||
e.preventDefault()
|
||||
})
|
||||
.val('optionValue2')
|
||||
.triggerNative('change')
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
||||
|
||||
QUnit.test('inputs inside disabled fieldset are not submitted on remote forms', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
$('#qunit-fixture form')
|
||||
.append('<fieldset>\
|
||||
<input name="description" value="A wise man" />\
|
||||
</fieldset>')
|
||||
.append('<fieldset disabled="disabled">\
|
||||
<input name="age" />\
|
||||
</fieldset>')
|
||||
.bindNative('ajax:success', function(e, data, status, xhr) {
|
||||
assert.equal(data.params.user_name, 'john')
|
||||
assert.equal(data.params.description, 'A wise man')
|
||||
assert.equal(data.params.age, undefined)
|
||||
|
||||
done()
|
||||
})
|
||||
.triggerNative('submit')
|
||||
})
|
||||
|
||||
|
||||
|
||||
QUnit.test('clicking on a link with contenteditable attribute does not fire ajaxyness', function(assert) {
|
||||
const done = assert.async()
|
||||
assert.expect(0)
|
||||
|
||||
var contenteditable_div = $('#qunit-fixture').find('div')
|
||||
var link = $('a[data-remote]')
|
||||
contenteditable_div.append(link)
|
||||
|
||||
link
|
||||
.bindNative('ajax:beforeSend', function() {
|
||||
assert.ok(false, 'ajax should not be triggered')
|
||||
})
|
||||
.bindNative('click', function(e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('click')
|
||||
|
||||
setTimeout(function() { done() }, 20)
|
||||
})
|
@ -1,52 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
var realHref
|
||||
|
||||
QUnit.module('override', {
|
||||
beforeEach: function() {
|
||||
realHref = $.rails.href
|
||||
$('#qunit-fixture')
|
||||
.append($('<a />', {
|
||||
href: '/real/href', 'data-remote': 'true', 'data-method': 'delete', 'data-href': '/data/href'
|
||||
}))
|
||||
},
|
||||
afterEach: function() {
|
||||
$.rails.href = realHref
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('the getter for an element\'s href is publicly accessible', function(assert) {
|
||||
assert.ok($.rails.href)
|
||||
})
|
||||
|
||||
QUnit.test('the getter for an element\'s href is overridable', function(assert) {
|
||||
$.rails.href = function(element) { return $(element).data('href') }
|
||||
$('#qunit-fixture a')
|
||||
.bindNative('ajax:beforeSend', function(e, xhr, options) {
|
||||
assert.equal('/data/href', options.url)
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('the getter for an element\'s href works normally if not overridden', function(assert) {
|
||||
$('#qunit-fixture a')
|
||||
.bindNative('ajax:beforeSend', function(e, xhr, options) {
|
||||
assert.equal(location.protocol + '//' + location.host + '/real/href', options.url)
|
||||
e.preventDefault()
|
||||
})
|
||||
.triggerNative('click')
|
||||
})
|
||||
|
||||
QUnit.test('the event selector strings are overridable', function(assert) {
|
||||
assert.ok($.rails.linkClickSelector.indexOf(', a[data-custom-remote-link]') != -1, 'linkClickSelector contains custom selector')
|
||||
})
|
||||
|
||||
QUnit.test('including rails-ujs multiple times throws error', function(assert) {
|
||||
const done = assert.async()
|
||||
|
||||
assert.throws(function() {
|
||||
Rails.start()
|
||||
}, 'appending rails.js again throws error')
|
||||
setTimeout(function() { done() }, 50)
|
||||
})
|
@ -1,130 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import Rails from '../../../../app/javascript/rails-ujs/index'
|
||||
|
||||
$.rails = Rails
|
||||
|
||||
var App = App || {}
|
||||
var Turbolinks = Turbolinks || {}
|
||||
|
||||
window.Turbolinks = Turbolinks
|
||||
window.jQuery = $
|
||||
|
||||
QUnit.assert.callbackInvoked = function(callbackName) {
|
||||
this.ok(true, callbackName + ' callback should have been invoked')
|
||||
}
|
||||
|
||||
QUnit.assert.callbackNotInvoked = function(callbackName) {
|
||||
this.ok(false, callbackName + ' callback should not have been invoked')
|
||||
}
|
||||
|
||||
QUnit.assert.getRequest = function(requestEnv) {
|
||||
this.equal(requestEnv['REQUEST_METHOD'], 'GET', 'request type should be GET')
|
||||
}
|
||||
|
||||
QUnit.assert.postRequest = function(requestEnv) {
|
||||
this.equal(requestEnv['REQUEST_METHOD'], 'POST', 'request type should be POST')
|
||||
}
|
||||
|
||||
QUnit.assert.requestPath = function(requestEnv, path) {
|
||||
this.equal(requestEnv['PATH_INFO'], path, 'request should be sent to right URL')
|
||||
}
|
||||
|
||||
App.getVal = function(el) {
|
||||
return el.is('input,textarea,select') ? el.val() : el.text()
|
||||
}
|
||||
|
||||
App.disabled = function(el) {
|
||||
return el.is('input,textarea,select,button') ?
|
||||
(el.is(':disabled') && $.rails.getData(el[0], 'ujs:disabled')) :
|
||||
$.rails.getData(el[0], 'ujs:disabled')
|
||||
}
|
||||
|
||||
QUnit.assert.enabledState = function(el, text) {
|
||||
this.ok(!App.disabled(el), el.get(0).tagName + ' should not be disabled')
|
||||
this.equal(App.getVal(el), text, el.get(0).tagName + ' text should be original value')
|
||||
}
|
||||
|
||||
QUnit.assert.disabledState = function(el, text) {
|
||||
this.ok(App.disabled(el), el.get(0).tagName + ' should be disabled')
|
||||
this.equal(App.getVal(el), text, el.get(0).tagName + ' text should be disabled value')
|
||||
}
|
||||
|
||||
// hijacks normal form submit; lets it submit to an iframe to prevent
|
||||
// navigating away from the test suite
|
||||
$(document).bind('submit', function(e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var form = $(e.target), action = form.attr('action'),
|
||||
name = 'form-frame' + jQuery.guid++,
|
||||
iframe = $('<iframe name="' + name + '" />'),
|
||||
iframeInput = '<input name="iframe" value="true" type="hidden" />',
|
||||
targetInput = '<input name="_target" value="' + (form.attr('target') || '') + '" type="hidden" />'
|
||||
|
||||
if (action && action.indexOf('iframe') < 0) {
|
||||
if (action.indexOf('?') < 0) {
|
||||
form.attr('action', action + '?iframe=true')
|
||||
} else {
|
||||
form.attr('action', action + '&iframe=true')
|
||||
}
|
||||
}
|
||||
form.attr('target', name).append(iframeInput, targetInput)
|
||||
$('#qunit-fixture').append(iframe)
|
||||
$.event.trigger('iframe:loading', form)
|
||||
}
|
||||
})
|
||||
|
||||
var _MouseEvent = window.MouseEvent
|
||||
|
||||
try {
|
||||
new _MouseEvent()
|
||||
} catch (e) {
|
||||
_MouseEvent = function(type, options) {
|
||||
var evt = document.createEvent('MouseEvents')
|
||||
evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, null)
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
$.fn.extend({
|
||||
// trigger a native click event
|
||||
triggerNative: function(type, options) {
|
||||
var el = this[0],
|
||||
event,
|
||||
Evt = {
|
||||
'click': _MouseEvent,
|
||||
'change': Event,
|
||||
'pageshow': PageTransitionEvent,
|
||||
'submit': Event
|
||||
}[type]
|
||||
|
||||
options = options || {}
|
||||
options.bubbles = true
|
||||
options.cancelable = true
|
||||
|
||||
event = new Evt(type, options)
|
||||
|
||||
el.dispatchEvent(event)
|
||||
|
||||
if (type === 'submit' && !event.defaultPrevented) {
|
||||
el.submit()
|
||||
}
|
||||
return this
|
||||
},
|
||||
bindNative: function(event, handler) {
|
||||
if (!handler) return this
|
||||
|
||||
var el = this[0]
|
||||
el.addEventListener(event, function(e) {
|
||||
var args = []
|
||||
if (e.detail) {
|
||||
args = e.detail.slice()
|
||||
}
|
||||
args.unshift(e)
|
||||
return handler.apply(el, args)
|
||||
}, false)
|
||||
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
Turbolinks.clearCache = function() {}
|
||||
Turbolinks.visit = function() {}
|
@ -1,68 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rack"
|
||||
require "rails"
|
||||
require "action_controller/railtie"
|
||||
require "action_view/railtie"
|
||||
require "json"
|
||||
|
||||
module UJS
|
||||
class Server < Rails::Application
|
||||
routes.append do
|
||||
match "/echo" => "tests#echo", via: :all
|
||||
get "/error" => proc { |env| [403, { "content-type" => "text/plain" }, []] }
|
||||
end
|
||||
|
||||
config.enable_reloading = true
|
||||
config.eager_load = false
|
||||
config.secret_key_base = "59d7a4dbd349fa3838d79e330e39690fc22b931e7dc17d9162f03d633d526fbb92dfdb2dc9804c8be3e199631b9c1fbe43fc3e4fc75730b515851849c728d5c7"
|
||||
config.paths["app/views"].unshift("#{Rails.root}/views")
|
||||
config.public_file_server.enabled = true
|
||||
config.logger = Logger.new(STDOUT)
|
||||
config.log_level = :error
|
||||
config.hosts << proc { true }
|
||||
|
||||
config.content_security_policy do |policy|
|
||||
policy.default_src :self, :https
|
||||
policy.font_src :self, :https, :data
|
||||
policy.img_src :self, :https, :data
|
||||
policy.object_src :none
|
||||
policy.script_src :self, :https
|
||||
policy.style_src :self, :https
|
||||
end
|
||||
|
||||
config.content_security_policy_nonce_generator = ->(req) { SecureRandom.base64(16) }
|
||||
end
|
||||
end
|
||||
|
||||
class TestsController < ActionController::Base
|
||||
def echo
|
||||
data = { params: params.to_unsafe_h }.update(request.env)
|
||||
|
||||
if params[:content_type] && params[:content]
|
||||
render plain: params[:content], content_type: params[:content_type]
|
||||
elsif request.xhr?
|
||||
if params[:with_xhr_redirect]
|
||||
response.set_header("X-Xhr-Redirect", "http://example.com/")
|
||||
render inline: %{Turbolinks.clearCache()\nTurbolinks.visit("http://example.com/", {"action":"replace"})}
|
||||
else
|
||||
render json: JSON.generate(data)
|
||||
end
|
||||
elsif params[:iframe]
|
||||
payload = JSON.generate(data).gsub("<", "<").gsub(">", ">")
|
||||
html = <<-HTML
|
||||
<script nonce="#{request.content_security_policy_nonce}">
|
||||
if (window.top && window.top !== window)
|
||||
window.parent.jQuery.event.trigger('iframe:loaded', #{payload})
|
||||
</script>
|
||||
<p>You shouldn't be seeing this. <a href="#{request.env['HTTP_REFERER']}">Go back</a></p>
|
||||
HTML
|
||||
|
||||
render html: html.html_safe
|
||||
else
|
||||
render plain: "ERROR: #{request.path} requested without ajax", status: 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UJS::Server.initialize!
|
@ -1,11 +0,0 @@
|
||||
// Must go before rails-ujs.
|
||||
document.addEventListener('rails:attachBindings', function() {
|
||||
// This is for test in override.js.
|
||||
window.Rails.linkClickSelector += ', a[data-custom-remote-link]';
|
||||
|
||||
// Hijacks link click before ujs binds any handlers
|
||||
// This is only used for ctrl-clicking test on remote links
|
||||
window.Rails.delegate(document, '#qunit-fixture a', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
import "./attach-bindings"
|
||||
|
||||
import Rails from '../../../app/javascript/rails-ujs/index'
|
||||
|
||||
import "../public/test/settings"
|
||||
|
||||
import "../public/test/data-confirm"
|
||||
import "../public/test/data-remote"
|
||||
import "../public/test/data-disable"
|
||||
import "../public/test/data-disable-with"
|
||||
import "../public/test/call-remote"
|
||||
import "../public/test/call-remote-callbacks"
|
||||
import "../public/test/data-method"
|
||||
import "../public/test/override"
|
||||
import "../public/test/csrf-refresh"
|
||||
import "../public/test/csrf-token"
|
||||
import "../public/test/call-ajax"
|
@ -621,10 +621,6 @@ URL helper.
|
||||
<%= user_url(@user, host: 'example.com') %>
|
||||
```
|
||||
|
||||
NOTE: non-`GET` links require [rails-ujs](https://github.com/rails/rails/blob/main/actionview/app/assets/javascripts) or
|
||||
[jQuery UJS](https://github.com/rails/jquery-ujs), and won't work in mailer templates.
|
||||
They will result in normal `GET` requests.
|
||||
|
||||
### Adding Images in Action Mailer Views
|
||||
|
||||
Unlike controllers, the mailer instance doesn't have any context about the
|
||||
|
@ -160,7 +160,7 @@ To generate the guides in HTML format, you will need to install the guides depen
|
||||
|
||||
```bash
|
||||
# only install gems necessary for the guides. To undo run: bundle config --delete without
|
||||
$ bundle install --without job cable storage ujs test db
|
||||
$ bundle install --without job cable storage test db
|
||||
$ cd guides/
|
||||
$ bundle exec rake guides:generate:html GUIDES_LANGUAGE=it-IT
|
||||
```
|
||||
@ -289,8 +289,6 @@ Inspecting 1 file
|
||||
1 file inspected, no offenses detected
|
||||
```
|
||||
|
||||
For `rails-ujs` CoffeeScript and JavaScript files, you can run `npm run lint` in `actionview` folder.
|
||||
|
||||
#### Spell Checking
|
||||
|
||||
We run [codespell](https://github.com/codespell-project/codespell) with GitHub Actions to check spelling and
|
||||
|
@ -3,8 +3,7 @@
|
||||
"workspaces": [
|
||||
"actioncable",
|
||||
"actiontext",
|
||||
"activestorage",
|
||||
"actionview"
|
||||
"activestorage"
|
||||
],
|
||||
"dependencies": {
|
||||
"webpack": "^4.17.1"
|
||||
|
@ -3,7 +3,6 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@rails/actioncable": "file:../../../../actioncable",
|
||||
"@rails/activestorage": "file:../../../../activestorage",
|
||||
"@rails/ujs": "file:../../../../actionview"
|
||||
"@rails/activestorage": "file:../../../../activestorage"
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,6 @@
|
||||
railties
|
||||
)
|
||||
FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") }
|
||||
NPM_PACKAGES = { "actionview" => "ujs" }
|
||||
NPM_PACKAGES.default_proc = -> (_, framework) { framework }
|
||||
|
||||
root = File.expand_path("..", __dir__)
|
||||
version = File.read("#{root}/RAILS_VERSION").strip
|
||||
@ -123,10 +121,8 @@
|
||||
if /[a-z]/.match?(version)
|
||||
npm_tag = " --tag pre"
|
||||
else
|
||||
remote_package_version = `npm view @rails/#{NPM_PACKAGES[framework]}@latest version`.chomp
|
||||
local_major_version = version.split(".", 4)[0]
|
||||
remote_major_version = remote_package_version.split(".", 4)[0]
|
||||
npm_tag = remote_major_version <= local_major_version ? " --tag latest" : " --tag v#{local_major_version}"
|
||||
npm_tag = " --tag v#{local_major_version}"
|
||||
end
|
||||
|
||||
sh "npm publish#{npm_tag}#{npm_otp}"
|
||||
|
47
yarn.lock
47
yarn.lock
@ -28,11 +28,6 @@
|
||||
resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.4.tgz"
|
||||
integrity sha512-KE/SxsDqNs3rrWwFHcRh15ZLVFrI0YoZtgAdIyIq9k5hUNmiWRXXThPomIxHuL20sLdgzbDFyvkUMna14bvtrw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.15":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
||||
"@rails/activestorage@>= 7.1.0-alpha":
|
||||
version "7.1.0-beta1"
|
||||
resolved "https://registry.npmjs.org/@rails/activestorage/-/activestorage-7.1.0-beta1.tgz"
|
||||
@ -65,14 +60,6 @@
|
||||
is-module "^1.0.0"
|
||||
resolve "^1.19.0"
|
||||
|
||||
"@rollup/plugin-replace@^5.0.4":
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.4.tgz#fef548dc751d06747e8dca5b0e8e1fbf647ac7e1"
|
||||
integrity sha512-E2hmRnlh09K8HGT0rOnnri9OTh+BILGr7NVJGB30S4E3cLRn3J0xjdiyOZ74adPs4NiAMgrjUMGAZNJDBgsdmQ==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^5.0.1"
|
||||
magic-string "^0.30.3"
|
||||
|
||||
"@rollup/pluginutils@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz"
|
||||
@ -82,25 +69,11 @@
|
||||
estree-walker "^1.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@rollup/pluginutils@^5.0.1":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.5.tgz#bbb4c175e19ebfeeb8c132c2eea0ecb89941a66c"
|
||||
integrity sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
estree-walker "^2.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
"@types/estree@*", "@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/estree@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
||||
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
|
||||
@ -1727,7 +1700,7 @@ eslint-visitor-keys@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz"
|
||||
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
|
||||
|
||||
eslint@^4.19.1, eslint@^4.3.0:
|
||||
eslint@^4.3.0:
|
||||
version "4.19.1"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz"
|
||||
integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==
|
||||
@ -1813,7 +1786,7 @@ estree-walker@^1.0.1:
|
||||
resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz"
|
||||
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
|
||||
|
||||
estree-walker@^2.0.1, estree-walker@^2.0.2:
|
||||
estree-walker@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
@ -2816,11 +2789,6 @@ jest-worker@^26.2.1:
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^7.0.0"
|
||||
|
||||
jquery@^2.2.0:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz"
|
||||
integrity sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==
|
||||
|
||||
js-tokens@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
|
||||
@ -3072,13 +3040,6 @@ magic-string@^0.25.7:
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
magic-string@^0.30.3:
|
||||
version "0.30.5"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
|
||||
integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||
|
||||
make-dir@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz"
|
||||
@ -3607,7 +3568,7 @@ performance-now@^2.1.0:
|
||||
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz"
|
||||
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
@ -3982,7 +3943,7 @@ rollup-plugin-terser@^7.0.2:
|
||||
serialize-javascript "^4.0.0"
|
||||
terser "^5.0.0"
|
||||
|
||||
rollup@^2.35.1, rollup@^2.53.3:
|
||||
rollup@^2.35.1:
|
||||
version "2.79.1"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz"
|
||||
integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
|
||||
|
Loading…
Reference in New Issue
Block a user