Convert ActionCable tests from CoffeeScript to ES2015 and replace Blade with Karma and Rollup (#34440)

* Rename .coffee files in ActionCable test suite in prep for decaffeination

* Decaffeinate ActionCable tests

* Replace Blade with Karma and Rollup to run ActionCable JS tests

- Add karma and qunit devDependencies

- Add test script to ActionCable package

- Use rollup to bundle ActionCable tests

- Use karma as the ActionCable JS test runner

* Replace vendored mock-socket with package devDependency in ActionCable

* Move ActionCable yarn install to TravisCI before_install config

* Clean up decaffeinated ActionCable tests to use consistent formatting
This commit is contained in:
rmacklin 2018-11-26 14:16:02 -08:00 committed by Javan Makhmali
parent 38b676181b
commit 85b0803653
23 changed files with 2480 additions and 4778 deletions

View File

@ -37,6 +37,7 @@ before_install:
- "travis_retry gem update --system"
- "travis_retry gem install bundler"
- "[[ -z $encrypted_0fb9444d0374_key && -z $encrypted_0fb9444d0374_iv ]] || openssl aes-256-cbc -K $encrypted_0fb9444d0374_key -iv $encrypted_0fb9444d0374_iv -in activestorage/test/service/configurations.yml.enc -out activestorage/test/service/configurations.yml -d"
- "[[ $GEM != 'ac:integration' ]] || (cd actioncable && yarn install)"
- "[[ $GEM != 'av:ujs' ]] || nvm install node"
- "[[ $GEM != 'av:ujs' ]] || node --version"
- "[[ $GEM != 'av:ujs' ]] || (cd actionview && npm install)"

View File

@ -1,3 +1,3 @@
/app/javascript/action_cable/internal.js
/lib/assets/compiled/
/test/javascript/compiled/
/tmp/

View File

@ -25,12 +25,7 @@ namespace :test do
end
task :integration do
require "blade"
if ENV["CI"]
Blade.start(interface: :ci)
else
Blade.start(interface: :runner)
end
system("yarn test") || raise("Failures")
end
end

View File

@ -1,28 +0,0 @@
load_paths:
- app/assets/javascripts
- test/javascript/src
- test/javascript/vendor
logical_paths:
- test.js
plugins:
sauce_labs:
browsers:
Google Chrome:
os: Mac, Windows
version: -1
Firefox:
os: Mac, Windows
version: -1
Safari:
platform: Mac
version: -1
Microsoft Edge:
version: -1
Internet Explorer:
version: 11
iPhone:
version: -1
Motorola Droid 4 Emulator:
version: -1

64
actioncable/karma.conf.js Normal file
View File

@ -0,0 +1,64 @@
const config = {
browsers: ["ChromeHeadless"],
frameworks: ["qunit"],
files: [
"test/javascript/compiled/test.js",
],
client: {
clearContext: false,
qunit: {
showUI: true
}
},
singleRun: true,
autoWatch: false,
captureTimeout: 180000,
browserDisconnectTimeout: 180000,
browserDisconnectTolerance: 3,
browserNoActivityTimeout: 300000,
}
if (process.env.CI) {
config.customLaunchers = {
sl_chrome: sauce("chrome", 70),
sl_ff: sauce("firefox", 63),
sl_safari: sauce("safari", 12.0, "macOS 10.13"),
sl_edge: sauce("microsoftedge", 17.17134, "Windows 10"),
sl_ie_11: sauce("internet explorer", 11, "Windows 8.1"),
}
config.browsers = Object.keys(config.customLaunchers)
config.reporters = ["dots", "saucelabs"]
config.sauceLabs = {
testName: "ActionCable JS Client",
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 { TRAVIS_BUILD_NUMBER, TRAVIS_BUILD_ID } = process.env
return TRAVIS_BUILD_NUMBER && TRAVIS_BUILD_ID
? `TRAVIS #${TRAVIS_BUILD_NUMBER} (${TRAVIS_BUILD_ID})`
: ""
}
}
module.exports = function(karmaConfig) {
karmaConfig.set(config)
}

View File

@ -27,6 +27,12 @@
"babel-preset-env": "^1.6.0",
"eslint": "^4.3.0",
"eslint-plugin-import": "^2.7.0",
"karma": "^3.1.1",
"karma-chrome-launcher": "^2.2.0",
"karma-qunit": "^2.1.0",
"karma-sauce-launcher": "^1.2.0",
"mock-socket": "^2.0.0",
"qunit": "^2.8.0",
"rollup": "^0.58.2",
"rollup-plugin-babel": "^3.0.4",
"rollup-plugin-commonjs": "^9.1.0",
@ -36,6 +42,8 @@
"scripts": {
"prebuild": "yarn lint && bundle exec rake assets:codegen",
"build": "rollup --config rollup.config.js",
"lint": "eslint app/javascript"
"lint": "eslint app/javascript",
"pretest": "bundle exec rake assets:codegen && rollup --config rollup.config.test.js",
"test": "karma start"
}
}

View File

@ -0,0 +1,18 @@
import babel from "rollup-plugin-babel"
import commonjs from "rollup-plugin-commonjs"
import resolve from "rollup-plugin-node-resolve"
export default {
input: "test/javascript/src/test.js",
output: {
file: "test/javascript/compiled/test.js",
format: "iife"
},
plugins: [
resolve(),
commonjs(),
babel()
]
}

View File

@ -1,3 +0,0 @@
#= require action_cable
#= require ./test_helpers
#= require_tree ./unit

View File

@ -0,0 +1,5 @@
import "./test_helpers/index"
import "./unit/action_cable_test"
import "./unit/consumer_test"
import "./unit/subscription_test"
import "./unit/subscriptions_test"

View File

@ -1,47 +0,0 @@
#= require mock-socket
{TestHelpers} = ActionCable
TestHelpers.consumerTest = (name, options = {}, callback) ->
unless callback?
callback = options
options = {}
options.url ?= TestHelpers.testURL
QUnit.test name, (assert) ->
doneAsync = assert.async()
ActionCable.WebSocket = MockWebSocket
server = new MockServer options.url
consumer = ActionCable.createConsumer(options.url)
server.on "connection", ->
clients = server.clients()
assert.equal clients.length, 1
assert.equal clients[0].readyState, WebSocket.OPEN
server.broadcastTo = (subscription, data = {}, callback) ->
data.identifier = subscription.identifier
if data.message_type
data.type = ActionCable.INTERNAL.message_types[data.message_type]
delete data.message_type
server.send(JSON.stringify(data))
TestHelpers.defer(callback)
done = ->
consumer.disconnect()
server.close()
doneAsync()
testData = {assert, consumer, server, done}
if options.connect is false
callback(testData)
else
server.on "connection", ->
testData.client = server.clients()[0]
callback(testData)
consumer.connect()

View File

@ -0,0 +1,58 @@
import { WebSocket as MockWebSocket, Server as MockServer } from "mock-socket"
import ActionCable from "../../../../app/javascript/action_cable/index"
import {defer, testURL} from "./index"
export default function(name, options, callback) {
if (options == null) { options = {} }
if (callback == null) {
callback = options
options = {}
}
if (options.url == null) { options.url = testURL }
return QUnit.test(name, function(assert) {
const doneAsync = assert.async()
ActionCable.WebSocket = MockWebSocket
const server = new MockServer(options.url)
const consumer = ActionCable.createConsumer(options.url)
server.on("connection", function() {
const clients = server.clients()
assert.equal(clients.length, 1)
assert.equal(clients[0].readyState, WebSocket.OPEN)
})
server.broadcastTo = function(subscription, data, callback) {
if (data == null) { data = {} }
data.identifier = subscription.identifier
if (data.message_type) {
data.type = ActionCable.INTERNAL.message_types[data.message_type]
delete data.message_type
}
server.send(JSON.stringify(data))
defer(callback)
}
const done = function() {
consumer.disconnect()
server.close()
doneAsync()
}
const testData = {assert, consumer, server, done}
if (options.connect === false) {
callback(testData)
} else {
server.on("connection", function() {
testData.client = server.clients()[0]
callback(testData)
})
consumer.connect()
}
})
}

View File

@ -1,11 +0,0 @@
#= require_self
#= require_tree .
ActionCable.TestHelpers =
testURL: "ws://cable.example.com/"
defer: (callback) ->
setTimeout(callback, 1)
originalWebSocket = ActionCable.WebSocket
QUnit.testDone -> ActionCable.WebSocket = originalWebSocket

