├── init.rb
├── rails
└── init.rb
├── VERSION.yml
├── .gitignore
├── lib
├── start_celerity.rb
├── culerity
│ ├── persistent_delivery.rb
│ ├── remote_browser_proxy.rb
│ ├── celerity_server.rb
│ └── remote_object_proxy.rb
├── culerity.rb
└── tasks
│ └── rspec.rake
├── bin
└── run_celerity_server.rb
├── spec
├── spec_helper.rb
├── remote_browser_proxy_spec.rb
├── culerity_spec.rb
├── remote_object_proxy_spec.rb
└── celerity_server_spec.rb
├── features
├── step_definitions
│ ├── culerity_setup_steps.rb
│ ├── jruby_steps.rb
│ ├── rails_setup_steps.rb
│ └── common_steps.rb
├── support
│ ├── matchers.rb
│ ├── env.rb
│ └── common.rb
├── fixtures
│ ├── sample_feature
│ └── jquery
├── running_cucumber_without_explicitly_running_external_services.feature
└── installing_culerity.feature
├── rails_generators
└── culerity
│ ├── templates
│ ├── features
│ │ ├── support
│ │ │ └── env.rb
│ │ └── step_definitions
│ │ │ └── culerity_steps.rb
│ ├── config
│ │ └── environments
│ │ │ ├── culerity_continuousintegration.rb
│ │ │ └── culerity.rb
│ ├── public
│ │ └── javascripts
│ │ │ └── culerity.js
│ └── lib
│ │ └── tasks
│ │ └── culerity.rake
│ └── culerity_generator.rb
├── script
├── destroy
├── generate
└── console
├── CHANGES.md
├── MIT-LICENSE
├── Rakefile
├── culerity.gemspec
└── README.md
/init.rb:
--------------------------------------------------------------------------------
1 | require 'rails/init'
--------------------------------------------------------------------------------
/rails/init.rb:
--------------------------------------------------------------------------------
1 | require 'culerity'
--------------------------------------------------------------------------------
/VERSION.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :minor: 2
3 | :build:
4 | :patch: 15
5 | :major: 0
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage
2 | rdoc
3 | pkg
4 | tmp
5 | *.sw?
6 | *.gem
7 | .rvmrc
8 |
--------------------------------------------------------------------------------
/lib/start_celerity.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require File.dirname(__FILE__) + '/culerity/celerity_server'
3 | Culerity::CelerityServer.new(STDIN, STDOUT)
4 |
--------------------------------------------------------------------------------
/bin/run_celerity_server.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env jruby
2 | require File.dirname(__FILE__) << '/../lib/culerity/celerity_server'
3 | Culerity::CelerityServer.new(STDIN, STDOUT)
4 |
5 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | if RUBY_PLATFORM != 'java'
2 | puts "You need JRuby to run these specs"
3 | exit -1
4 | end
5 |
6 | require File.dirname(__FILE__) + '/../lib/culerity'
7 | require File.dirname(__FILE__) + '/../lib/culerity/celerity_server'
--------------------------------------------------------------------------------
/features/step_definitions/culerity_setup_steps.rb:
--------------------------------------------------------------------------------
1 | When /^I setup load path to local code$/ do
2 | project_lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
3 | in_project_folder do
4 | force_local_lib_override(:target => 'features/step_definitions/culerity_steps.rb')
5 | end
6 | end
7 |
8 |
--------------------------------------------------------------------------------
/rails_generators/culerity/templates/features/support/env.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "culerity"
2 | require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
3 | require 'cucumber/rails/world'
4 |
5 | Cucumber::Rails::World.use_transactional_fixtures = false
6 | ActionController::Base.allow_rescue = false
7 |
8 | require 'cucumber/formatter/unicode'
9 | require 'cucumber/rails/rspec'
--------------------------------------------------------------------------------
/features/support/matchers.rb:
--------------------------------------------------------------------------------
1 | module Matchers
2 | def contain(expected)
3 | simple_matcher("contain #{expected.inspect}") do |given, matcher|
4 | matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
5 | matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
6 | given.index expected
7 | end
8 | end
9 | end
10 |
11 | World(Matchers)
12 |
--------------------------------------------------------------------------------
/script/destroy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3 |
4 | begin
5 | require 'rubigen'
6 | rescue LoadError
7 | require 'rubygems'
8 | require 'rubigen'
9 | end
10 | require 'rubigen/scripts/destroy'
11 |
12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14 | RubiGen::Scripts::Destroy.new.run(ARGV)
15 |
--------------------------------------------------------------------------------
/script/generate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3 |
4 | begin
5 | require 'rubigen'
6 | rescue LoadError
7 | require 'rubygems'
8 | require 'rubigen'
9 | end
10 | require 'rubigen/scripts/generate'
11 |
12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14 | RubiGen::Scripts::Generate.new.run(ARGV)
15 |
--------------------------------------------------------------------------------
/features/fixtures/sample_feature:
--------------------------------------------------------------------------------
1 | Feature: Check that default Rails index.html shows information
2 | In order to value
3 | As a role
4 | I want feature
5 |
6 | Scenario: Check javascript runs on static file
7 | Given I go to the homepage
8 | Then I should not see "Rails version"
9 | When I follow "About your application’s environment"
10 | And I wait for the AJAX call to finish
11 | Then I should see "No route matches"
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/script/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # File: script/console
3 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4 |
5 | libs = " -r irb/completion"
6 | # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7 | # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8 | libs << " -r #{File.dirname(__FILE__) + '/../lib/culerity.rb'}"
9 | puts "Loading culerity gem"
10 | exec "#{irb} #{libs} --simple-prompt"
--------------------------------------------------------------------------------
/features/step_definitions/jruby_steps.rb:
--------------------------------------------------------------------------------
1 | Given /^I have jruby installed$/ do
2 | @jruby_cmd = `which jruby`.strip
3 | raise "Need to setup @jruby_cmd to test jruby environment" if @jruby_cmd.blank?
4 | end
5 |
6 | Then /^the gem "([^\"]*)" is installed into jruby environment$/ do |gem_name|
7 | raise "Need to setup @jruby_cmd to test jruby environment" if @jruby_cmd.blank?
8 | gem_list = `#{@jruby_cmd} -S gem list #{gem_name}`
9 | gem_list.should =~ /#{gem_name}/
10 | end
11 |
12 |
--------------------------------------------------------------------------------
/rails_generators/culerity/templates/config/environments/culerity_continuousintegration.rb:
--------------------------------------------------------------------------------
1 | # Settings specified here will take precedence over those in config/environment.rb
2 |
3 | # The production environment is meant for finished, "live" apps.
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.action_controller.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 | config.action_view.cache_template_loading = true
11 |
12 | config.action_mailer.delivery_method = :persistent
13 |
14 | config.after_initialize do
15 | require 'culerity/persistent_delivery'
16 | end
17 |
--------------------------------------------------------------------------------
/features/support/env.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + "/../../lib/culerity"
2 |
3 | gem 'cucumber'
4 | require 'cucumber'
5 | gem 'rspec'
6 | require 'spec'
7 |
8 | Before do
9 | @tmp_root = File.dirname(__FILE__) + "/../../tmp"
10 | @home_path = File.expand_path(File.join(@tmp_root, "home"))
11 | @lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
12 | FileUtils.rm_rf @tmp_root
13 | FileUtils.mkdir_p @home_path
14 | ENV['HOME'] = @home_path
15 | end
16 |
17 | require 'rubigen'
18 | require 'rubigen/helpers/generator_test_helper'
19 | include RubiGen::GeneratorTestHelper
20 |
21 | SOURCES = Dir[File.dirname(__FILE__) + "/../../generators"].map do |f|
22 | RubiGen::PathSource.new(:test, File.expand_path(f))
23 | end
24 |
25 |
--------------------------------------------------------------------------------
/rails_generators/culerity/templates/public/javascripts/culerity.js:
--------------------------------------------------------------------------------
1 | // this allows culerity to wait until all ajax requests have finished
2 | jQuery(function($) {
3 | var original_ajax = $.ajax;
4 | var count_down = function(callback) {
5 | return function() {
6 | try {
7 | if(callback) {
8 | callback.apply(this, arguments);
9 | };
10 | } catch(e) {
11 | window.running_ajax_calls -= 1;
12 | throw(e);
13 | }
14 | window.running_ajax_calls -= 1;
15 | };
16 | };
17 | window.running_ajax_calls = 0;
18 |
19 | var ajax_with_count = function(options) {
20 | if(options.async == false) {
21 | return(original_ajax(options));
22 | } else {
23 | window.running_ajax_calls += 1;
24 | options.success = count_down(options.success);
25 | options.error = count_down(options.error);
26 | return original_ajax(options);
27 | }
28 | };
29 |
30 | $.ajax = ajax_with_count;
31 | });
--------------------------------------------------------------------------------
/rails_generators/culerity/culerity_generator.rb:
--------------------------------------------------------------------------------
1 | class CulerityGenerator < Rails::Generator::Base
2 |
3 | def manifest
4 | record do |m|
5 | m.directory 'features/step_definitions'
6 | m.file 'features/step_definitions/culerity_steps.rb', 'features/step_definitions/culerity_steps.rb'
7 | m.file 'features/support/env.rb', 'features/support/env.rb'
8 | m.file 'config/environments/culerity.rb', 'config/environments/culerity.rb'
9 |
10 | m.gsub_file 'config/database.yml', /cucumber:.*\n/, "cucumber: &CUCUMBER\n"
11 |
12 | m.gsub_file 'config/database.yml', /\z/, "\nculerity:\n <<: *CUCUMBER"
13 |
14 | m.file "lib/tasks/culerity.rake", "lib/tasks/culerity.rake"
15 |
16 | m.file 'public/javascripts/culerity.js', 'public/javascripts/culerity.js'
17 | end
18 | end
19 |
20 | protected
21 |
22 | def banner
23 | "Usage: #{$0} culerity"
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/rails_generators/culerity/templates/config/environments/culerity.rb:
--------------------------------------------------------------------------------
1 | config.cache_classes = true # set because of https://rspec.lighthouseapp.com/projects/16211/tickets/165
2 |
3 | # Log error messages when you accidentally call methods on nil.
4 | config.whiny_nils = true
5 |
6 | # Show full error reports and disable caching
7 | config.action_controller.consider_all_requests_local = true
8 | config.action_controller.perform_caching = false
9 | config.action_view.cache_template_loading = false
10 |
11 | # Disable request forgery protection in test environment
12 | config.action_controller.allow_forgery_protection = false
13 |
14 | # Tell Action Mailer not to deliver emails to the real world.
15 | # The :test delivery method accumulates sent emails in the
16 | # ActionMailer::Base.deliveries array.
17 | config.action_mailer.delivery_method = :persistent
18 |
19 | config.after_initialize do
20 | require 'culerity/persistent_delivery'
21 | end
22 |
23 |
--------------------------------------------------------------------------------
/features/support/common.rb:
--------------------------------------------------------------------------------
1 | module CommonHelpers
2 | def in_tmp_folder(&block)
3 | FileUtils.chdir(@tmp_root, &block)
4 | end
5 |
6 | def in_project_folder(&block)
7 | project_folder = @active_project_folder || @tmp_root
8 | FileUtils.chdir(project_folder, &block)
9 | end
10 |
11 | def in_home_folder(&block)
12 | FileUtils.chdir(@home_path, &block)
13 | end
14 |
15 | def force_local_lib_override(options = {})
16 | target_path = options[:target_path] || options[:target_file] || options[:target] || 'Rakefile'
17 | in_project_folder do
18 | contents = File.read(target_path)
19 | File.open(target_path, "w+") do |f|
20 | f << "$:.unshift('#{@lib_path}')\n"
21 | f << contents
22 | end
23 | end
24 | end
25 |
26 | def setup_active_project_folder project_name
27 | @active_project_folder = File.join(@tmp_root, project_name)
28 | @project_name = project_name
29 | end
30 | end
31 |
32 | World(CommonHelpers)
--------------------------------------------------------------------------------
/lib/culerity/persistent_delivery.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 |
3 | module Culerity
4 | module PersistentDelivery
5 |
6 | DELIVERIES_PATH =
7 | File.join(RAILS_ROOT, 'tmp', 'action_mailer_acceptance_deliveries.cache')
8 |
9 | def self.included(base)
10 | base.class_eval do
11 | def self.deliveries
12 | return [] unless File.exist?(DELIVERIES_PATH)
13 | File.open(DELIVERIES_PATH,'r') do |f|
14 | Marshal.load(f)
15 | end
16 | end
17 |
18 | def self.clear_deliveries
19 | FileUtils.rm_f DELIVERIES_PATH
20 | end
21 | end
22 | end
23 |
24 | def perform_delivery_persistent(mail)
25 | deliveries = self.class.deliveries << mail
26 | File.open(DELIVERIES_PATH,'w') do |f|
27 | f << Marshal.dump(deliveries)
28 | end
29 | end
30 | end
31 | end
32 |
33 | ActionMailer::Base.send :include, Culerity::PersistentDelivery if defined?(ActionMailer)
34 |
35 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## 0.2.10
2 | * another speed improvement by only clearing cookies instead of closing browsers after each scenario (dewind)
3 |
4 | ## 0.2.9
5 | * fixed memory leaks within the java process by clearing proxies (dewind)
6 | * fixed syntax error when sending multiline lambdas (endor, langalex)
7 |
8 | ## 0.2.8
9 |
10 | * removed separate development and continuous integration environments and replaced them with a single one (thilo)
11 | * more webrat like step definitions (lupine)
12 | * improve on stability issues (mattmatt)
13 |
14 | ## 0.2.7
15 |
16 | * fixed RemoteBrowser#confirm called celerity remove_listener with invalid arguments
17 | * extended communication protocol to be able to send procs as arguments and blocks
18 | * default mail delivery method is now 'persistent', ActionMailer::Base.deliveries works again in features
19 |
20 |
21 | ## 0.2.5
22 |
23 | * added javascript helper to make 'I wait for the AJAX call to finish' work reliably (langalex)
24 |
25 | ## Before that
26 |
27 | Lots of important contributions from a bunch of people. check the commit logs.
28 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008 Alexander Lang
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'spec/rake/spectask'
3 | require 'rake/rdoctask'
4 | require 'cucumber/rake/task'
5 |
6 | begin
7 | require 'rubygems' unless ENV['NO_RUBYGEMS']
8 | require 'jeweler'
9 | Jeweler::Tasks.new do |s|
10 | s.name = "culerity"
11 | s.summary = %Q{Culerity integrates Cucumber and Celerity in order to test your application's full stack.}
12 | s.email = "alex@upstream-berlin.com"
13 | s.homepage = "http://github.com/langalex/culerity"
14 | s.description = "Culerity integrates Cucumber and Celerity in order to test your application's full stack."
15 | s.authors = ["Alexander Lang"]
16 |
17 | s.add_development_dependency 'cucumber'
18 | s.add_development_dependency 'rspec'
19 | end
20 | rescue LoadError
21 | puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
22 | end
23 |
24 | desc "Run all unit specs"
25 | Spec::Rake::SpecTask.new(:spec) do |t|
26 | t.spec_files = FileList['spec/*_spec.rb']
27 | end
28 |
29 | desc "Run all features"
30 | Cucumber::Rake::Task.new(:features) do |t|
31 | t.fork = true
32 | t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
33 | end
34 |
35 | Rake::RDocTask.new do |rdoc|
36 | rdoc.rdoc_dir = 'rdoc'
37 | rdoc.title = 'Culerity'
38 | rdoc.options << '--line-numbers' << '--inline-source'
39 | rdoc.rdoc_files.include('README*')
40 | rdoc.rdoc_files.include('lib/**/*.rb')
41 | end
42 |
43 | task :default => :spec
44 |
--------------------------------------------------------------------------------
/features/running_cucumber_without_explicitly_running_external_services.feature:
--------------------------------------------------------------------------------
1 | Feature: Running cucumber without explicitly running external services
2 | In order to reduce learning cost of using culerity
3 | As a rails developer
4 | I want the headless browser and rails processes to launch and shutdown automatically
5 |
6 | Background:
7 | Given a Rails app
8 | And I run executable "script/generate" with arguments "cucumber --rspec --webrat"
9 | And I delete file "features/step_definitions/web_steps.rb"
10 | And I delete file "features/support/env.rb"
11 | And culerity is installed as a plugin
12 | And I invoke task "rake db:migrate"
13 | When I run executable "script/generate" with arguments "culerity"
14 | And I setup load path to local code
15 | And I setup the culerity javascript helpers
16 | And I add the JRUBY_INVOCATION check to "features/support/env.rb"
17 | And I add an rvm_verbose_flag=0-wielding .rvmrc to the home folder
18 |
19 | Scenario: Successfully run scenarios without requiring celerity or rails processes running
20 | When I add a feature file to test Rails index.html default file
21 | And I run executable "cucumber" with arguments "features/"
22 | Then file "tmp/culerity_rails_server.pid" is not created
23 | And I should see "1 scenario"
24 | And I should see "5 steps (5 passed)"
25 | And I should see "WARNING: Speed up execution by running 'rake culerity:rails:start'"
26 |
--------------------------------------------------------------------------------
/rails_generators/culerity/templates/lib/tasks/culerity.rake:
--------------------------------------------------------------------------------
1 | namespace 'culerity' do
2 | namespace 'rails' do
3 | desc "Starts a rails server for cucumber/culerity tests"
4 | task :start do
5 | port = ENV['PORT'] || 3001
6 | environment = ENV["RAILS_ENV"] || 'culerity'
7 | pid_file = RAILS_ROOT + "/tmp/culerity_rails_server.pid"
8 | if File.exists?(pid_file)
9 | puts "culerity rails server already running; if not, delete tmp/culerity_rails_server.pid and try again"
10 | exit 1
11 | end
12 | rails_server = IO.popen("script/server -e #{environment} -p #{port}", 'r+')
13 | File.open(pid_file, "w") { |file| file << rails_server.pid }
14 | end
15 |
16 | desc "Stops the running rails server for cucumber/culerity tests"
17 | task :stop do
18 | pid_file = RAILS_ROOT + "/tmp/culerity_rails_server.pid"
19 | if File.exists?(pid_file)
20 | pid = File.read(pid_file).to_i
21 | Process.kill(6, pid)
22 | File.delete(pid_file)
23 | else
24 | puts "No culerity rails server running. Doing nothing."
25 | end
26 | end
27 |
28 | desc "Restarts the rails server for cucumber/culerity tests"
29 | task :restart => [:stop, :start]
30 | end
31 |
32 | desc "Install required gems into jruby"
33 | task :install do
34 | jgem_cmd = `which jruby`.strip
35 | raise "ERROR: You need to install jruby to use culerity and celerity." if jgem_cmd.blank?
36 | sh "#{jgem_cmd} -S gem install celerity"
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/culerity.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/culerity/remote_object_proxy'
2 | require File.dirname(__FILE__) + '/culerity/remote_browser_proxy'
3 |
4 | Symbol.class_eval do
5 | def to_proc
6 | Proc.new{|object| object.send(self)}
7 | end
8 | end unless :symbol.respond_to?(:to_proc)
9 |
10 | module Culerity
11 |
12 | module ServerCommands
13 | def exit_server
14 | self << '["_exit_"]'
15 | Process.kill(6, self.pid.to_i)
16 | end
17 |
18 | def close_browsers
19 | self.puts '["_close_browsers_"]'
20 | end
21 |
22 | def clear_proxies
23 | self.puts '["_clear_proxies_"]'
24 | end
25 | end
26 |
27 | def self.culerity_root
28 | File.expand_path('../../', __FILE__)
29 | end
30 |
31 | def self.celerity_invocation
32 | %{#{culerity_root}/lib/start_celerity.rb}
33 | end
34 |
35 | def self.jruby_invocation
36 | @jruby_invocation ||= (ENV["JRUBY_INVOCATION"] || "jruby")
37 | end
38 |
39 | def self.jruby_invocation=(invocation)
40 | @jruby_invocation = invocation
41 | end
42 |
43 | def self.run_server
44 | IO.popen(%{RUBYOPT="" #{jruby_invocation} "#{celerity_invocation}"}, 'r+').extend(ServerCommands)
45 | end
46 |
47 | def self.run_rails(options = {})
48 | if defined?(Rails) && !File.exists?("tmp/culerity_rails_server.pid")
49 | puts "WARNING: Speed up execution by running 'rake culerity:rails:start'"
50 | port = options[:port] || 3001
51 | environment = options[:environment] || 'culerity'
52 | rails_server = fork do
53 | $stdin.reopen "/dev/null"
54 | $stdout.reopen "/dev/null"
55 | $stderr.reopen "/dev/null"
56 | Dir.chdir(Rails.root) do
57 | exec "script/server -e #{environment} -p #{port}"
58 | end
59 | end
60 | sleep 5
61 | rails_server
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/features/installing_culerity.feature:
--------------------------------------------------------------------------------
1 | Feature: Installing culerity
2 | In order to not have to use f@#$ing selenium and receive hate into our lives
3 | As a self-respective Rails/JavaScript developer
4 | I want to install culerity into my Rails app
5 |
6 | Background:
7 | Given a Rails app
8 | And I run executable "script/generate" with arguments "cucumber --rspec --webrat"
9 | And I delete file "features/step_definitions/web_steps.rb"
10 | And I delete file "features/support/env.rb"
11 | And culerity is installed as a plugin
12 | And I invoke task "rake db:migrate"
13 | When I run executable "script/generate" with arguments "culerity"
14 | And I setup load path to local code
15 | And I setup the culerity javascript helpers
16 | And I add the JRUBY_INVOCATION check to "features/support/env.rb"
17 | And I add an rvm_verbose_flag=0-wielding .rvmrc to the home folder
18 |
19 | Scenario: Install culerity and test the rails start + stop tasks
20 | When I invoke task "rake culerity:rails:start"
21 | Then file "tmp/culerity_rails_server.pid" is created
22 | And I invoke task "rake culerity:rails:stop"
23 | Then file "tmp/culerity_rails_server.pid" is not created
24 |
25 | Scenario: Install culerity into a Rails app and check it works
26 | Then file "features/step_definitions/culerity_steps.rb" is created
27 | Then file "config/environments/culerity.rb" is created
28 |
29 | When I run executable "cucumber" with arguments "features/"
30 | Then I should see "0 scenarios"
31 | And I should see "0 steps"
32 | Given I invoke task "rake culerity:rails:start"
33 | When I add a feature file to test Rails index.html default file
34 | And I run executable "cucumber" with arguments "features/"
35 | Then I should see "1 scenario"
36 | And I should see "5 steps (5 passed)"
37 | And I should not see "WARNING: Speed up executing by running 'rake culerity:rails:start'"
38 |
--------------------------------------------------------------------------------
/lib/culerity/remote_browser_proxy.rb:
--------------------------------------------------------------------------------
1 | module Culerity
2 | class RemoteBrowserProxy < RemoteObjectProxy
3 | def initialize(io, browser_options = {})
4 | @io = io
5 | #sets the remote receiver to celerity for the new_browser message.
6 | @remote_object_id = "celerity".inspect
7 | #celerity server will create a new browser which shall receive the remote calls from now on.
8 | @remote_object_id = new_browser(browser_options).inspect
9 | end
10 |
11 | #
12 | # Calls the block until it returns true or +time_to_wait+ is reached.
13 | # +time_to_wait+ is 30 seconds by default
14 | #
15 | # Returns true upon success
16 | # Raises RuntimeError when +time_to_wait+ is reached.
17 | #
18 | def wait_until time_to_wait=30, &block
19 | time_limit = Time.now + time_to_wait
20 | until block.call
21 | if Time.now > time_limit
22 | raise "wait_until timeout after #{time_to_wait} seconds"
23 | end
24 | sleep 0.1
25 | end
26 | true
27 | end
28 |
29 | #
30 | # Calls the block until it doesn't return true or +time_to_wait+ is reached.
31 | # +time_to_wait+ is 30 seconds by default
32 | #
33 | # Returns true upon success
34 | # Raises RuntimeError when +time_to_wait+ is reached.
35 | #
36 | def wait_while time_to_wait=30, &block
37 | time_limit = Time.now + time_to_wait
38 | while block.call
39 | if Time.now > time_limit
40 | raise "wait_while timeout after #{time_to_wait} seconds"
41 | end
42 | sleep 0.1
43 | end
44 | true
45 | end
46 |
47 |
48 | #
49 | # Specify whether to accept or reject all confirm js dialogs
50 | # for the code in the block that's run.
51 | #
52 | def confirm(bool, &block)
53 | blk = "lambda { #{bool} }"
54 |
55 | self.send_remote(:add_listener, :confirm) { blk }
56 | block.call
57 | self.send_remote(:remove_listener, :confirm, lambda {blk})
58 | end
59 |
60 | end
61 |
62 |
63 | end
64 |
--------------------------------------------------------------------------------
/lib/tasks/rspec.rake:
--------------------------------------------------------------------------------
1 | gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9
2 |
3 | # Don't load rspec if running "rake gems:*"
4 | unless ARGV.any? {|a| a =~ /^gems/}
5 |
6 | begin
7 | require 'spec/rake/spectask'
8 | rescue MissingSourceFile
9 | module Spec
10 | module Rake
11 | class SpecTask
12 | def initialize(name)
13 | task name do
14 | # if rspec-rails is a configured gem, this will output helpful material and exit ...
15 | require File.expand_path(File.dirname(__FILE__) + "/../../config/environment")
16 |
17 | # ... otherwise, do this:
18 | raise <<-MSG
19 |
20 | #{"*" * 80}
21 | * You are trying to run an rspec rake task defined in
22 | * #{__FILE__},
23 | * but rspec can not be found in vendor/gems, vendor/plugins or system gems.
24 | #{"*" * 80}
25 | MSG
26 | end
27 | end
28 | end
29 | end
30 | end
31 | end
32 |
33 | Rake.application.instance_variable_get('@tasks').delete('default')
34 |
35 | spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop
36 | task :noop do
37 | end
38 |
39 | task :default => [:spec,:features]
40 | task :stats => "spec:statsetup"
41 |
42 | desc "Run all specs in spec directory (excluding plugin specs)"
43 | Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
44 | t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
45 | t.spec_files = FileList['spec/**/*/*_spec.rb']
46 | end
47 |
48 | desc "Run all specs in the spec directory with html output (excluding plugins specs)"
49 | Spec::Rake::SpecTask.new(:spec_html => spec_prereq) do |t|
50 | t.spec_opts = ['--colour', '--format html', '--loadby mtime', '--reverse']
51 | t.spec_files = FileList['spec/**/*/*_spec.rb']
52 | end
53 |
54 | namespace :spec do
55 | desc "Run all specs in spec directory with RCov (excluding plugin specs)"
56 | Spec::Rake::SpecTask.new(:rcov) do |t|
57 | t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
58 | t.spec_files = FileList['spec/**/*/*_spec.rb']
59 | t.rcov = true
60 | t.rcov_opts = lambda do
61 | IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
62 | end
63 | end
64 | end
65 |
66 | end
--------------------------------------------------------------------------------
/features/step_definitions/rails_setup_steps.rb:
--------------------------------------------------------------------------------
1 | Given /^a Rails app$/ do
2 | FileUtils.chdir(@tmp_root) do
3 | `rails my_project`
4 | end
5 | @active_project_folder = File.expand_path(File.join(@tmp_root, "my_project"))
6 | end
7 |
8 | Given /^culerity is installed as a plugin$/ do
9 | generators_folder = 'vendor/generators'
10 | plugin_folder = 'vendor/plugins/culerity'
11 | in_project_folder do
12 | FileUtils.mkdir_p(generators_folder)
13 | FileUtils.mkdir_p(plugin_folder)
14 | end
15 | `cp -rf #{File.dirname(__FILE__) + "/../../rails_generators/*"} #{File.join(@active_project_folder, generators_folder)}`
16 | `cp -rf #{File.dirname(__FILE__) + "/../../lib"} #{File.join(@active_project_folder, plugin_folder)}`
17 | `cp -rf #{File.dirname(__FILE__) + "/../../rails"} #{File.join(@active_project_folder, plugin_folder)}`
18 | `cp -rf #{File.dirname(__FILE__) + "/../../init.rb"} #{File.join(@active_project_folder, plugin_folder)}`
19 | `cp -rf #{File.dirname(__FILE__) + "/../../bin"} #{File.join(@active_project_folder, plugin_folder)}`
20 | end
21 |
22 | When /^I add a feature file to test Rails index.html default file$/ do
23 | sample_feature = File.expand_path(File.dirname(__FILE__) + "/../fixtures/sample_feature")
24 | in_project_folder do
25 | `cp -rf #{sample_feature} features/sample.feature`
26 | end
27 | end
28 |
29 | When /^(?:I )?add an rvm_verbose_flag=0-wielding \.rvmrc to the home folder$/ do
30 | in_home_folder do
31 | File.open('.rvmrc', 'w+') do |f|
32 | f.puts "rvm_verbose_flag=0"
33 | end
34 | end
35 | end
36 |
37 | When /^I setup the culerity javascript helpers$/ do
38 | `cp #{File.dirname(__FILE__) + "/../fixtures/jquery"} #{File.join(@active_project_folder, 'public', 'javascripts', 'jquery.js')}`
39 | in_project_folder do
40 | _index = File.read('public/index.html')
41 | File.open('public/index.html', 'w') do |f|
42 | f << _index.sub('', '')
43 | end
44 | end
45 | end
46 |
47 | After do
48 | in_project_folder do
49 | Given 'I invoke task "rake culerity:rails:stop"'
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/remote_browser_proxy_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper'
2 |
3 | describe Culerity::RemoteBrowserProxy do
4 | before(:each) do
5 | @io = stub 'io', :gets => "[:return, \"browser0\"]", :<< => nil
6 | end
7 |
8 | it "should send the serialized method call to the output" do
9 | @io.should_receive(:<<).with("[[\"celerity\", \"new_browser\", {}]]\n").ordered
10 | @io.should_receive(:<<).with("[[\"browser0\", \"goto\", \"/homepage\"]]\n").ordered
11 | proxy = Culerity::RemoteBrowserProxy.new @io
12 | proxy.goto '/homepage'
13 | end
14 |
15 | it "should return the deserialized return value" do
16 | io = stub 'io', :gets => "[:return, :okay]\n", :<< => nil
17 | proxy = Culerity::RemoteBrowserProxy.new io
18 | proxy.goto.should == :okay
19 | end
20 |
21 | it "should send the browser options to the remote server" do
22 | io = stub 'io', :gets => "[:return, \"browser0\"]"
23 | io.should_receive(:<<).with('[["celerity", "new_browser", {:browser=>:firefox}]]' + "\n")
24 | proxy = Culerity::RemoteBrowserProxy.new io, {:browser => :firefox}
25 | end
26 |
27 | it "should timeout if wait_until takes too long" do
28 | proxy = Culerity::RemoteBrowserProxy.new @io
29 | lambda {
30 | proxy.wait_until(0.1) { false }
31 | }.should raise_error(RuntimeError)
32 | end
33 |
34 | it "should return successfully when wait_until returns true" do
35 | proxy = Culerity::RemoteBrowserProxy.new @io
36 | proxy.wait_until(0.1) { true }.should == true
37 | end
38 |
39 | it "should timeout if wait_while takes too long" do
40 | proxy = Culerity::RemoteBrowserProxy.new @io
41 | lambda {
42 | proxy.wait_while(0.1) { true }
43 | }.should raise_error(RuntimeError)
44 | end
45 |
46 | it "should return successfully when wait_while returns !true" do
47 | proxy = Culerity::RemoteBrowserProxy.new @io
48 | proxy.wait_while(0.1) { false }.should == true
49 | end
50 |
51 | it "should accept all javascript confirmation dialogs" do
52 | proxy = Culerity::RemoteBrowserProxy.new @io
53 |
54 | proxy.should_receive(:send_remote).with(:add_listener, :confirm).and_return(true)
55 | proxy.should_receive(:send_remote).with(:goto, "http://example.com").and_return(true)
56 | proxy.should_receive(:send_remote).with(:remove_listener, :confirm, an_instance_of(Proc)).and_return(true)
57 |
58 | proxy.confirm(true) do
59 | proxy.goto "http://example.com"
60 | end
61 | end
62 |
63 | end
64 |
--------------------------------------------------------------------------------
/lib/culerity/celerity_server.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'celerity'
3 |
4 |
5 | module Culerity
6 | class CelerityServer
7 |
8 | def initialize(_in, _out)
9 | @proxies = {}
10 | @browsers = []
11 |
12 | while(true)
13 | call, block = eval _in.gets.to_s.strip
14 | return if call == "_exit_"
15 | next(close_browsers) if call == "_close_browsers_"
16 | next(clear_proxies) if call == "_clear_proxies_"
17 |
18 | unless call.nil?
19 | begin
20 | result = target(call.first).send call[1], *call[2..-1], &block
21 | _out << "[:return, #{proxify result}]\n"
22 | rescue => e
23 | _out << "[:exception, \"#{e.class.name}\", #{e.message.inspect}, #{prepend_js_stack_trace(e).inspect}]\n"
24 | end
25 | end
26 |
27 | end
28 |
29 | end
30 |
31 | private
32 |
33 | def clear_proxies
34 | @proxies = {}
35 | end
36 |
37 | def configure_browser(options)
38 | @browser_options = options
39 | end
40 |
41 | def new_browser(options, number = nil)
42 | number ||= @browsers.size
43 | @browsers[number] = Celerity::Browser.new(options || @browser_options || {})
44 | "browser#{number}"
45 | end
46 |
47 | def close_browsers
48 | @browsers.each { |browser| browser.close }
49 | @browsers = []
50 | @proxies = {}
51 | end
52 |
53 | def browser(number)
54 | unless @browsers[number]
55 | new_browser(nil, number)
56 | end
57 | @browsers[number]
58 | end
59 |
60 | def target(object_id)
61 | if object_id =~ /browser(\d+)/
62 | browser($1.to_i)
63 | elsif object_id == 'celerity'
64 | self
65 | else
66 | @proxies[object_id]
67 | end
68 | end
69 |
70 | def proxify(result)
71 | if result.is_a?(Array)
72 | "[" + result.map {|x| proxify(x) }.join(", ") + "]"
73 | elsif [Symbol, String, TrueClass, FalseClass, Fixnum, Float, NilClass].include?(result.class)
74 | result.inspect
75 | else
76 | @proxies[result.object_id] = result
77 | "Culerity::RemoteObjectProxy.new(#{result.object_id}, @io)"
78 | end
79 | end
80 |
81 | def prepend_js_stack_trace(exception)
82 | def extract_js_strack_trace(e)
83 | if e.respond_to?(:getScriptStackTrace)
84 | e.getScriptStackTrace
85 | elsif e.respond_to?(:cause) && e.cause
86 | extract_js_strack_trace e.cause
87 | else
88 | ""
89 | end
90 | end
91 | extract_js_strack_trace(exception).split("\n") + exception.backtrace
92 | end
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/lib/culerity/remote_object_proxy.rb:
--------------------------------------------------------------------------------
1 | module Culerity
2 |
3 | class CulerityException < StandardError
4 | def initialize(message, backtrace)
5 | super message
6 | set_backtrace(backtrace)
7 | end
8 | end
9 |
10 | class RemoteObjectProxy
11 | def initialize(remote_object_id, io)
12 | @remote_object_id = remote_object_id
13 | @io = io
14 | end
15 |
16 | #
17 | # Commonly used to get the HTML id attribute
18 | # Use `object_id` to get the local objects' id.
19 | #
20 | def id
21 | send_remote(:id)
22 | end
23 |
24 | def inspect
25 | send_remote(:inspect)
26 | end
27 |
28 | def respond_to?(name)
29 | send_remote :respond_to?, name
30 | end
31 |
32 | def method_missing(name, *args, &block)
33 | send_remote(name, *args, &block)
34 | end
35 |
36 | #
37 | # Calls the passed method on the remote object with any arguments specified.
38 | # Behaves the same as Object#send.
39 | #
40 | # If you pass it a block then it will append the block as a "lambda { … }".
41 | # If your block returns a lambda string ("lambda { … }") then it will be passed
42 | # straight through, otherwise it will be wrapped in a lambda string before sending.
43 | #
44 | def send_remote(name, *args, &blk)
45 | input = [remote_object_id, %Q{"#{name}"}, *args.map{|a| arg_to_string(a)}]
46 | serialized_block = ", #{block_to_string(&blk)}" if block_given?
47 | @io << "[[#{input.join(", ")}]#{serialized_block}]\n"
48 | process_result @io.gets.to_s.strip
49 | end
50 |
51 | def exit
52 | @io << '["_exit_"]'
53 | end
54 |
55 | private
56 |
57 | def process_result(result)
58 | res = eval result
59 | if res.first == :return
60 | res[1]
61 | elsif res.first == :exception
62 | begin
63 | raise "local trace"
64 | rescue => ex
65 | raise CulerityException.new("#{res[1]}: #{res[2]}", res[3] + ex.backtrace)
66 | end
67 | end
68 | end
69 |
70 | #
71 | # Takes a block and either returns the result (if it returns "lambda { … }")
72 | # or builds the lambda string with the result of the block in it.
73 | #
74 | # Returns a string in the format "lambda { … }"
75 | #
76 | def block_to_string &block
77 | result = block.call.to_s.strip
78 | unless result.is_a?(String) && result[/^lambda\s*(\{|do).+(\}|end)/xm]
79 | result = "lambda { #{result} }"
80 | end
81 | result.gsub("\n", ";")
82 | end
83 |
84 | def arg_to_string(arg)
85 | if arg.is_a?(Proc)
86 | block_to_string(&arg)
87 | else
88 | arg.inspect
89 | end
90 | end
91 |
92 | def remote_object_id
93 | @remote_object_id
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/culerity.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.name = %q{culerity}
8 | s.version = "0.2.15"
9 |
10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 | s.authors = ["Alexander Lang"]
12 | s.date = %q{2011-01-21}
13 | s.default_executable = %q{run_celerity_server.rb}
14 | s.description = %q{Culerity integrates Cucumber and Celerity in order to test your application's full stack.}
15 | s.email = %q{alex@upstream-berlin.com}
16 | s.executables = ["run_celerity_server.rb"]
17 | s.extra_rdoc_files = [
18 | "README.md"
19 | ]
20 | s.files = [
21 | "CHANGES.md",
22 | "MIT-LICENSE",
23 | "README.md",
24 | "Rakefile",
25 | "VERSION.yml",
26 | "bin/run_celerity_server.rb",
27 | "culerity.gemspec",
28 | "features/fixtures/jquery",
29 | "features/fixtures/sample_feature",
30 | "features/installing_culerity.feature",
31 | "features/running_cucumber_without_explicitly_running_external_services.feature",
32 | "features/step_definitions/common_steps.rb",
33 | "features/step_definitions/culerity_setup_steps.rb",
34 | "features/step_definitions/jruby_steps.rb",
35 | "features/step_definitions/rails_setup_steps.rb",
36 | "features/support/common.rb",
37 | "features/support/env.rb",
38 | "features/support/matchers.rb",
39 | "init.rb",
40 | "lib/culerity.rb",
41 | "lib/culerity/celerity_server.rb",
42 | "lib/culerity/persistent_delivery.rb",
43 | "lib/culerity/remote_browser_proxy.rb",
44 | "lib/culerity/remote_object_proxy.rb",
45 | "lib/start_celerity.rb",
46 | "lib/tasks/rspec.rake",
47 | "rails/init.rb",
48 | "rails_generators/culerity/culerity_generator.rb",
49 | "rails_generators/culerity/templates/config/environments/culerity.rb",
50 | "rails_generators/culerity/templates/config/environments/culerity_continuousintegration.rb",
51 | "rails_generators/culerity/templates/features/step_definitions/culerity_steps.rb",
52 | "rails_generators/culerity/templates/features/support/env.rb",
53 | "rails_generators/culerity/templates/lib/tasks/culerity.rake",
54 | "rails_generators/culerity/templates/public/javascripts/culerity.js",
55 | "script/console",
56 | "script/destroy",
57 | "script/generate",
58 | "spec/celerity_server_spec.rb",
59 | "spec/culerity_spec.rb",
60 | "spec/remote_browser_proxy_spec.rb",
61 | "spec/remote_object_proxy_spec.rb",
62 | "spec/spec_helper.rb"
63 | ]
64 | s.homepage = %q{http://github.com/langalex/culerity}
65 | s.require_paths = ["lib"]
66 | s.rubygems_version = %q{1.3.7}
67 | s.summary = %q{Culerity integrates Cucumber and Celerity in order to test your application's full stack.}
68 | s.test_files = [
69 | "spec/celerity_server_spec.rb",
70 | "spec/culerity_spec.rb",
71 | "spec/remote_browser_proxy_spec.rb",
72 | "spec/remote_object_proxy_spec.rb",
73 | "spec/spec_helper.rb"
74 | ]
75 |
76 | if s.respond_to? :specification_version then
77 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
78 | s.specification_version = 3
79 |
80 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
81 | s.add_development_dependency(%q, [">= 0"])
82 | s.add_development_dependency(%q, [">= 0"])
83 | else
84 | s.add_dependency(%q, [">= 0"])
85 | s.add_dependency(%q, [">= 0"])
86 | end
87 | else
88 | s.add_dependency(%q, [">= 0"])
89 | s.add_dependency(%q, [">= 0"])
90 | end
91 | end
92 |
93 |
--------------------------------------------------------------------------------
/spec/culerity_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2 |
3 | describe Culerity do
4 | describe 'run_rails' do
5 | def stub_rails_root!
6 | unless defined?(::Rails)
7 | Kernel.const_set "Rails", stub()
8 | end
9 | Rails.stub!(:root).and_return(Dir.pwd)
10 | end
11 |
12 | before(:each) do
13 | Kernel.stub!(:sleep)
14 | IO.stub!(:popen)
15 | Culerity.stub!(:fork).and_yield.and_return(3200)
16 | Culerity.stub!(:exec)
17 | Culerity.stub!(:sleep)
18 | [$stdin, $stdout, $stderr].each{|io| io.stub(:reopen)}
19 | end
20 |
21 | it "should not run rails if we are not using rails" do
22 | Culerity.should_not_receive(:exec)
23 | Culerity.run_rails :port => 4000, :environment => 'culerity'
24 | end
25 |
26 | describe "when Rails is being used" do
27 | before(:each) do
28 | stub_rails_root!
29 | end
30 |
31 | it "should run rails with default values" do
32 | Culerity.should_receive(:exec).with("script/server -e culerity -p 3001")
33 | Culerity.run_rails
34 | end
35 |
36 | it "should run rails with the given values" do
37 | Culerity.should_receive(:exec).with("script/server -e culerity -p 4000")
38 | Culerity.run_rails :port => 4000, :environment => 'culerity'
39 | end
40 |
41 | it "should change into the rails root directory" do
42 | Dir.should_receive(:chdir).with(Dir.pwd)
43 | Culerity.run_rails :port => 4000, :environment => 'culerity'
44 | end
45 |
46 | it "should wait for the server to start up" do
47 | Culerity.should_receive(:sleep)
48 | Culerity.run_rails :port => 4000, :environment => 'culerity'
49 | end
50 |
51 | it "should reopen the i/o channels to /dev/null" do
52 | [$stdin, $stdout, $stderr].each{|io| io.should_receive(:reopen).with("/dev/null")}
53 | Culerity.run_rails :port => 4000, :environment => 'culerity'
54 | end
55 | end
56 | end
57 |
58 | describe "run_server" do
59 | before(:each) do
60 | IO.stub!(:popen)
61 | end
62 |
63 | after(:each) do
64 | Culerity.jruby_invocation = nil
65 | end
66 |
67 | it "knows where it is located" do
68 | Culerity.culerity_root.should == File.expand_path(File.dirname(__FILE__) + '/../')
69 | end
70 |
71 | it "has access to the Celerity invocation" do
72 | Culerity.stub!(:culerity_root).and_return('/path/to/culerity')
73 |
74 | Culerity.celerity_invocation.should == "/path/to/culerity/lib/start_celerity.rb"
75 | end
76 |
77 | describe "invoking JRuby" do
78 | it "knows how to invoke it" do
79 | Culerity.jruby_invocation.should == 'jruby'
80 | end
81 |
82 | it "allows for the invocation to be overridden directly" do
83 | Culerity.jruby_invocation = '/opt/local/bin/jruby'
84 |
85 | Culerity.jruby_invocation.should == '/opt/local/bin/jruby'
86 | end
87 |
88 | it "allows for the invocation to be overridden from an environment variable" do
89 | ENV['JRUBY_INVOCATION'] = 'rvm jruby ruby'
90 |
91 | Culerity.jruby_invocation.should == 'rvm jruby ruby'
92 | end
93 | end
94 |
95 | it "shells out and sparks up jruby with the correct invocation" do
96 | Culerity.stub!(:celerity_invocation).and_return('/path/to/start_celerity.rb')
97 |
98 | IO.should_receive(:popen).with('RUBYOPT="" jruby "/path/to/start_celerity.rb"', 'r+')
99 |
100 | Culerity.run_server
101 | end
102 |
103 | it "allows a more complex situation, e.g. using RVM + named gemset" do
104 | Culerity.stub!(:celerity_invocation).and_return('/path/to/start_celerity.rb')
105 |
106 | IO.should_receive(:popen).with('RUBYOPT="" rvm jruby@culerity ruby "/path/to/start_celerity.rb"', 'r+')
107 |
108 | Culerity.jruby_invocation = "rvm jruby@culerity ruby"
109 | Culerity.run_server
110 | end
111 | end
112 | end
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | Culerity integrates Cucumber and Celerity in order to test your application's full stack.
4 |
5 | Culerity lets you:
6 | * run Celerity from within Cucumber which allows you to test the full stack of your Rails (or other web) application from Database to in browser JavaScript
7 | * run your application in any Ruby (like MRI 1.8.6) while Celerity runs in JRuby so you can still use gems/plugins that would not work with JRuby
8 | * reuse existing Webrat-Style step definitions
9 |
10 | ## Getting Started
11 |
12 | The following guide is written for a Rails application (tested with 2.3.5) but Culerity should work with any other Web Framework that is supported by Cucumber.
13 |
14 | First download JRuby and unpack it to some location, for example $HOME/jruby. Make sure that the jruby executable is in your path. You can do this by either setting your PATH accordingly...
15 |
16 | export PATH=$HOME/jruby/bin:$PATH
17 |
18 | ... or by creating a symlink from your bin directory:
19 |
20 | ln -s $HOME/jruby/bin/jruby /usr/bin/jruby
21 |
22 | You will need the celerity gem installed into JRuby:
23 |
24 | jruby -S gem install celerity
25 |
26 | Now install the Culerity gem:
27 |
28 | gem install culerity --source http://gemcutter.org
29 |
30 | Assuming you have a Rails application set up already you can run the RSpec, Cucumber and Culerity generators:
31 |
32 | cd RAILS_ROOT
33 | script/generate rspec
34 | script/generate cucumber
35 | script/generate culerity
36 |
37 | This creates the features folder and a file culerity_steps.rb into your application. This file contains step definitions for basic interactions like clicking links or filling out forms. Now is a good time to get rid of any webrat specific files generated by cucumber (i.e. features/step_definitions/webrat_steps.rb).
38 |
39 | After you have written a first feature you can run it just like you would run a standard cucumber feature. The only difference is that you have to start a web server (e.g. mongrel) with the test environment enabled beforehand.
40 |
41 | rake culerity:rails:start
42 | cucumber features/my_feature.feature
43 |
44 | The Rails instance uses a special environment culerity.
45 |
46 | When you have finished running culerity/cucumber you can turn off the Rails instance:
47 |
48 | NOTE: The default port for this server is 3001. You can change this in features/step_definitions/common_celerity.rb
49 |
50 | rake culerity:rails:stop
51 |
52 | ### RVM
53 |
54 | If you are using RVM there's an [integration guide for culerity](http://rvm.beginrescueend.com/integration/culerity/) on their website.
55 |
56 | ## How does it work
57 |
58 | While Celerity is based on Java and requires JRuby to run, with Culerity you can still run your tests in your own Ruby Environment. When you run your features a separate JRuby process for Celerity is spawned and all Celerity Commands are redirected to this other process.
59 |
60 | ## Troubleshooting
61 |
62 | I get a broken pipe error:
63 |
64 | * make sure JRuby is installed and in your path: running _jruby -v_ should not produce an error
65 |
66 | I get _Connection Refused_ errors
67 |
68 | * make sure you have started a server in the test environment that runs on port 3001
69 |
70 | My application can't find the data I create in my steps
71 |
72 | * make sure you have disabled transactional fixtures in your env.rb
73 |
74 | My database is not cleared automatically between scenarios
75 |
76 | * Rails can't clean the database because you had to disable transactional fixtures - which only work if your test process is the same as your web server process. Hence you have to clean your database manually. A quick way would be:
77 |
78 | Before do
79 | [User, .... all your models].each do |model|
80 | model.delete_all
81 | end
82 | end
83 |
84 |
85 | ## Links to Celerity documentation
86 |
87 | * [How to select elements](http://celerity.rubyforge.org/yard/Celerity/Container.html)
88 | * [FAQ](http://wiki.github.com/jarib/celerity/faq)
89 | * [Tutorial](http://wiki.github.com/jarib/celerity/getting-started)
90 | * [API docs](http://celerity.rubyforge.org/yard/)
91 |
92 | ## Links
93 |
94 | * [Cucumber](http://github.com/aslakhellesoy/cucumber/wikis)
95 | * [Celerity](http://celerity.rubyforge.org)
96 | * [JRuby](http://jruby.org)
97 | * [RSpec](http://rspec.info)
98 | * [HtmlUnit](http://htmlunit.sourceforge.net/)
99 |
100 | ## Contact
101 |
102 | Written 2009 by Alexander Lang, contact alex[at]upstream-berlin.com or , released under the MIT license
103 |
--------------------------------------------------------------------------------
/spec/remote_object_proxy_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper'
2 |
3 | describe Culerity::RemoteObjectProxy do
4 | describe "block_to_string method" do
5 | it "should return block result when result is lambda string" do
6 | proxy = Culerity::RemoteObjectProxy.new nil, nil
7 | block = lambda { "lambda { true}" }
8 | proxy.send(:block_to_string, &block).should == "lambda { true}"
9 | end
10 |
11 | it "should replace newlines in lambda string with semicolons so that the server can parse it as one command" do
12 | proxy = Culerity::RemoteObjectProxy.new nil, nil
13 | block = lambda { "lambda { \ntrue\n}" }
14 | proxy.send(:block_to_string, &block).should == "lambda { ;true;}"
15 | end
16 |
17 | it "should accept do end in lambda string instead of {}" do
18 | code = <<-CODE
19 | lambda do |page, message|
20 | true
21 | end
22 | CODE
23 |
24 | proxy = Culerity::RemoteObjectProxy.new nil, nil
25 | block = lambda { code }
26 | proxy.send(:block_to_string, &block).should == "lambda do |page, message|; true; end"
27 | end
28 |
29 | it "should return lambda string when block result isn't a lambda string" do
30 | proxy = Culerity::RemoteObjectProxy.new nil, nil
31 | [true, false, "blah", 5].each do |var|
32 | block = lambda { var }
33 | proxy.send(:block_to_string, &block).should == "lambda { #{var} }"
34 | end
35 | end
36 | end
37 |
38 | it "should send the serialized method call to the output" do
39 | io = stub 'io', :gets => '[:return]'
40 | io.should_receive(:<<).with(%Q{[[345, "goto", "/homepage"]]\n})
41 | proxy = Culerity::RemoteObjectProxy.new 345, io
42 | proxy.goto '/homepage'
43 | end
44 |
45 | it "should send inspect as a serialized method call to the output" do
46 | io = stub 'io', :gets => '[:return, "inspect output"]'
47 | io.should_receive(:<<).with(%Q{[[345, "inspect"]]\n})
48 | proxy = Culerity::RemoteObjectProxy.new 345, io
49 | proxy.inspect.should == "inspect output"
50 | end
51 |
52 | it "should send respond_to? as a serialized method call to the output" do
53 | io = stub 'io', :gets => '[:return, true]'
54 | io.should_receive(:<<).with(%Q{[[345, "respond_to?", :xyz]]\n})
55 | proxy = Culerity::RemoteObjectProxy.new 345, io
56 | proxy.respond_to?(:xyz).should == true
57 | end
58 |
59 | it "should send the serialized method call with a proc argument to the output" do
60 | io = stub 'io', :gets => "[:return]"
61 | io.should_receive(:<<).with(%Q{[[345, "method", true, lambda { true }]]\n})
62 | proxy = Culerity::RemoteObjectProxy.new 345, io
63 |
64 | proxy.send_remote(:method, true, lambda{true})
65 | end
66 |
67 | it "should send the serialized method call and a block to the output" do
68 | io = stub 'io', :gets => "[:return]"
69 | io.should_receive(:<<).with(%Q{[[345, "method"], lambda { true }]\n})
70 | proxy = Culerity::RemoteObjectProxy.new 345, io
71 |
72 | proxy.send_remote(:method) { "lambda { true }" }
73 | end
74 |
75 | it "should return the deserialized return value" do
76 | io = stub 'io', :gets => "[:return, :okay]\n", :<< => nil
77 | proxy = Culerity::RemoteObjectProxy.new 345, io
78 | proxy.goto.should == :okay
79 | end
80 |
81 | it "should raise the received exception" do
82 | io = stub 'io', :gets => %Q{[:exception, "RuntimeError", "test exception", []]}, :<< => nil
83 | proxy = Culerity::RemoteObjectProxy.new 345, io
84 | lambda {
85 | proxy.goto '/home'
86 | }.should raise_error(Culerity::CulerityException)
87 | end
88 |
89 | it "should include the full 'local' back trace in addition to the 'remote' backtrace" do
90 | io = stub 'io', :gets => %Q{[:exception, "RuntimeError", "test exception", ["Remote", "Backtrace"]]}, :<< => nil
91 | proxy = Culerity::RemoteObjectProxy.new 345, io
92 | begin
93 | proxy.goto '/home'
94 | rescue => ex
95 | puts ex.backtrace
96 | ex.backtrace[0].should == "Remote"
97 | ex.backtrace[1].should == "Backtrace"
98 | ex.backtrace.detect {|line| line =~ /lib\/culerity\/remote_object_proxy\.rb/}.should_not be_nil
99 | ex.backtrace.detect {|line| line =~ /spec\/remote_object_proxy_spec\.rb/}.should_not be_nil
100 | end
101 | end
102 |
103 | it "should send exit" do
104 | io = stub 'io', :gets => '[:return]'
105 | io.should_receive(:<<).with('["_exit_"]')
106 | proxy = Culerity::RemoteObjectProxy.new 345, io
107 | proxy.exit
108 | end
109 | end
--------------------------------------------------------------------------------
/rails_generators/culerity/templates/features/step_definitions/culerity_steps.rb:
--------------------------------------------------------------------------------
1 | require 'culerity'
2 |
3 | Before do
4 | $rails_server_pid ||= Culerity::run_rails(:environment => 'culerity', :port => 3001)
5 | $server ||= Culerity::run_server
6 | unless $browser
7 | $browser = Culerity::RemoteBrowserProxy.new $server, {:browser => :firefox3,
8 | :javascript_exceptions => true,
9 | :resynchronize => true,
10 | :status_code_exceptions => true
11 | }
12 | $browser.log_level = :warning
13 | end
14 | @host = 'http://localhost:3001'
15 | end
16 |
17 | After do
18 | $server.clear_proxies
19 | $browser.clear_cookies
20 | end
21 |
22 | at_exit do
23 | $browser.exit if $browser
24 | $server.exit_server if $server
25 | Process.kill(6, $rails_server_pid) if $rails_server_pid
26 | end
27 |
28 | Given /^(?:|I )am on (.+)$/ do |page_name|
29 | $browser.goto @host + path_to(page_name)
30 | end
31 |
32 | When /I follow "([^\"]*)"/ do |link|
33 | _link = [
34 | [:text, /^#{Regexp.escape(link)}$/ ],
35 | [:id, link],
36 | [:title, link],
37 | [:text, /#{Regexp.escape(link)}/ ],
38 | ].map{|args| $browser.link(*args)}.find{|__link| __link.exist?}
39 | raise "link \"#{link}\" not found" unless _link
40 | _link.click
41 | assert_successful_response
42 | end
43 |
44 | When /I press "([^\"]*)"/ do |button|
45 | $browser.button(:text, button).click
46 | assert_successful_response
47 | end
48 |
49 | When /I fill in "([^\"]*)" with "([^\"]*)"/ do |field, value|
50 | find_by_label_or_id(:text_field, field).set(value)
51 | end
52 |
53 | When /I fill in "([^\"]*)" for "([^\"]*)"/ do |value, field|
54 | find_by_label_or_id(:text_field, field).set(value)
55 | end
56 |
57 | When /I check "([^\"]*)"/ do |field|
58 | find_by_label_or_id(:check_box, field).set(true)
59 | end
60 |
61 | When /^I uncheck "([^\"]*)"$/ do |field|
62 | find_by_label_or_id(:check_box, field).set(false)
63 | end
64 |
65 | When /I select "([^\"]*)" from "([^\"]*)"/ do |value, field|
66 | find_by_label_or_id(:select_list, field).select value
67 | end
68 |
69 | When /I choose "([^\"]*)"/ do |field|
70 | find_by_label_or_id(:radio, field).set(true)
71 | end
72 |
73 | When /I go to (.+)/ do |path|
74 | $browser.goto @host + path_to(path)
75 | assert_successful_response
76 | end
77 |
78 | When /^I wait for the AJAX call to finish$/ do
79 | $browser.wait_while do
80 | begin
81 | count = $browser.execute_script("window.running_ajax_calls").to_i
82 | count.to_i > 0
83 | rescue => e
84 | if e.message.include?('HtmlunitCorejsJavascript::Undefined')
85 | raise "For 'I wait for the AJAX call to finish' to work please include culerity.js after including jQuery. If you don't use jQuery please rewrite culerity.js accordingly."
86 | else
87 | raise(e)
88 | end
89 | end
90 | end
91 | end
92 |
93 | Then /^(?:|I )should be on (.+)$/ do |page_name|
94 | current_path = URI.parse($browser.url)
95 | expected_path = URI.parse(path_to(page_name))
96 |
97 | # If our expected path doesn't specify a query-string, ignore any query string
98 | # in the current path
99 | current_path, expected_path = if expected_path.query.nil?
100 | [ current_path.path, expected_path.path ]
101 | else
102 | [ current_path.select(:path, :query).compact.join('?'), path_to(page_name) ]
103 | end
104 |
105 | if defined?(Spec::Rails::Matchers)
106 | current_path.should == path_to(page_name)
107 | else
108 | assert_equal path_to(page_name), current_path
109 | end
110 | end
111 |
112 | Then /^the "([^\"]*)" field should contain "([^\"]*)"$/ do |field, value|
113 | f = find_by_label_or_id(:text_field, field)
114 | if defined?(Spec::Rails::Matchers)
115 | f.text.should =~ /#{Regexp::escape(value)}/
116 | else
117 | assert_match(/#{Regexp::escape(value)}/, f.text)
118 | end
119 | end
120 |
121 | Then /^the "([^\"]*)" field should not contain "([^\"]*)"$/ do |field, value|
122 | f = find_by_label_or_id(:text_field, field)
123 | if defined?(Spec::Rails::Matchers)
124 | f.text.should_not =~ /#{Regexp::escape(value)}/
125 | else
126 | assert_no_match(/#{Regexp::escape(value)}/, f.text)
127 | end
128 | end
129 |
130 | Then /^the "([^\"]*)" checkbox should be checked$/ do |label|
131 | f = find_by_label_or_id(:check_box, label)
132 | if defined?(Spec::Rails::Matchers)
133 | f.should be_checked
134 | else
135 | assert f.checked?
136 | end
137 | end
138 |
139 | Then /^the "([^\"]*)" checkbox should not be checked$/ do |label|
140 | f = find_by_label_or_id(:check_box, label)
141 | if defined?(Spec::Rails::Matchers)
142 | f.should_not be_checked
143 | else
144 | assert !f.checked?
145 | end
146 | end
147 |
148 | Then /I should see "([^\"]*)"/ do |text|
149 | $browser.text.include?(text).should be_true
150 | end
151 |
152 | Then /I should not see "([^\"]*)"/ do |text|
153 | $browser.text.include?(text).should_not be_true
154 | end
155 |
156 | def find_by_label_or_id(element, attribute)
157 | matchers = [[attribute, :id], [attribute, :name]]
158 | matchers << [$browser.label(:text, attribute).for, :id] if $browser.label(:text, attribute).exist?
159 | field = matchers.map{|_field, matcher| $browser.send(element, matcher, _field)}.find(&:exist?) || raise("#{element} not found using \"#{attribute}\"")
160 | end
161 |
162 | def assert_successful_response
163 | status = $browser.page.web_response.status_code
164 | if(status == 302 || status == 301)
165 | location = $browser.page.web_response.get_response_header_value('Location')
166 | puts "Being redirected to #{location}"
167 | $browser.goto location
168 | assert_successful_response
169 | elsif status != 200
170 | filename = "culerity-#{Time.now.to_i}.html"
171 | File.open(RAILS_ROOT + "/tmp/#{filename}", "w") do |f|
172 | f.write $browser.html
173 | end
174 | `open tmp/#{filename}`
175 | raise "Browser returned Response Code #{$browser.page.web_response.status_code}"
176 | end
177 | end
178 |
--------------------------------------------------------------------------------
/features/step_definitions/common_steps.rb:
--------------------------------------------------------------------------------
1 | Given /^this project is active project folder/ do
2 | @active_project_folder = File.expand_path(File.dirname(__FILE__) + "/../..")
3 | end
4 |
5 | Given /^env variable \$([\w_]+) set to "(.*)"/ do |env_var, value|
6 | ENV[env_var] = value
7 | end
8 |
9 | Given /I delete (folder|file) "([^\"]*)"/ do |type, folder|
10 | in_project_folder { FileUtils.rm_rf folder }
11 | end
12 |
13 | When /^I invoke "(.*)" generator with arguments "(.*)"$/ do |generator, arguments|
14 | @stdout = StringIO.new
15 | in_project_folder do
16 | if Object.const_defined?("APP_ROOT")
17 | APP_ROOT.replace(FileUtils.pwd)
18 | else
19 | APP_ROOT = FileUtils.pwd
20 | end
21 | run_generator(generator, arguments.split(' '), SOURCES, :stdout => @stdout)
22 | end
23 | File.open(File.join(@tmp_root, "generator.out"), "w") do |f|
24 | @stdout.rewind
25 | f << @stdout.read
26 | end
27 | end
28 |
29 | When /^I run executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
30 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
31 | in_project_folder do
32 | system "#{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
33 | end
34 | end
35 |
36 | When /^I run project executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
37 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
38 | in_project_folder do
39 | system "ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
40 | end
41 | end
42 |
43 | When /^I run local executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
44 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
45 | executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable))
46 | in_project_folder do
47 | system "ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
48 | end
49 | end
50 |
51 | When /^I invoke task "rake (.*)"/ do |task|
52 | @stdout = File.expand_path(File.join(@tmp_root, "rake.out"))
53 | @stderr = File.expand_path(File.join(@tmp_root, "rake.err"))
54 | in_project_folder do
55 | system "rake #{task} --trace > #{@stdout} 2> #{@stderr}"
56 | end
57 | File.read(@stderr).should_not =~ /rake aborted!/
58 | end
59 |
60 | Then /^folder "(.*)" (is|is not) created/ do |folder, is|
61 | in_project_folder do
62 | File.exists?(folder).should(is == 'is' ? be_true : be_false)
63 | end
64 | end
65 |
66 | Then /^file "(.*)" (is|is not) created/ do |file, is|
67 | in_project_folder do
68 | File.exists?(file).should(is == 'is' ? be_true : be_false)
69 | end
70 | end
71 |
72 | Then /^file with name matching "(.*)" is created/ do |pattern|
73 | in_project_folder do
74 | Dir[pattern].should_not be_empty
75 | end
76 | end
77 |
78 | Then /^file "(.*)" contents (does|does not) match \/(.*)\// do |file, does, regex|
79 | in_project_folder do
80 | actual_output = File.read(file)
81 | (does == 'does') ?
82 | actual_output.should(match(/#{regex}/)) :
83 | actual_output.should_not(match(/#{regex}/))
84 | end
85 | end
86 |
87 | Then /gem file "(.*)" and generated file "(.*)" should be the same/ do |gem_file, project_file|
88 | File.exists?(gem_file).should be_true
89 | File.exists?(project_file).should be_true
90 | gem_file_contents = File.read(File.dirname(__FILE__) + "/../../#{gem_file}")
91 | project_file_contents = File.read(File.join(@active_project_folder, project_file))
92 | project_file_contents.should == gem_file_contents
93 | end
94 |
95 | Then /^(does|does not) invoke generator "(.*)"$/ do |does_invoke, generator|
96 | actual_output = File.read(@stdout)
97 | does_invoke == "does" ?
98 | actual_output.should(match(/dependency\s+#{generator}/)) :
99 | actual_output.should_not(match(/dependency\s+#{generator}/))
100 | end
101 |
102 | Then /help options "(.*)" and "(.*)" are displayed/ do |opt1, opt2|
103 | actual_output = File.read(@stdout)
104 | actual_output.should match(/#{opt1}/)
105 | actual_output.should match(/#{opt2}/)
106 | end
107 |
108 | Then /^I should see "([^\"]*)"$/ do |text|
109 | actual_output = File.read(@stdout)
110 | actual_output.should contain(text)
111 | end
112 |
113 | Then /^I should not see "([^\"]*)"$/ do |text|
114 | actual_output = File.read(@stdout)
115 | actual_output.should_not contain(text)
116 | end
117 |
118 | Then /^I should see$/ do |text|
119 | actual_output = File.read(@stdout)
120 | actual_output.should contain(text)
121 | end
122 |
123 | Then /^I should not see$/ do |text|
124 | actual_output = File.read(@stdout)
125 | actual_output.should_not contain(text)
126 | end
127 |
128 | Then /^I should see exactly$/ do |text|
129 | actual_output = File.read(@stdout)
130 | actual_output.should == text
131 | end
132 |
133 | Then /^I should see all (\d+) tests pass/ do |expected_test_count|
134 | expected = %r{^#{expected_test_count} tests, \d+ assertions, 0 failures, 0 errors}
135 | actual_output = File.read(@stdout)
136 | actual_output.should match(expected)
137 | end
138 |
139 | Then /^I should see all (\d+) examples pass/ do |expected_test_count|
140 | expected = %r{^#{expected_test_count} examples?, 0 failures}
141 | actual_output = File.read(@stdout)
142 | actual_output.should match(expected)
143 | end
144 |
145 | Then /^yaml file "(.*)" contains (\{.*\})/ do |file, yaml|
146 | in_project_folder do
147 | yaml = eval yaml
148 | YAML.load(File.read(file)).should == yaml
149 | end
150 | end
151 |
152 | Then /^Rakefile can display tasks successfully/ do
153 | @stdout = File.expand_path(File.join(@tmp_root, "rakefile.out"))
154 | in_project_folder do
155 | system "rake -T > #{@stdout} 2> #{@stdout}"
156 | end
157 | actual_output = File.read(@stdout)
158 | actual_output.should match(/^rake\s+\w+\s+#\s.*/)
159 | end
160 |
161 | Then /^task "rake (.*)" is executed successfully/ do |task|
162 | @stdout.should_not be_nil
163 | actual_output = File.read(@stdout)
164 | actual_output.should_not match(/^Don't know how to build task '#{task}'/)
165 | actual_output.should_not match(/Error/i)
166 | end
167 |
168 | Then /^gem spec key "(.*)" contains \/(.*)\// do |key, regex|
169 | in_project_folder do
170 | gem_file = Dir["pkg/*.gem"].first
171 | gem_spec = Gem::Specification.from_yaml(`gem spec #{gem_file}`)
172 | spec_value = gem_spec.send(key.to_sym)
173 | spec_value.to_s.should match(/#{regex}/)
174 | end
175 | end
176 |
--------------------------------------------------------------------------------
/spec/celerity_server_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper'
2 |
3 | describe Culerity::CelerityServer do
4 | before(:each) do
5 | @browser = stub 'browser'
6 | Celerity::Browser.stub!(:new).and_return(@browser)
7 | end
8 |
9 | it "should pass the method call to the celerity browser" do
10 | @browser.should_receive(:goto).with('/homepage')
11 | _in = stub 'in'
12 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[\"_exit_\"]\n")
13 | _out = stub 'out', :<< => nil
14 | Culerity::CelerityServer.new(_in, _out)
15 | end
16 |
17 | it "should pass procs" do
18 | @browser.should_receive(:remove_listener).with(:confirm, instance_of(Proc))
19 | _in = stub 'in'
20 | _in.stub!(:gets).and_return("[[\"browser0\", \"remove_listener\", :confirm, lambda{true}]]\n", "[\"_exit_\"]\n")
21 | _out = stub 'out', :<< => nil
22 | Culerity::CelerityServer.new(_in, _out)
23 | end
24 |
25 | it "should pass blocks" do
26 | def @browser.add_listener(type, &block)
27 | @type = type
28 | @block = block
29 | end
30 | _in = stub 'in'
31 | _in.stub!(:gets).and_return("[[\"browser0\", \"add_listener\", :confirm], lambda{true}]\n", "[\"_exit_\"]\n")
32 | _out = stub 'out', :<< => nil
33 | Culerity::CelerityServer.new(_in, _out)
34 | @browser.instance_variable_get(:@type).should == :confirm
35 | @browser.instance_variable_get(:@block).call.should == true
36 | end
37 |
38 | it "should return the browser id when a new browser is requested" do
39 | _in = stub 'in'
40 | _in.stub!(:gets).and_return("[[\"celerity\", \"new_browser\", {}]]\n", "[\"_exit_\"]\n")
41 | _out = stub 'out'
42 | _out.should_receive(:<<).with("[:return, \"browser0\"]\n")
43 | Culerity::CelerityServer.new(_in, _out)
44 | end
45 |
46 | it "should create a new browser with the provided options" do
47 | _in = stub 'in'
48 | _in.stub!(:gets).and_return("[[\"celerity\", \"new_browser\", {:browser => :firefox}]]\n", "[\"_exit_\"]\n")
49 | Celerity::Browser.should_receive(:new).with(:browser => :firefox)
50 | Culerity::CelerityServer.new(_in, stub.as_null_object)
51 | end
52 |
53 | it "should create multiple browsers and return the appropriate id for each" do
54 | _in = stub 'in'
55 | _in.stub!(:gets).and_return("[[\"celerity\", \"new_browser\", {}]]\n", "[[\"celerity\", \"new_browser\", {}]]\n", "[\"_exit_\"]\n")
56 | Celerity::Browser.should_receive(:new).twice
57 | _out = stub 'out'
58 | _out.should_receive(:<<).with("[:return, \"browser0\"]\n").ordered
59 | _out.should_receive(:<<).with("[:return, \"browser1\"]\n").ordered
60 | Culerity::CelerityServer.new(_in, _out)
61 |
62 | end
63 |
64 |
65 | it "should send back the return value of the call" do
66 | @browser.stub!(:goto).and_return(true)
67 | _in = stub 'in'
68 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[\"_exit_\"]\n")
69 | _out = stub 'out'
70 | _out.should_receive(:<<).with("[:return, true]\n")
71 | Culerity::CelerityServer.new(_in, _out)
72 | end
73 |
74 | it "should ignore empty inputs" do
75 | _in = stub 'in'
76 | _in.stub!(:gets).and_return("\n", "[\"_exit_\"]\n")
77 | _out = stub 'out'
78 | _out.should_not_receive(:<<)
79 | Culerity::CelerityServer.new(_in, _out)
80 | end
81 |
82 | it "should send back a symbol" do
83 | @browser.stub!(:goto).and_return(:ok)
84 | _in = stub 'in'
85 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\"]]\n", "[\"_exit_\"]\n")
86 | _out = stub 'out'
87 | _out.should_receive(:<<).with("[:return, :ok]\n")
88 | Culerity::CelerityServer.new(_in, _out)
89 | end
90 |
91 | it "should send back a proxy if the return value is not a string, number, nil, symbol or boolean" do
92 | @browser.stub!(:goto).and_return(stub('123', :object_id => 456))
93 | _in = stub 'in'
94 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[\"_exit_\"]\n")
95 | _out = stub 'out'
96 | _out.should_receive(:<<).with("[:return, Culerity::RemoteObjectProxy.new(456, @io)]\n")
97 | Culerity::CelerityServer.new(_in, _out)
98 | end
99 |
100 | it "should send back arrays" do
101 | @browser.stub!(:goto).and_return([true, false, "test", 1, 12.3, nil, stub('123', :object_id => 456), ["test2", 32.1, 5000, nil, false, true, stub('789', :object_id => 101)]])
102 | _in = stub 'in'
103 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[\"_exit_\"]\n")
104 | _out = stub 'out'
105 | _out.should_receive(:<<).with("[:return, [true, false, \"test\", 1, 12.3, nil, Culerity::RemoteObjectProxy.new(456, @io), [\"test2\", 32.1, 5000, nil, false, true, Culerity::RemoteObjectProxy.new(101, @io)]]]\n")
106 | Culerity::CelerityServer.new(_in, _out)
107 | end
108 |
109 | it "should pass the method call to a proxy" do
110 | proxy = stub('123', :object_id => 456)
111 | @browser.stub!(:goto).and_return(proxy)
112 | _in = stub 'in'
113 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[[456, \"goto_2\", \"1\"]]", "[\"_exit_\"]\n")
114 | _out = stub 'out', :<< => nil
115 | proxy.should_receive(:goto_2).with('1')
116 | Culerity::CelerityServer.new(_in, _out)
117 | end
118 |
119 | it "should pass multiple method calls" do
120 | @browser.should_receive(:goto).with('/homepage')
121 | @browser.should_receive(:goto).with('/page2')
122 | _in = stub 'in'
123 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[[\"browser0\", \"goto\", \"/page2\"]]\n", "[\"_exit_\"]\n")
124 | _out = stub 'out', :<< => nil
125 | Culerity::CelerityServer.new(_in, _out)
126 | end
127 |
128 | it "should return an exception" do
129 | @browser.stub!(:goto).and_raise(RuntimeError.new('test exception with "quotes"'))
130 | _in = stub 'in'
131 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[\"_exit_\"]\n")
132 | _out = stub 'out'
133 | _out.should_receive(:<<).with(/^\[:exception, \"RuntimeError\", \"test exception with \\\"quotes\\\"\", \[.*\]\]\n$/)
134 | Culerity::CelerityServer.new(_in, _out)
135 | end
136 |
137 | it "should extract a js stack trace if available and prepend it on the regular backtrace" do
138 | exception = RuntimeError.new("the exception")
139 | exception.stub!(:cause => stub('ex2', :cause => stub('ex3', :getScriptStackTrace => "The\nStack\nTrace")))
140 |
141 | @browser.stub!(:goto).and_raise(exception)
142 | _in = stub 'in'
143 | _in.stub!(:gets).and_return("[[\"browser0\", \"goto\", \"/homepage\"]]\n", "[\"_exit_\"]\n")
144 | _out = stub 'out'
145 | _out.should_receive(:<<).with(/^\[:exception, \"RuntimeError\", \"the exception\", \[\"The\", \"Stack\", \"Trace\", \".*\"\]\]\n$/)
146 | Culerity::CelerityServer.new(_in, _out)
147 | end
148 | end
149 |
--------------------------------------------------------------------------------
/features/fixtures/jquery:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery JavaScript Library v1.3.2
3 | * http://jquery.com/
4 | *
5 | * Copyright (c) 2009 John Resig
6 | * Dual licensed under the MIT and GPL licenses.
7 | * http://docs.jquery.com/License
8 | *
9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
10 | * Revision: 6246
11 | */
12 | (function(){
13 |
14 | var
15 | // Will speed up references to window, and allows munging its name.
16 | window = this,
17 | // Will speed up references to undefined, and allows munging its name.
18 | undefined,
19 | // Map over jQuery in case of overwrite
20 | _jQuery = window.jQuery,
21 | // Map over the $ in case of overwrite
22 | _$ = window.$,
23 |
24 | jQuery = window.jQuery = window.$ = function( selector, context ) {
25 | // The jQuery object is actually just the init constructor 'enhanced'
26 | return new jQuery.fn.init( selector, context );
27 | },
28 |
29 | // A simple way to check for HTML strings or ID strings
30 | // (both of which we optimize for)
31 | quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
32 | // Is it a simple selector
33 | isSimple = /^.[^:#\[\.,]*$/;
34 |
35 | jQuery.fn = jQuery.prototype = {
36 | init: function( selector, context ) {
37 | // Make sure that a selection was provided
38 | selector = selector || document;
39 |
40 | // Handle $(DOMElement)
41 | if ( selector.nodeType ) {
42 | this[0] = selector;
43 | this.length = 1;
44 | this.context = selector;
45 | return this;
46 | }
47 | // Handle HTML strings
48 | if ( typeof selector === "string" ) {
49 | // Are we dealing with HTML string or an ID?
50 | var match = quickExpr.exec( selector );
51 |
52 | // Verify a match, and that no context was specified for #id
53 | if ( match && (match[1] || !context) ) {
54 |
55 | // HANDLE: $(html) -> $(array)
56 | if ( match[1] )
57 | selector = jQuery.clean( [ match[1] ], context );
58 |
59 | // HANDLE: $("#id")
60 | else {
61 | var elem = document.getElementById( match[3] );
62 |
63 | // Handle the case where IE and Opera return items
64 | // by name instead of ID
65 | if ( elem && elem.id != match[3] )
66 | return jQuery().find( selector );
67 |
68 | // Otherwise, we inject the element directly into the jQuery object
69 | var ret = jQuery( elem || [] );
70 | ret.context = document;
71 | ret.selector = selector;
72 | return ret;
73 | }
74 |
75 | // HANDLE: $(expr, [context])
76 | // (which is just equivalent to: $(content).find(expr)
77 | } else
78 | return jQuery( context ).find( selector );
79 |
80 | // HANDLE: $(function)
81 | // Shortcut for document ready
82 | } else if ( jQuery.isFunction( selector ) )
83 | return jQuery( document ).ready( selector );
84 |
85 | // Make sure that old selector state is passed along
86 | if ( selector.selector && selector.context ) {
87 | this.selector = selector.selector;
88 | this.context = selector.context;
89 | }
90 |
91 | return this.setArray(jQuery.isArray( selector ) ?
92 | selector :
93 | jQuery.makeArray(selector));
94 | },
95 |
96 | // Start with an empty selector
97 | selector: "",
98 |
99 | // The current version of jQuery being used
100 | jquery: "1.3.2",
101 |
102 | // The number of elements contained in the matched element set
103 | size: function() {
104 | return this.length;
105 | },
106 |
107 | // Get the Nth element in the matched element set OR
108 | // Get the whole matched element set as a clean array
109 | get: function( num ) {
110 | return num === undefined ?
111 |
112 | // Return a 'clean' array
113 | Array.prototype.slice.call( this ) :
114 |
115 | // Return just the object
116 | this[ num ];
117 | },
118 |
119 | // Take an array of elements and push it onto the stack
120 | // (returning the new matched element set)
121 | pushStack: function( elems, name, selector ) {
122 | // Build a new jQuery matched element set
123 | var ret = jQuery( elems );
124 |
125 | // Add the old object onto the stack (as a reference)
126 | ret.prevObject = this;
127 |
128 | ret.context = this.context;
129 |
130 | if ( name === "find" )
131 | ret.selector = this.selector + (this.selector ? " " : "") + selector;
132 | else if ( name )
133 | ret.selector = this.selector + "." + name + "(" + selector + ")";
134 |
135 | // Return the newly-formed element set
136 | return ret;
137 | },
138 |
139 | // Force the current matched set of elements to become
140 | // the specified array of elements (destroying the stack in the process)
141 | // You should use pushStack() in order to do this, but maintain the stack
142 | setArray: function( elems ) {
143 | // Resetting the length to 0, then using the native Array push
144 | // is a super-fast way to populate an object with array-like properties
145 | this.length = 0;
146 | Array.prototype.push.apply( this, elems );
147 |
148 | return this;
149 | },
150 |
151 | // Execute a callback for every element in the matched set.
152 | // (You can seed the arguments with an array of args, but this is
153 | // only used internally.)
154 | each: function( callback, args ) {
155 | return jQuery.each( this, callback, args );
156 | },
157 |
158 | // Determine the position of an element within
159 | // the matched set of elements
160 | index: function( elem ) {
161 | // Locate the position of the desired element
162 | return jQuery.inArray(
163 | // If it receives a jQuery object, the first element is used
164 | elem && elem.jquery ? elem[0] : elem
165 | , this );
166 | },
167 |
168 | attr: function( name, value, type ) {
169 | var options = name;
170 |
171 | // Look for the case where we're accessing a style value
172 | if ( typeof name === "string" )
173 | if ( value === undefined )
174 | return this[0] && jQuery[ type || "attr" ]( this[0], name );
175 |
176 | else {
177 | options = {};
178 | options[ name ] = value;
179 | }
180 |
181 | // Check to see if we're setting style values
182 | return this.each(function(i){
183 | // Set all the styles
184 | for ( name in options )
185 | jQuery.attr(
186 | type ?
187 | this.style :
188 | this,
189 | name, jQuery.prop( this, options[ name ], type, i, name )
190 | );
191 | });
192 | },
193 |
194 | css: function( key, value ) {
195 | // ignore negative width and height values
196 | if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
197 | value = undefined;
198 | return this.attr( key, value, "curCSS" );
199 | },
200 |
201 | text: function( text ) {
202 | if ( typeof text !== "object" && text != null )
203 | return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
204 |
205 | var ret = "";
206 |
207 | jQuery.each( text || this, function(){
208 | jQuery.each( this.childNodes, function(){
209 | if ( this.nodeType != 8 )
210 | ret += this.nodeType != 1 ?
211 | this.nodeValue :
212 | jQuery.fn.text( [ this ] );
213 | });
214 | });
215 |
216 | return ret;
217 | },
218 |
219 | wrapAll: function( html ) {
220 | if ( this[0] ) {
221 | // The elements to wrap the target around
222 | var wrap = jQuery( html, this[0].ownerDocument ).clone();
223 |
224 | if ( this[0].parentNode )
225 | wrap.insertBefore( this[0] );
226 |
227 | wrap.map(function(){
228 | var elem = this;
229 |
230 | while ( elem.firstChild )
231 | elem = elem.firstChild;
232 |
233 | return elem;
234 | }).append(this);
235 | }
236 |
237 | return this;
238 | },
239 |
240 | wrapInner: function( html ) {
241 | return this.each(function(){
242 | jQuery( this ).contents().wrapAll( html );
243 | });
244 | },
245 |
246 | wrap: function( html ) {
247 | return this.each(function(){
248 | jQuery( this ).wrapAll( html );
249 | });
250 | },
251 |
252 | append: function() {
253 | return this.domManip(arguments, true, function(elem){
254 | if (this.nodeType == 1)
255 | this.appendChild( elem );
256 | });
257 | },
258 |
259 | prepend: function() {
260 | return this.domManip(arguments, true, function(elem){
261 | if (this.nodeType == 1)
262 | this.insertBefore( elem, this.firstChild );
263 | });
264 | },
265 |
266 | before: function() {
267 | return this.domManip(arguments, false, function(elem){
268 | this.parentNode.insertBefore( elem, this );
269 | });
270 | },
271 |
272 | after: function() {
273 | return this.domManip(arguments, false, function(elem){
274 | this.parentNode.insertBefore( elem, this.nextSibling );
275 | });
276 | },
277 |
278 | end: function() {
279 | return this.prevObject || jQuery( [] );
280 | },
281 |
282 | // For internal use only.
283 | // Behaves like an Array's method, not like a jQuery method.
284 | push: [].push,
285 | sort: [].sort,
286 | splice: [].splice,
287 |
288 | find: function( selector ) {
289 | if ( this.length === 1 ) {
290 | var ret = this.pushStack( [], "find", selector );
291 | ret.length = 0;
292 | jQuery.find( selector, this[0], ret );
293 | return ret;
294 | } else {
295 | return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
296 | return jQuery.find( selector, elem );
297 | })), "find", selector );
298 | }
299 | },
300 |
301 | clone: function( events ) {
302 | // Do the clone
303 | var ret = this.map(function(){
304 | if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
305 | // IE copies events bound via attachEvent when
306 | // using cloneNode. Calling detachEvent on the
307 | // clone will also remove the events from the orignal
308 | // In order to get around this, we use innerHTML.
309 | // Unfortunately, this means some modifications to
310 | // attributes in IE that are actually only stored
311 | // as properties will not be copied (such as the
312 | // the name attribute on an input).
313 | var html = this.outerHTML;
314 | if ( !html ) {
315 | var div = this.ownerDocument.createElement("div");
316 | div.appendChild( this.cloneNode(true) );
317 | html = div.innerHTML;
318 | }
319 |
320 | return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
321 | } else
322 | return this.cloneNode(true);
323 | });
324 |
325 | // Copy the events from the original to the clone
326 | if ( events === true ) {
327 | var orig = this.find("*").andSelf(), i = 0;
328 |
329 | ret.find("*").andSelf().each(function(){
330 | if ( this.nodeName !== orig[i].nodeName )
331 | return;
332 |
333 | var events = jQuery.data( orig[i], "events" );
334 |
335 | for ( var type in events ) {
336 | for ( var handler in events[ type ] ) {
337 | jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
338 | }
339 | }
340 |
341 | i++;
342 | });
343 | }
344 |
345 | // Return the cloned set
346 | return ret;
347 | },
348 |
349 | filter: function( selector ) {
350 | return this.pushStack(
351 | jQuery.isFunction( selector ) &&
352 | jQuery.grep(this, function(elem, i){
353 | return selector.call( elem, i );
354 | }) ||
355 |
356 | jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
357 | return elem.nodeType === 1;
358 | }) ), "filter", selector );
359 | },
360 |
361 | closest: function( selector ) {
362 | var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
363 | closer = 0;
364 |
365 | return this.map(function(){
366 | var cur = this;
367 | while ( cur && cur.ownerDocument ) {
368 | if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
369 | jQuery.data(cur, "closest", closer);
370 | return cur;
371 | }
372 | cur = cur.parentNode;
373 | closer++;
374 | }
375 | });
376 | },
377 |
378 | not: function( selector ) {
379 | if ( typeof selector === "string" )
380 | // test special case where just one selector is passed in
381 | if ( isSimple.test( selector ) )
382 | return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
383 | else
384 | selector = jQuery.multiFilter( selector, this );
385 |
386 | var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
387 | return this.filter(function() {
388 | return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
389 | });
390 | },
391 |
392 | add: function( selector ) {
393 | return this.pushStack( jQuery.unique( jQuery.merge(
394 | this.get(),
395 | typeof selector === "string" ?
396 | jQuery( selector ) :
397 | jQuery.makeArray( selector )
398 | )));
399 | },
400 |
401 | is: function( selector ) {
402 | return !!selector && jQuery.multiFilter( selector, this ).length > 0;
403 | },
404 |
405 | hasClass: function( selector ) {
406 | return !!selector && this.is( "." + selector );
407 | },
408 |
409 | val: function( value ) {
410 | if ( value === undefined ) {
411 | var elem = this[0];
412 |
413 | if ( elem ) {
414 | if( jQuery.nodeName( elem, 'option' ) )
415 | return (elem.attributes.value || {}).specified ? elem.value : elem.text;
416 |
417 | // We need to handle select boxes special
418 | if ( jQuery.nodeName( elem, "select" ) ) {
419 | var index = elem.selectedIndex,
420 | values = [],
421 | options = elem.options,
422 | one = elem.type == "select-one";
423 |
424 | // Nothing was selected
425 | if ( index < 0 )
426 | return null;
427 |
428 | // Loop through all the selected options
429 | for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
430 | var option = options[ i ];
431 |
432 | if ( option.selected ) {
433 | // Get the specifc value for the option
434 | value = jQuery(option).val();
435 |
436 | // We don't need an array for one selects
437 | if ( one )
438 | return value;
439 |
440 | // Multi-Selects return an array
441 | values.push( value );
442 | }
443 | }
444 |
445 | return values;
446 | }
447 |
448 | // Everything else, we just grab the value
449 | return (elem.value || "").replace(/\r/g, "");
450 |
451 | }
452 |
453 | return undefined;
454 | }
455 |
456 | if ( typeof value === "number" )
457 | value += '';
458 |
459 | return this.each(function(){
460 | if ( this.nodeType != 1 )
461 | return;
462 |
463 | if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
464 | this.checked = (jQuery.inArray(this.value, value) >= 0 ||
465 | jQuery.inArray(this.name, value) >= 0);
466 |
467 | else if ( jQuery.nodeName( this, "select" ) ) {
468 | var values = jQuery.makeArray(value);
469 |
470 | jQuery( "option", this ).each(function(){
471 | this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
472 | jQuery.inArray( this.text, values ) >= 0);
473 | });
474 |
475 | if ( !values.length )
476 | this.selectedIndex = -1;
477 |
478 | } else
479 | this.value = value;
480 | });
481 | },
482 |
483 | html: function( value ) {
484 | return value === undefined ?
485 | (this[0] ?
486 | this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
487 | null) :
488 | this.empty().append( value );
489 | },
490 |
491 | replaceWith: function( value ) {
492 | return this.after( value ).remove();
493 | },
494 |
495 | eq: function( i ) {
496 | return this.slice( i, +i + 1 );
497 | },
498 |
499 | slice: function() {
500 | return this.pushStack( Array.prototype.slice.apply( this, arguments ),
501 | "slice", Array.prototype.slice.call(arguments).join(",") );
502 | },
503 |
504 | map: function( callback ) {
505 | return this.pushStack( jQuery.map(this, function(elem, i){
506 | return callback.call( elem, i, elem );
507 | }));
508 | },
509 |
510 | andSelf: function() {
511 | return this.add( this.prevObject );
512 | },
513 |
514 | domManip: function( args, table, callback ) {
515 | if ( this[0] ) {
516 | var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
517 | scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
518 | first = fragment.firstChild;
519 |
520 | if ( first )
521 | for ( var i = 0, l = this.length; i < l; i++ )
522 | callback.call( root(this[i], first), this.length > 1 || i > 0 ?
523 | fragment.cloneNode(true) : fragment );
524 |
525 | if ( scripts )
526 | jQuery.each( scripts, evalScript );
527 | }
528 |
529 | return this;
530 |
531 | function root( elem, cur ) {
532 | return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
533 | (elem.getElementsByTagName("tbody")[0] ||
534 | elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
535 | elem;
536 | }
537 | }
538 | };
539 |
540 | // Give the init function the jQuery prototype for later instantiation
541 | jQuery.fn.init.prototype = jQuery.fn;
542 |
543 | function evalScript( i, elem ) {
544 | if ( elem.src )
545 | jQuery.ajax({
546 | url: elem.src,
547 | async: false,
548 | dataType: "script"
549 | });
550 |
551 | else
552 | jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
553 |
554 | if ( elem.parentNode )
555 | elem.parentNode.removeChild( elem );
556 | }
557 |
558 | function now(){
559 | return +new Date;
560 | }
561 |
562 | jQuery.extend = jQuery.fn.extend = function() {
563 | // copy reference to target object
564 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
565 |
566 | // Handle a deep copy situation
567 | if ( typeof target === "boolean" ) {
568 | deep = target;
569 | target = arguments[1] || {};
570 | // skip the boolean and the target
571 | i = 2;
572 | }
573 |
574 | // Handle case when target is a string or something (possible in deep copy)
575 | if ( typeof target !== "object" && !jQuery.isFunction(target) )
576 | target = {};
577 |
578 | // extend jQuery itself if only one argument is passed
579 | if ( length == i ) {
580 | target = this;
581 | --i;
582 | }
583 |
584 | for ( ; i < length; i++ )
585 | // Only deal with non-null/undefined values
586 | if ( (options = arguments[ i ]) != null )
587 | // Extend the base object
588 | for ( var name in options ) {
589 | var src = target[ name ], copy = options[ name ];
590 |
591 | // Prevent never-ending loop
592 | if ( target === copy )
593 | continue;
594 |
595 | // Recurse if we're merging object values
596 | if ( deep && copy && typeof copy === "object" && !copy.nodeType )
597 | target[ name ] = jQuery.extend( deep,
598 | // Never move original objects, clone them
599 | src || ( copy.length != null ? [ ] : { } )
600 | , copy );
601 |
602 | // Don't bring in undefined values
603 | else if ( copy !== undefined )
604 | target[ name ] = copy;
605 |
606 | }
607 |
608 | // Return the modified object
609 | return target;
610 | };
611 |
612 | // exclude the following css properties to add px
613 | var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
614 | // cache defaultView
615 | defaultView = document.defaultView || {},
616 | toString = Object.prototype.toString;
617 |
618 | jQuery.extend({
619 | noConflict: function( deep ) {
620 | window.$ = _$;
621 |
622 | if ( deep )
623 | window.jQuery = _jQuery;
624 |
625 | return jQuery;
626 | },
627 |
628 | // See test/unit/core.js for details concerning isFunction.
629 | // Since version 1.3, DOM methods and functions like alert
630 | // aren't supported. They return false on IE (#2968).
631 | isFunction: function( obj ) {
632 | return toString.call(obj) === "[object Function]";
633 | },
634 |
635 | isArray: function( obj ) {
636 | return toString.call(obj) === "[object Array]";
637 | },
638 |
639 | // check if an element is in a (or is an) XML document
640 | isXMLDoc: function( elem ) {
641 | return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
642 | !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
643 | },
644 |
645 | // Evalulates a script in a global context
646 | globalEval: function( data ) {
647 | if ( data && /\S/.test(data) ) {
648 | // Inspired by code by Andrea Giammarchi
649 | // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
650 | var head = document.getElementsByTagName("head")[0] || document.documentElement,
651 | script = document.createElement("script");
652 |
653 | script.type = "text/javascript";
654 | if ( jQuery.support.scriptEval )
655 | script.appendChild( document.createTextNode( data ) );
656 | else
657 | script.text = data;
658 |
659 | // Use insertBefore instead of appendChild to circumvent an IE6 bug.
660 | // This arises when a base node is used (#2709).
661 | head.insertBefore( script, head.firstChild );
662 | head.removeChild( script );
663 | }
664 | },
665 |
666 | nodeName: function( elem, name ) {
667 | return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
668 | },
669 |
670 | // args is for internal usage only
671 | each: function( object, callback, args ) {
672 | var name, i = 0, length = object.length;
673 |
674 | if ( args ) {
675 | if ( length === undefined ) {
676 | for ( name in object )
677 | if ( callback.apply( object[ name ], args ) === false )
678 | break;
679 | } else
680 | for ( ; i < length; )
681 | if ( callback.apply( object[ i++ ], args ) === false )
682 | break;
683 |
684 | // A special, fast, case for the most common use of each
685 | } else {
686 | if ( length === undefined ) {
687 | for ( name in object )
688 | if ( callback.call( object[ name ], name, object[ name ] ) === false )
689 | break;
690 | } else
691 | for ( var value = object[0];
692 | i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
693 | }
694 |
695 | return object;
696 | },
697 |
698 | prop: function( elem, value, type, i, name ) {
699 | // Handle executable functions
700 | if ( jQuery.isFunction( value ) )
701 | value = value.call( elem, i );
702 |
703 | // Handle passing in a number to a CSS property
704 | return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
705 | value + "px" :
706 | value;
707 | },
708 |
709 | className: {
710 | // internal only, use addClass("class")
711 | add: function( elem, classNames ) {
712 | jQuery.each((classNames || "").split(/\s+/), function(i, className){
713 | if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
714 | elem.className += (elem.className ? " " : "") + className;
715 | });
716 | },
717 |
718 | // internal only, use removeClass("class")
719 | remove: function( elem, classNames ) {
720 | if (elem.nodeType == 1)
721 | elem.className = classNames !== undefined ?
722 | jQuery.grep(elem.className.split(/\s+/), function(className){
723 | return !jQuery.className.has( classNames, className );
724 | }).join(" ") :
725 | "";
726 | },
727 |
728 | // internal only, use hasClass("class")
729 | has: function( elem, className ) {
730 | return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
731 | }
732 | },
733 |
734 | // A method for quickly swapping in/out CSS properties to get correct calculations
735 | swap: function( elem, options, callback ) {
736 | var old = {};
737 | // Remember the old values, and insert the new ones
738 | for ( var name in options ) {
739 | old[ name ] = elem.style[ name ];
740 | elem.style[ name ] = options[ name ];
741 | }
742 |
743 | callback.call( elem );
744 |
745 | // Revert the old values
746 | for ( var name in options )
747 | elem.style[ name ] = old[ name ];
748 | },
749 |
750 | css: function( elem, name, force, extra ) {
751 | if ( name == "width" || name == "height" ) {
752 | var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
753 |
754 | function getWH() {
755 | val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
756 |
757 | if ( extra === "border" )
758 | return;
759 |
760 | jQuery.each( which, function() {
761 | if ( !extra )
762 | val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
763 | if ( extra === "margin" )
764 | val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
765 | else
766 | val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
767 | });
768 | }
769 |
770 | if ( elem.offsetWidth !== 0 )
771 | getWH();
772 | else
773 | jQuery.swap( elem, props, getWH );
774 |
775 | return Math.max(0, Math.round(val));
776 | }
777 |
778 | return jQuery.curCSS( elem, name, force );
779 | },
780 |
781 | curCSS: function( elem, name, force ) {
782 | var ret, style = elem.style;
783 |
784 | // We need to handle opacity special in IE
785 | if ( name == "opacity" && !jQuery.support.opacity ) {
786 | ret = jQuery.attr( style, "opacity" );
787 |
788 | return ret == "" ?
789 | "1" :
790 | ret;
791 | }
792 |
793 | // Make sure we're using the right name for getting the float value
794 | if ( name.match( /float/i ) )
795 | name = styleFloat;
796 |
797 | if ( !force && style && style[ name ] )
798 | ret = style[ name ];
799 |
800 | else if ( defaultView.getComputedStyle ) {
801 |
802 | // Only "float" is needed here
803 | if ( name.match( /float/i ) )
804 | name = "float";
805 |
806 | name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
807 |
808 | var computedStyle = defaultView.getComputedStyle( elem, null );
809 |
810 | if ( computedStyle )
811 | ret = computedStyle.getPropertyValue( name );
812 |
813 | // We should always get a number back from opacity
814 | if ( name == "opacity" && ret == "" )
815 | ret = "1";
816 |
817 | } else if ( elem.currentStyle ) {
818 | var camelCase = name.replace(/\-(\w)/g, function(all, letter){
819 | return letter.toUpperCase();
820 | });
821 |
822 | ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
823 |
824 | // From the awesome hack by Dean Edwards
825 | // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
826 |
827 | // If we're not dealing with a regular pixel number
828 | // but a number that has a weird ending, we need to convert it to pixels
829 | if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
830 | // Remember the original values
831 | var left = style.left, rsLeft = elem.runtimeStyle.left;
832 |
833 | // Put in the new values to get a computed value out
834 | elem.runtimeStyle.left = elem.currentStyle.left;
835 | style.left = ret || 0;
836 | ret = style.pixelLeft + "px";
837 |
838 | // Revert the changed values
839 | style.left = left;
840 | elem.runtimeStyle.left = rsLeft;
841 | }
842 | }
843 |
844 | return ret;
845 | },
846 |
847 | clean: function( elems, context, fragment ) {
848 | context = context || document;
849 |
850 | // !context.createElement fails in IE with an error but returns typeof 'object'
851 | if ( typeof context.createElement === "undefined" )
852 | context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
853 |
854 | // If a single string is passed in and it's a single tag
855 | // just do a createElement and skip the rest
856 | if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
857 | var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
858 | if ( match )
859 | return [ context.createElement( match[1] ) ];
860 | }
861 |
862 | var ret = [], scripts = [], div = context.createElement("div");
863 |
864 | jQuery.each(elems, function(i, elem){
865 | if ( typeof elem === "number" )
866 | elem += '';
867 |
868 | if ( !elem )
869 | return;
870 |
871 | // Convert html string into DOM nodes
872 | if ( typeof elem === "string" ) {
873 | // Fix "XHTML"-style tags in all browsers
874 | elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
875 | return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
876 | all :
877 | front + ">" + tag + ">";
878 | });
879 |
880 | // Trim whitespace, otherwise indexOf won't work as expected
881 | var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
882 |
883 | var wrap =
884 | // option or optgroup
885 | !tags.indexOf("", "" ] ||
887 |
888 | !tags.indexOf("", "" ] ||
890 |
891 | tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
892 | [ 1, "" ] ||
893 |
894 | !tags.indexOf("
", "" ] ||
896 |
897 | // matched above
898 | (!tags.indexOf(" | ", "
" ] ||
900 |
901 | !tags.indexOf("", "" ] ||
903 |
904 | // IE can't serialize and