#!/usr/bin/env ruby JSPEC_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) $:.unshift JSPEC_ROOT require 'rubygems' require 'commander/import' require 'bind' require 'fileutils' require 'server/server' RHINO = 'java org.mozilla.javascript.tools.shell.Main' program :name, 'JSpec' program :version, '2.11.7' program :description, 'JavaScript BDD Testing Framework' default_command :bind command :init do |c| c.syntax = 'jspec init [dest]' c.summary = 'Initialize a JSpec project template' c.description = 'Initialize a JSpec project template. Defaults to the current directory when [dest] is not specified. The template includes several files for running via Rhino, DOM, and the JSpec Rack server. Additional switches --freeze, and --symlink are available in order to preserve the version of JSpec at the time of initialization. Otherwise incompatibilities from later versions may prevent your suite from running properly.' c.option '-R', '--rails', 'Initialize rails template from rails root directory' c.option '-f', '--freeze', 'Copy the JSpec library' c.option '-s', '--symlink', 'Symlink the JSpec library instead of copying it' c.example 'Create a directory foo, initialized with a jspec template', 'jspec init foo' c.when_called do |args, options| dest = args.shift || '.' if options.rails initialize_rails_at dest, options else initialize_at dest, options end say "Template initialized at `#{dest}'" end end command :shell do |c| c.syntax = 'jspec shell [path ...]' c.summary = 'JSpec interactive shell' c.description = 'Launch interactive shell with jspec.js, jspec.shell.js, and any [path]s given. Simply type "quit" or "exit" to terminate the shell.' c.example 'Run shell', 'jspec shell' c.example 'Run shell with glob of files', 'jspec shell lib/*.js' c.example 'Run shell with list of files', 'jspec shell lib/foo.js lib/bar.js' c.when_called do |args, options| paths = ['jspec.js', 'jspec.shell.js'] | args paths.map! do |path| if path.include? 'jspec' "-f #{JSPEC_ROOT}/lib/#{path}" else "-f #{path}" end end say "JSpec #{program(:version)}" `#{RHINO} #{paths.join(' ')} -f -` end end command :update do |c| c.syntax = 'jspec update [path ...]' c.summary = 'Update JSpec releases' c.description = 'Update JSpec release in [paths], this will allow you to utilize the latest JSpec features. Execute from JSpec project root without [paths] to update the default template spec files.' c.when_called do |args, options| if args.empty? if rails? paths = 'jspec/spec.dom.html', 'jspec/spec.rhino.js' else paths = 'spec/spec.dom.html', 'spec/spec.rhino.js' end else paths = args end update_version_in *paths end end command :run do |c| c.syntax = 'jspec run [path] [options]' c.summary = 'Run specifications' c.description = 'Run specifications, defaulting [path] to spec/spec.dom.html. You will need to supply [path] if your specs do not reside in this location. `run --bind` is the default sub-command of jspec so you may simply execute `jspec` in order to bind execution of your specs when a file is altered. JSpec supports Rhino execution when installed. The [path] is assumed to be spec/spec.rhino.js unless specified. See examples below for using the --rhino switch. JSpec\'s server is also available via --server, which defaults the [path] to spec/server.html' c.example 'Run once in Safari', 'jspec run' c.example 'Run once in Safari and Firefox', 'jspec run --browsers Safari,Firefox' c.example 'Run once in Opera, Firefox, and Chrome', 'jspec run --browsers opera,ff,chrome' c.example 'Run custom spec file', 'jspec run foo.html' c.example 'Auto-run browsers when a file is altered', 'jspec run --bind --browsers Safari,Firefox' c.example 'Shortcut for the previous example', 'jspec --browsers Safari,Firefox' c.example 'Auto-run rhino when a file is altered', 'jspec --rhino' c.example 'Run Rhino specs at spec/rhino.js', 'jspec run --rhino' c.example 'Run Rhino specs once', 'jspec run specs/something.js --rhino' c.option '-b', '--browsers BROWSERS', Array, 'Specify browsers to test' c.option '-p', '--paths PATHS', Array, 'Specify paths when binding, defaults to javascript within ./lib and ./spec' c.option '-B', '--bind', 'Auto-run specs when source files or specs are altered' c.option '-R', '--rhino', 'Run specs using Rhino' c.option '-S', '--server', 'Run specs using the JSpec server' c.option '-P', '--port NUMBER', Integer, 'Start JSpec server using the given port number' c.when_called do |args, options| # Rails if rails? options.default :paths => ['public/javascripts/**/*.js', 'jspec/**/*.js'], :port => 4444 else options.default :paths => ['lib/**/*.js', 'spec/**/*.js'], :port => 4444 end # Actions if options.rhino suite = args.shift || path_to('spec.rhino.js') action = lambda { rhino suite } elsif options.server raise 'Cannot use --server with --bind' if options.bind suite = args.shift || path_to('spec.server.html') action = lambda { start_server suite, options } else suite = args.shift || path_to('spec.dom.html') browsers = browsers_for options.browsers || ['safari'] action = lambda do browsers.each do |browser| browser.visit File.expand_path(suite) end end end # Binding if options.bind listener = Bind::Listener.new :paths => options.paths, :interval => 1, :actions => [action], :debug => $stdout listener.run! else action.call File.new(suite) end end end alias_command :bind, :run, '--bind' ## # Initialize template at _dest_. def initialize_at dest, options unless Dir[dest + '/*'].empty? abort unless agree "'#{dest}' is not empty; continue? " end copy_template_to 'default', dest setup_lib_dir dest, options replace_root_in dest, 'spec/spec.dom.html', 'spec/spec.rhino.js' end ## # Initialize rails template at _dest_. def initialize_rails_at dest, options unless looks_like_rails_root?(dest) abort unless agree "'#{dest}' does not look like root of a rails project; continue? " end copy_template_to 'rails', "#{dest}/jspec" setup_lib_dir "#{dest}/jspec", options replace_root_in "#{dest}/jspec", 'spec.dom.html', 'spec.rhino.js' end ## # Copy template _name_ to _dest_. def copy_template_to name, dest FileUtils.mkdir_p dest FileUtils.cp_r path_to_template(name), dest end ## # Return path to template _name_. def path_to_template name File.join JSPEC_ROOT, 'templates', name, '.' end ## # Resolve path to _file_. Supports rails and unbound projects. def path_to file rails? ? "jspec/#{file}" : "spec/#{file}" end ## # Execute _file_ with Rhino. def rhino file raise "#{file} not found" unless File.exists? file system "#{RHINO} #{file}" end ## # Start server with _suite_ html and _options_. def start_server suite, options set :port, options.port set :server, 'Mongrel' enable :sessions disable :logging hook = File.expand_path path_to('server.rb') load hook if File.exists? hook JSpec::Server.new(suite, options.port).start(options.browsers ? browsers_for(options.browsers) : nil) end ## # Return array of browser instances for the given _names_. def browsers_for names names.map do |name| begin Browser.subclasses.find do |browser| browser.matches_name? name end.new rescue raise "Unsupported browser `#{name}'" end end end ## # Check if the current directory looks like a rails app. def rails? File.directory? 'jspec' end ## # Replace JSPEC_ROOT placeholder in _paths_ relative to _dest_. def replace_root_in dest, *paths if rails? && File.exist?("#{dest}/jspec/lib") root = './jspec' elsif File.exist?("#{dest}/spec/lib") root = "./spec" else root = JSPEC_ROOT end paths.each do |path| path = File.join dest, path contents = File.read(path).gsub 'JSPEC_ROOT', root File.open(path, 'w') { |file| file.write contents } end end ## # Update JSpec version in _paths_. Matches jspec-TRIPLE def update_version_in *paths paths.each do |path| next unless File.exists? path contents = File.read(path).gsub /jspec-(\d+\.\d+\.\d+)/, "jspec-#{program(:version)}" File.open(path, 'r+'){ |file| file.write contents } say "Updated #{path}; #{$1} -> #{program(:version)}" end say "Finished updating JSpec" end ## # Check if _path_ looks like a rails root directory. def looks_like_rails_root? path = '.' File.directory? "#{path}/vendor" end ## # Copy or symlink library to the specified path. def setup_lib_dir dest, options return unless options.symlink || options.freeze if rails? dest = File.join dest, "lib" else dest = File.join dest, "spec", "lib" end from = File.join JSPEC_ROOT, "lib" if options.symlink FileUtils.symlink from, dest, :force => true else FileUtils.cp_r from, dest end end