View File

@ -0,0 +1,10 @@
import ActionCable from "../../../../app/javascript/action_cable/index"
export const testURL = "ws://cable.example.com/"
export function defer(callback) {
setTimeout(callback, 1)
}
const originalWebSocket = ActionCable.WebSocket
QUnit.testDone(() => ActionCable.WebSocket = originalWebSocket)

View File

@ -1,41 +0,0 @@
{module, test} = QUnit
{testURL} = ActionCable.TestHelpers
module "ActionCable", ->
module "Adapters", ->
module "WebSocket", ->
test "default is window.WebSocket", (assert) ->
assert.equal ActionCable.WebSocket, window.WebSocket
test "configurable", (assert) ->
ActionCable.WebSocket = ""
assert.equal ActionCable.WebSocket, ""
module "logger", ->
test "default is window.console", (assert) ->
assert.equal ActionCable.logger, window.console
test "configurable", (assert) ->
ActionCable.logger = ""
assert.equal ActionCable.logger, ""
module "#createConsumer", ->
test "uses specified URL", (assert) ->
consumer = ActionCable.createConsumer(testURL)
assert.equal consumer.url, testURL
test "uses default URL", (assert) ->
pattern = ///#{ActionCable.INTERNAL.default_mount_path}$///
consumer = ActionCable.createConsumer()
assert.ok pattern.test(consumer.url), "Expected #{consumer.url} to match #{pattern}"
test "uses URL from meta tag", (assert) ->
element = document.createElement("meta")
element.setAttribute("name", "action-cable-url")
element.setAttribute("content", testURL)
document.head.appendChild(element)
consumer = ActionCable.createConsumer()
document.head.removeChild(element)
assert.equal consumer.url, testURL

View File

@ -0,0 +1,55 @@
import ActionCable from "../../../../app/javascript/action_cable/index"
import {testURL} from "../test_helpers/index"
const {module, test} = QUnit
module("ActionCable", () => {
module("Adapters", () => {
module("WebSocket", () => {
test("default is window.WebSocket", assert => {
assert.equal(ActionCable.WebSocket, window.WebSocket)
})
test("configurable", assert => {
ActionCable.WebSocket = ""
assert.equal(ActionCable.WebSocket, "")
})
})
module("logger", () => {
test("default is window.console", assert => {
assert.equal(ActionCable.logger, window.console)
})
test("configurable", assert => {
ActionCable.logger = ""
assert.equal(ActionCable.logger, "")
})
})
})
module("#createConsumer", () => {
test("uses specified URL", assert => {
const consumer = ActionCable.createConsumer(testURL)
assert.equal(consumer.url, testURL)
})
test("uses default URL", assert => {
const pattern = new RegExp(`${ActionCable.INTERNAL.default_mount_path}$`)
const consumer = ActionCable.createConsumer()
assert.ok(pattern.test(consumer.url), `Expected ${consumer.url} to match ${pattern}`)
})
test("uses URL from meta tag", assert => {
const element = document.createElement("meta")
element.setAttribute("name", "action-cable-url")
element.setAttribute("content", testURL)
document.head.appendChild(element)
const consumer = ActionCable.createConsumer()
document.head.removeChild(element)
assert.equal(consumer.url, testURL)
})
})
})

View File

@ -1,14 +0,0 @@
{module, test} = QUnit
{consumerTest} = ActionCable.TestHelpers
module "ActionCable.Consumer", ->
consumerTest "#connect", connect: false, ({consumer, server, assert, done}) ->
server.on "connection", ->
assert.equal consumer.connect(), false
done()
consumer.connect()
consumerTest "#disconnect", ({consumer, client, done}) ->
client.addEventListener("close", done)
consumer.disconnect()

View File

@ -0,0 +1,19 @@
import consumerTest from "../test_helpers/consumer_test_helper"
const {module} = QUnit
module("ActionCable.Consumer", () => {
consumerTest("#connect", {connect: false}, ({consumer, server, assert, done}) => {
server.on("connection", () => {
assert.equal(consumer.connect(), false)
done()
})
consumer.connect()
})
consumerTest("#disconnect", ({consumer, client, done}) => {
client.addEventListener("close", done)
consumer.disconnect()
})
})

View File

@ -1,40 +0,0 @@
{module, test} = QUnit
{consumerTest} = ActionCable.TestHelpers
module "ActionCable.Subscription", ->
consumerTest "#initialized callback", ({server, consumer, assert, done}) ->
consumer.subscriptions.create "chat",
initialized: ->
assert.ok true
done()
consumerTest "#connected callback", ({server, consumer, assert, done}) ->
subscription = consumer.subscriptions.create "chat",
connected: ->
assert.ok true
done()
server.broadcastTo(subscription, message_type: "confirmation")
consumerTest "#disconnected callback", ({server, consumer, assert, done}) ->
subscription = consumer.subscriptions.create "chat",
disconnected: ->
assert.ok true
done()
server.broadcastTo subscription, message_type: "confirmation", ->
server.close()
consumerTest "#perform", ({consumer, server, assert, done}) ->
subscription = consumer.subscriptions.create "chat",
connected: ->
@perform(publish: "hi")
server.on "message", (message) ->
data = JSON.parse(message)
assert.equal data.identifier, subscription.identifier
assert.equal data.command, "message"
assert.deepEqual data.data, JSON.stringify(action: { publish: "hi" })
done()
server.broadcastTo(subscription, message_type: "confirmation")

View File

@ -0,0 +1,54 @@
import consumerTest from "../test_helpers/consumer_test_helper"
const {module} = QUnit
module("ActionCable.Subscription", () => {
consumerTest("#initialized callback", ({server, consumer, assert, done}) =>
consumer.subscriptions.create("chat", {
initialized() {
assert.ok(true)
done()
}
})
)
consumerTest("#connected callback", ({server, consumer, assert, done}) => {
const subscription = consumer.subscriptions.create("chat", {
connected() {
assert.ok(true)
done()
}
})
server.broadcastTo(subscription, {message_type: "confirmation"})
})
consumerTest("#disconnected callback", ({server, consumer, assert, done}) => {
const subscription = consumer.subscriptions.create("chat", {
disconnected() {
assert.ok(true)
done()
}
})
server.broadcastTo(subscription, {message_type: "confirmation"}, () => server.close())
})
consumerTest("#perform", ({consumer, server, assert, done}) => {
const subscription = consumer.subscriptions.create("chat", {
connected() {
this.perform({publish: "hi"})
}
})
server.on("message", (message) => {
const data = JSON.parse(message)
assert.equal(data.identifier, subscription.identifier)
assert.equal(data.command, "message")
assert.deepEqual(data.data, JSON.stringify({action: { publish: "hi" }}))
done()
})
server.broadcastTo(subscription, {message_type: "confirmation"})
})
})

View File

@ -1,25 +0,0 @@
{module, test} = QUnit
{consumerTest} = ActionCable.TestHelpers
module "ActionCable.Subscriptions", ->
consumerTest "create subscription with channel string", ({consumer, server, assert, done}) ->
channel = "chat"
server.on "message", (message) ->
data = JSON.parse(message)
assert.equal data.command, "subscribe"
assert.equal data.identifier, JSON.stringify({channel})
done()
consumer.subscriptions.create(channel)
consumerTest "create subscription with channel object", ({consumer, server, assert, done}) ->
channel = channel: "chat", room: "action"
server.on "message", (message) ->
data = JSON.parse(message)
assert.equal data.command, "subscribe"
assert.equal data.identifier, JSON.stringify(channel)
done()
consumer.subscriptions.create(channel)

View File

@ -0,0 +1,31 @@
import consumerTest from "../test_helpers/consumer_test_helper"
const {module} = QUnit
module("ActionCable.Subscriptions", () => {
consumerTest("create subscription with channel string", ({consumer, server, assert, done}) => {
const channel = "chat"
server.on("message", (message) => {
const data = JSON.parse(message)
assert.equal(data.command, "subscribe")
assert.equal(data.identifier, JSON.stringify({channel}))
done()
})
consumer.subscriptions.create(channel)
})
consumerTest("create subscription with channel object", ({consumer, server, assert, done}) => {
const channel = {channel: "chat", room: "action"}
server.on("message", (message) => {
const data = JSON.parse(message)
assert.equal(data.command, "subscribe")
assert.equal(data.identifier, JSON.stringify(channel))
done()
})
consumer.subscriptions.create(channel)
})
})

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff