Skip to content

Instantly share code, notes, and snippets.

@doitian
Created April 11, 2012 06:29
Show Gist options
  • Save doitian/2357356 to your computer and use it in GitHub Desktop.
Save doitian/2357356 to your computer and use it in GitHub Desktop.
Rails/Rack HTML exporter && jasmine jsconverage integration
require 'guard'
require 'guard/guard'
require 'pathname'
require 'fileutils'
require 'rack_file_exporter'
require 'sprockets_environment_builder'
# auto export files for coverage test
module Guard
class DevelopmentExport < Guard
EXCLUDE_JS = %w(application.js specs.js)
INJECT_JS = '<script type="text/javascript" src="jscoverage-inject.js"></script>'
def start
UI.info "Development Export started..."
run_all
end
def reload
@javascripts_dir = Pathname.new('assets/javascripts')
@specs_dir = Pathname.new('assets/specs')
@dest = Pathname.new('public/coverage')
@dest.mkpath
@sprockets_env = SprocketsEnvironmentBuilder.build(:development)
@rack_exporter = RackFileExporter.new
@has_jscoverage = `jscoverage --version 2> /dev/null`.include?('jscoverage 0.5')
if @has_jscoverage
UI.info "-- jscoverage 0.5 is found"
else
UI.info "-- jscoverage 0.5 not found, coverage test is disable"
end
end
def run_all
reload
@dest.rmtree
@dest.mkpath
UI.info "Export jasmine.html..."
html = export_jasmine_html
UI.info "Export js/css..."
assets = html.scan(/"assets\/([^"]+)"/).collect(&:first)
sources = html.scan(/"src\/([^"]+)"/).collect(&:first)
specs = html.scan(/"spec\/([^"]+)"/).collect(&:first)
assets.each { |file| export_asset(file, @dest + 'assets') }
sources.each { |file| export_asset(file, @dest + 'raw') }
specs.each { |file| export_asset(file, @dest + 'spec') }
run_jscoverage
true
end
#
# assets/javascripts -> src
# spec/javascripts ->
#
def run_on_change(paths)
result = paths.collect do |path|
file, dir = parse_path(path)
if file
UI.info 'Export asset ' + file
if dest_file = export_asset(file, dir)
if dir.basename.to_s == 'raw'
run_jscoverage(@dest + 'tmp')
from = @dest + 'tmp' + file
dest_file = (@dest + 'src' + file).to_s
UI.info "cp #{from} #{dest_file}"
FileUtils.cp(from, dest_file)
sleep 1
FileUtils.touch(dest_file)
end
dest_file
else
UI.warning 'Asset not found: ' + path
nil
end
else
UI.warning 'Ignore path ' + path
nil
end
end
UI.info 'Export jasmine.html'
export_jasmine_html
result.compact + [@dest + 'jasmine.html']
end
def run_on_deletion
run_all
end
private
def parse_path(path)
path = path.to_s
if path =~ %r{^(assets|spec|vendor/assets)/javascripts/(.+)\.(?:js|coffee|jst\.eco)$}
file = $2 + '.js'
dir = case $1
when 'assets' then 'raw' # assets are sources
when 'vendor/assets' then 'assets' # vendor still put in assets
else $1 # spec into spec directory
end
dir = Pathname.new(@dest + dir)
[file, dir]
end
end
def export_jasmine_html
html = @rack_exporter.export('/jasmine')
File.open(@dest + 'jasmine.html', 'w') { |f| f.write(html) }
html
end
def export_asset(file, dir)
asset = @sprockets_env.find_asset(file)
if asset
dest = dir + file
dest.parent.mkpath
asset.write_to(dest)
dest.to_s
end
end
def run_jscoverage(dest = nil)
raw = @dest + 'raw'
src = dest || (@dest + 'src')
report = @dest + 'report'
report.mkpath
if @has_jscoverage
run_command "jscoverage #{raw} #{src}"
# copy jscoverage files to top dir and reports dir
Pathname.glob(src + 'jscoverage*').each do |file|
if file.basename.to_s == 'jscoverage.html'
# inject jscoverage-inject.js
injected = file.read.sub(/<\/head>/, [INJECT_JS, '\&'].join("\n"))
File.open(@dest + 'index.html', 'w') { |f| f.write(injected) }
File.open(report + 'index.html', 'w') { |f| f.write(injected) }
else
FileUtils.cp file, @dest
FileUtils.cp file, report
end
end
# always open jasmine.html
File.open(@dest + 'jscoverage-inject.js', 'w') do |f|
f.write <<-JS.gsub(/^ {12}/, '')
(function() {
var original = window.jscoverage_initTabContents;
window.jscoverage_initTabContents = function(queryString) {
original('?url=jasmine.html&missing=1');
};
})();
JS
end
FileUtils.touch(report + 'jscoverage-inject.js')
else
UI.info "cp -r #{raw}/* #{src}"
FileUtils.cp_r Dir[raw + '*'], src
# make jasmine.html as index.html
FileUtils.cp_f(@dest + 'jasmine.html', @dest + 'index.html')
end
end
def run_command(command)
UI.info command
system(command)
end
end
end
guard 'development-export' do
watch(%r{^vendor/assets/javascripts/.+\.(?:coffee|eco|js)$}) {
[
'vendor/assets/javascripts/vendor.coffee',
'spec/javascripts/spec_helper.coffee'
]
}
watch(%r{^(?:assets|spec)/javascripts/.+\.(?:coffee|eco|js)$})
end
group 'livereload' do
guard 'livereload' do
watch(%r{^vendor/assets/.+\.(coffee|js)})
watch(%r{^assets/(.+)\.(coffee|js|eco)})
watch(%r{^spec/(.+)\.(coffee|js)})
end
end
group 'headless' do
spec_location = "public/coverage/spec/%s_spec.js"
guard 'jasmine-headless-webkit' do
watch(%r{^vendor/assets/javascripts/.+\.(?:coffee|eco|js)$}) {
'public/coverage/spec'
}
watch('spec/javascripts/spec_helper.coffee') { 'public/coverage/spec' }
watch(%r{^assets/javascripts/(.*)\.(?:coffee|jst\.eco|js)$}) { |m| spec_location % m[1] }
watch(%r{^spec/javascripts/(.*)_spec\.coffee$}) { |m| spec_location % m[1] }
end
end
# src_files
#
# Return an array of filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# src_files:
# - lib/source1.js
# - lib/source2.js
# - dist/**/*.js
#
src_files:
- assets/vendor.js
- src/**/*.js
# stylesheets
#
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# stylesheets:
# - css/style.css
# - stylesheets/*.css
#
stylesheets: []
# helpers
#
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
# Default: ["helpers/**/*.js"]
#
# EXAMPLE:
#
# helpers:
# - helpers/**/*.js
#
helpers:
- spec/factories.js
- spec/spec_helper.js
- spec/specs_loader.js
# spec_files
#
# Return an array of filepaths relative to spec_dir to include.
# Default: ["**/*[sS]pec.js"]
#
# EXAMPLE:
#
# spec_files:
# - **/*[sS]pec.js
#
spec_files:
- spec/**/*_spec.js
# src_dir
#
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
# Default: project root
#
# EXAMPLE:
#
# src_dir: public
#
src_dir: public/coverage
# spec_dir
#
# Spec directory path. Your spec_files must be returned relative to this path.
# Default: spec/javascripts
#
# EXAMPLE:
#
# spec_dir: spec/javascripts
#
spec_dir: public/coverage
require 'pathname'
module Jasmine
class Config
end
end
# Note - this is necessary for rspec2, which has removed the backtrace
module Jasmine
class SpecBuilder
def stop
save_coverage_report
@runner.stop
end
def declare_spec(parent, spec)
me = self
example_name = spec["name"]
@spec_ids << spec["id"]
backtrace = @example_locations[parent.description + " " + example_name]
parent.it example_name, {} do
me.report_spec(spec["id"])
end
end
def save_coverage_report
report_js = Pathname.new(File.join(@config.project_root, 'public/coverage/report/jscoverage-inject.js'))
report_js.parent.mkpath
jscoverage = eval_js "return JSON.stringify(window.jasmine.coverageReport());"
json = json_generate(jscoverage)
File.open(report_js, 'w') do |f|
f.write <<-JS.gsub(/^ {10}/, '')
jscoverage_isReport = true;
var jscoverage_reportJSON = #{json};
jscoverage_createRequest = function() {
return {
open: function() {},
send: function() {
var file;
for (file in jscoverage_reportJSON) {
if (!jscoverage_reportJSON.hasOwnProperty(file)) {
continue;
}
var fileCoverage = jscoverage_reportJSON[file];
_$jscoverage[file] = fileCoverage.coverage;
_$jscoverage[file].source = fileCoverage.source;
}
jscoverage_recalculateSummaryTab();
document.getElementById('summaryThrobber').style.visibility = 'hidden';
document.getElementById('warningDiv').style.visibility = 'hidden';
}
};
};
JS
end
end
end
end
require 'rack/test'
require 'your_app'
class RackFileExporter
include Rack::Test::Methods
def app
Application::App
end
def export(path)
get path
last_response.body
end
end
class TemplateExporter < ActionDispatch::IntegrationTest
def initialize
super('exporter')
end
def export(path)
get path
response.body
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment