From d9e79ce7f4eb86a8f74c0f583c3fc0105c399a71 Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Sat, 8 Oct 2022 23:34:42 +1100 Subject: [PATCH] use Karma as the test runner for the UJS tests --- Gemfile | 1 - Gemfile.lock | 38 - actionview/.gitignore | 1 + actionview/RUNNING_UJS_TESTS.rdoc | 11 +- actionview/Rakefile | 16 +- actionview/karma.conf.js | 64 + actionview/package.json | 11 +- actionview/rollup.config.test.js | 16 + .../test/ujs/public/test/attach-bindings.js | 8 + actionview/test/ujs/public/test/call-ajax.js | 5 +- .../ujs/public/test/call-remote-callbacks.js | 4 +- .../test/ujs/public/test/call-remote.js | 4 +- .../test/ujs/public/test/csrf-refresh.js | 6 +- actionview/test/ujs/public/test/csrf-token.js | 8 +- .../test/ujs/public/test/data-confirm.js | 2 + .../test/ujs/public/test/data-disable-with.js | 23 +- .../test/ujs/public/test/data-disable.js | 24 +- .../test/ujs/public/test/data-method.js | 4 +- .../test/ujs/public/test/data-remote.js | 4 +- actionview/test/ujs/public/test/override.js | 4 +- actionview/test/ujs/public/test/settings.js | 10 +- .../test/ujs/public/vendor/jquery-2.2.0.js | 9831 ----------------- actionview/test/ujs/public/vendor/qunit.css | 525 - actionview/test/ujs/public/vendor/qunit.js | 7730 ------------- actionview/test/ujs/server.rb | 8 +- actionview/test/ujs/src/test.js | 17 + .../ujs/views/layouts/application.html.erb | 26 - .../test/ujs/views/tests/index.html.erb | 11 - ci/qunit-selenium-runner.rb | 33 - ci/test_run.rb | 73 - yarn.lock | 8738 +++++++-------- 31 files changed, 3902 insertions(+), 23354 deletions(-) create mode 100644 actionview/karma.conf.js create mode 100644 actionview/rollup.config.test.js create mode 100644 actionview/test/ujs/public/test/attach-bindings.js delete mode 100644 actionview/test/ujs/public/vendor/jquery-2.2.0.js delete mode 100644 actionview/test/ujs/public/vendor/qunit.css delete mode 100644 actionview/test/ujs/public/vendor/qunit.js create mode 100644 actionview/test/ujs/src/test.js delete mode 100644 actionview/test/ujs/views/layouts/application.html.erb delete mode 100644 actionview/test/ujs/views/tests/index.html.erb delete mode 100644 ci/qunit-selenium-runner.rb delete mode 100644 ci/test_run.rb diff --git a/Gemfile b/Gemfile index 247c96a0490..54282054bce 100644 --- a/Gemfile +++ b/Gemfile @@ -112,7 +112,6 @@ end # Action View group :view do - gem "blade", require: false, platforms: [:ruby] gem "sprockets-export", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 1c1344fc5c9..459b4ae4c16 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,19 +140,6 @@ GEM beaneater (1.1.1) benchmark-ips (2.9.2) bindex (0.8.1) - blade (0.7.3) - activesupport (>= 3.0.0) - blade-qunit_adapter (>= 2.0.1) - coffee-script - coffee-script-source - curses (>= 1.4.0) - eventmachine - faye - sprockets (>= 3.0) - thin (>= 1.6.0) - thor (>= 0.19.1) - useragent (>= 0.16.7) - blade-qunit_adapter (2.0.1) bootsnap (1.9.3) msgpack (~> 1.0) builder (3.2.4) @@ -170,20 +157,13 @@ GEM xpath (~> 3.2) cgi (0.3.6) childprocess (4.1.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) concurrent-ruby (1.1.9) connection_pool (2.2.5) - cookiejar (0.3.3) crack (0.4.5) rexml crass (1.0.6) cssbundling-rails (1.1.0) railties (>= 6.0.0) - curses (1.4.4) - daemons (1.4.1) dalli (3.2.0) dante (0.2.0) debug (1.4.0) @@ -211,7 +191,6 @@ GEM et-orbi (1.2.6) tzinfo event_emitter (0.2.6) - eventmachine (1.2.7) execjs (2.8.1) faraday (1.8.0) faraday-em_http (~> 1.0) @@ -234,17 +213,6 @@ GEM faraday-rack (1.0.0) faraday_middleware (1.2.0) faraday (~> 1.0) - faye (1.4.0) - cookiejar (>= 0.3.0) - em-http-request (>= 1.1.6) - eventmachine (>= 0.12.0) - faye-websocket (>= 0.11.0) - multi_json (>= 1.0.0) - rack (>= 1.0.0) - websocket-driver (>= 0.5.1) - faye-websocket (0.11.1) - eventmachine (>= 0.12.0) - websocket-driver (>= 0.5.1) ffi (1.15.4) fugit (1.5.2) et-orbi (~> 1.1, >= 1.1.8) @@ -286,7 +254,6 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.0.1) - http_parser.rb (0.8.0) httpclient (2.8.3) i18n (1.8.11) concurrent-ruby (~> 1.0) @@ -518,10 +485,6 @@ GEM railties (>= 6.0.0) terser (1.1.8) execjs (>= 0.3.0, < 3) - thin (1.8.1) - daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0, >= 1.0.4) - rack (>= 1, < 3) thor (1.2.1) tilt (2.0.10) timeout (0.3.1) @@ -575,7 +538,6 @@ DEPENDENCIES backburner bcrypt (~> 3.1.11) benchmark-ips - blade bootsnap (>= 1.4.4) capybara (>= 3.26) cgi (>= 0.3.6) diff --git a/actionview/.gitignore b/actionview/.gitignore index 246aabbb7f2..b23fae57212 100644 --- a/actionview/.gitignore +++ b/actionview/.gitignore @@ -1,4 +1,5 @@ /lib/assets/compiled/ +/test/ujs/compiled/ /log/ /test/fixtures/public/absolute/ /test/ujs/log/ diff --git a/actionview/RUNNING_UJS_TESTS.rdoc b/actionview/RUNNING_UJS_TESTS.rdoc index e30c2aee55c..c4630144649 100644 --- a/actionview/RUNNING_UJS_TESTS.rdoc +++ b/actionview/RUNNING_UJS_TESTS.rdoc @@ -1,8 +1,9 @@ == Running UJS tests -Ensure that you can build the project by running: +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 - -Then run the web tests by visiting the following URL in your browser: - - http://localhost:4567 diff --git a/actionview/Rakefile b/actionview/Rakefile index d24f067c8de..507c2322b49 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -3,6 +3,7 @@ require "rake/testtask" require "fileutils" require "open3" +require "base64" desc "Default Task" task default: :test @@ -38,15 +39,6 @@ namespace :test do listen_host = "localhost" listen_port = "4567" - runner_command = %w(ruby ../ci/qunit-selenium-runner.rb) - if ENV["SELENIUM_DRIVER_URL"] - require "socket" - runner_command += %W(http://#{Socket.gethostname}:#{listen_port}/ #{ENV["SELENIUM_DRIVER_URL"]}) - listen_host = "0.0.0.0" - else - runner_command += %W(http://localhost:#{listen_port}/) - end - Dir.mkdir("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) @@ -66,8 +58,7 @@ namespace :test do sleep 0.2 end - - system(*runner_command) + system(Hash[*Base64.decode64(ENV.fetch("ENCODED", "")).split(/[ =]/)], "npm", "test") status = $?.exitstatus ensure Process.kill("KILL", -pid) if pid @@ -101,7 +92,8 @@ end namespace :ujs do desc "Starts the test server" task :server do - system "bundle exec rackup test/ujs/config.ru -p 4567 -s puma" + spawn("bundle exec rackup test/ujs/config.ru -p 4567 -s puma") + system("npm test -- --no-single-run --browsers Chrome") end end diff --git a/actionview/karma.conf.js b/actionview/karma.conf.js new file mode 100644 index 00000000000..aabef07e16b --- /dev/null +++ b/actionview/karma.conf.js @@ -0,0 +1,64 @@ +const config = { + browsers: ["ChromeHeadless"], + frameworks: ["qunit"], + files: [ + "test/ujs/compiled/test.js", + ], + + client: { + clearContext: false, + qunit: { + showUI: true + } + }, + + singleRun: true, + autoWatch: false, + + captureTimeout: 180000, + browserDisconnectTimeout: 180000, + browserDisconnectTolerance: 3, + browserNoActivityTimeout: 300000, + proxies: { + '/echo': 'http://localhost:4567/echo', + '/error': 'http://localhost:4567/error' + } +} + +if (process.env.CI) { + config.customLaunchers = { + sl_chrome: sauce("chrome", "latest", "Windows 10") + } + + config.browsers = Object.keys(config.customLaunchers) + config.reporters = ["dots", "saucelabs"] + + config.sauceLabs = { + testName: "Rails UJS", + retryLimit: 3, + build: buildId(), + } + + function sauce(browserName, version, platform) { + const options = { + base: "SauceLabs", + browserName: browserName.toString(), + version: version.toString(), + } + if (platform) { + options.platform = platform.toString() + } + return options + } + + function buildId() { + const { BUILDKITE_JOB_ID } = process.env + return BUILDKITE_JOB_ID + ? `Buildkite ${BUILDKITE_JOB_ID}` + : "" + } +} + +module.exports = function(karmaConfig) { + karmaConfig.set(config) +} diff --git a/actionview/package.json b/actionview/package.json index 5dbbc640257..ce1f299a2e6 100644 --- a/actionview/package.json +++ b/actionview/package.json @@ -12,7 +12,8 @@ }, "scripts": { "build": "rollup --config rollup.config.js", - "test": "echo \"See the README: https://github.com/rails/rails/blob/main/actionview/app/assets/javascripts#how-to-run-tests\" && exit 1", + "pretest": "rollup --config rollup.config.test.js", + "test": "karma start", "lint": "eslint app/javascript" }, "repository": { @@ -31,8 +32,16 @@ }, "homepage": "https://rubyonrails.org/", "devDependencies": { + "@rollup/plugin-commonjs": "^19.0.1", + "@rollup/plugin-node-resolve": "^11.0.1", "eslint": "^4.19.1", "eslint-plugin-import": "^2.23.4", + "jquery": "^2.2.0", + "karma": "^3.1.1", + "karma-chrome-launcher": "^2.2.0", + "karma-qunit": "^2.1.0", + "karma-sauce-launcher": "^1.2.0", + "qunit": "^2.8.0", "rollup": "^2.53.3", "rollup-plugin-terser": "^7.0.2" } diff --git a/actionview/rollup.config.test.js b/actionview/rollup.config.test.js new file mode 100644 index 00000000000..69a85e8d876 --- /dev/null +++ b/actionview/rollup.config.test.js @@ -0,0 +1,16 @@ +import commonjs from "@rollup/plugin-commonjs" +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: [ + resolve(), + commonjs() + ] +} diff --git a/actionview/test/ujs/public/test/attach-bindings.js b/actionview/test/ujs/public/test/attach-bindings.js new file mode 100644 index 00000000000..fd668edb718 --- /dev/null +++ b/actionview/test/ujs/public/test/attach-bindings.js @@ -0,0 +1,8 @@ +document.addEventListener('rails:attachBindings', function() { + 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(); + }); +}); diff --git a/actionview/test/ujs/public/test/call-ajax.js b/actionview/test/ujs/public/test/call-ajax.js index 12cd9700369..a7a90c73e32 100644 --- a/actionview/test/ujs/public/test/call-ajax.js +++ b/actionview/test/ujs/public/test/call-ajax.js @@ -1,4 +1,5 @@ -(function() { +import $ from 'jquery' +import Rails from "../../../../app/javascript/rails-ujs/index" QUnit.module('call-ajax', { beforeEach: function() { @@ -24,5 +25,3 @@ QUnit.test('call ajax without "ajax:beforeSend"', function(assert) { link.triggerNative('click') }) - -})() diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js index 8fb3a8b3a68..d26b6ca6fdc 100644 --- a/actionview/test/ujs/public/test/call-remote-callbacks.js +++ b/actionview/test/ujs/public/test/call-remote-callbacks.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' QUnit.module('call-remote-callbacks', { beforeEach: function() { @@ -264,5 +264,3 @@ QUnit.test('binding to ajax callbacks via .delegate() triggers handlers properly }) $('form[data-remote]').triggerNative('submit') }) - -})() diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js index 9e7f30a944a..07d26657039 100644 --- a/actionview/test/ujs/public/test/call-remote.js +++ b/actionview/test/ujs/public/test/call-remote.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' function buildForm(attrs) { attrs = $.extend({ action: '/echo', 'data-remote': 'true' }, attrs) @@ -352,5 +352,3 @@ QUnit.test('intelligently guesses crossDomain behavior when target URL consists setTimeout(function() { done() }, 13) }) - -})() diff --git a/actionview/test/ujs/public/test/csrf-refresh.js b/actionview/test/ujs/public/test/csrf-refresh.js index 09f52ef822b..66352a76100 100644 --- a/actionview/test/ujs/public/test/csrf-refresh.js +++ b/actionview/test/ujs/public/test/csrf-refresh.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' QUnit.module('csrf-refresh', {}) @@ -15,9 +15,7 @@ QUnit.test('refresh all csrf tokens', function(assert) { .append(form) $.rails.refreshCSRFTokens() - currentToken = $('#qunit-fixture #authenticity_token').val() + var currentToken = $('#qunit-fixture #authenticity_token').val() assert.equal(currentToken, correctToken) }) - -})() diff --git a/actionview/test/ujs/public/test/csrf-token.js b/actionview/test/ujs/public/test/csrf-token.js index ce6985a3889..d12067202a2 100644 --- a/actionview/test/ujs/public/test/csrf-token.js +++ b/actionview/test/ujs/public/test/csrf-token.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' QUnit.module('csrf-token', {}) @@ -7,7 +7,7 @@ QUnit.test('find csrf token', function(assert) { $('#qunit-fixture').append('') - currentToken = $.rails.csrfToken() + var currentToken = $.rails.csrfToken() assert.equal(currentToken, correctToken) }) @@ -17,9 +17,7 @@ QUnit.test('find csrf param', function(assert) { $('#qunit-fixture').append('') - currentParam = $.rails.csrfParam() + var currentParam = $.rails.csrfParam() assert.equal(currentParam, correctParam) }) - -})() diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js index 0fb2ac64f7b..7ff22f65300 100644 --- a/actionview/test/ujs/public/test/data-confirm.js +++ b/actionview/test/ujs/public/test/data-confirm.js @@ -1,3 +1,5 @@ +import $ from 'jquery' + QUnit.module('data-confirm', { beforeEach: function() { $('#qunit-fixture').append($('', { diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js index a8601a1eed2..f62c6c05a6f 100644 --- a/actionview/test/ujs/public/test/data-disable-with.js +++ b/actionview/test/ujs/public/test/data-disable-with.js @@ -1,3 +1,5 @@ +import $ from 'jquery' + QUnit.module('data-disable-with', { beforeEach: function() { $('#qunit-fixture').append($('
', { @@ -326,12 +328,12 @@ QUnit.test('a[data-remote][data-disable-with] re-enables when `ajax:error` event .bindNative('ajax:beforeSend', function() { assert.disabledState(link, 'clicking...') }) + .bindNative('ajax:complete', function() { + setTimeout(function() { + assert.enabledState(link, 'Click me') + done() + }, 30) }) .triggerNative('click') - - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) }) QUnit.test('form[data-remote] input|button|textarea[data-disable-with] does not disable when `ajax:beforeSend` event is cancelled', function(assert) { @@ -454,10 +456,11 @@ QUnit.test('button[data-remote][data-disable-with] re-enables when `ajax:error` .bindNative('ajax:send', function() { assert.disabledState(button, 'clicking...') }) + .bindNative('ajax:complete', function() { + setTimeout(function() { + assert.enabledState(button, 'Click me') + done() + }, 30) + }) .triggerNative('click') - - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) }) diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js index feae3dc9374..b4094d26101 100644 --- a/actionview/test/ujs/public/test/data-disable.js +++ b/actionview/test/ujs/public/test/data-disable.js @@ -1,3 +1,5 @@ +import $ from 'jquery' + QUnit.module('data-disable', { beforeEach: function() { $('#qunit-fixture').append($('', { @@ -226,12 +228,13 @@ QUnit.test('a[data-remote][data-disable] re-enables when `ajax:error` event is t .bindNative('ajax:send', function() { assert.disabledState(link, 'Click me') }) + .bindNative('ajax:complete', function() { + setTimeout(function() { + assert.enabledState(link, 'Click me') + done() + }, 30) + }) .triggerNative('click') - - setTimeout(function() { - assert.enabledState(link, 'Click me') - done() - }, 30) }) QUnit.test('form[data-remote] input|button|textarea[data-disable] does not disable when `ajax:beforeSend` event is cancelled', function(assert) { @@ -355,12 +358,13 @@ QUnit.test('button[data-remote][data-disable] re-enables when `ajax:error` event .bindNative('ajax:send', function() { assert.disabledState(button, 'Click me') }) + .bindNative('ajax:complete', function() { + setTimeout(function() { + assert.enabledState(button, 'Click me') + done() + }, 30) + }) .triggerNative('click') - - setTimeout(function() { - assert.enabledState(button, 'Click me') - done() - }, 30) }) QUnit.test('do not enable elements for XHR redirects', function(assert) { diff --git a/actionview/test/ujs/public/test/data-method.js b/actionview/test/ujs/public/test/data-method.js index 04764acf7c8..639128cbb1f 100644 --- a/actionview/test/ujs/public/test/data-method.js +++ b/actionview/test/ujs/public/test/data-method.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' QUnit.module('data-method', { beforeEach: function() { @@ -89,5 +89,3 @@ QUnit.test('link with "data-method" and cross origin', function(assert) { assert.notEqual(data.authenticity_token, 'cf50faa3fe97702ca1ae') }) - -})() diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js index e1f33a8a7c9..51ecdfa6fb1 100644 --- a/actionview/test/ujs/public/test/data-remote.js +++ b/actionview/test/ujs/public/test/data-remote.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' function buildSelect(attrs) { attrs = $.extend({ @@ -574,5 +574,3 @@ QUnit.test('inputs inside disabled fieldset are not submitted on remote forms', }) .triggerNative('submit') }) - -})() diff --git a/actionview/test/ujs/public/test/override.js b/actionview/test/ujs/public/test/override.js index ba241f77abe..52ad827f4bc 100644 --- a/actionview/test/ujs/public/test/override.js +++ b/actionview/test/ujs/public/test/override.js @@ -1,4 +1,4 @@ -(function() { +import $ from 'jquery' var realHref @@ -50,5 +50,3 @@ QUnit.test('including rails-ujs multiple times throws error', function(assert) { }, 'appending rails.js again throws error') setTimeout(function() { done() }, 50) }) - -})() diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js index 064c421b395..48755f92fc5 100644 --- a/actionview/test/ujs/public/test/settings.js +++ b/actionview/test/ujs/public/test/settings.js @@ -1,6 +1,14 @@ +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') } @@ -48,7 +56,7 @@ $(document).bind('submit', function(e) { var form = $(e.target), action = form.attr('action'), name = 'form-frame' + jQuery.guid++, iframe = $('