Skip to content

Commit 54bd51b

Browse files
committed
Test JavaScript on CI
1 parent 3ea9e99 commit 54bd51b

File tree

7 files changed

+219
-11
lines changed

7 files changed

+219
-11
lines changed

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ AllCops:
66
TargetRailsVersion: 5.0
77
TargetRubyVersion: 2.3
88
Exclude:
9+
- 'gemfiles/vendor/bundle/**/*'
910
- 'node_modules/**/*'
1011
- 'vendor/bundle/**/*'
1112

@@ -28,6 +29,7 @@ Metrics/AbcSize:
2829
Metrics/BlockLength:
2930
Exclude:
3031
- '*.gemspec'
32+
- 'Rakefile'
3133
- 'test/**/*'
3234

3335
Metrics/ClassLength:

.travis.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
language: ruby
2+
23
rvm:
34
- 2.3.8
45
- 2.4.10
56
- 2.5.8
67
- 2.6.6
78
- 2.7.1
89
- ruby-head
10+
11+
addons:
12+
chrome: stable
13+
914
gemfile:
1015
- gemfiles/csv_15.0.gemfile
1116
- gemfiles/csv_16.0.gemfile
17+
18+
cache:
19+
bundler: true
20+
directories:
21+
- node_modules
22+
yarn: true
23+
24+
before_install:
25+
- nvm install 12
26+
- node -v
27+
- npm i -g yarn@^1.21.1
28+
- yarn
29+
- LATEST_CHROMEDRIVER_VERSION=`curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"`
30+
- curl "https://chromedriver.storage.googleapis.com/${LATEST_CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -O
31+
- unzip chromedriver_linux64.zip -d ~/bin
32+
1233
matrix:
1334
allow_failures:
1435
- rvm: ruby-head
1536
fast_finish: true
37+
38+
script: bundle exec rake test:all

Rakefile

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,35 @@ namespace :test do
2020
test.warning = false
2121
end
2222

23-
desc %(Test Javascript code)
24-
multitask js: ['regenerate_javascript', 'test:server', 'test:open']
23+
desc %(Test JavaScript code)
24+
task js: ['regenerate_javascript', 'test:server', 'test:open']
2525

2626
desc %(Starts the test server)
2727
task :server do
28-
system 'bundle exec ruby test/javascript/server.rb'
28+
puts "Opening test app at #{test_url} ..."
29+
server_command = 'bundle exec ruby test/javascript/server.rb'
30+
31+
if ENV['UI']
32+
system server_command
33+
else
34+
@server = fork { exec server_command }
35+
end
36+
37+
# Give Sinatra some time to start
38+
sleep 3
2939
end
3040

3141
desc %(Starts the test server which reloads everything on each refresh)
3242
task :reloadable do
33-
exec "bundle exec shotgun test/javascript/config.ru -p #{PORT} --server thin"
43+
exec "bundle exec shotgun test/javascript/config.ru -p #{test_port} --server thin"
3444
end
3545

3646
task :open do
37-
url = "http://localhost:#{PORT}"
38-
puts "Opening test app at #{url} ..."
39-
sleep 3
40-
system(*browse_cmd(url))
47+
if ENV['UI']
48+
system(*browse_cmd(url))
49+
else
50+
run_headless_tests
51+
end
4152
end
4253
end
4354

@@ -61,8 +72,6 @@ def perform_git_commit
6172
end
6273
end
6374

64-
PORT = 4567
65-
6675
# Returns an array e.g.: ['open', 'http://example.com']
6776
def browse_cmd(url)
6877
require 'rbconfig'
@@ -87,5 +96,24 @@ def which(cmd)
8796
nil
8897
end
8998

99+
def run_headless_tests
100+
require 'English'
101+
102+
system "yarn test #{test_url}?autostart=false"
103+
exit_code = $CHILD_STATUS.exitstatus
104+
105+
Process.kill 'INT', @server
106+
107+
exit exit_code unless exit_code.zero?
108+
end
109+
110+
def test_port
111+
@test_port ||= 4567
112+
end
113+
114+
def test_url
115+
@test_url ||= "http://localhost:#{test_port}"
116+
end
117+
90118
task(:build).prerequisites.unshift task(:commit_javascript)
91119
task(:build).prerequisites.unshift task(:regenerate_javascript)

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
},
2020
"homepage": "https://github.com/DavyJonesLocker/client_side_validations-simple_form",
2121
"scripts": {
22-
"build": "standard --verbose && rollup -c"
22+
"build": "standard --verbose && rollup -c",
23+
"test": "test/javascript/run-qunit.js"
2324
},
2425
"devDependencies": {
2526
"@babel/core": "^7.9.0",
2627
"@babel/preset-env": "^7.9.5",
2728
"@rollup/plugin-node-resolve": "^7.1.1",
29+
"chrome-launcher": "^0.13.1",
30+
"puppeteer-core": "^2.1.1",
2831
"rollup": "^2.6.0",
2932
"rollup-plugin-babel": "^4.4.0",
3033
"rollup-plugin-copy": "^3.3.0",
@@ -50,6 +53,7 @@
5053
"standard": {
5154
"ignore": [
5255
"/dist/",
56+
"/gemfiles/",
5357
"/test/",
5458
"/vendor/"
5559
]

test/javascript/public/test/settings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
QUnit.config.autostart = window.location.search.search('autostart=false') < 0
2+
13
QUnit.config.urlConfig.push({
24
id: 'jquery',
35
label: 'jQuery version',

test/javascript/run-qunit.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env node
2+
3+
// Inspired by David Taylor's qunit puppeteer
4+
//
5+
6+
/* global QUnit */
7+
8+
var args = process.argv.slice(2)
9+
10+
if (args.length < 1 || args.length > 2) {
11+
console.log('Usage: node run-qunit.js <URL> <timeout>')
12+
process.exit(1)
13+
}
14+
15+
const targetURL = args[0]
16+
const timeout = parseInt(args[1] || 300000, 10)
17+
18+
const chromeLauncher = require('chrome-launcher')
19+
const puppeteer = require('puppeteer-core');
20+
21+
(async () => {
22+
console.log('\nRunning QUnit tests\n')
23+
24+
function wait (ms) {
25+
return new Promise(resolve => setTimeout(resolve, ms))
26+
}
27+
28+
try {
29+
const chromeExecutablePath = chromeLauncher.Launcher.getInstallations()[0]
30+
const browser = await puppeteer.launch({ executablePath: chromeExecutablePath, headless: true })
31+
const page = await browser.newPage()
32+
33+
// Attach to browser console log events, and log to node console
34+
await page.on('console', (...params) => {
35+
for (let i = 0; i < params.length; ++i) { console.log(`${(typeof (params[i]) === 'object') ? params[i]._text : params[i]}`) }
36+
})
37+
38+
var moduleErrors = []
39+
var testErrors = []
40+
var assertionErrors = []
41+
42+
await page.exposeFunction('harness_moduleStart', context => {
43+
testErrors = []
44+
var skippedTests = context.tests.filter(t => t.skip).length
45+
if (skippedTests === context.tests.length) {
46+
// console.log(`\x1b[4m\x1b[36mSkipping Module: ${context.name}\x1b[0m`)
47+
} else {
48+
// console.log(`\x1b[4mRunning Module: ${context.name}\x1b[0m`)
49+
}
50+
})
51+
52+
await page.exposeFunction('harness_moduleDone', context => {
53+
if (context.failed) {
54+
var msg = 'Module Failed: ' + context.name + '\n' + testErrors.join('\n')
55+
moduleErrors.push(msg)
56+
}
57+
})
58+
59+
await page.exposeFunction('harness_testDone', context => {
60+
if (context.failed) {
61+
var msg = ' Test Failed: ' + context.name + assertionErrors.join(' ')
62+
testErrors.push(msg)
63+
assertionErrors = []
64+
process.stdout.write('\x1b[31mF\x1b[0m')
65+
} else if (context.skipped) {
66+
process.stdout.write('\x1b[33mS\x1b[0m')
67+
} else {
68+
process.stdout.write('\x1b[32m.\x1b[0m')
69+
}
70+
})
71+
72+
await page.exposeFunction('harness_log', context => {
73+
if (context.result) { return } // If success don't log
74+
75+
var msg = '\n Assertion Failed:'
76+
if (context.message) {
77+
msg += ' ' + context.message
78+
}
79+
80+
if (context.expected) {
81+
msg += '\n Expected: ' + context.expected + ', Actual: ' + context.actual
82+
}
83+
84+
assertionErrors.push(msg)
85+
})
86+
87+
await page.exposeFunction('harness_done', context => {
88+
process.stdout.write('\n')
89+
90+
if (moduleErrors.length > 0) {
91+
for (var idx = 0; idx < moduleErrors.length; idx++) {
92+
console.log(moduleErrors[idx])
93+
}
94+
}
95+
96+
var stats = [
97+
'Time: ' + context.runtime + 'ms',
98+
'Total: ' + context.total,
99+
'Passed: ' + context.passed,
100+
'Failed: ' + context.failed
101+
]
102+
103+
console.log(`\n${stats.join(', ')}\n`)
104+
105+
browser.close()
106+
107+
if (context.failed > 0) {
108+
process.exit(1)
109+
} else {
110+
process.exit()
111+
}
112+
})
113+
114+
await page.goto(targetURL)
115+
116+
await page.evaluate(() => {
117+
QUnit.config.testTimeout = 10000
118+
119+
// Cannot pass the window.harness_blah methods directly, because they are
120+
// automatically defined as async methods, which QUnit does not support
121+
QUnit.log((context) => { window.harness_log(context) })
122+
QUnit.moduleStart((context) => { window.harness_moduleStart(context) })
123+
QUnit.moduleDone((context) => { window.harness_moduleDone(context) })
124+
QUnit.testDone((context) => { window.harness_testDone(context) })
125+
QUnit.done((context) => { window.harness_done(context) })
126+
127+
if (Object.keys(QUnit.urlParams).length) {
128+
console.log('With params: ' + JSON.stringify(QUnit.urlParams) + '\n')
129+
}
130+
131+
if (!QUnit.config.autostart) {
132+
QUnit.start()
133+
}
134+
})
135+
136+
await wait(timeout)
137+
138+
console.error('\nTests timed out\n')
139+
browser.close()
140+
process.exit(124)
141+
} catch (err) {
142+
console.error(`\nERROR: ${err}\n`)
143+
process.exit(1)
144+
}
145+
})()

test/javascript/server.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ def test(*types)
6060
get '/' do
6161
erb :index
6262
end
63+
64+
get '/favicon.ico' do
65+
halt 200
66+
end

0 commit comments

Comments
 (0)