pax_global_header00006660000000000000000000000064115404415230014511gustar00rootroot0000000000000052 comment=dc769d3524b187cbe8858b30b39bb216583d139f ruby-shoulda-matchers-1.0.0~beta2/000077500000000000000000000000001154044152300170455ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/.autotest000066400000000000000000000005171154044152300207210ustar00rootroot00000000000000Autotest.add_hook :initialize do |at| at.add_mapping(%r{^lib/\w.*\.rb}) do at.files_matching(%r{^test/*/\w.*_test\.rb}) end at.add_mapping(%r{^test/rails_root/\w.*}) do at.files_matching(%r{^test/*/\w.*_test\.rb}) end at.add_exception(%r{.svn}) at.add_exception(%r{.log$}) at.add_exception(%r{^.autotest$}) end ruby-shoulda-matchers-1.0.0~beta2/.gitignore000066400000000000000000000001221154044152300210300ustar00rootroot00000000000000test/*/log/*.log doc coverage .svn/ pkg *.swp *.swo tags tmp .bundle Gemfile.lock ruby-shoulda-matchers-1.0.0~beta2/CONTRIBUTION_GUIDELINES.rdoc000066400000000000000000000022741154044152300233520ustar00rootroot00000000000000We're using GitHub[http://github.com/thoughtbot/shoulda/tree/master], and we've been getting any combination of github pull requests, tickets, patches, emails, etc. We need to normalize this workflow to make sure we don't miss any fixes. * Make sure you're accessing the source from the {official repository}[http://github.com/thoughtbot/shoulda/tree/master]. * We prefer git branches over patches, but we can take either. * If you're using git, please make a branch for each separate contribution. We can cherry pick your commits, but pulling from a branch is easier. * If you're submitting patches, please cut each fix or feature into a separate patch. * There should be an issue[http://github.com/thoughtbot/shoulda/issues] for any submission. If you've found a bug and want to fix it, open a new ticket at the same time. * Please don't send pull requests Just update the issue with the url for your fix (or attach the patch) when it's ready. The github pull requests pretty much get dropped on the floor until someone with commit rights notices them in the mailbox. * Contributions without tests won't be accepted. The file /test/README explains the testing system pretty thoroughly. ruby-shoulda-matchers-1.0.0~beta2/Gemfile000066400000000000000000000000471154044152300203410ustar00rootroot00000000000000source 'http://rubygems.org' gemspec ruby-shoulda-matchers-1.0.0~beta2/MIT-LICENSE000066400000000000000000000021111154044152300204740ustar00rootroot00000000000000Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-shoulda-matchers-1.0.0~beta2/README.rdoc000066400000000000000000000040071154044152300206540ustar00rootroot00000000000000= shoulda-matchers {Official Documentation}[http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames] Test::Unit- and RSpec-compatible one-liners that test common Rails functionality. These tests would otherwise be much longer, more complex, and error-prone. Refer to the {shoulda}[https://github.com/thoughtbot/shoulda] gem if you want to know more about using shoulda with Test::Unit. === ActiveRecord Matchers Matchers to test associations and validations: describe Post do it { should belong_to(:user) } it { should have_many(:tags).through(:taggings) } it { should validate_uniqueness_of(:title) } it { should validate_presence_of(:body).with_message(/wtf/) } it { should validate_presence_of(:title) } it { should validate_numericality_of(:user_id) } end describe User do it { should have_many(:posts) } it { should_not allow_value("blah").for(:email) } it { should allow_value("a@b.com").for(:email) } it { should ensure_inclusion_of(:age).in_range(1..100) } it { should_not allow_mass_assignment_of(:password) } end === ActionController Matchers Matchers to test common patterns: describe PostsController, "#show" do context "for a fictional user" do before do get :show, :id => 1 end it { should assign_to(:user) } it { should respond_with(:success) } it { should render_template(:show) } it { should_not set_the_flash } end end = Installation In Rails 3 and Bundler, add the following to your Gemfile: group :test do gem "shoulda-matchers" gem "rspec-rails" end Shoulda will automatically include matchers into the appropriate example groups. = Credits Shoulda is maintained and funded by {thoughtbot}[http://thoughtbot.com/community]. Thank you to all the {contributors}[https://github.com/thoughtbot/shoulda-matchers/contributors]. = License Shoulda is Copyright © 2006-2010 thoughtbot, inc. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file. ruby-shoulda-matchers-1.0.0~beta2/Rakefile000066400000000000000000000026241154044152300205160ustar00rootroot00000000000000require 'rubygems' require 'bundler/setup' require 'rake' require 'rake/rdoctask' require 'rake/gempackagetask' require 'rspec/core/rake_task' require 'cucumber/rake/task' $LOAD_PATH.unshift("lib") require 'shoulda/matchers/version' Rake::RDocTask.new { |rdoc| rdoc.rdoc_dir = 'doc' rdoc.title = "Shoulda -- Making tests easy on the fingers and eyes" rdoc.options << '--line-numbers' rdoc.template = "#{ENV['template']}.rb" if ENV['template'] rdoc.rdoc_files.include('README.rdoc', 'CONTRIBUTION_GUIDELINES.rdoc', 'lib/**/*.rb') } RSpec::Core::RakeTask.new do |t| t.pattern = "spec/**/*_spec.rb" t.rspec_opts = '--color --format progress' t.verbose = false end desc "Run code-coverage analysis using rcov" RSpec::Core::RakeTask.new(:coverage) do |t| t.rspec_opts = '--color --format progress' t.rcov = true t.rcov_opts = %{--exclude osx\/objc,spec,gems\/ --failure-threshold 100} t.pattern = "spec/**/*_spec.rb" end eval("$specification = begin; #{IO.read('shoulda-matchers.gemspec')}; end") Rake::GemPackageTask.new $specification do |pkg| pkg.need_tar = true pkg.need_zip = true end desc "Clean files generated by rake tasks" task :clobber => [:clobber_rdoc, :clobber_package] Cucumber::Rake::Task.new do |t| t.fork = true t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'progress')] end desc 'Default: run specs and cucumber features' task :default => [:spec, :cucumber] ruby-shoulda-matchers-1.0.0~beta2/features/000077500000000000000000000000001154044152300206635ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/features/rails_integration.feature000066400000000000000000000054501154044152300257610ustar00rootroot00000000000000@disable-bundler Feature: integrate with Rails Background: When I generate a new rails application And I write to "db/migrate/1_create_users.rb" with: """ class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :name end end end """ When I successfully run "rake db:migrate --trace" And I write to "app/models/user.rb" with: """ class User < ActiveRecord::Base validates_presence_of :name end """ When I write to "app/controllers/examples_controller.rb" with: """ class ExamplesController < ApplicationController def show @example = 'hello' render :nothing => true end end """ When I configure a wildcard route Scenario: generate a rails application and use matchers in Test::Unit When I configure the application to use shoulda-context And I configure the application to use "shoulda-matchers" from this project And I write to "test/unit/user_test.rb" with: """ require 'test_helper' class UserTest < ActiveSupport::TestCase should validate_presence_of(:name) end """ When I write to "test/functional/examples_controller_test.rb" with: """ require 'test_helper' class ExamplesControllerTest < ActionController::TestCase def setup get :show end should respond_with(:success) should assign_to(:example) end """ When I successfully run "rake test TESTOPTS=-v --trace" Then the output should contain "1 tests, 1 assertions, 0 failures, 0 errors" And the output should contain "2 tests, 2 assertions, 0 failures, 0 errors" And the output should contain "User should require name to be set" And the output should contain "ExamplesController should assign @example" Scenario: generate a rails application and use matchers in Rspec When I configure the application to use rspec-rails And I configure the application to use "shoulda-matchers" from this project And I run the rspec generator And I write to "spec/models/user_spec.rb" with: """ require 'spec_helper' describe User do it { should validate_presence_of(:name) } end """ When I write to "spec/controllers/examples_controller_spec.rb" with: """ require 'spec_helper' describe ExamplesController, "show" do before { get :show } it { should assign_to(:example) } end """ When I successfully run "rake spec SPEC_OPTS=-fs --trace" Then the output should contain "2 examples, 0 failures" And the output should contain "should require name to be set" And the output should contain "should assign @example" ruby-shoulda-matchers-1.0.0~beta2/features/step_definitions/000077500000000000000000000000001154044152300242315ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/features/step_definitions/rails_steps.rb000066400000000000000000000031771154044152300271160ustar00rootroot00000000000000PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze APP_NAME = 'testapp'.freeze When /^I generate a new rails application$/ do steps %{ When I run "rails _3.0.3_ new #{APP_NAME}" And I cd to "#{APP_NAME}" And I write to "Gemfile" with: """ source "http://rubygems.org" gem 'rails', '3.0.3' gem 'sqlite3-ruby', :require => 'sqlite3' """ And I successfully run "bundle install --local" } end When /^I configure the application to use "([^\"]+)" from this project$/ do |name| append_to_gemfile "gem '#{name}', :path => '#{PROJECT_ROOT}'" steps %{And I run "bundle install --local"} end When /^I run the rspec generator$/ do steps %{ When I successfully run "rails generate rspec:install" } end When /^I configure the application to use rspec\-rails$/ do append_to_gemfile "gem 'rspec-rails'" steps %{And I run "bundle install --local"} end When /^I configure the application to use shoulda-context$/ do append_to_gemfile "gem 'shoulda-context', :git => 'git@github.com:thoughtbot/shoulda-context.git'" steps %{And I run "bundle install --local"} end When /^I configure a wildcard route$/ do steps %{ When I write to "config/routes.rb" with: """ Rails.application.routes.draw do match ':controller(/:action(/:id(.:format)))' end """ } end module AppendHelpers def append_to(path, contents) in_current_dir do File.open(path, "a") do |file| file.puts file.puts contents end end end def append_to_gemfile(contents) append_to('Gemfile', contents) end end World(AppendHelpers) ruby-shoulda-matchers-1.0.0~beta2/features/support/000077500000000000000000000000001154044152300223775ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/features/support/env.rb000066400000000000000000000001061154044152300235110ustar00rootroot00000000000000require 'aruba/cucumber' Before do @aruba_timeout_seconds = 15 end ruby-shoulda-matchers-1.0.0~beta2/lib/000077500000000000000000000000001154044152300176135ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda-matchers.rb000066400000000000000000000000331154044152300233770ustar00rootroot00000000000000require 'shoulda/matchers' ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/000077500000000000000000000000001154044152300212525ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers.rb000066400000000000000000000003171154044152300234060ustar00rootroot00000000000000require 'shoulda/matchers/version' require 'shoulda/matchers/assertion_error' if defined?(RSpec) require 'shoulda/matchers/integrations/rspec' else require 'shoulda/matchers/integrations/test_unit' end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/000077500000000000000000000000001154044152300230605ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller.rb000066400000000000000000000026161154044152300271320ustar00rootroot00000000000000require 'shoulda/matchers/action_controller/assign_to_matcher' require 'shoulda/matchers/action_controller/filter_param_matcher' require 'shoulda/matchers/action_controller/set_the_flash_matcher' require 'shoulda/matchers/action_controller/render_with_layout_matcher' require 'shoulda/matchers/action_controller/respond_with_matcher' require 'shoulda/matchers/action_controller/respond_with_content_type_matcher' require 'shoulda/matchers/action_controller/set_session_matcher' require 'shoulda/matchers/action_controller/route_matcher' require 'shoulda/matchers/action_controller/redirect_to_matcher' require 'shoulda/matchers/action_controller/render_template_matcher' module Shoulda module Matchers # By using the matchers you can quickly and easily create concise and # easy to read test suites. # # This code segment: # # describe UsersController, "on GET to show with a valid id" do # before(:each) do # get :show, :id => User.first.to_param # end # # it { should assign_to(:user) } # it { should respond_with(:success) } # it { should render_template(:show) } # it { should not_set_the_flash) } # # it "should do something else really cool" do # assigns[:user].id.should == 1 # end # end # # Would produce 5 tests for the show action module ActionController end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/000077500000000000000000000000001154044152300266005ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/assign_to_matcher.rb000066400000000000000000000066271154044152300326310ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures that the controller assigned to the named instance variable. # # Options: # * with_kind_of - The expected class of the instance variable # being checked. # * with - The value that should be assigned. # # Example: # # it { should assign_to(:user) } # it { should_not assign_to(:user) } # it { should assign_to(:user).with_kind_of(User) } # it { should assign_to(:user).with(@user) } def assign_to(variable) AssignToMatcher.new(variable) end class AssignToMatcher # :nodoc: def initialize(variable) @variable = variable.to_s @check_value = false end def with_kind_of(expected_class) @expected_class = expected_class self end def with(expected_value = nil, &block) @check_value = true @expected_value = expected_value @expectation_block = block self end def matches?(controller) @controller = controller @expected_value = @context.instance_eval(&@expectation_block) if @expectation_block assigned_value? && kind_of_expected_class? && equal_to_expected_value? end attr_reader :failure_message, :negative_failure_message def description description = "assign @#{@variable}" description << " with a kind of #{@expected_class}" if @expected_class description end def in_context(context) @context = context self end private def assigned_value? if !@controller.instance_variables.map(&:to_s).include?("@#{@variable}") @failure_message = "Expected action to assign a value for @#{@variable}" false else @negative_failure_message = "Didn't expect action to assign a value for @#{@variable}, " << "but it was assigned to #{assigned_value.inspect}" true end end def kind_of_expected_class? return true unless @expected_class if assigned_value.kind_of?(@expected_class) @negative_failure_message = "Didn't expect action to assign a kind of #{@expected_class} " << "for #{@variable}, but got one anyway" true else @failure_message = "Expected action to assign a kind of #{@expected_class} " << "for #{@variable}, but got #{@variable.inspect} " << "(#{@variable.class.name})" false end end def equal_to_expected_value? return true unless @check_value if @expected_value == assigned_value @negative_failure_message = "Didn't expect action to assign #{@expected_value.inspect} " << "for #{@variable}, but got it anyway" true else @failure_message = "Expected action to assign #{@expected_value.inspect} " << "for #{@variable}, but got #{assigned_value.inspect}" false end end def assigned_value @controller.instance_variable_get("@#{@variable}") end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/filter_param_matcher.rb000066400000000000000000000020471154044152300333000ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures that filter_parameter_logging is set for the specified key. # # Example: # # it { should filter_param(:password) } def filter_param(key) FilterParamMatcher.new(key) end class FilterParamMatcher # :nodoc: def initialize(key) @key = key.to_s end def matches?(controller) @controller = controller filters_key? end def failure_message "Expected #{@key} to be filtered; filtered keys: #{filtered_keys.join(', ')}" end def negative_failure_message "Did not expect #{@key} to be filtered" end def description "filter #{@key}" end private def filters_key? filtered_keys.include?(@key) end def filtered_keys Rails.application.config.filter_parameters.map { |filter| filter.to_s } end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb000066400000000000000000000030011154044152300331250ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures a controller redirected to the given url. # # Example: # # it { should redirect_to('http://somewhere.com') } # it { should redirect_to(users_path) } def redirect_to(url_or_description, &block) RedirectToMatcher.new(url_or_description, self, &block) end class RedirectToMatcher # :nodoc: def initialize(url_or_description, context, &block) if block @url_block = block @location = url_or_description else @url = url_or_description @location = @url end @context = context end def in_context(context) @context = context self end def matches?(controller) @controller = controller redirects_to_url? end attr_reader :failure_message, :negative_failure_message def description "redirect to #{@location}" end private def redirects_to_url? @url = @context.instance_eval(&@url_block) if @url_block begin @context.send(:assert_redirected_to, @url) @negative_failure_message = "Didn't expect to redirect to #{@url}" true rescue Shoulda::Matchers::AssertionError => error @failure_message = error.message false end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/render_template_matcher.rb000066400000000000000000000023021154044152300337770ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures a controller rendered the given template. # # Example: # # it { should render_template(:show) } def render_template(template) RenderTemplateMatcher.new(template, self) end class RenderTemplateMatcher # :nodoc: def initialize(template, context) @template = template.to_s @context = context end def matches?(controller) @controller = controller renders_template? end attr_reader :failure_message, :negative_failure_message def description "render template #{@template}" end def in_context(context) @context = context self end private def renders_template? begin @context.send(:assert_template, @template) @negative_failure_message = "Didn't expect to render #{@template}" true rescue Shoulda::Matchers::AssertionError => error @failure_message = error.message false end end end end end end render_with_layout_matcher.rb000066400000000000000000000047501154044152300344660ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controllermodule Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures that the controller rendered with the given layout. # # Example: # # it { should render_with_layout } # it { should render_with_layout(:special) } # it { should_not render_with_layout } def render_with_layout(expected_layout = nil) RenderWithLayoutMatcher.new(expected_layout).in_context(self) end class RenderWithLayoutMatcher # :nodoc: def initialize(expected_layout) @expected_layout = expected_layout.to_s unless expected_layout.nil? end # Used to provide access to layouts recorded by # ActionController::TemplateAssertions in Rails 3 def in_context(context) @context = context self end def matches?(controller) @controller = controller rendered_with_layout? && rendered_with_expected_layout? end def failure_message "Expected #{expectation}, but #{result}" end def negative_failure_message "Did not expect #{expectation}, but #{result}" end def description description = "render with " if @expected_layout.nil? description << "a layout" else description << "the #{@expected_layout.inspect} layout" end description end private def rendered_with_layout? !rendered_layouts.empty? end def rendered_with_expected_layout? return true if @expected_layout.nil? rendered_layouts.include?(@expected_layout) end def rendered_layouts if recorded_layouts recorded_layouts.keys.compact.map { |layout| layout.sub(%r{^layouts/}, '') } else layout = @controller.response.layout if layout.nil? [] else [layout.split('/').last] end end end def recorded_layouts if @context @context.instance_variable_get('@layouts') end end def expectation "to #{description}" end def result if rendered_with_layout? "rendered with " << rendered_layouts.map { |layout| layout.inspect }.join(", ") else "rendered without a layout" end end end end end end respond_with_content_type_matcher.rb000066400000000000000000000040261154044152300360530ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controllermodule Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures a controller responded with expected 'response' content type. # # You can pass an explicit content type such as 'application/rss+xml' # or its symbolic equivalent :rss # or a regular expression such as /rss/ # # Example: # # it { should respond_with_content_type(:xml) } # it { should respond_with_content_type(:csv) } # it { should respond_with_content_type(:atom) } # it { should respond_with_content_type(:yaml) } # it { should respond_with_content_type(:text) } # it { should respond_with_content_type('application/rss+xml') } # it { should respond_with_content_type(/json/) } def respond_with_content_type(content_type) RespondWithContentTypeMatcher.new(content_type) end class RespondWithContentTypeMatcher # :nodoc: def initialize(content_type) @content_type = if content_type.is_a?(Symbol) lookup_by_extension(content_type) else content_type end end def description "respond with content type of #{@content_type}" end def matches?(controller) @controller = controller if @content_type.is_a?(Regexp) response_content_type =~ @content_type else response_content_type == @content_type end end def failure_message "Expected #{expectation}" end def negative_failure_message "Did not expect #{expectation}" end protected def response_content_type @controller.response.content_type.to_s end def lookup_by_extension(extension) Mime::Type.lookup_by_extension(extension.to_s).to_s end def expectation "content type to be #{@content_type}, " << "but was #{response_content_type}" end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/respond_with_matcher.rb000066400000000000000000000043221154044152300333360ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures a controller responded with expected 'response' status code. # # You can pass an explicit status number like 200, 301, 404, 500 # or its symbolic equivalent :success, :redirect, :missing, :error. # See ActionController::StatusCodes for a full list. # # Example: # # it { should respond_with(:success) } # it { should respond_with(:redirect) } # it { should respond_with(:missing) } # it { should respond_with(:error) } # it { should respond_with(501) } def respond_with(status) RespondWithMatcher.new(status) end class RespondWithMatcher # :nodoc: def initialize(status) @status = symbol_to_status_code(status) end def matches?(controller) @controller = controller correct_status_code? || correct_status_code_range? end def failure_message "Expected #{expectation}" end def negative_failure_message "Did not expect #{expectation}" end def description "respond with #{@status}" end protected def correct_status_code? response_code == @status end def correct_status_code_range? @status.is_a?(Range) && @status.include?(response_code) end def response_code @controller.response.response_code end def symbol_to_status_code(potential_symbol) case potential_symbol when :success then 200 when :redirect then 300..399 when :missing then 404 when :error then 500..599 when Symbol if defined?(::Rack::Utils::SYMBOL_TO_STATUS_CODE) ::Rack::Utils::SYMBOL_TO_STATUS_CODE[potential_symbol] else ::ActionController::Base::SYMBOL_TO_STATUS_CODE[potential_symbol] end else potential_symbol end end def expectation "response to be a #{@status}, but was #{response_code}" end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/route_matcher.rb000066400000000000000000000053211154044152300317670ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures that requesting +path+ using +method+ routes to +options+. # # If you don't specify a controller, it will use the controller from the # example group. # # +to_param+ is called on the +options+ given. # # Examples: # # it { should route(:get, "/posts"). # to(:controller => :posts, :action => :index) } # it { should route(:get, "/posts/new").to(:action => :new) } # it { should route(:post, "/posts").to(:action => :create) } # it { should route(:get, "/posts/1").to(:action => :show, :id => 1) } # it { should route(:edit, "/posts/1").to(:action => :show, :id => 1) } # it { should route(:put, "/posts/1").to(:action => :update, :id => 1) } # it { should route(:delete, "/posts/1"). # to(:action => :destroy, :id => 1) } # it { should route(:get, "/users/1/posts/1"). # to(:action => :show, :id => 1, :user_id => 1) } def route(method, path) RouteMatcher.new(method, path, self) end class RouteMatcher # :nodoc: def initialize(method, path, context) @method = method @path = path @context = context end def to(params) @params = params stringify_params! self end def in_context(context) @context = context self end def matches?(controller) @controller = controller guess_controller! route_recognized? end attr_reader :failure_message, :negative_failure_message def description "route #{@method.to_s.upcase} #{@path} to/from #{@params.inspect}" end private def guess_controller! @params[:controller] ||= @controller.controller_path end def stringify_params! @params.each do |key, value| @params[key] = value.is_a?(Array) ? value.collect {|v| v.to_param } : value.to_param end end def route_recognized? begin @context.send(:assert_routing, { :method => @method, :path => @path }, @params) @negative_failure_message = "Didn't expect to #{description}" true rescue ::ActionController::RoutingError => error @failure_message = error.message false rescue Shoulda::Matchers::AssertionError => error @failure_message = error.message false end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/set_session_matcher.rb000066400000000000000000000044221154044152300331700ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures that a session key was set to the expected value. # # Example: # # it { should set_session(:message) } # it { should set_session(:user_id).to(@user.id) } # it { should_not set_session(:user_id) } def set_session(key) SetSessionMatcher.new(key) end class SetSessionMatcher # :nodoc: def initialize(key) @key = key.to_s end def to(value = nil, &block) @value = value @value_block = block self end def matches?(controller) @controller = controller @value = @context.instance_eval(&@value_block) if @value_block (assigned_value? && assigned_correct_value?) || cleared_value? end def failure_message "Expected #{expectation}, but #{result}" end def negative_failure_message "Didn't expect #{expectation}, but #{result}" end def description description = "set session variable #{@key.inspect}" description << " to #{@value.inspect}" if defined?(@value) description end def in_context(context) @context = context self end private def assigned_value? !assigned_value.nil? end def cleared_value? defined?(@value) && @value.nil? && assigned_value.nil? end def assigned_correct_value? return true if @value.nil? assigned_value == @value end def assigned_value session[@key] end def session if @controller.request.respond_to?(:session) @controller.request.session.to_hash else @controller.response.session.data end end def expectation expectation = "session variable #{@key} to be set" expectation << " to #{@value.inspect}" if @value expectation end def result if session.empty? "no session variables were set" else "the session was #{session.inspect}" end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb000066400000000000000000000044111154044152300334400ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionController # :nodoc: # Ensures that the flash contains the given value. Can be a String, a # Regexp, or nil (indicating that the flash should not be set). # # Example: # # it { should set_the_flash } # it { should set_the_flash.to("Thank you for placing this order.") } # it { should set_the_flash.to(/created/i) } # it { should set_the_flash.to(/logged in/i).now } # it { should_not set_the_flash } def set_the_flash SetTheFlashMatcher.new end class SetTheFlashMatcher # :nodoc: def to(value) @value = value self end def now @now = true self end def matches?(controller) @controller = controller sets_the_flash? && string_value_matches? && regexp_value_matches? end attr_reader :failure_message, :negative_failure_message def description description = "set the flash" description << " to #{@value.inspect}" unless @value.nil? description end def failure_message "Expected #{expectation}" end def negative_failure_message "Did not expect #{expectation}" end private def sets_the_flash? !flash.blank? end def string_value_matches? return true unless String === @value flash.values.any? {|value| value == @value } end def regexp_value_matches? return true unless Regexp === @value flash.values.any? {|value| value =~ @value } end def flash return @flash if @flash @flash = @controller.flash.dup @flash.sweep unless @now @flash end def expectation expectation = "the flash#{".now" if @now} to be set" expectation << " to #{@value.inspect}" unless @value.nil? expectation << ", but #{flash_description}" expectation end def flash_description if flash.blank? "no flash was set" else "was #{flash.inspect}" end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_mailer.rb000066400000000000000000000014171154044152300262160ustar00rootroot00000000000000require 'shoulda/matchers/action_mailer/have_sent_email' module Shoulda module Matchers # = Matchers for your mailers # # This matcher will test that email is sent properly # # describe User do # it { should have_sent_email.with_subject(/is spam$/) } # it { should have_sent_email.from('do-not-reply@example.com') } # it { should have_sent_email.with_body(/is spam\./) } # it { should have_sent_email.to('myself@me.com') } # it { should have_sent_email.with_subject(/spam/). # from('do-not-reply@example.com'). # with_body(/spam/). # to('myself@me.com') } # end module ActionMailer end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_mailer/000077500000000000000000000000001154044152300256665ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/action_mailer/have_sent_email.rb000066400000000000000000000070151154044152300313410ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActionMailer # :nodoc: # The right email is sent. # # it { should have_sent_email.with_subject(/is spam$/) } # it { should have_sent_email.from('do-not-reply@example.com') } # it { should have_sent_email.with_body(/is spam\./) } # it { should have_sent_email.to('myself@me.com') } # it { should have_sent_email.with_subject(/spam/). # from('do-not-reply@example.com'). # with_body(/spam/). # to('myself@me.com') } def have_sent_email HaveSentEmailMatcher.new end class HaveSentEmailMatcher # :nodoc: def initialize end def with_subject(email_subject) @email_subject = email_subject self end def from(sender) @sender = sender self end def with_body(body) @body = body self end def to(recipient) @recipient = recipient self end def matches?(subject) ::ActionMailer::Base.deliveries.each do |mail| @subject_failed = !regexp_or_string_match(mail.subject, @email_subject) if @email_subject @body_failed = !regexp_or_string_match(mail.body, @body) if @body @sender_failed = !regexp_or_string_match_in_array(mail.from, @sender) if @sender @recipient_failed = !regexp_or_string_match_in_array(mail.to, @recipient) if @recipient return true unless anything_failed? end false end def failure_message "Expected #{expectation}" end def negative_failure_message "Did not expect #{expectation}" end def description description = "send an email" description << " with a subject of #{@email_subject.inspect}" if @email_subject description << " containing #{@body.inspect}" if @body description << " from #{@sender.inspect}" if @sender description << " to #{@recipient.inspect}" if @recipient description end private def expectation expectation = "sent email" expectation << " with subject #{@email_subject.inspect}" if @subject_failed expectation << " with body #{@body.inspect}" if @body_failed expectation << " from #{@sender.inspect}" if @sender_failed expectation << " to #{@recipient.inspect}" if @recipient_failed expectation << "\nDeliveries:\n#{inspect_deliveries}" end def inspect_deliveries ::ActionMailer::Base.deliveries.map do |delivery| "#{delivery.subject.inspect} to #{delivery.to.inspect}" end.join("\n") end def anything_failed? @subject_failed || @body_failed || @sender_failed || @recipient_failed end def regexp_or_string_match(a_string, a_regexp_or_string) case a_regexp_or_string when Regexp a_string =~ a_regexp_or_string when String a_string == a_regexp_or_string end end def regexp_or_string_match_in_array(an_array, a_regexp_or_string) case a_regexp_or_string when Regexp an_array.any? { |string| string =~ a_regexp_or_string } when String an_array.include?(a_regexp_or_string) end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record.rb000066400000000000000000000034561154044152300262260ustar00rootroot00000000000000require 'shoulda/matchers/active_record/helpers' require 'shoulda/matchers/active_record/validation_matcher' require 'shoulda/matchers/active_record/allow_value_matcher' require 'shoulda/matchers/active_record/ensure_length_of_matcher' require 'shoulda/matchers/active_record/ensure_inclusion_of_matcher' require 'shoulda/matchers/active_record/validate_presence_of_matcher' require 'shoulda/matchers/active_record/validate_format_of_matcher' require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher' require 'shoulda/matchers/active_record/validate_acceptance_of_matcher' require 'shoulda/matchers/active_record/validate_numericality_of_matcher' require 'shoulda/matchers/active_record/association_matcher' require 'shoulda/matchers/active_record/have_db_column_matcher' require 'shoulda/matchers/active_record/have_db_index_matcher' require 'shoulda/matchers/active_record/have_readonly_attribute_matcher' require 'shoulda/matchers/active_record/allow_mass_assignment_of_matcher' module Shoulda module Matchers # = Matchers for your active record models # # These matchers will test most of the validations and associations for your # ActiveRecord models. # # describe User do # it { should validate_presence_of(:name) } # it { should validate_presence_of(:phone_number) } # %w(abcd 1234).each do |value| # it { should_not allow_value(value).for(:phone_number) } # end # it { should allow_value("(123) 456-7890").for(:phone_number) } # it { should_not allow_mass_assignment_of(:password) } # it { should have_one(:profile) } # it { should have_many(:dogs) } # it { should have_many(:messes).through(:dogs) } # it { should belong_to(:lover) } # end # module ActiveRecord end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/000077500000000000000000000000001154044152300256715ustar00rootroot00000000000000allow_mass_assignment_of_matcher.rb000066400000000000000000000042741154044152300347260ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_recordmodule Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the attribute can be set on mass update. # # it { should_not allow_mass_assignment_of(:password) } # it { should allow_mass_assignment_of(:first_name) } # def allow_mass_assignment_of(value) AllowMassAssignmentOfMatcher.new(value) end class AllowMassAssignmentOfMatcher # :nodoc: def initialize(attribute) @attribute = attribute.to_s end def matches?(subject) @subject = subject if attr_mass_assignable? if whitelisting? @negative_failure_message = "#{@attribute} was made accessible" else if protected_attributes.empty? @negative_failure_message = "no attributes were protected" else @negative_failure_message = "#{class_name} is protecting " << "#{protected_attributes.to_a.to_sentence}, " << "but not #{@attribute}." end end true else if whitelisting? @failure_message = "Expected #{@attribute} to be accessible" else @failure_message = "Did not expect #{@attribute} to be protected" end false end end attr_reader :failure_message, :negative_failure_message def description "allow mass assignment of #{@attribute}" end private def protected_attributes @protected_attributes ||= (@subject.class.protected_attributes || []) end def accessible_attributes @accessible_attributes ||= (@subject.class.accessible_attributes || []) end def whitelisting? !accessible_attributes.empty? end def attr_mass_assignable? if whitelisting? accessible_attributes.include?(@attribute) else !protected_attributes.include?(@attribute) end end def class_name @subject.class.name end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/allow_value_matcher.rb000066400000000000000000000056161154044152300322430ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the attribute can be set to the given value. # # Options: # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or string. If omitted, # the test looks for any errors in errors.on(:attribute). # # Example: # it { should_not allow_value('bad').for(:isbn) } # it { should allow_value("isbn 1 2345 6789 0").for(:isbn) } # def allow_value(value) AllowValueMatcher.new(value) end class AllowValueMatcher # :nodoc: include Helpers def initialize(value) @value = value end def for(attribute) @attribute = attribute self end def with_message(message) @expected_message = message if message self end def matches?(instance) @instance = instance if Symbol === @expected_message @expected_message = default_error_message(@expected_message) end @instance.send("#{@attribute}=", @value) !errors_match? end def failure_message "Did not expect #{expectation}, got error: #{@matched_error}" end def negative_failure_message "Expected #{expectation}, got #{error_description}" end def description "allow #{@attribute} to be set to #{@value.inspect}" end private def errors_match? @instance.valid? @errors = errors_for_attribute(@instance, @attribute) @errors = [@errors] unless @errors.is_a?(Array) @expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?) end def errors_for_attribute(instance, attribute) if instance.errors.respond_to?(:[]) instance.errors[attribute] else instance.errors.on(attribute) end end def errors_match_regexp? if Regexp === @expected_message @matched_error = @errors.detect { |e| e =~ @expected_message } !@matched_error.nil? else false end end def errors_match_string? if @errors.include?(@expected_message) @matched_error = @expected_message true else false end end def expectation "errors " << (@expected_message ? "to include #{@expected_message.inspect} " : "") << "when #{@attribute} is set to #{@value.inspect}" end def error_description if @instance.errors.empty? "no errors" else "errors: #{pretty_error_messages(@instance)}" end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/association_matcher.rb000066400000000000000000000140401154044152300322340ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensure that the belongs_to relationship exists. # # it { should belong_to(:parent) } # def belong_to(name) AssociationMatcher.new(:belongs_to, name) end # Ensures that the has_many relationship exists. Will also test that the # associated table has the required columns. Works with polymorphic # associations. # # Options: # * through - association name for has_many :through # * dependent - tests that the association makes use of the # dependent option. # # Example: # it { should have_many(:friends) } # it { should have_many(:enemies).through(:friends) } # it { should have_many(:enemies).dependent(:destroy) } # def have_many(name) AssociationMatcher.new(:has_many, name) end # Ensure that the has_one relationship exists. Will also test that the # associated table has the required columns. Works with polymorphic # associations. # # Options: # * :dependent - tests that the association makes use of the # dependent option. # # Example: # it { should have_one(:god) } # unless hindu # def have_one(name) AssociationMatcher.new(:has_one, name) end # Ensures that the has_and_belongs_to_many relationship exists, and that # the join table is in place. # # it { should have_and_belong_to_many(:posts) } # def have_and_belong_to_many(name) AssociationMatcher.new(:has_and_belongs_to_many, name) end class AssociationMatcher # :nodoc: def initialize(macro, name) @macro = macro @name = name end def through(through) @through = through self end def dependent(dependent) @dependent = dependent self end def matches?(subject) @subject = subject association_exists? && macro_correct? && foreign_key_exists? && through_association_valid? && dependent_correct? && join_table_exists? end def failure_message "Expected #{expectation} (#{@missing})" end def negative_failure_message "Did not expect #{expectation}" end def description description = "#{macro_description} #{@name}" description += " through #{@through}" if @through description += " dependent => #{@dependent}" if @dependent description end protected def association_exists? if reflection.nil? @missing = "no association called #{@name}" false else true end end def macro_correct? if reflection.macro == @macro true else @missing = "actual association type was #{reflection.macro}" false end end def foreign_key_exists? !(belongs_foreign_key_missing? || has_foreign_key_missing?) end def belongs_foreign_key_missing? @macro == :belongs_to && !class_has_foreign_key?(model_class) end def has_foreign_key_missing? [:has_many, :has_one].include?(@macro) && !through? && !class_has_foreign_key?(associated_class) end def through_association_valid? @through.nil? || (through_association_exists? && through_association_correct?) end def through_association_exists? if through_reflection.nil? @missing = "#{model_class.name} does not have any relationship to #{@through}" false else true end end def through_association_correct? if @through == reflection.options[:through] true else @missing = "Expected #{model_class.name} to have #{@name} through #{@through}, " << "but got it through #{reflection.options[:through]}" false end end def dependent_correct? if @dependent.nil? || @dependent.to_s == reflection.options[:dependent].to_s true else @missing = "#{@name} should have #{@dependent} dependency" false end end def join_table_exists? if @macro != :has_and_belongs_to_many || ::ActiveRecord::Base.connection.tables.include?(join_table.to_s) true else @missing = "join table #{join_table} doesn't exist" false end end def class_has_foreign_key?(klass) if klass.column_names.include?(foreign_key.to_s) true else @missing = "#{klass} does not have a #{foreign_key} foreign key." false end end def model_class @subject.class end def join_table reflection.options[:join_table] end def associated_class reflection.klass end def foreign_key reflection.primary_key_name end def through? reflection.options[:through] end def reflection @reflection ||= model_class.reflect_on_association(@name) end def through_reflection @through_reflection ||= model_class.reflect_on_association(@through) end def expectation "#{model_class.name} to have a #{@macro} association called #{@name}" end def macro_description case @macro.to_s when 'belongs_to' then 'belong to' when 'has_many' then 'have many' when 'has_one' then 'have one' when 'has_and_belongs_to_many' then 'have and belong to many' end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/ensure_inclusion_of_matcher.rb000066400000000000000000000043421154044152300337740ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensure that the attribute's value is in the range specified # # Options: # * in_range - the range of allowed values for this attribute # * with_low_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :inclusion. # * with_high_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :inclusion. # # Example: # it { should ensure_inclusion_of(:age).in_range(0..100) } # def ensure_inclusion_of(attr) EnsureInclusionOfMatcher.new(attr) end class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc: def in_range(range) @range = range @minimum = range.first @maximum = range.last self end def with_message(message) if message @low_message = message @high_message = message end self end def with_low_message(message) @low_message = message if message self end def with_high_message(message) @high_message = message if message self end def description "ensure inclusion of #{@attribute} in #{@range.inspect}" end def matches?(subject) super(subject) @low_message ||= :inclusion @high_message ||= :inclusion disallows_lower_value && allows_minimum_value && disallows_higher_value && allows_maximum_value end private def disallows_lower_value @minimum == 0 || disallows_value_of(@minimum - 1, @low_message) end def disallows_higher_value disallows_value_of(@maximum + 1, @high_message) end def allows_minimum_value allows_value_of(@minimum, @low_message) end def allows_maximum_value allows_value_of(@maximum, @high_message) end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/ensure_length_of_matcher.rb000066400000000000000000000104641154044152300332540ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the length of the attribute is validated. # # Options: # * is_at_least - minimum length of this attribute # * is_at_most - maximum length of this attribute # * is_equal_to - exact requred length of this attribute # * with_short_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :too_short. # * with_long_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :too_long. # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :wrong_length. Used in conjunction with # is_equal_to. # # Examples: # it { should ensure_length_of(:password). # is_at_least(6). # is_at_most(20) } # it { should ensure_length_of(:name). # is_at_least(3). # with_short_message(/not long enough/) } # it { should ensure_length_of(:ssn). # is_equal_to(9). # with_message(/is invalid/) } def ensure_length_of(attr) EnsureLengthOfMatcher.new(attr) end class EnsureLengthOfMatcher < ValidationMatcher # :nodoc: include Helpers def is_at_least(length) @minimum = length @short_message ||= :too_short self end def is_at_most(length) @maximum = length @long_message ||= :too_long self end def is_equal_to(length) @minimum = length @maximum = length @short_message ||= :wrong_length self end def with_short_message(message) @short_message = message if message self end alias_method :with_message, :with_short_message def with_long_message(message) @long_message = message if message self end def description description = "ensure #{@attribute} has a length " if @minimum && @maximum if @minimum == @maximum description << "of exactly #{@minimum}" else description << "between #{@minimum} and #{@maximum}" end else description << "of at least #{@minimum}" if @minimum description << "of at most #{@maximum}" if @maximum end description end def matches?(subject) super(subject) translate_messages! disallows_lower_length && allows_minimum_length && ((@minimum == @maximum) || (disallows_higher_length && allows_maximum_length)) end private def translate_messages! if Symbol === @short_message @short_message = default_error_message(@short_message, :count => @minimum) end if Symbol === @long_message @long_message = default_error_message(@long_message, :count => @maximum) end end def disallows_lower_length @minimum == 0 || @minimum.nil? || disallows_length_of(@minimum - 1, @short_message) end def disallows_higher_length @maximum.nil? || disallows_length_of(@maximum + 1, @long_message) end def allows_minimum_length allows_length_of(@minimum, @short_message) end def allows_maximum_length allows_length_of(@maximum, @long_message) end def allows_length_of(length, message) length.nil? || allows_value_of(string_of_length(length), message) end def disallows_length_of(length, message) length.nil? || disallows_value_of(string_of_length(length), message) end def string_of_length(length) 'x' * length end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/have_db_column_matcher.rb000066400000000000000000000117231154044152300326720ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures the database column exists. # # Options: # * of_type - db column type (:integer, :string, etc.) # * with_options - same options available in migrations # (:default, :null, :limit, :precision, :scale) # # Examples: # it { should_not have_db_column(:admin).of_type(:boolean) } # it { should have_db_column(:salary). # of_type(:decimal). # with_options(:precision => 10, :scale => 2) } # def have_db_column(column) HaveDbColumnMatcher.new(:have_db_column, column) end class HaveDbColumnMatcher # :nodoc: def initialize(macro, column) @macro = macro @column = column end def of_type(column_type) @column_type = column_type self end def with_options(opts = {}) @precision = opts[:precision] @limit = opts[:limit] @default = opts[:default] @null = opts[:null] @scale = opts[:scale] self end def matches?(subject) @subject = subject column_exists? && correct_column_type? && correct_precision? && correct_limit? && correct_default? && correct_null? && correct_scale? end def failure_message "Expected #{expectation} (#{@missing})" end def negative_failure_message "Did not expect #{expectation}" end def description desc = "have db column named #{@column}" desc << " of type #{@column_type}" unless @column_type.nil? desc << " of precision #{@precision}" unless @precision.nil? desc << " of limit #{@limit}" unless @limit.nil? desc << " of default #{@default}" unless @default.nil? desc << " of null #{@null}" unless @null.nil? desc << " of primary #{@primary}" unless @primary.nil? desc << " of scale #{@scale}" unless @scale.nil? desc end protected def column_exists? if model_class.column_names.include?(@column.to_s) true else @missing = "#{model_class} does not have a db column named #{@column}." false end end def correct_column_type? return true if @column_type.nil? if matched_column.type.to_s == @column_type.to_s true else @missing = "#{model_class} has a db column named #{@column} " << "of type #{matched_column.type}, not #{@column_type}." false end end def correct_precision? return true if @precision.nil? if matched_column.precision.to_s == @precision.to_s true else @missing = "#{model_class} has a db column named #{@column} " << "of precision #{matched_column.precision}, " << "not #{@precision}." false end end def correct_limit? return true if @limit.nil? if matched_column.limit.to_s == @limit.to_s true else @missing = "#{model_class} has a db column named #{@column} " << "of limit #{matched_column.limit}, " << "not #{@limit}." false end end def correct_default? return true if @default.nil? if matched_column.default.to_s == @default.to_s true else @missing = "#{model_class} has a db column named #{@column} " << "of default #{matched_column.default}, " << "not #{@default}." false end end def correct_null? return true if @null.nil? if matched_column.null.to_s == @null.to_s true else @missing = "#{model_class} has a db column named #{@column} " << "of null #{matched_column.null}, " << "not #{@null}." false end end def correct_scale? return true if @scale.nil? if matched_column.scale.to_s == @scale.to_s true else @missing = "#{model_class} has a db column named #{@column} " << "of scale #{matched_column.scale}, not #{@scale}." false end end def matched_column model_class.columns.detect { |each| each.name == @column.to_s } end def model_class @subject.class end def expectation expected = "#{model_class.name} to #{description}" end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/have_db_index_matcher.rb000066400000000000000000000053631154044152300325070ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that there are DB indices on the given columns or tuples of # columns. # # Options: # * unique - whether or not the index has a unique # constraint. Use true to explicitly test for a unique # constraint. Use false to explicitly test for a non-unique # constraint. Use nil if you don't care whether the index is # unique or not. Default = nil # # Examples: # # it { should have_db_index(:age) } # it { should have_db_index([:commentable_type, :commentable_id]) } # it { should have_db_index(:ssn).unique(true) } # def have_db_index(columns) HaveDbIndexMatcher.new(:have_index, columns) end class HaveDbIndexMatcher # :nodoc: def initialize(macro, columns) @macro = macro @columns = normalize_columns_to_array(columns) end def unique(unique) @unique = unique self end def matches?(subject) @subject = subject index_exists? && correct_unique? end def failure_message "Expected #{expectation} (#{@missing})" end def negative_failure_message "Did not expect #{expectation}" end def description "have a #{index_type} index on columns #{@columns.join(' and ')}" end protected def index_exists? ! matched_index.nil? end def correct_unique? return true if @unique.nil? if matched_index.unique == @unique true else @missing = "#{table_name} has an index named #{matched_index.name} " << "of unique #{matched_index.unique}, not #{@unique}." false end end def matched_index indexes.detect { |each| each.columns == @columns } end def model_class @subject.class end def table_name model_class.table_name end def indexes ::ActiveRecord::Base.connection.indexes(table_name) end def expectation expected = "#{model_class.name} to #{description}" end def index_type case @unique when nil '' when false 'non-unique' else 'unique' end end def normalize_columns_to_array(columns) if columns.class == Array columns.collect { |each| each.to_s } else [columns.to_s] end end end end end end have_readonly_attribute_matcher.rb000066400000000000000000000027571154044152300345600ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_recordmodule Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the attribute cannot be changed once the record has been # created. # # it { should have_readonly_attributes(:password) } # def have_readonly_attribute(value) HaveReadonlyAttributeMatcher.new(value) end class HaveReadonlyAttributeMatcher # :nodoc: def initialize(attribute) @attribute = attribute.to_s end def matches?(subject) @subject = subject if readonly_attributes.include?(@attribute) @negative_failure_message = "Did not expect #{@attribute} to be read-only" true else if readonly_attributes.empty? @failure_message = "#{class_name} attribute #{@attribute} " << "is not read-only" else @failure_message = "#{class_name} is making " << "#{readonly_attributes.to_a.to_sentence} " << "read-only, but not #{@attribute}." end false end end attr_reader :failure_message, :negative_failure_message def description "make #{@attribute} read-only" end private def readonly_attributes @readonly_attributes ||= (@subject.class.readonly_attributes || []) end def class_name @subject.class.name end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/helpers.rb000066400000000000000000000021241154044152300276570ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: module Helpers def pretty_error_messages(obj) # :nodoc: obj.errors.map do |a, m| msg = "#{a} #{m}" msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base end end # Helper method that determines the default error message used by Active # Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly # introduced I18n module used for localization. # # default_error_message(:blank) # default_error_message(:too_short, :count => 5) # default_error_message(:too_long, :count => 60) def default_error_message(key, values = {}) if Object.const_defined?(:I18n) # Rails >= 2.2 I18n.translate(:"activerecord.errors.messages.#{key}", {:default => :"errors.messages.#{key}"}.merge(values)) else # Rails <= 2.1.x ::ActiveRecord::Errors.default_error_messages[key] % values[:count] end end end end end end validate_acceptance_of_matcher.rb000066400000000000000000000020071154044152300342640ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_recordmodule Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the model cannot be saved the given attribute is not # accepted. # # Options: # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :accepted. # # Example: # it { should validate_acceptance_of(:eula) } # def validate_acceptance_of(attr) ValidateAcceptanceOfMatcher.new(attr) end class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc: def with_message(message) @expected_message = message if message self end def matches?(subject) super(subject) @expected_message ||= :accepted disallows_value_of(false, @expected_message) end def description "require #{@attribute} to be accepted" end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/validate_format_of_matcher.rb000066400000000000000000000035371154044152300335560ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the model is not valid if the given attribute is not # formatted correctly. # # Options: # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or String. # Defaults to the translation for :invalid. # * with(string to test against) # * not_with(string to test against) # # Examples: # it { should validate_format_of(:name). # with('12345'). # with_message(/is not optional/) } # it { should validate_format_of(:name). # not_with('12D45'). # with_message(/is not optional/) } # def validate_format_of(attr) ValidateFormatOfMatcher.new(attr) end class ValidateFormatOfMatcher < ValidationMatcher # :nodoc: def initialize(attribute) super end def with_message(message) @expected_message = message if message self end def with(value) raise "You may not call both with and not_with" if @value_to_fail @value_to_pass = value self end def not_with(value) raise "You may not call both with and not_with" if @value_to_pass @value_to_fail = value self end def matches?(subject) super(subject) @expected_message ||= :invalid return disallows_value_of(@value_to_fail, @expected_message) if @value_to_fail allows_value_of(@value_to_pass, @expected_message) if @value_to_pass end def description "#{@attribute} have a valid format" end end end end end validate_numericality_of_matcher.rb000066400000000000000000000017561154044152300347150ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_recordmodule Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensure that the attribute is numeric # # Options: # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or string. Defaults to the # translation for :not_a_number. # # Example: # it { should validate_numericality_of(:age) } # def validate_numericality_of(attr) ValidateNumericalityOfMatcher.new(attr) end class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc: def with_message(message) @expected_message = message if message self end def matches?(subject) super(subject) @expected_message ||= :not_a_number disallows_value_of('abcd', @expected_message) end def description "only allow numeric values for #{@attribute}" end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/validate_presence_of_matcher.rb000066400000000000000000000030641154044152300340650ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the model is not valid if the given attribute is not # present. # # Options: # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or String. # Defaults to the translation for :blank. # # Examples: # it { should validate_presence_of(:name) } # it { should validate_presence_of(:name). # with_message(/is not optional/) } # def validate_presence_of(attr) ValidatePresenceOfMatcher.new(attr) end class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc: def with_message(message) @expected_message = message if message self end def matches?(subject) super(subject) @expected_message ||= :blank disallows_value_of(blank_value, @expected_message) end def description "require #{@attribute} to be set" end private def blank_value if collection? [] else nil end end def collection? if @subject.class.respond_to?(:reflect_on_association) && reflection = @subject.class.reflect_on_association(@attribute) [:has_many, :has_and_belongs_to_many].include?(reflection.macro) else false end end end end end end validate_uniqueness_of_matcher.rb000066400000000000000000000107631154044152300344050ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_recordmodule Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: # Ensures that the model is invalid if the given attribute is not unique. # # Internally, this uses values from existing records to test validations, # so this will always fail if you have not saved at least one record for # the model being tested, like so: # # describe User do # before(:each) { User.create!(:email => 'address@example.com') } # it { should validate_uniqueness_of(:email) } # end # # Options: # # * with_message - value the test expects to find in # errors.on(:attribute). Regexp or String. # Defaults to the translation for :taken. # * scoped_to - field(s) to scope the uniqueness to. # * case_insensitive - ensures that the validation does not # check case. Off by default. Ignored by non-text attributes. # # Examples: # it { should validate_uniqueness_of(:keyword) } # it { should validate_uniqueness_of(:keyword).with_message(/dup/) } # it { should validate_uniqueness_of(:email).scoped_to(:name) } # it { should validate_uniqueness_of(:email). # scoped_to(:first_name, :last_name) } # it { should validate_uniqueness_of(:keyword).case_insensitive } # def validate_uniqueness_of(attr) ValidateUniquenessOfMatcher.new(attr) end class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc: include Helpers def initialize(attribute) @attribute = attribute end def scoped_to(*scopes) @scopes = [*scopes].flatten self end def with_message(message) @expected_message = message self end def case_insensitive @case_insensitive = true self end def description result = "require " result << "case sensitive " unless @case_insensitive result << "unique value for #{@attribute}" result << " scoped to #{@scopes.join(', ')}" unless @scopes.blank? result end def matches?(subject) @subject = subject.class.new @expected_message ||= :taken find_existing && set_scoped_attributes && validate_attribute && validate_after_scope_change end private def find_existing if @existing = @subject.class.find(:first) true else @failure_message = "Can't find first #{class_name}" false end end def set_scoped_attributes unless @scopes.blank? @scopes.each do |scope| setter = :"#{scope}=" unless @subject.respond_to?(setter) @failure_message = "#{class_name} doesn't seem to have a #{scope} attribute." return false end @subject.send("#{scope}=", @existing.send(scope)) end end true end def validate_attribute disallows_value_of(existing_value, @expected_message) end # TODO: There is a chance that we could change the scoped field # to a value that's already taken. An alternative implementation # could actually find all values for scope and create a unique def validate_after_scope_change if @scopes.blank? true else @scopes.all? do |scope| previous_value = @existing.send(scope) # Assume the scope is a foreign key if the field is nil previous_value ||= 0 next_value = previous_value.next @subject.send("#{scope}=", next_value) if allows_value_of(existing_value, @expected_message) @negative_failure_message << " (with different value of #{scope})" true else @failure_message << " (with different value of #{scope})" false end end end end def class_name @subject.class.name end def existing_value value = @existing.send(@attribute) value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!) value end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/active_record/validation_matcher.rb000066400000000000000000000024521154044152300320560ustar00rootroot00000000000000module Shoulda # :nodoc: module Matchers module ActiveRecord # :nodoc: class ValidationMatcher # :nodoc: attr_reader :failure_message def initialize(attribute) @attribute = attribute end def negative_failure_message @negative_failure_message || @failure_message end def matches?(subject) @subject = subject false end private def allows_value_of(value, message = nil) allow = AllowValueMatcher. new(value). for(@attribute). with_message(message) if allow.matches?(@subject) @negative_failure_message = allow.failure_message true else @failure_message = allow.negative_failure_message false end end def disallows_value_of(value, message = nil) disallow = AllowValueMatcher. new(value). for(@attribute). with_message(message) if disallow.matches?(@subject) @failure_message = disallow.negative_failure_message false else @negative_failure_message = disallow.failure_message true end end end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/assertion_error.rb000066400000000000000000000004101154044152300266200ustar00rootroot00000000000000module Shoulda module Matchers if RUBY_VERSION > "1.9" require 'minitest/unit' AssertionError = MiniTest::Assertion else require 'test/unit/assertionfailederror' AssertionError = Test::Unit::AssertionFailedError end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/integrations/000077500000000000000000000000001154044152300255665ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/integrations/rspec.rb000066400000000000000000000011331154044152300272250ustar00rootroot00000000000000# :enddoc: if defined?(::ActiveRecord) require 'shoulda/matchers/active_record' module RSpec::Matchers include Shoulda::Matchers::ActiveRecord end end if defined?(::ActionController) require 'shoulda/matchers/action_controller' module RSpec module Rails module ControllerExampleGroup include Shoulda::Matchers::ActionController end end end end if defined?(::ActionMailer) require 'shoulda/matchers/action_mailer' module RSpec module Rails module MailerExampleGroup include Shoulda::Matchers::ActionMailer end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/integrations/test_unit.rb000066400000000000000000000013741154044152300301360ustar00rootroot00000000000000# :enddoc: if defined?(ActionController) require 'shoulda/matchers/action_controller' class ActionController::TestCase include Shoulda::Matchers::ActionController extend Shoulda::Matchers::ActionController def subject @controller end end end if defined?(ActionMailer) require 'shoulda/matchers/action_mailer' module Test module Unit class TestCase include Shoulda::Matchers::ActionMailer extend Shoulda::Matchers::ActionMailer end end end end if defined?(ActiveRecord) require 'shoulda/matchers/active_record' module Test module Unit class TestCase include Shoulda::Matchers::ActiveRecord extend Shoulda::Matchers::ActiveRecord end end end end ruby-shoulda-matchers-1.0.0~beta2/lib/shoulda/matchers/version.rb000066400000000000000000000001131154044152300250650ustar00rootroot00000000000000module Shoulda module Matchers VERSION = "1.0.0.beta2".dup end end ruby-shoulda-matchers-1.0.0~beta2/shoulda-matchers.gemspec000066400000000000000000000027271154044152300236650ustar00rootroot00000000000000$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') require 'shoulda/matchers/version' Gem::Specification.new do |s| s.name = %q{shoulda-matchers} s.version = Shoulda::Matchers::VERSION.dup s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Tammer Saleh", "Joe Ferris", "Ryan McGeary", "Dan Croak", "Matt Jankowski"] s.date = Time.now.strftime("%Y-%m-%d") s.email = %q{support@thoughtbot.com} s.extra_rdoc_files = ["README.rdoc", "CONTRIBUTION_GUIDELINES.rdoc"] s.files = Dir["[A-Z]*", "{bin,lib,rails,test}/**/*"] s.homepage = %q{http://thoughtbot.com/community/} s.rdoc_options = ["--line-numbers", "--main", "README.rdoc"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.5} s.summary = %q{Making tests easy on the fingers and eyes} s.description = %q{Making tests easy on the fingers and eyes} s.add_development_dependency("rails", "3.0.3") s.add_development_dependency("sqlite3-ruby", "~> 1.3.2") s.add_development_dependency("mocha", "~> 0.9.10") s.add_development_dependency("rspec-rails", "~> 2.3.0") s.add_development_dependency("cucumber", "~> 0.10.0") s.add_development_dependency("aruba", "~> 0.2.7") if RUBY_VERSION >= "1.9" s.add_development_dependency("ruby-debug19", "~> 0.11.6") else s.add_development_dependency("ruby-debug", "~> 0.10.4") end if s.respond_to? :specification_version then s.specification_version = 3 else end end ruby-shoulda-matchers-1.0.0~beta2/spec/000077500000000000000000000000001154044152300177775ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/000077500000000000000000000000001154044152300216505ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/addresses.yml000066400000000000000000000000611154044152300243450ustar00rootroot00000000000000first: title: Home addressable: first (User) ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/friendships.yml000066400000000000000000000000001154044152300246770ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/posts.yml000066400000000000000000000001331154044152300235400ustar00rootroot00000000000000first: id: 1 title: My Cute Kitten! body: This is totally a cute kitten user_id: 1 ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/products.yml000066400000000000000000000000001154044152300242240ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/taggings.yml000066400000000000000000000000001154044152300241640ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/tags.yml000066400000000000000000000001311154044152300233240ustar00rootroot00000000000000first: id: 1 name: Stuff second: id: 2 name: Rails third: id: 3 name: Nothingruby-shoulda-matchers-1.0.0~beta2/spec/fixtures/users.yml000066400000000000000000000001221154044152300235270ustar00rootroot00000000000000first: id: 1 name: Some dude age: 2 email: none@none.com ssn: 123456789 ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/000077500000000000000000000000001154044152300222375ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/.gitignore000066400000000000000000000000501154044152300242220ustar00rootroot00000000000000.bundle db/*.sqlite3 log/*.log tmp/**/* ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/Gemfile000066400000000000000000000002231154044152300235270ustar00rootroot00000000000000source 'http://rubygems.org' gem 'rails', '3.0.0.beta4' gem 'sqlite3-ruby', :require => 'sqlite3' gem 'mocha' gem 'rspec-rails', '2.0.0.beta.12' ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/Gemfile.lock000066400000000000000000000045121154044152300244630ustar00rootroot00000000000000GEM remote: http://rubygems.org/ specs: abstract (1.0.0) actionmailer (3.0.0.beta4) actionpack (= 3.0.0.beta4) mail (~> 2.2.3) actionpack (3.0.0.beta4) activemodel (= 3.0.0.beta4) activesupport (= 3.0.0.beta4) builder (~> 2.1.2) erubis (~> 2.6.5) i18n (~> 0.4.1) rack (~> 1.1.0) rack-mount (~> 0.6.3) rack-test (~> 0.5.4) tzinfo (~> 0.3.16) activemodel (3.0.0.beta4) activesupport (= 3.0.0.beta4) builder (~> 2.1.2) i18n (~> 0.4.1) activerecord (3.0.0.beta4) activemodel (= 3.0.0.beta4) activesupport (= 3.0.0.beta4) arel (~> 0.4.0) tzinfo (~> 0.3.16) activeresource (3.0.0.beta4) activemodel (= 3.0.0.beta4) activesupport (= 3.0.0.beta4) activesupport (3.0.0.beta4) arel (0.4.0) activesupport (>= 3.0.0.beta) builder (2.1.2) diff-lcs (1.1.2) erubis (2.6.6) abstract (>= 1.0.0) i18n (0.4.1) mail (2.2.12) activesupport (>= 2.3.6) i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.16) mocha (0.9.10) rake nokogiri (1.4.4) polyglot (0.3.1) rack (1.1.0) rack-mount (0.6.13) rack (>= 1.0.0) rack-test (0.5.6) rack (>= 1.0) rails (3.0.0.beta4) actionmailer (= 3.0.0.beta4) actionpack (= 3.0.0.beta4) activerecord (= 3.0.0.beta4) activeresource (= 3.0.0.beta4) activesupport (= 3.0.0.beta4) bundler (>= 0.9.26) railties (= 3.0.0.beta4) railties (3.0.0.beta4) actionpack (= 3.0.0.beta4) activesupport (= 3.0.0.beta4) rake (>= 0.8.3) thor (~> 0.13.6) rake (0.8.7) rspec (2.0.0.beta.12) rspec-core (= 2.0.0.beta.12) rspec-expectations (= 2.0.0.beta.12) rspec-mocks (= 2.0.0.beta.12) rspec-core (2.0.0.beta.12) rspec-expectations (2.0.0.beta.12) diff-lcs (>= 1.1.2) rspec-mocks (2.0.0.beta.12) rspec-rails (2.0.0.beta.12) rspec (= 2.0.0.beta.12) webrat (>= 0.7.0) sqlite3-ruby (1.3.2) thor (0.13.8) treetop (1.4.9) polyglot (>= 0.3.1) tzinfo (0.3.23) webrat (0.7.2) nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) PLATFORMS ruby DEPENDENCIES mocha rails (= 3.0.0.beta4) rspec-rails (= 2.0.0.beta.12) sqlite3-ruby ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/README000066400000000000000000000234301154044152300231210ustar00rootroot00000000000000== Welcome to Rails Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Control pattern. This pattern splits the view (also called the presentation) into "dumb" templates that are primarily responsible for inserting pre-built data in between HTML tags. The model contains the "smart" domain objects (such as Account, Product, Person, Post) that holds all the business logic and knows how to persist themselves to a database. The controller handles the incoming requests (such as Save New Account, Update Product, Show Post) by manipulating the model and directing data to the view. In Rails, the model is handled by what's called an object-relational mapping layer entitled Active Record. This layer allows you to present the data from database rows as objects and embellish these data objects with business logic methods. You can read more about Active Record in link:files/vendor/rails/activerecord/README.html. The controller and view are handled by the Action Pack, which handles both layers by its two parts: Action View and Action Controller. These two layers are bundled in a single package due to their heavy interdependence. This is unlike the relationship between the Active Record and Action Pack that is much more separate. Each of these packages can be used independently outside of Rails. You can read more about Action Pack in link:files/vendor/rails/actionpack/README.html. == Getting Started 1. At the command prompt, start a new Rails application using the rails command and your application name. Ex: rails myapp 2. Change directory into myapp and start the web server: rails server (run with --help for options) 3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" 4. Follow the guidelines to start developing your application == Web Servers By default, Rails will try to use Mongrel if it's installed when started with rails server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails with a variety of other web servers. Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is suitable for development and deployment of Rails applications. If you have Ruby Gems installed, getting up and running with mongrel is as easy as: gem install mongrel. More info at: http://mongrel.rubyforge.org Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. == Apache .htaccess example for FCGI/CGI # General Apache options AddHandler fastcgi-script .fcgi AddHandler cgi-script .cgi Options +FollowSymLinks +ExecCGI # If you don't want Rails to look in certain directories, # use the following rewrite rules so that Apache won't rewrite certain requests # # Example: # RewriteCond %{REQUEST_URI} ^/notrails.* # RewriteRule .* - [L] # Redirect all requests not available on the filesystem to Rails # By default the cgi dispatcher is used which is very slow # # For better performance replace the dispatcher with the fastcgi one # # Example: # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteEngine On # If your Rails application is accessed via an Alias directive, # then you MUST also set the RewriteBase in this htaccess file. # # Example: # Alias /myrailsapp /path/to/myrailsapp/public # RewriteBase /myrailsapp RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ dispatch.cgi [QSA,L] # In case Rails experiences terminal errors # Instead of displaying this message you can supply a file here which will be rendered instead # # Example: # ErrorDocument 500 /500.html ErrorDocument 500 "

Application error

Rails application failed to start properly" == Debugging Rails Sometimes your application goes wrong. Fortunately there are a lot of tools that will help you debug it and get it back on the rails. First area to check is the application log files. Have "tail -f" commands running on the server.log and development.log. Rails will automatically display debugging and runtime information to these files. Debugging info will also be shown in the browser on requests from 127.0.0.1. You can also log your own messages directly into the log file from your code using the Ruby logger class from inside your controllers. Example: class WeblogController < ActionController::Base def destroy @weblog = Weblog.find(params[:id]) @weblog.destroy logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") end end The result will be a message in your log file along the lines of: Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 More information on how to use the logger is at http://www.ruby-doc.org/core/ Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: * The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) These two online (and free) books will bring you up to speed on the Ruby language and also on programming in general. == Debugger Debugger support is available through the debugger command when you start your Mongrel or Webrick server with --debugger. This means that you can break out of execution at any point in the code, investigate and change the model, AND then resume execution! You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' Example: class WeblogController < ActionController::Base def index @posts = Post.find(:all) debugger end end So the controller will accept the action, run the first line, then present you with a IRB prompt in the server window. Here you can do things like: >> @posts.inspect => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" >> @posts.first.title = "hello from a debugger" => "hello from a debugger" ...and even better is that you can examine how your runtime objects actually work: >> f = @posts.first => #nil, "body"=>nil, "id"=>"1"}> >> f. Display all 152 possibilities? (y or n) Finally, when you're ready to resume execution, you enter "cont" == Console You can interact with the domain model by starting the console through rails console. Here you'll have all parts of the application configured, just like it is when the application is running. You can inspect domain models, change values, and save to the database. Starting the script without arguments will launch it in the development environment. Passing an argument will specify a different environment, like rails console production. To reload your controllers and models after launching the console run reload! == dbconsole You can go to the command line of your database directly through rails dbconsole. You would be connected to the database with the credentials defined in database.yml. Starting the script without arguments will connect you to the development database. Passing an argument will connect you to a different database, like rails dbconsole production. Currently works for mysql, postgresql and sqlite. == Description of Contents app Holds all the code that's specific to this particular application. app/controllers Holds controllers that should be named like weblogs_controller.rb for automated URL mapping. All controllers should descend from ApplicationController which itself descends from ActionController::Base. app/models Holds models that should be named like post.rb. Most models will descend from ActiveRecord::Base. app/views Holds the template files for the view that should be named like weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby syntax. app/views/layouts Holds the template files for layouts to be used with views. This models the common header/footer method of wrapping views. In your views, define a layout using the layout :default and create a file named default.html.erb. Inside default.html.erb, call <% yield %> to render the view using this layout. app/helpers Holds view helpers that should be named like weblogs_helper.rb. These are generated for you automatically when using rails generate for controllers. Helpers can be used to wrap functionality for your views into methods. config Configuration files for the Rails environment, the routing map, the database, and other dependencies. db Contains the database schema in schema.rb. db/migrate contains all the sequence of Migrations for your schema. doc This directory is where your application documentation will be stored when generated using rake doc:app lib Application specific libraries. Basically, any kind of custom code that doesn't belong under controllers, models, or helpers. This directory is in the load path. public The directory available for the web server. Contains subdirectories for images, stylesheets, and javascripts. Also contains the dispatchers and the default HTML files. This should be set as the DOCUMENT_ROOT of your web server. script Helper scripts for automation and generation. test Unit and functional tests along with fixtures. When using the rails generate command, template test files will be generated for you and placed in this directory. vendor External libraries that the application depends on. Also includes the plugins subdirectory. If the app has frozen rails, those gems also go here, under vendor/rails/. This directory is in the load path. ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/Rakefile000066400000000000000000000004721154044152300237070ustar00rootroot00000000000000# Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) require 'rake' require 'rake/testtask' require 'rake/rdoctask' Rails::Application.load_tasks ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/000077500000000000000000000000001154044152300230175ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/controllers/000077500000000000000000000000001154044152300253655ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/controllers/application_controller.rb000066400000000000000000000013221154044152300324560ustar00rootroot00000000000000# Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base def ensure_logged_in unless session[:logged_in] respond_to do |accepts| accepts.html do flash[:error] = 'What do you think you\'re doing?' redirect_to '/' end accepts.xml do headers["Status"] = "Unauthorized" headers["WWW-Authenticate"] = %(Basic realm="Web Password") render :text => "Couldn't authenticate you", :status => '401 Unauthorized' end end return false end return true end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/controllers/posts_controller.rb000066400000000000000000000040461154044152300313310ustar00rootroot00000000000000class PostsController < ApplicationController before_filter :ensure_logged_in before_filter :load_user def index @posts = @user.posts respond_to do |format| format.html # index.rhtml format.xml { render :xml => @posts.to_xml } format.rss do headers['Content-Type'] = 'application/rss+xml' session[:special] = '$2 off your next purchase' session[:special_user_id] = @user.id session[:false_var] = false head :ok end end end def show @post = @user.posts.find(params[:id]) @false_flag = false respond_to do |format| format.html { render :layout => 'wide' } format.xml { render :xml => @post.to_xml } end end def new @post = @user.posts.build render :layout => false end def edit @post = @user.posts.find(params[:id]) end def create @post = @user.posts.build(params[:post]) respond_to do |format| if @post.save flash[:notice] = 'Post was successfully created.' format.html { redirect_to user_post_url(@post.user, @post) } format.xml { head :created, :location => user_post_url(@post.user, @post) } else format.html { render :action => "new" } format.xml { render :xml => @post.errors.to_xml } end end end def update @post = @user.posts.find(params[:id]) respond_to do |format| if @post.update_attributes(params[:post]) flash[:notice] = 'Post was successfully updated.' format.html { redirect_to user_post_url(@post.user, @post) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @post.errors.to_xml } end end end def destroy @post = @user.posts.find(params[:id]) @post.destroy flash[:notice] = "Post was removed" respond_to do |format| format.html { redirect_to user_posts_url(@post.user) } format.xml { head :ok } end end private def load_user @user = User.find(params[:user_id]) end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/controllers/users_controller.rb000066400000000000000000000033461154044152300313240ustar00rootroot00000000000000class UsersController < ApplicationController # GET /users # GET /users.xml def index @users = User.find(:all) respond_to do |format| format.html # index.rhtml format.xml { render :xml => @users.to_xml } end end # GET /users/1 # GET /users/1.xml def show @user = User.find(params[:id]) respond_to do |format| format.html # show.rhtml format.xml { render :xml => @user.to_xml } end end # GET /users/new def new @user = User.new end # GET /users/1;edit def edit @user = User.find(params[:id]) end # POST /users # POST /users.xml def create @user = User.new(params[:user]) respond_to do |format| if @user.save flash[:notice] = 'User was successfully created.' format.html { redirect_to user_url(@user) } format.xml { head :created, :location => user_url(@user) } else format.html { render :action => "new" } format.xml { render :xml => @user.errors.to_xml } end end end # PUT /users/1 # PUT /users/1.xml def update @user = User.find(params[:id]) respond_to do |format| if @user.update_attributes(params[:user]) flash[:notice] = 'User was successfully updated.' format.html { redirect_to user_url(@user) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @user.errors.to_xml } end end end # DELETE /users/1 # DELETE /users/1.xml def destroy @user = User.find(params[:id]) @user.destroy flash[:notice] = "User was removed" respond_to do |format| format.html { redirect_to users_url } format.xml { head :ok } end end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/helpers/000077500000000000000000000000001154044152300244615ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/helpers/application_helper.rb000066400000000000000000000000351154044152300306460ustar00rootroot00000000000000module ApplicationHelper end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/000077500000000000000000000000001154044152300243025ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/address.rb000066400000000000000000000003701154044152300262540ustar00rootroot00000000000000class Address < ActiveRecord::Base belongs_to :addressable, :polymorphic => true validates_uniqueness_of :title, :scope => [:addressable_type, :addressable_id] validates_length_of :zip, :minimum => 5 validates_numericality_of :zip end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/flea.rb000066400000000000000000000002601154044152300255340ustar00rootroot00000000000000class Flea < ActiveRecord::Base has_and_belongs_to_many :dogs after_create :send_notification private def send_notification Notifier.the_email.deliver end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/friendship.rb000066400000000000000000000001511154044152300267570ustar00rootroot00000000000000class Friendship < ActiveRecord::Base belongs_to :user belongs_to :friend, :class_name => "User" end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/notifier.rb000066400000000000000000000003241154044152300264450ustar00rootroot00000000000000class Notifier < ActionMailer::Base def the_email from "do-not-reply@example.com" recipients "myself@me.com" subject "This is spam" body :body => "Every email is spam." end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/pets/000077500000000000000000000000001154044152300252555ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/pets/cat.rb000066400000000000000000000002701154044152300263500ustar00rootroot00000000000000module Pets class Cat < ActiveRecord::Base belongs_to :owner, :class_name => 'User' belongs_to :address, :dependent => :destroy validates_presence_of :owner_id end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/pets/dog.rb000066400000000000000000000004351154044152300263550ustar00rootroot00000000000000module Pets class Dog < ActiveRecord::Base belongs_to :user, :foreign_key => :owner_id belongs_to :address, :dependent => :destroy has_many :treats has_and_belongs_to_many :fleas validates_presence_of :treats, :fleas validates_presence_of :owner_id end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/post.rb000066400000000000000000000006341154044152300256170ustar00rootroot00000000000000class Post < ActiveRecord::Base belongs_to :user belongs_to :owner, :foreign_key => :user_id, :class_name => 'User' has_many :taggings has_many :tags, :through => :taggings has_many :through_tags, :through => :taggings, :source => :tag validates_uniqueness_of :title validates_presence_of :title validates_presence_of :body, :message => 'Seriously, wtf' validates_numericality_of :user_id end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/product.rb000066400000000000000000000010451154044152300263070ustar00rootroot00000000000000class Product < ActiveRecord::Base validates_presence_of :title validates_inclusion_of :price, :in => 0..99, :unless => :tangible validates_format_of :size, :with => /^\d+\D+$/, :unless => :tangible validates_presence_of :price, :if => :tangible validates_inclusion_of :price, :in => 1..9999, :if => :tangible validates_inclusion_of :weight, :in => 1..100, :if => :tangible validates_format_of :size, :with => /.+x.+x.+/, :if => :tangible validates_length_of :size, :in => 5..20, :if => :tangible end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/profile.rb000066400000000000000000000000471154044152300262700ustar00rootroot00000000000000class Profile < ActiveRecord::Base end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/registration.rb000066400000000000000000000000541154044152300273400ustar00rootroot00000000000000class Registration < ActiveRecord::Base end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/tag.rb000066400000000000000000000003001154044152300253730ustar00rootroot00000000000000class Tag < ActiveRecord::Base has_many :taggings, :dependent => :destroy has_many :posts, :through => :taggings validates_length_of :name, :minimum => 2 attr_accessible :name end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/tagging.rb000066400000000000000000000001141154044152300262430ustar00rootroot00000000000000class Tagging < ActiveRecord::Base belongs_to :post belongs_to :tag end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/treat.rb000066400000000000000000000000471154044152300257470ustar00rootroot00000000000000class Treat < ActiveRecord::Base endruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/models/user.rb000066400000000000000000000021741154044152300256110ustar00rootroot00000000000000class User < ActiveRecord::Base has_many :posts has_many :dogs, :foreign_key => :owner_id, :class_name => "Pets::Dog" has_many :cats, :foreign_key => :owner_id, :class_name => "Pets::Cat" has_many :friendships has_many :friends, :through => :friendships has_one :address, :as => :addressable, :dependent => :destroy has_one :registration has_one :profile, :through => :registration scope :old, :conditions => "age > 50" scope :eighteen, :conditions => { :age => 18 } scope :recent, lambda {|count| { :limit => count } } def self.recent_via_method(count) scoped(:limit => count) end attr_protected :password attr_readonly :name validates_format_of :email, :with => /\w*@\w*.com/ validates_length_of :email, :in => 1..100 validates_numericality_of :age, :greater_than_or_equal_to => 1, :less_than_or_equal_to => 100 validates_acceptance_of :eula validates_uniqueness_of :email, :scope => :name, :case_sensitive => false validates_length_of :ssn, :is => 9, :message => "Social Security Number is not the right length" validates_numericality_of :ssn end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/000077500000000000000000000000001154044152300241545ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/layouts/000077500000000000000000000000001154044152300256545ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/layouts/application.html.erb000066400000000000000000000003131154044152300316110ustar00rootroot00000000000000 Rails3Root <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <%= yield %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/layouts/posts.rhtml000066400000000000000000000011031154044152300300670ustar00rootroot00000000000000 Posts: <%= controller.action_name %> <%= stylesheet_link_tag 'scaffold' %>

<%= flash[:notice] %>

<%= yield %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/layouts/users.rhtml000066400000000000000000000007241154044152300300700ustar00rootroot00000000000000 Users: <%= controller.action_name %> <%= stylesheet_link_tag 'scaffold' %>

<%= flash[:notice] %>

<%= yield %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/layouts/wide.html.erb000066400000000000000000000000311154044152300302330ustar00rootroot00000000000000<%= yield %>ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/notifier/000077500000000000000000000000001154044152300257735ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/notifier/the_email.html.erb000066400000000000000000000000151154044152300313530ustar00rootroot00000000000000<%= @body %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/posts/000077500000000000000000000000001154044152300253245ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/posts/edit.rhtml000066400000000000000000000010151154044152300273160ustar00rootroot00000000000000

Editing post

<%= error_messages_for :post %> <% form_for(:post, :url => user_post_path(@post.user, @post), :html => { :method => :put }) do |f| %>

User
<%= f.text_field :user_id %>

Title
<%= f.text_field :title %>

Body
<%= f.text_area :body %>

<%= submit_tag "Update" %>

<% end %> <%= link_to 'Show', user_post_path(@post.user, @post) %> | <%= link_to 'Back', user_posts_path(@post.user) %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/posts/index.rhtml000066400000000000000000000011411154044152300275000ustar00rootroot00000000000000

Listing posts

<% for post in @posts %> <% end %>
User Title Body
<%=h post.user_id %> <%=h post.title %> <%=h post.body %> <%= link_to 'Show', user_post_path(post.user, post) %> <%= link_to 'Edit', edit_user_post_path(post.user, post) %> <%= link_to 'Destroy', user_post_path(post.user, post), :confirm => 'Are you sure?', :method => :delete %>

<%= link_to 'New post', new_user_post_path(post.user) %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/posts/new.rhtml000066400000000000000000000006001154044152300271610ustar00rootroot00000000000000

New post

<%= form_for(:post, :url => user_posts_path(@user)) do |f| %>

User
<%= f.text_field :user_id %>

Title
<%= f.text_field :title %>

Body
<%= f.text_area :body %>

<%= submit_tag "Create" %>

<% end %> <%= link_to 'Back', user_posts_path(@user) %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/posts/show.rhtml000066400000000000000000000004031154044152300273510ustar00rootroot00000000000000

User: <%=h @post.user_id %>

Title: <%=h @post.title %>

Body: <%=h @post.body %>

<%= link_to 'Edit', edit_user_post_path(@post.user, @post) %> | <%= link_to 'Back', user_posts_path(@post.user) %> ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/users/000077500000000000000000000000001154044152300253155ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/users/edit.rhtml000066400000000000000000000006231154044152300273130ustar00rootroot00000000000000

Editing user

<%= error_messages_for :user %> <% form_for(:user, :url => user_path(@user), :html => { :method => :put }) do |f| %>

Email
<%= f.text_field :email %>

Age
<%= f.text_field :age %>

<%= submit_tag "Update" %>

<% end %> <%= link_to 'Show', user_path(@user) %> | <%= link_to 'Back', users_path %>ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/users/index.rhtml000066400000000000000000000007251154044152300275000ustar00rootroot00000000000000

Listing users

<% for user in @users %> <% end %>
Email Age
<%=h user.email %> <%=h user.age %> <%= link_to 'Show', user_path(user) %> <%= link_to 'Edit', edit_user_path(user) %> <%= link_to 'Destroy', user_path(user), :confirm => 'Are you sure?', :method => :delete %>

<%= link_to 'New user', new_user_path %>ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/users/new.rhtml000066400000000000000000000005011154044152300271520ustar00rootroot00000000000000

New user

<%= error_messages_for :user %> <% form_for(:user, :url => users_path) do |f| %>

Email
<%= f.text_field :email %>

Age
<%= f.text_field :age %>

<%= submit_tag "Create" %>

<% end %> <%= link_to 'Back', users_path %>ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/app/views/users/show.rhtml000066400000000000000000000002551154044152300273470ustar00rootroot00000000000000

Email: <%=h @user.email %>

Age: <%=h @user.age %>

<%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config.ru000066400000000000000000000002401154044152300240500ustar00rootroot00000000000000# This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) run Rails3Root::Application ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/000077500000000000000000000000001154044152300235045ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/application.rb000066400000000000000000000037461154044152300263460ustar00rootroot00000000000000require File.expand_path('../boot', __FILE__) require 'rails/all' # If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler) module Rails3Root class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Add additional load paths for your own custom dirs # config.load_paths += %W( #{config.root}/extras ) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector, :forum_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # Configure generators values. Many other options are available, be sure to check the documentation. # config.generators do |g| # g.orm :active_record # g.template_engine :erb # g.test_framework :test_unit, :fixture => true # end # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password, :ssn] end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/boot.rb000066400000000000000000000002341154044152300247730ustar00rootroot00000000000000require 'rubygems' # Set up gems listed in the Gemfile. if File.exist?(File.expand_path('../../Gemfile', __FILE__)) require 'bundler' Bundler.setup end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/database.yml000066400000000000000000000010321154044152300257670ustar00rootroot00000000000000# SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 pool: 5 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 pool: 5 timeout: 5000 ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/environment.rb000066400000000000000000000002321154044152300263720ustar00rootroot00000000000000# Load the rails application require File.expand_path('../application', __FILE__) # Initialize the rails application Rails3Root::Application.initialize! ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/environments/000077500000000000000000000000001154044152300262335ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/environments/development.rb000066400000000000000000000014131154044152300311010ustar00rootroot00000000000000Rails3Root::Application.configure do # Settings specified here will take precedence over those in config/environment.rb # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the webserver when you make code changes. config.cache_classes = false # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.consider_all_requests_local = true config.action_view.debug_rjs = true config.action_controller.perform_caching = false # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = false end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/environments/production.rb000066400000000000000000000027121154044152300307500ustar00rootroot00000000000000Rails3Root::Application.configure do # Settings specified here will take precedence over those in config/environment.rb # The production environment is meant for finished, "live" apps. # Code is not reloaded between requests config.cache_classes = true # Full error reports are disabled and caching is turned on config.consider_all_requests_local = false config.action_controller.perform_caching = true # Specifies the header that your server uses for sending files config.action_dispatch.x_sendfile_header = "X-Sendfile" # For nginx: # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # If you have no front-end server that supports something like X-Sendfile, # just comment this out and Rails will serve the files # See everything in the log (default is :info) # config.log_level = :debug # Use a different logger for distributed setups # config.logger = SyslogLogger.new # Use a different cache store in production # config.cache_store = :mem_cache_store # Disable Rails's static asset server # In production, Apache or nginx will already do this config.serve_static_assets = false # Enable serving of images, stylesheets, and javascripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" # Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false # Enable threaded mode # config.threadsafe! end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/environments/test.rb000066400000000000000000000026751154044152300275510ustar00rootroot00000000000000Rails3Root::Application.configure do # Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Use SQL instead of Active Record's schema dumper when creating the test database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql config.active_support.deprecation = :stderr end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/initializers/000077500000000000000000000000001154044152300262125ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/initializers/backtrace_silencers.rb000066400000000000000000000006241154044152300325270ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/initializers/inflections.rb000066400000000000000000000005711154044152300310570ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Add new inflection rules using the following format # (all these examples are active by default): # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/initializers/mime_types.rb000066400000000000000000000003151154044152300307110ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/initializers/secret_token.rb000066400000000000000000000007601154044152300312270ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Your secret key for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. Rails.application.config.secret_token = 'c6810f429ae9c783ed0283b376be29678ad11dcb7d6d2d97cae53dc2fd5e9af133303743764beae8862ba5928fa9289bb922d922948cfd3b8c2a336b29cfff8d' ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/initializers/session_store.rb000066400000000000000000000006251154044152300314410ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. Rails.application.config.session_store :cookie_store, :key => '_rails3_root_session' # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rake db:sessions:create") # Rails.application.config.session_store :active_record_store ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/locales/000077500000000000000000000000001154044152300251265ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/locales/en.yml000066400000000000000000000003251154044152300262530ustar00rootroot00000000000000# Sample localization file for English. Add more files in this directory for other locales. # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: hello: "Hello world" ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/config/routes.rb000066400000000000000000000000531154044152300253500ustar00rootroot00000000000000Rails3Root::Application.routes.draw do end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/000077500000000000000000000000001154044152300226245ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/000077500000000000000000000000001154044152300242545ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/001_create_users.rb000066400000000000000000000007131154044152300276460ustar00rootroot00000000000000class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.column :name, :string t.column :email, :string t.column :age, :integer t.column :ssn, :string t.column :phone, :string end add_index :users, :email, :unique => true add_index :users, :name add_index :users, :age add_index :users, [:email, :name], :unique => true end def self.down drop_table :users end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/002_create_posts.rb000066400000000000000000000003651154044152300276610ustar00rootroot00000000000000class CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.column :user_id, :integer t.column :title, :string t.column :body, :text end end def self.down drop_table :posts end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/003_create_taggings.rb000066400000000000000000000003441154044152300303120ustar00rootroot00000000000000class CreateTaggings < ActiveRecord::Migration def self.up create_table :taggings do |t| t.column :post_id, :integer t.column :tag_id, :integer end end def self.down drop_table :taggings end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/004_create_tags.rb000066400000000000000000000002631154044152300274460ustar00rootroot00000000000000class CreateTags < ActiveRecord::Migration def self.up create_table :tags do |t| t.column :name, :string end end def self.down drop_table :tags end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/005_create_dogs.rb000066400000000000000000000003351154044152300274450ustar00rootroot00000000000000class CreateDogs < ActiveRecord::Migration def self.up create_table :dogs do |t| t.column :owner_id, :integer t.column :address_id, :integer end end def self.down drop_table :dogs end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/006_create_addresses.rb000066400000000000000000000004631154044152300304710ustar00rootroot00000000000000class CreateAddresses < ActiveRecord::Migration def self.up create_table :addresses do |t| t.column :title, :string t.column :addressable_id, :integer t.column :addressable_type, :string t.column :zip, :string end end def self.down drop_table :addresses end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/007_create_fleas.rb000066400000000000000000000002551154044152300276060ustar00rootroot00000000000000class CreateFleas < ActiveRecord::Migration def self.up create_table :fleas do |t| t.string :name end end def self.down drop_table :fleas end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/008_create_dogs_fleas.rb000066400000000000000000000003451154044152300306230ustar00rootroot00000000000000class CreateDogsFleas < ActiveRecord::Migration def self.up create_table :dogs_fleas, :id => false do |t| t.integer :dog_id t.integer :flea_id end end def self.down drop_table :dogs_fleas end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/009_create_products.rb000066400000000000000000000004511154044152300303570ustar00rootroot00000000000000class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :title t.integer :price t.integer :weight t.string :size t.boolean :tangible t.timestamps end end def self.down drop_table :products end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/010_create_friendships.rb000066400000000000000000000003621154044152300310230ustar00rootroot00000000000000class CreateFriendships < ActiveRecord::Migration def self.up create_table :friendships do |t| t.integer :user_id t.integer :friend_id t.timestamps end end def self.down drop_table :friendships end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/011_create_treats.rb000066400000000000000000000003051154044152300300050ustar00rootroot00000000000000class CreateTreats < ActiveRecord::Migration def self.up create_table :treats do |t| t.integer :dog_id t.timestamps end end def self.down drop_table :treats end endruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/20090506203502_create_profiles.rb000066400000000000000000000002651154044152300314130ustar00rootroot00000000000000class CreateProfiles < ActiveRecord::Migration def self.up create_table :profiles do |t| t.timestamps end end def self.down drop_table :profiles end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/20090506203536_create_registrations.rb000066400000000000000000000003711154044152300324720ustar00rootroot00000000000000class CreateRegistrations < ActiveRecord::Migration def self.up create_table :registrations do |t| t.integer :user_id t.integer :profile_id t.timestamps end end def self.down drop_table :registrations end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/migrate/20090513104502_create_cats.rb000066400000000000000000000003351154044152300305160ustar00rootroot00000000000000class CreateCats < ActiveRecord::Migration def self.up create_table :cats do |t| t.column :owner_id, :integer t.column :address_id, :integer end end def self.down drop_table :cats end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/db/seeds.rb000066400000000000000000000005411154044152300242540ustar00rootroot00000000000000# This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). # # Examples: # # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) # Mayor.create(:name => 'Daley', :city => cities.first) ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/lib/000077500000000000000000000000001154044152300230055ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/lib/tasks/000077500000000000000000000000001154044152300241325ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/lib/tasks/.gitkeep000066400000000000000000000000001154044152300255510ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/000077500000000000000000000000001154044152300235155ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/404.html000066400000000000000000000013301154044152300247070ustar00rootroot00000000000000 The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/422.html000066400000000000000000000013071154044152300247130ustar00rootroot00000000000000 The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/500.html000066400000000000000000000013301154044152300247040ustar00rootroot00000000000000 We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

We've been notified about this issue and we'll take a look at it shortly.

ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/favicon.ico000066400000000000000000000000001154044152300256240ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/images/000077500000000000000000000000001154044152300247625ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/images/rails.png000066400000000000000000000147661154044152300266200ustar00rootroot00000000000000PNG  IHDR2@X${tEXtSoftwareAdobe ImageReadyqe<IDATxڬ[ \eR֮^;Iwga@`gGgDgtqDFqFDqF@NRU]˫|_-Qy^Ǹ.݋0W_6kbf̻ܸ6<4 w5~*r?9m&"M7@vm' {_S)Vi\WG?իjMd@ lDLX鸺W-TU+@EPo\&*Rnn, fDWrX|3M=\EJB[d6A'=tx^$a86̈{, ϱPepN*_W_3o;ޥ(0E:i6eXnhGf"L|S+(+.gФg=Ych=m#V_#}Ǫ|tR D8VՄM~xg!ni%Dy( B,{(Np$3iر$h.@e[a'eJԂyϠ4>H*MHQ(Jgt-֢QI ^d„@s-'- 51{'0 |n4ۉh{V@ܩw"BT =rzqPpBHȃ?ň ]-qpgsPiSӪg`jn)m 御B2L.x!jJP! K/\ ʮRB[09Trӈu. uH$ DDQ+:ݘٻ 3/nލ%Sjm2!&D/[EHwW A-RR!PeuHim"t6lFgЫ-O.1?ƞksX~VtmZJR11Nu&<⽩,Tb,`w WPx-G5 `մ/5pbAtIVJ_]0/DiH=ô#*77-3 VuQ0.pݔ%yw hљW0),2$b6&I/@bj$I(fx' JnO"`<-/LѮ%^ȫͶn2wҗ2}}XսL'Q-,m/ꤋ4#0Q&00NKrsA,Aײ)aIEC(ERK{8Ȭ[y?iI5$%f{}u F 1~;v1l'@F 'IF'm!K7"&]w 15#4Vižn[v 8Ě)>C=LBo~/3% wF4֓ʿ8>bWX@bb@IzP9IvFfQL!2cEP(se4~5RhAŽ90_? cMEteVOaOr B]pȱؓ"Eyx: NJ)bl׋hYuTdԫw=آMgwVPOFΒ25-TD[Z2>]V,xӛIOƅ)Jͺ[)?cn28p#(mk+./phʮQ6?w7HIoSj)1<#-N9O1ͰސkIKr:(ŗ;rR&<93v@w(w:~:TFSޒ" ՊTdT9PIb3JzTQׄBP23ƵW*|@^)Qw?Iq =,<@ B8);50H-=T SA@@f5r[T%#c|Z&w(B)tDQ%vyC(,Ɵ|ʰi&<#u:3EHkzд)ndF>1V2kFGYL KMQlR&TB,igv8]C8Sf#ą4Q'?,= aV9WEXYrr*!cƯ~),=yџ]jlGeE̺5r_2Ԏ}d"a]0M9PZG17nE"Rr\YQ)!|5U(d=^ŗo8+2NU6jB[B5V.]ŲW/^䩬 ;Y"Vi$2ٲ_c(F^Egq{CP/ #K8Y+Q M1>ܞAߏ,gytޕn,zE$V.v.PyLapG9Tn:uiRZ! zI0?Џ1u#$6ɱGMhFdtd|~d\O9Ij**zD؍b)PBҽh-q ql%/{Gz*d7=QS]:RQbUMPᒯ$% du] XefQz$('ИZH#ARXDB ~`0.F|XXK)wFolzyhߚKz>.&n EjU,2' &iw[d[ V)*Qavl QDit[VIQhR@$)y~m|>?cJ+VH'6? 7 i.XH8Fި)dAYUBjE".4w-?l2Y.RjWD@Bج.߆s[H-gASF3Fj]آBP떬_>M%bt ?_rլ -h]r_ž[nȶQ+Gԭ_\Ê Z٦fet(|U('.g VFEN9}Ll4T&nto¨Ӓ X F "_fYzF~y& Gu]$O[v#].@$VA`ⱧTѰZ[2u+/mUC_ TnyѠ |l\ M"G[R$d|:ěFIire"ٵt,+ی1Z11udt*K2 sd; [)xW.z2jTh#DV\NO &e_vU2B^%0FH(/ԘI2>=L]dv UUpk"ijB$,O-0y<}~*T5LErE4B߳XXN:<9>Ed -V*uBLsN**JxRU辖,T( Gu @ůY{u|CJF(OLbnմiKhpFtx8#9FsFڋDTAn1veF^M ^kf.ĆݠVʓǰ3JaY@n&jLl:McӚ…vu?9w!/~#hM ڛ ̴nMA}m W,)(î.N y%$*={P9c DzH>Blu޾K78x->V,'JU \L]l>W*r-hXf~oI Z3f玱>vN3 uZTgg}Վ363:.g /-H+"PKۉSZ4Z_GlXMc7";ҿ (5fMUCOF6 CNft>$S1VaR&4) ٗay]%W A*|gX{Qc>iTX1F M`|![$P4ʊ$#,dɌ(?KTJR۸S%C7jHb浃j+N$,[.@˹_ ?.3ĵH"U$Z^ X02!Kc 8q.NMI6N&3n8exoWfPIJB<pREAdo$*m)e9D 5X[T$LΠ:]C$n#mC[P~Yt*d?\q^WXs!E-2#_mw8;2!vw:DUn$)GiGn3_o EZE3k-EHv.OûzE>"֛}l\/-nرQHԽab*#K׋eIƳd#G et\ ,:MێÜIC}m ٽO?eb%ːٰStB|Aznaz*FlQ/K uu*1wDvE֯SJTK;(4kƣ;v2P9`k{?~_[hʢ^9фǡ;m|]o9<#jz\wD,8V]]%K9er懇0n^FcI>`Ub+kօO1|NO]t+,Ȑl_ˮ6 ĒDbrz^pe7^[aþo確jN+xsNC߅wμ7|za2, omrbZ~,pN>;?Y,z[u◿jq 4aqڶNu6Zid@h!!F9#,#UrOa0=Då ,,,bE#ȮX3ªޏ=a< =&_~ ٵѽacj񫒆LsXuXB (wzEk_QIف*4'ѣSl{.,p۵2`jp^؇nZXPź^]wމ]aQ-oI5O3a] _wb ŭL]$"|sԩȬ= VсLIUbYY搮͢I$tf$2|r;~'GSXkᇦԭF4b4 xo[,04F~<}ۭR%myb׾\mlO.4}tE\7}M)tՉ13xF [-26t䢄&E"9;ٜrq e)K!:bwY }g;Jר)5D$!Kɤ9߫-K$$ hlDUFF J{s2R6rC&&0;@>]/Z3E,k;( 2^09 Ruby on Rails: Welcome aboard

Getting started

Here’s how to get rolling:

  1. Use rails generate to create your models and controllers

    To see all available options, run it without parameters.

  2. Set up a default route and remove or rename this file

    Routes are set up in config/routes.rb.

  3. Create your database

    Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/000077500000000000000000000000001154044152300260465ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/application.js000066400000000000000000000002241154044152300307050ustar00rootroot00000000000000// Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/controls.js000066400000000000000000001037431154044152300302570ustar00rootroot00000000000000// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // // Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time // the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized // autocompletion. Specific auto-completion logic (AJAX, etc) // belongs in getUpdatedChoices. // // Tokenized incremental autocompletion is enabled automatically // when an autocompleter is instantiated with the 'tokens' option // in the options parameter, e.g.: // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which // enables autocompletion on multiple tokens. This is most // useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); var Autocompleter = { }; Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { element = $(element); this.element = element; this.update = $(update); this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; if(this.setOptions) this.setOptions(options); else this.options = options || { }; this.options.paramName = this.options.paramName || this.element.name; this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; this.element.setAttribute('autocomplete','off'); Element.hide(this.update); Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); }, show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, ''); this.iefix = $(this.update.id+'_iefix'); } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; this.update.style.zIndex = 2; Element.show(this.iefix); }, hide: function() { this.stopIndicator(); if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); }, startIndicator: function() { if(this.options.indicator) Element.show(this.options.indicator); }, stopIndicator: function() { if(this.options.indicator) Element.hide(this.options.indicator); }, onKeyPress: function(event) { if(this.active) switch(event.keyCode) { case Event.KEY_TAB: case Event.KEY_RETURN: this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: this.markPrevious(); this.render(); Event.stop(event); return; case Event.KEY_DOWN: this.markNext(); this.render(); Event.stop(event); return; } else if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, activate: function() { this.changed = false; this.hasFocus = true; this.getUpdatedChoices(); }, onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; this.active = false; }, render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) this.index==i ? Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); if(this.hasFocus) { this.show(); this.active = true; } } else { this.active = false; this.hide(); } }, markPrevious: function() { if(this.index > 0) this.index--; else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, markNext: function() { if(this.index < this.entryCount-1) this.index++; else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, getCurrentEntry: function() { return this.getEntry(this.index); }, selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); }, updateElement: function(selectedElement) { if (this.options.updateElement) { this.options.updateElement(selectedElement); return; } var value = ''; if (this.options.select) { var nodes = $(selectedElement).select('.' + this.options.select) || []; if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); if (whitespace) newValue += whitespace[0]; this.element.value = newValue + value + this.element.value.substr(bounds[1]); } else { this.element.value = value; } this.oldElementValue = this.element.value; this.element.focus(); if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, updateChoices: function(choices) { if(!this.changed && this.hasFocus) { this.update.innerHTML = choices; Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); } else { this.render(); } } }, addObservers: function(element) { Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); Event.observe(element, "click", this.onClick.bindAsEventListener(this)); }, onObserverEvent: function() { this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); } else { this.active = false; this.hide(); } this.oldElementValue = this.element.value; }, getToken: function() { var bounds = this.getTokenBounds(); return this.element.value.substring(bounds[0], bounds[1]).strip(); }, getTokenBounds: function() { if (null != this.tokenBounds) return this.tokenBounds; var value = this.element.value; if (value.strip().empty()) return [-1, 0]; var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); var offset = (diff == this.oldElementValue.length ? 1 : 0); var prevTokenPos = -1, nextTokenPos = value.length; var tp; for (var index = 0, l = this.options.tokens.length; index < l; ++index) { tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); if (tp > prevTokenPos) prevTokenPos = tp; tp = value.indexOf(this.options.tokens[index], diff + offset); if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; } return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); } }); Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { var boundary = Math.min(newS.length, oldS.length); for (var index = 0; index < boundary; ++index) if (newS[index] != oldS[index]) return index; return boundary; }; Ajax.Autocompleter = Class.create(Autocompleter.Base, { initialize: function(element, update, url, options) { this.baseInitialize(element, update, options); this.options.asynchronous = true; this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; }, getUpdatedChoices: function() { this.startIndicator(); var entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; new Ajax.Request(this.url, this.options); }, onComplete: function(request) { this.updateChoices(request.responseText); } }); // The local array autocompleter. Used when you'd prefer to // inject an array of autocompletion options into the page, rather // than sending out Ajax queries, which can be quite slow sometimes. // // The constructor takes four parameters. The first two are, as usual, // the id of the monitored textbox, and id of the autocompletion menu. // The third is the array you want to autocomplete from, and the fourth // is the options block. // // Extra local autocompletion options: // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered // text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to // search anywhere in the string, additionally set // the option fullSearch to true (default: off). // // - fullSsearch - Search anywhere in autocomplete array strings. // // - partialChars - How many characters to enter before triggering // a partial match (unlike minChars, which defines // how many characters are required to do any match // at all). Defaults to 2. // // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // // It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. Autocompleter.Local = Class.create(Autocompleter.Base, { initialize: function(element, update, array, options) { this.baseInitialize(element, update, options); this.options.array = array; }, getUpdatedChoices: function() { this.updateChoices(this.options.selector(this)); }, setOptions: function(options) { this.options = Object.extend({ choices: 10, partialSearch: true, partialChars: 2, ignoreCase: true, fullSearch: false, selector: function(instance) { var ret = []; // Beginning matches var partial = []; // Inside matches var entry = instance.getToken(); var count = 0; for (var i = 0; i < instance.options.array.length && ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; var foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { if (foundPos == 0 && elem.length != entry.length) { ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + elem.substr(foundPos, entry.length) + "" + elem.substr( foundPos + entry.length) + "
  • "); break; } } foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); return "
      " + ret.join('') + "
    "; } }, options || { }); } }); // AJAX in-place editor and collection editor // Full rewrite by Christophe Porteneuve (April 2007). // Use this if you notice weird scrolling problems on some browsers, // the DOM might be a bit confused when this gets called so do this // waits 1 ms (with setTimeout) until it does the activation Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); }; Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { this.url = url; this.element = element = $(element); this.prepareOptions(); this._controls = { }; arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! Object.extend(this.options, options || { }); if (!this.options.formId && this.element.id) { this.options.formId = this.element.id + '-inplaceeditor'; if ($(this.options.formId)) this.options.formId = ''; } if (this.options.externalControl) this.options.externalControl = $(this.options.externalControl); if (!this.options.externalControl) this.options.externalControlOnly = false; this._originalBackground = this.element.getStyle('background-color') || 'transparent'; this.element.title = this.options.clickToEditText; this._boundCancelHandler = this.handleFormCancellation.bind(this); this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); this._boundFailureHandler = this.handleAJAXFailure.bind(this); this._boundSubmitHandler = this.handleFormSubmission.bind(this); this._boundWrapperHandler = this.wrapUp.bind(this); this.registerListeners(); }, checkForEscapeOrReturn: function(e) { if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; if (Event.KEY_ESC == e.keyCode) this.handleFormCancellation(e); else if (Event.KEY_RETURN == e.keyCode) this.handleFormSubmission(e); }, createControl: function(mode, handler, extraClasses) { var control = this.options[mode + 'Control']; var text = this.options[mode + 'Text']; if ('button' == control) { var btn = document.createElement('input'); btn.type = 'submit'; btn.value = text; btn.className = 'editor_' + mode + '_button'; if ('cancel' == mode) btn.onclick = this._boundCancelHandler; this._form.appendChild(btn); this._controls[mode] = btn; } else if ('link' == control) { var link = document.createElement('a'); link.href = '#'; link.appendChild(document.createTextNode(text)); link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; link.className = 'editor_' + mode + '_link'; if (extraClasses) link.className += ' ' + extraClasses; this._form.appendChild(link); this._controls[mode] = link; } }, createEditField: function() { var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); var fld; if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { fld = document.createElement('input'); fld.type = 'text'; var size = this.options.size || this.options.cols || 0; if (0 < size) fld.size = size; } else { fld = document.createElement('textarea'); fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); fld.cols = this.options.cols || 40; } fld.name = this.options.paramName; fld.value = text; // No HTML breaks conversion anymore fld.className = 'editor_field'; if (this.options.submitOnBlur) fld.onblur = this._boundSubmitHandler; this._controls.editor = fld; if (this.options.loadTextURL) this.loadExternalText(); this._form.appendChild(this._controls.editor); }, createForm: function() { var ipe = this; function addText(mode, condition) { var text = ipe.options['text' + mode + 'Controls']; if (!text || condition === false) return; ipe._form.appendChild(document.createTextNode(text)); }; this._form = $(document.createElement('form')); this._form.id = this.options.formId; this._form.addClassName(this.options.formClassName); this._form.onsubmit = this._boundSubmitHandler; this.createEditField(); if ('textarea' == this._controls.editor.tagName.toLowerCase()) this._form.appendChild(document.createElement('br')); if (this.options.onFormCustomization) this.options.onFormCustomization(this, this._form); addText('Before', this.options.okControl || this.options.cancelControl); this.createControl('ok', this._boundSubmitHandler); addText('Between', this.options.okControl && this.options.cancelControl); this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); addText('After', this.options.okControl || this.options.cancelControl); }, destroy: function() { if (this._oldInnerHTML) this.element.innerHTML = this._oldInnerHTML; this.leaveEditMode(); this.unregisterListeners(); }, enterEditMode: function(e) { if (this._saving || this._editing) return; this._editing = true; this.triggerCallback('onEnterEditMode'); if (this.options.externalControl) this.options.externalControl.hide(); this.element.hide(); this.createForm(); this.element.parentNode.insertBefore(this._form, this.element); if (!this.options.loadTextURL) this.postProcessEditField(); if (e) Event.stop(e); }, enterHover: function(e) { if (this.options.hoverClassName) this.element.addClassName(this.options.hoverClassName); if (this._saving) return; this.triggerCallback('onEnterHover'); }, getText: function() { return this.element.innerHTML.unescapeHTML(); }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); if (this._oldInnerHTML) { this.element.innerHTML = this._oldInnerHTML; this._oldInnerHTML = null; } }, handleFormCancellation: function(e) { this.wrapUp(); if (e) Event.stop(e); }, handleFormSubmission: function(e) { var form = this._form; var value = $F(this._controls.editor); this.prepareSubmission(); var params = this.options.callback(form, value) || ''; if (Object.isString(params)) params = params.toQueryParams(); params.editorId = this.element.id; if (this.options.htmlResponse) { var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); Object.extend(options, { parameters: params, onComplete: this._boundWrapperHandler, onFailure: this._boundFailureHandler }); new Ajax.Updater({ success: this.element }, this.url, options); } else { var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: params, onComplete: this._boundWrapperHandler, onFailure: this._boundFailureHandler }); new Ajax.Request(this.url, options); } if (e) Event.stop(e); }, leaveEditMode: function() { this.element.removeClassName(this.options.savingClassName); this.removeForm(); this.leaveHover(); this.element.style.backgroundColor = this._originalBackground; this.element.show(); if (this.options.externalControl) this.options.externalControl.show(); this._saving = false; this._editing = false; this._oldInnerHTML = null; this.triggerCallback('onLeaveEditMode'); }, leaveHover: function(e) { if (this.options.hoverClassName) this.element.removeClassName(this.options.hoverClassName); if (this._saving) return; this.triggerCallback('onLeaveHover'); }, loadExternalText: function() { this._form.addClassName(this.options.loadingClassName); this._controls.editor.disabled = true; var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { this._form.removeClassName(this.options.loadingClassName); var text = transport.responseText; if (this.options.stripLoadedTextTags) text = text.stripTags(); this._controls.editor.value = text; this._controls.editor.disabled = false; this.postProcessEditField(); }.bind(this), onFailure: this._boundFailureHandler }); new Ajax.Request(this.options.loadTextURL, options); }, postProcessEditField: function() { var fpc = this.options.fieldPostCreation; if (fpc) $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); }, prepareOptions: function() { this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); [this._extraDefaultOptions].flatten().compact().each(function(defs) { Object.extend(this.options, defs); }.bind(this)); }, prepareSubmission: function() { this._saving = true; this.removeForm(); this.leaveHover(); this.showSaving(); }, registerListeners: function() { this._listeners = { }; var listener; $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { listener = this[pair.value].bind(this); this._listeners[pair.key] = listener; if (!this.options.externalControlOnly) this.element.observe(pair.key, listener); if (this.options.externalControl) this.options.externalControl.observe(pair.key, listener); }.bind(this)); }, removeForm: function() { if (!this._form) return; this._form.remove(); this._form = null; this._controls = { }; }, showSaving: function() { this._oldInnerHTML = this.element.innerHTML; this.element.innerHTML = this.options.savingText; this.element.addClassName(this.options.savingClassName); this.element.style.backgroundColor = this._originalBackground; this.element.show(); }, triggerCallback: function(cbName, arg) { if ('function' == typeof this.options[cbName]) { this.options[cbName](this, arg); } }, unregisterListeners: function() { $H(this._listeners).each(function(pair) { if (!this.options.externalControlOnly) this.element.stopObserving(pair.key, pair.value); if (this.options.externalControl) this.options.externalControl.stopObserving(pair.key, pair.value); }.bind(this)); }, wrapUp: function(transport) { this.leaveEditMode(); // Can't use triggerCallback due to backward compatibility: requires // binding + direct element this._boundComplete(transport, this.element); } }); Object.extend(Ajax.InPlaceEditor.prototype, { dispose: Ajax.InPlaceEditor.prototype.destroy }); Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { initialize: function($super, element, url, options) { this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; $super(element, url, options); }, createEditField: function() { var list = document.createElement('select'); list.name = this.options.paramName; list.size = 1; this._controls.editor = list; this._collection = this.options.collection || []; if (this.options.loadCollectionURL) this.loadCollection(); else this.checkForExternalText(); this._form.appendChild(this._controls.editor); }, loadCollection: function() { this._form.addClassName(this.options.loadingClassName); this.showLoadingText(this.options.loadingCollectionText); var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check throw('Server returned an invalid collection representation.'); this._collection = eval(js); this.checkForExternalText(); }.bind(this), onFailure: this.onFailure }); new Ajax.Request(this.options.loadCollectionURL, options); }, showLoadingText: function(text) { this._controls.editor.disabled = true; var tempOption = this._controls.editor.firstChild; if (!tempOption) { tempOption = document.createElement('option'); tempOption.value = ''; this._controls.editor.appendChild(tempOption); tempOption.selected = true; } tempOption.update((text || '').stripScripts().stripTags()); }, checkForExternalText: function() { this._text = this.getText(); if (this.options.loadTextURL) this.loadExternalText(); else this.buildOptionList(); }, loadExternalText: function() { this.showLoadingText(this.options.loadingText); var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { this._text = transport.responseText.strip(); this.buildOptionList(); }.bind(this), onFailure: this.onFailure }); new Ajax.Request(this.options.loadTextURL, options); }, buildOptionList: function() { this._form.removeClassName(this.options.loadingClassName); this._collection = this._collection.map(function(entry) { return 2 === entry.length ? entry : [entry, entry].flatten(); }); var marker = ('value' in this.options) ? this.options.value : this._text; var textFound = this._collection.any(function(entry) { return entry[0] == marker; }.bind(this)); this._controls.editor.update(''); var option; this._collection.each(function(entry, index) { option = document.createElement('option'); option.value = entry[0]; option.selected = textFound ? entry[0] == marker : 0 == index; option.appendChild(document.createTextNode(entry[1])); this._controls.editor.appendChild(option); }.bind(this)); this._controls.editor.disabled = false; Field.scrollFreeActivate(this._controls.editor); } }); //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** //**** This only exists for a while, in order to let **** //**** users adapt to the new API. Read up on the new **** //**** API and convert your code to it ASAP! **** Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { if (!options) return; function fallback(name, expr) { if (name in options || expr === undefined) return; options[name] = expr; }; fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : options.cancelLink == options.cancelButton == false ? false : undefined))); fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : options.okLink == options.okButton == false ? false : undefined))); fallback('highlightColor', options.highlightcolor); fallback('highlightEndColor', options.highlightendcolor); }; Object.extend(Ajax.InPlaceEditor, { DefaultOptions: { ajaxOptions: { }, autoRows: 3, // Use when multi-line w/ rows == 1 cancelControl: 'link', // 'link'|'button'|false cancelText: 'cancel', clickToEditText: 'Click to edit', externalControl: null, // id|elt externalControlOnly: false, fieldPostCreation: 'activate', // 'activate'|'focus'|false formClassName: 'inplaceeditor-form', formId: null, // id|elt highlightColor: '#ffff99', highlightEndColor: '#ffffff', hoverClassName: '', htmlResponse: true, loadingClassName: 'inplaceeditor-loading', loadingText: 'Loading...', okControl: 'button', // 'link'|'button'|false okText: 'ok', paramName: 'value', rows: 1, // If 1 and multi-line, uses autoRows savingClassName: 'inplaceeditor-saving', savingText: 'Saving...', size: 0, stripLoadedTextTags: false, submitOnBlur: false, textAfterControls: '', textBeforeControls: '', textBetweenControls: '' }, DefaultCallbacks: { callback: function(form) { return Form.serialize(form); }, onComplete: function(transport, element) { // For backward compatibility, this one is bound to the IPE, and passes // the element directly. It was too often customized, so we don't break it. new Effect.Highlight(element, { startcolor: this.options.highlightColor, keepBackgroundImage: true }); }, onEnterEditMode: null, onEnterHover: function(ipe) { ipe.element.style.backgroundColor = ipe.options.highlightColor; if (ipe._effect) ipe._effect.cancel(); }, onFailure: function(transport, ipe) { alert('Error communication with the server: ' + transport.responseText.stripTags()); }, onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. onLeaveEditMode: null, onLeaveHover: function(ipe) { ipe._effect = new Effect.Highlight(ipe.element, { startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, restorecolor: ipe._originalBackground, keepBackgroundImage: true }); } }, Listeners: { click: 'enterEditMode', keydown: 'checkForEscapeOrReturn', mouseover: 'enterHover', mouseout: 'leaveHover' } }); Ajax.InPlaceCollectionEditor.DefaultOptions = { loadingCollectionText: 'Loading options...' }; // Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields Form.Element.DelayedObserver = Class.create({ initialize: function(element, delay, callback) { this.delay = delay || 0.5; this.element = $(element); this.callback = callback; this.timer = null; this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { if(this.lastValue == $F(this.element)) return; if(this.timer) clearTimeout(this.timer); this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); this.lastValue = $F(this.element); }, onTimerEvent: function() { this.timer = null; this.callback(this.element, $F(this.element)); } });ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/dragdrop.js000066400000000000000000000745201154044152300302160ustar00rootroot00000000000000// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ if(Object.isUndefined(Effect)) throw("dragdrop.js requires including script.aculo.us' effects.js library"); var Droppables = { drops: [], remove: function(element) { this.drops = this.drops.reject(function(d) { return d.element==$(element) }); }, add: function(element) { element = $(element); var options = Object.extend({ greedy: true, hoverclass: null, tree: false }, arguments[1] || { }); // cache containers if(options.containment) { options._containers = []; var containment = options.containment; if(Object.isArray(containment)) { containment.each( function(c) { options._containers.push($(c)) }); } else { options._containers.push($(containment)); } } if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE options.element = element; this.drops.push(options); }, findDeepestChild: function(drops) { deepest = drops[0]; for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, deactivate: function(drop) { if(drop.hoverclass) Element.removeClassName(drop.element, drop.hoverclass); this.last_active = null; }, activate: function(drop) { if(drop.hoverclass) Element.addClassName(drop.element, drop.hoverclass); this.last_active = drop; }, show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); if(affected.length>0) drop = Droppables.findDeepestChild(affected); if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); if (drop) { Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); if (drop != this.last_active) Droppables.activate(drop); } }, fire: function(event, element) { if(!this.last_active) return; Position.prepare(); if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) { this.last_active.onDrop(element, this.last_active.element, event); return true; } }, reset: function() { if(this.last_active) this.deactivate(this.last_active); } }; var Draggables = { drags: [], observers: [], register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { Event.stopObserving(document, "mouseup", this.eventMouseUp); Event.stopObserving(document, "mousemove", this.eventMouseMove); Event.stopObserving(document, "keypress", this.eventKeypress); } }, activate: function(draggable) { if(draggable.options.delay) { this._timeout = setTimeout(function() { Draggables._timeout = null; window.focus(); Draggables.activeDraggable = draggable; }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, deactivate: function() { this.activeDraggable = null; }, updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; // Mozilla-based browsers fire successive mousemove events with // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; this.activeDraggable.updateDrag(event, pointer); }, endDrag: function(event) { if(this._timeout) { clearTimeout(this._timeout); this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { if(o[eventName]) o[eventName](eventName, draggable, event); }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( function(o) { return o[eventName]; } ).length; }); } }; /*--------------------------------------------------------------------------*/ var Draggable = Class.create({ initialize: function(element) { var defaults = { handle: false, reverteffect: function(element, top_offset, left_offset) { var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, queue: {scope:'_draggable', position:'end'} }); }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, afterFinish: function(){ Draggable._dragging[element] = false } }); }, zindex: 1000, revert: false, quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } Element.makePositioned(this.element); // fix IE this.options = options; this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); Draggables.register(this); }, destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( tag_name=='INPUT' || tag_name=='SELECT' || tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = this.element.cumulativeOffset(); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); Draggables.activate(this); Event.stop(event); } }, startDrag: function(event) { this.dragging = true; if(!this.delta) this.delta = this.currentDelta(); if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } if(this.options.ghosting) { this._clone = this.element.cloneNode(true); this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); if (!this._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); this.originalScrollLeft = where.left; this.originalScrollTop = where.top; } else { this.originalScrollLeft = this.options.scroll.scrollLeft; this.originalScrollTop = this.options.scroll.scrollTop; } } Draggables.notify('onStart', this, event); if(this.options.starteffect) this.options.starteffect(this.element); }, updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); } Draggables.notify('onDrag', this, event); this.draw(pointer); if(this.options.change) this.options.change(this); if(this.options.scroll) { this.stopScrolling(); var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } } else { p = Position.page(this.options.scroll); p[0] += this.options.scroll.scrollLeft + Position.deltaX; p[1] += this.options.scroll.scrollTop + Position.deltaY; p.push(p[0]+this.options.scroll.offsetWidth); p.push(p[1]+this.options.scroll.offsetHeight); } var speed = [0,0]; if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } // fix AppleWebKit rendering if(Prototype.Browser.WebKit) window.scrollBy(0,0); Event.stop(event); }, finishDrag: function(event, success) { this.dragging = false; if(this.options.quiet){ Position.prepare(); var pointer = [Event.pointerX(event), Event.pointerY(event)]; Droppables.show(pointer, this.element); } if(this.options.ghosting) { if (!this._originallyAbsolute) Position.relativize(this.element); delete this._originallyAbsolute; Element.remove(this._clone); this._clone = null; } var dropped = false; if(success) { dropped = Droppables.fire(event, this.element); if (!dropped) dropped = false; } if(dropped && this.options.onDropped) this.options.onDropped(this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && Object.isFunction(revert)) revert = revert(this.element); var d = this.currentDelta(); if(revert && this.options.reverteffect) { if (dropped == 0 || revert != 'failure') this.options.reverteffect(this.element, d[1]-this.delta[1], d[0]-this.delta[0]); } else { this.delta = d; } if(this.options.zindex) this.element.style.zIndex = this.originalZ; if(this.options.endeffect) this.options.endeffect(this.element); Draggables.deactivate(this); Droppables.reset(); }, keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, draw: function(point) { var pos = this.element.cumulativeOffset(); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { if(Object.isFunction(this.options.snap)) { p = this.options.snap(p[0],p[1],this); } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); } else { p = p.map( function(v) { return (v/this.options.snap).round()*this.options.snap }.bind(this)); } }} var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); this.scrollInterval = null; Draggables._lastScrollPointer = null; } }, startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; this.lastScrolled = current; if(this.options.scroll == window) { with (this._getWindowScroll(this.options.scroll)) { if (this.scrollSpeed[0] || this.scrollSpeed[1]) { var d = delta / 1000; this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); } } } else { this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); if (this._isScrollChild) { Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; if (Draggables._lastScrollPointer[0] < 0) Draggables._lastScrollPointer[0] = 0; if (Draggables._lastScrollPointer[1] < 0) Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } if(this.options.change) this.options.change(this); }, _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { if (w.document.documentElement && documentElement.scrollTop) { T = documentElement.scrollTop; L = documentElement.scrollLeft; } else if (w.document.body) { T = body.scrollTop; L = body.scrollLeft; } if (w.innerWidth) { W = w.innerWidth; H = w.innerHeight; } else if (w.document.documentElement && documentElement.clientWidth) { W = documentElement.clientWidth; H = documentElement.clientHeight; } else { W = body.offsetWidth; H = body.offsetHeight; } } return { top: T, left: L, width: W, height: H }; } }); Draggable._dragging = { }; /*--------------------------------------------------------------------------*/ var SortableObserver = Class.create({ initialize: function(element, observer) { this.element = $(element); this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, onStart: function() { this.lastValue = Sortable.serialize(this.element); }, onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } }); var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, sortables: { }, _findRootElement: function(element) { while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } }, options: function(element) { element = Sortable._findRootElement($(element)); if(!element) return; return Sortable.sortables[element.id]; }, destroy: function(element){ element = $(element); var s = Sortable.sortables[element.id]; if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, tree: false, treeTag: 'ul', overlap: 'vertical', // one of 'vertical', 'horizontal' constraint: 'vertical', // one of 'vertical', 'horizontal', false containment: element, // also takes array of elements (or id's); or false handle: false, // or a CSS class only: false, delay: 0, hoverclass: null, ghosting: false, quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, // these take arrays of elements or ids and can be // used for better initialization performance elements: false, handles: false, onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || { }); // clear any old sortable with same element this.destroy(element); // build options for the draggables var options_for_draggable = { revert: true, quiet: options.quiet, scroll: options.scroll, scrollSpeed: options.scrollSpeed, scrollSensitivity: options.scrollSensitivity, delay: options.delay, ghosting: options.ghosting, constraint: options.constraint, handle: options.handle }; if(options.starteffect) options_for_draggable.starteffect = options.starteffect; if(options.reverteffect) options_for_draggable.reverteffect = options.reverteffect; else if(options.ghosting) options_for_draggable.reverteffect = function(element) { element.style.top = 0; element.style.left = 0; }; if(options.endeffect) options_for_draggable.endeffect = options.endeffect; if(options.zindex) options_for_draggable.zindex = options.zindex; // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover }; var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass }; // fix for gecko engine Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; // drop on empty handling if(options.dropOnEmpty || options.tree) { Droppables.add(element, options_for_tree); options.droppables.push(element); } (options.elements || this.findElements(element, options) || []).each( function(e,i) { var handle = options.handles ? $(options.handles[i]) : (options.handle ? $(e).select('.' + options.handle)[0] : e); options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; options.droppables.push(e); }); if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); e.treeNode = element; options.droppables.push(e); }); } // keep reference this.sortables[element.identify()] = options; // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, // return all suitable-for-sortable elements in a guaranteed order findElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); }, onHover: function(element, dropon, overlap) { if(Element.isParent(dropon, element)) return; if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { return; } else if(overlap>0.5) { Sortable.mark(dropon, 'before'); if(dropon.previousSibling != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } else { Sortable.mark(dropon, 'after'); var nextElement = dropon.nextSibling || null; if(nextElement != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); if(!Element.isParent(dropon, element)) { var index; var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { child = index + 1 < children.length ? children[index + 1] : null; break; } else { child = children[index]; break; } } } dropon.insertBefore(element, child); Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } }, unmark: function() { if(Sortable._marker) Sortable._marker.hide(); }, mark: function(dropon, position) { // mark on ghosting only var sortable = Sortable.options(dropon.parentNode); if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); } var offsets = dropon.cumulativeOffset(); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); if(position=='after') if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); Sortable._marker.show(); }, _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; var child = { id: encodeURIComponent(match ? match[1] : null), element: element, parent: parent, children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) }; /* Get the element containing the children and recurse over it */ if (child.container) this._tree(child.container, options, child); parent.children.push (child); } return parent; }, tree: function(element) { element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, treeTag: sortableOptions.treeTag, only: sortableOptions.only, name: element.id, format: sortableOptions.format }, arguments[1] || { }); var root = { id: null, parent: null, children: [], container: element, position: 0 }; return Sortable._tree(element, options, root); }, /* Construct a [i] index for a particular node */ _constructIndex: function(node) { var index = ''; do { if (node.id) index = '[' + node.position + ']' + index; } while ((node = node.parent) != null); return index; }, sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || { }); return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); }, setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || { }); var nodeMap = { }; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { n[1].appendChild(n[0]); delete nodeMap[ident]; } }); }, serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || { }); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { return Sortable.sequence(element, arguments[1]).map( function(item) { return name + "[]=" + encodeURIComponent(item); }).join('&'); } } }; // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); }; Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); var elements = []; $A(element.childNodes).each( function(e) { if(e.tagName && e.tagName.toUpperCase()==tagName && (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) elements.push(e); if(recursive) { var grandchildren = Element.findChildren(e, only, recursive, tagName); if(grandchildren) elements.push(grandchildren); } }); return (elements.length>0 ? elements.flatten() : []); }; Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; };ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/effects.js000066400000000000000000001131071154044152300300260ustar00rootroot00000000000000// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // converts rgb() and #xxx to #xxxxxx format, // returns self (or first argument) if not convertable String.prototype.parseColor = function() { var color = '#'; if (this.slice(0,4) == 'rgb(') { var cols = this.slice(4,this.length-1).split(','); var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); } else { if (this.slice(0,1) == '#') { if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); if (this.length==7) color = this.toLowerCase(); } } return (color.length==7 ? color : (arguments[0] || this)); }; /*--------------------------------------------------------------------------*/ Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); }; Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); }; Element.setContentZoom = function(element, percent) { element = $(element); element.setStyle({fontSize: (percent/100) + 'em'}); if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; }; Element.getInlineOpacity = function(element){ return $(element).style.opacity || ''; }; Element.forceRerendering = function(element) { try { element = $(element); var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch(e) { } }; /*--------------------------------------------------------------------------*/ var Effect = { _elementDoesNotExistError: { name: 'ElementDoesNotExistError', message: 'The specified DOM element does not exist, but is required for this effect to operate' }, Transitions: { linear: Prototype.K, sinoidal: function(pos) { return (-Math.cos(pos*Math.PI)/2) + .5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; }, pulse: function(pos, pulses) { return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; }, spring: function(pos) { return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none: function(pos) { return 0; }, full: function(pos) { return 1; } }, DefaultOptions: { duration: 1.0, // seconds fps: 100, // 100= assume 66fps max. sync: false, // true for combining from: 0.0, to: 1.0, delay: 0.0, queue: 'parallel' }, tagifyText: function(element) { var tagifyStyle = 'position:relative'; if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; element = $(element); $A(element.childNodes).each( function(child) { if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( new Element('span', {style: tagifyStyle}).update( character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); } }); }, multiple: function(element, effect) { var elements; if (((typeof element == 'object') || Object.isFunction(element)) && (element.length)) elements = element; else elements = $(element).childNodes; var options = Object.extend({ speed: 0.1, delay: 0.0 }, arguments[2] || { }); var masterDelay = options.delay; $A(elements).each( function(element, index) { new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); }); }, PAIRS: { 'slide': ['SlideDown','SlideUp'], 'blind': ['BlindDown','BlindUp'], 'appear': ['Appear','Fade'] }, toggle: function(element, effect, options) { element = $(element); effect = (effect || 'appear').toLowerCase(); return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } }, options || {})); } }; Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; /* ------------- core effects ------------- */ Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; switch(position) { case 'front': // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; }); break; case 'with-last': timestamp = this.effects.pluck('startOn').max() || timestamp; break; case 'end': // start effect after last queued effect has finished timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } effect.startOn += timestamp; effect.finishOn += timestamp; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, remove: function(effect) { this.effects = this.effects.reject(function(e) { return e==effect }); if (this.effects.length == 0) { clearInterval(this.interval); this.interval = null; } }, loop: function() { var timePos = new Date().getTime(); for(var i=0, len=this.effects.length;i= this.startOn) { if (timePos >= this.finishOn) { this.render(1.0); this.cancel(); this.event('beforeFinish'); if (this.finish) this.finish(); this.event('afterFinish'); return; } var pos = (timePos - this.startOn) / this.totalTime, frame = (pos * this.totalFrames).round(); if (frame > this.currentFrame) { this.render(pos); this.currentFrame = frame; } } }, cancel: function() { if (!this.options.sync) Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, event: function(eventName) { if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); if (this.options[eventName]) this.options[eventName](this); }, inspect: function() { var data = $H(); for(property in this) if (!Object.isFunction(this[property])) data.set(property, this[property]); return '#'; } }); Effect.Parallel = Class.create(Effect.Base, { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); }, update: function(position) { this.effects.invoke('render', position); }, finish: function(position) { this.effects.each( function(effect) { effect.render(1.0); effect.cancel(); effect.event('beforeFinish'); if (effect.finish) effect.finish(position); effect.event('afterFinish'); }); } }); Effect.Tween = Class.create(Effect.Base, { initialize: function(object, from, to) { object = Object.isString(object) ? $(object) : object; var args = $A(arguments), method = args.last(), options = args.length == 5 ? args[3] : null; this.method = Object.isFunction(method) ? method.bind(object) : Object.isFunction(object[method]) ? object[method].bind(object) : function(value) { object[method] = value }; this.start(Object.extend({ from: from, to: to }, options || { })); }, update: function(position) { this.method(position); } }); Effect.Event = Class.create(Effect.Base, { initialize: function() { this.start(Object.extend({ duration: 0 }, arguments[0] || { })); }, update: Prototype.emptyFunction }); Effect.Opacity = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); // make this work on IE on elements without 'layout' if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); var options = Object.extend({ from: this.element.getOpacity() || 0.0, to: 1.0 }, arguments[1] || { }); this.start(options); }, update: function(position) { this.element.setOpacity(position); } }); Effect.Move = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ x: 0, y: 0, mode: 'relative' }, arguments[1] || { }); this.start(options); }, setup: function() { this.element.makePositioned(); this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); this.originalTop = parseFloat(this.element.getStyle('top') || '0'); if (this.options.mode == 'absolute') { this.options.x = this.options.x - this.originalLeft; this.options.y = this.options.y - this.originalTop; } }, update: function(position) { this.element.setStyle({ left: (this.options.x * position + this.originalLeft).round() + 'px', top: (this.options.y * position + this.originalTop).round() + 'px' }); } }); // for backwards compatibility Effect.MoveBy = function(element, toTop, toLeft) { return new Effect.Move(element, Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); }; Effect.Scale = Class.create(Effect.Base, { initialize: function(element, percent) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or { } with provided values scaleFrom: 100.0, scaleTo: percent }, arguments[2] || { }); this.start(options); }, setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; this.elementPositioning = this.element.getStyle('position'); this.originalStyle = { }; ['top','left','width','height','fontSize'].each( function(k) { this.originalStyle[k] = this.element.style[k]; }.bind(this)); this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%','pt'].each( function(fontSizeType) { if (fontSize.indexOf(fontSizeType)>0) { this.fontSize = parseFloat(fontSize); this.fontSizeType = fontSizeType; } }.bind(this)); this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; this.dims = null; if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; if (/^content/.test(this.options.scaleMode)) this.dims = [this.element.scrollHeight, this.element.scrollWidth]; if (!this.dims) this.dims = [this.options.scaleMode.originalHeight, this.options.scaleMode.originalWidth]; }, update: function(position) { var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); if (this.options.scaleContent && this.fontSize) this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); }, finish: function(position) { if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); }, setDimensions: function(height, width) { var d = { }; if (this.options.scaleX) d.width = width.round() + 'px'; if (this.options.scaleY) d.height = height.round() + 'px'; if (this.options.scaleFromCenter) { var topd = (height - this.dims[0])/2; var leftd = (width - this.dims[1])/2; if (this.elementPositioning == 'absolute') { if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; } else { if (this.options.scaleY) d.top = -topd + 'px'; if (this.options.scaleX) d.left = -leftd + 'px'; } } this.element.setStyle(d); } }); Effect.Highlight = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); this.start(options); }, setup: function() { // Prevent executing on elements not in the layout flow if (this.element.getStyle('display')=='none') { this.cancel(); return; } // Disable background image during the effect this.oldStyle = { }; if (!this.options.keepBackgroundImage) { this.oldStyle.backgroundImage = this.element.getStyle('background-image'); this.element.setStyle({backgroundImage: 'none'}); } if (!this.options.endcolor) this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); if (!this.options.restorecolor) this.options.restorecolor = this.element.getStyle('background-color'); // init color calculations this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); }, update: function(position) { this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); }, finish: function() { this.element.setStyle(Object.extend(this.oldStyle, { backgroundColor: this.options.restorecolor })); } }); Effect.ScrollTo = function(element) { var options = arguments[1] || { }, scrollOffsets = document.viewport.getScrollOffsets(), elementOffsets = $(element).cumulativeOffset(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, elementOffsets[1], options, function(p){ scrollTo(scrollOffsets.left, p.round()); } ); }; /* ------------- combination effects ------------- */ Effect.Fade = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, afterFinishInternal: function(effect) { if (effect.options.to!=0) return; effect.element.hide().setStyle({opacity: oldOpacity}); } }, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Appear = function(element) { element = $(element); var options = Object.extend({ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), to: 1.0, // force Safari to render floated elements properly afterFinishInternal: function(effect) { effect.element.forceRerendering(); }, beforeSetup: function(effect) { effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Puff = function(element) { element = $(element); var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; return new Effect.Parallel( [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { Position.absolutize(effect.effects[0].element); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } }, arguments[1] || { }) ); }; Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }, arguments[1] || { }) ); }; Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } }, arguments[1] || { })); }; Effect.SwitchOff = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); return new Effect.Appear(element, Object.extend({ duration: 0.4, from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } }); } }, arguments[1] || { })); }; Effect.DropOut = function(element) { element = $(element); var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left'), opacity: element.getInlineOpacity() }; return new Effect.Parallel( [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, beforeSetup: function(effect) { effect.effects[0].element.makePositioned(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); } }, arguments[1] || { })); }; Effect.Shake = function(element) { element = $(element); var options = Object.extend({ distance: 20, duration: 0.5 }, arguments[1] || {}); var distance = parseFloat(options.distance); var split = parseFloat(options.duration) / 10.0; var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left') }; return new Effect.Move(element, { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); }}); }}); }}); }}); }}); }}); }; Effect.SlideDown = function(element) { element = $(element).cleanWhitespace(); // SlideDown need to have the content of the element wrapped in a container element with fixed height! var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: window.opera ? 0 : 1, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || { }) ); }; Effect.SlideUp = function(element) { element = $(element).cleanWhitespace(); var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, window.opera ? 0 : 1, Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'box', scaleFrom: 100, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || { }) ); }; // Bug in opera makes the TD containing this element expand for a instance after finish Effect.Squish = function(element) { return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }); }; Effect.Grow = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; switch (options.direction) { case 'top-left': initialMoveX = initialMoveY = moveX = moveY = 0; break; case 'top-right': initialMoveX = dims.width; initialMoveY = moveY = 0; moveX = -dims.width; break; case 'bottom-left': initialMoveX = moveX = 0; initialMoveY = dims.height; moveY = -dims.height; break; case 'bottom-right': initialMoveX = dims.width; initialMoveY = dims.height; moveX = -dims.width; moveY = -dims.height; break; case 'center': initialMoveX = dims.width / 2; initialMoveY = dims.height / 2; moveX = -dims.width / 2; moveY = -dims.height / 2; break; } return new Effect.Move(element, { x: initialMoveX, y: initialMoveY, duration: 0.01, beforeSetup: function(effect) { effect.element.hide().makeClipping().makePositioned(); }, afterFinishInternal: function(effect) { new Effect.Parallel( [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); } }); }; Effect.Shrink = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var moveX, moveY; switch (options.direction) { case 'top-left': moveX = moveY = 0; break; case 'top-right': moveX = dims.width; moveY = 0; break; case 'bottom-left': moveX = 0; moveY = dims.height; break; case 'bottom-right': moveX = dims.width; moveY = dims.height; break; case 'center': moveX = dims.width / 2; moveY = dims.height / 2; break; } return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) ], Object.extend({ beforeStartInternal: function(effect) { effect.effects[0].element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); }; Effect.Pulsate = function(element) { element = $(element); var options = arguments[1] || { }, oldOpacity = element.getInlineOpacity(), transition = options.transition || Effect.Transitions.linear, reverser = function(pos){ return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); }; return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); }; Effect.Fold = function(element) { element = $(element); var oldStyle = { top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; element.makeClipping(); return new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleX: false, afterFinishInternal: function(effect) { new Effect.Scale(element, 1, { scaleContent: false, scaleY: false, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); } }); }}, arguments[1] || { })); }; Effect.Morph = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ style: { } }, arguments[1] || { }); if (!Object.isString(options.style)) this.style = $H(options.style); else { if (options.style.include(':')) this.style = options.style.parseStyle(); else { this.element.addClassName(options.style); this.style = $H(this.element.getStyles()); this.element.removeClassName(options.style); var css = this.element.getStyles(); this.style = this.style.reject(function(style) { return style.value == css[style.key]; }); options.afterFinishInternal = function(effect) { effect.element.addClassName(effect.options.style); effect.transforms.each(function(transform) { effect.element.style[transform.style] = ''; }); }; } } this.start(options); }, setup: function(){ function parseColor(color){ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ return parseInt( color.slice(i*2+1,i*2+3), 16 ); }); } this.transforms = this.style.map(function(pair){ var property = pair[0], value = pair[1], unit = null; if (value.parseColor('#zzzzzz') != '#zzzzzz') { value = value.parseColor(); unit = 'color'; } else if (property == 'opacity') { value = parseFloat(value); if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); } else if (Element.CSS_LENGTH.test(value)) { var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); value = parseFloat(components[1]); unit = (components.length == 3) ? components[2] : null; } var originalValue = this.element.getStyle(property); return { style: property.camelize(), originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), targetValue: unit=='color' ? parseColor(value) : value, unit: unit }; }.bind(this)).reject(function(transform){ return ( (transform.originalValue == transform.targetValue) || ( transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) ); }); }, update: function(position) { var style = { }, transform, i = this.transforms.length; while(i--) style[(transform = this.transforms[i]).style] = transform.unit=='color' ? '#'+ (Math.round(transform.originalValue[0]+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + (Math.round(transform.originalValue[1]+ (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + (Math.round(transform.originalValue[2]+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : (transform.originalValue + (transform.targetValue - transform.originalValue) * position).toFixed(3) + (transform.unit === null ? '' : transform.unit); this.element.setStyle(style, true); } }); Effect.Transform = Class.create({ initialize: function(tracks){ this.tracks = []; this.options = arguments[1] || { }; this.addTracks(tracks); }, addTracks: function(tracks){ tracks.each(function(track){ track = $H(track); var data = track.values().first(); this.tracks.push($H({ ids: track.keys().first(), effect: Effect.Morph, options: { style: data } })); }.bind(this)); return this; }, play: function(){ return new Effect.Parallel( this.tracks.map(function(track){ var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); var elements = [$(ids) || $$(ids)].flatten(); return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); }).flatten(), this.options ); } }); Element.CSS_PROPERTIES = $w( 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + 'fontSize fontWeight height left letterSpacing lineHeight ' + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex'); Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; String.__parseStyleElement = document.createElement('div'); String.prototype.parseStyle = function(){ var style, styleRules = $H(); if (Prototype.Browser.WebKit) style = new Element('div',{style:this}).style; else { String.__parseStyleElement.innerHTML = '
    '; style = String.__parseStyleElement.childNodes[0].style; } Element.CSS_PROPERTIES.each(function(property){ if (style[property]) styleRules.set(property, style[property]); }); if (Prototype.Browser.IE && this.include('opacity')) styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); return styleRules; }; if (document.defaultView && document.defaultView.getComputedStyle) { Element.getStyles = function(element) { var css = document.defaultView.getComputedStyle($(element), null); return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { styles[property] = css[property]; return styles; }); }; } else { Element.getStyles = function(element) { element = $(element); var css = element.currentStyle, styles; styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { results[property] = css[property]; return results; }); if (!styles.opacity) styles.opacity = element.getOpacity(); return styles; }; } Effect.Methods = { morph: function(element, style) { element = $(element); new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); return element; }, visualEffect: function(element, effect, options) { element = $(element); var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); new Effect[klass](element, options); return element; }, highlight: function(element, options) { element = $(element); new Effect.Highlight(element, options); return element; } }; $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ 'pulsate shake puff squish switchOff dropOut').each( function(effect) { Effect.Methods[effect] = function(element, options){ element = $(element); Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); return element; }; } ); $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( function(f) { Effect.Methods[f] = Element[f]; } ); Element.addMethods(Effect.Methods);ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/prototype.js000066400000000000000000004211161154044152300304560ustar00rootroot00000000000000/* Prototype JavaScript framework, version 1.6.1 * (c) 2005-2009 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * *--------------------------------------------------------------------------*/ var Prototype = { Version: '1.6.1', Browser: (function(){ var ua = navigator.userAgent; var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; return { IE: !!window.attachEvent && !isOpera, Opera: isOpera, WebKit: ua.indexOf('AppleWebKit/') > -1, Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, MobileSafari: /Apple.*Mobile.*Safari/.test(ua) } })(), BrowserFeatures: { XPath: !!document.evaluate, SelectorsAPI: !!document.querySelector, ElementExtensions: (function() { var constructor = window.Element || window.HTMLElement; return !!(constructor && constructor.prototype); })(), SpecificElementExtensions: (function() { if (typeof window.HTMLDivElement !== 'undefined') return true; var div = document.createElement('div'); var form = document.createElement('form'); var isSupported = false; if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { isSupported = true; } div = form = null; return isSupported; })() }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, K: function(x) { return x } }; if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; var Abstract = { }; var Try = { these: function() { var returnValue; for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) { } } return returnValue; } }; /* Based on Alex Arnell's inheritance implementation. */ var Class = (function() { function subclass() {}; function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } function addMethods(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf"); } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } return { create: create, Methods: { addMethods: addMethods } }; })(); (function() { var _toString = Object.prototype.toString; function extend(destination, source) { for (var property in source) destination[property] = source[property]; return destination; } function inspect(object) { try { if (isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } } function toJSON(object) { var type = typeof object; switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); } if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); if (isElement(object)) return; var results = []; for (var property in object) { var value = toJSON(object[property]); if (!isUndefined(value)) results.push(property.toJSON() + ': ' + value); } return '{' + results.join(', ') + '}'; } function toQueryString(object) { return $H(object).toQueryString(); } function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); } function keys(object) { var results = []; for (var property in object) results.push(property); return results; } function values(object) { var results = []; for (var property in object) results.push(object[property]); return results; } function clone(object) { return extend({ }, object); } function isElement(object) { return !!(object && object.nodeType == 1); } function isArray(object) { return _toString.call(object) == "[object Array]"; } function isHash(object) { return object instanceof Hash; } function isFunction(object) { return typeof object === "function"; } function isString(object) { return _toString.call(object) == "[object String]"; } function isNumber(object) { return _toString.call(object) == "[object Number]"; } function isUndefined(object) { return typeof object === "undefined"; } extend(Object, { extend: extend, inspect: inspect, toJSON: toJSON, toQueryString: toQueryString, toHTML: toHTML, keys: keys, values: values, clone: clone, isElement: isElement, isArray: isArray, isHash: isHash, isFunction: isFunction, isString: isString, isNumber: isNumber, isUndefined: isUndefined }); })(); Object.extend(Function.prototype, (function() { var slice = Array.prototype.slice; function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; } function merge(array, args) { array = slice.call(array, 0); return update(array, args); } function argumentNames() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = slice.call(arguments, 1); return function() { var a = merge(args, arguments); return __method.apply(context, a); } } function bindAsEventListener(context) { var __method = this, args = slice.call(arguments, 1); return function(event) { var a = update([event || window.event], args); return __method.apply(context, a); } } function curry() { if (!arguments.length) return this; var __method = this, args = slice.call(arguments, 0); return function() { var a = merge(args, arguments); return __method.apply(this, a); } } function delay(timeout) { var __method = this, args = slice.call(arguments, 1); timeout = timeout * 1000 return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); } function defer() { var args = update([0.01], arguments); return this.delay.apply(this, args); } function wrap(wrapper) { var __method = this; return function() { var a = update([__method.bind(this)], arguments); return wrapper.apply(this, a); } } function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { var a = update([this], arguments); return __method.apply(null, a); }; } return { argumentNames: argumentNames, bind: bind, bindAsEventListener: bindAsEventListener, curry: curry, delay: delay, defer: defer, wrap: wrap, methodize: methodize } })()); Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + this.getUTCDate().toPaddedString(2) + 'T' + this.getUTCHours().toPaddedString(2) + ':' + this.getUTCMinutes().toPaddedString(2) + ':' + this.getUTCSeconds().toPaddedString(2) + 'Z"'; }; RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, registerCallback: function() { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, execute: function() { this.callback(this); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.execute(); this.currentlyExecuting = false; } catch(e) { this.currentlyExecuting = false; throw e; } } } }); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); }, specialChar: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } }); Object.extend(String.prototype, (function() { function prepareReplacement(replacement) { if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; } function gsub(pattern, replacement) { var result = '', source = this, match; replacement = prepareReplacement(replacement); if (Object.isString(pattern)) pattern = RegExp.escape(pattern); if (!(pattern.length || pattern.source)) { replacement = replacement(''); return replacement + source.split('').join(replacement) + replacement; } while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; } function sub(pattern, replacement, count) { replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); } function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); } function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); } function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); } function stripTags() { return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); } function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); } function extractScripts() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); } function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); } function escapeHTML() { return this.replace(/&/g,'&').replace(//g,'>'); } function unescapeHTML() { return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); } function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; } return hash; }); } function toArray() { return this.split(''); } function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); } function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); } function camelize() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; } function capitalize() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); } function underscore() { return this.replace(/::/g, '/') .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') .replace(/([a-z\d])([A-Z])/g, '$1_$2') .replace(/-/g, '_') .toLowerCase(); } function dasherize() { return this.replace(/_/g, '-'); } function inspect(useDoubleQuotes) { var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { if (character in String.specialChar) { return String.specialChar[character]; } return '\\u00' + character.charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } function toJSON() { return this.inspect(true); } function unfilterJSON(filter) { return this.replace(filter || Prototype.JSONFilter, '$1'); } function isJSON() { var str = this; if (str.blank()) return false; str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); } function evalJSON(sanitize) { var json = this.unfilterJSON(); try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); } function include(pattern) { return this.indexOf(pattern) > -1; } function startsWith(pattern) { return this.indexOf(pattern) === 0; } function endsWith(pattern) { var d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; } function empty() { return this == ''; } function blank() { return /^\s*$/.test(this); } function interpolate(object, pattern) { return new Template(this, pattern).evaluate(object); } return { gsub: gsub, sub: sub, scan: scan, truncate: truncate, strip: String.prototype.trim ? String.prototype.trim : strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, evalScripts: evalScripts, escapeHTML: escapeHTML, unescapeHTML: unescapeHTML, toQueryParams: toQueryParams, parseQuery: toQueryParams, toArray: toArray, succ: succ, times: times, camelize: camelize, capitalize: capitalize, underscore: underscore, dasherize: dasherize, inspect: inspect, toJSON: toJSON, unfilterJSON: unfilterJSON, isJSON: isJSON, evalJSON: evalJSON, include: include, startsWith: startsWith, endsWith: endsWith, empty: empty, blank: blank, interpolate: interpolate }; })()); var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; var ctx = object, expr = match[3]; var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; match = pattern.exec(expr); if (match == null) return before; while (match != null) { var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + String.interpret(ctx); }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; var Enumerable = (function() { function each(iterator, context) { var index = 0; try { this._each(function(value) { iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; } return this; } function eachSlice(number, iterator, context) { var index = -number, slices = [], array = this.toArray(); if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); } function all(iterator, context) { iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; } function any(iterator, context) { iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { if (result = !!iterator.call(context, value, index)) throw $break; }); return result; } function collect(iterator, context) { iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { results.push(iterator.call(context, value, index)); }); return results; } function detect(iterator, context) { var result; this.each(function(value, index) { if (iterator.call(context, value, index)) { result = value; throw $break; } }); return result; } function findAll(iterator, context) { var results = []; this.each(function(value, index) { if (iterator.call(context, value, index)) results.push(value); }); return results; } function grep(filter, iterator, context) { iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) filter = new RegExp(RegExp.escape(filter)); this.each(function(value, index) { if (filter.match(value)) results.push(iterator.call(context, value, index)); }); return results; } function include(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; } function inGroupsOf(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); } function inject(memo, iterator, context) { this.each(function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; } function invoke(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); } function max(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); return result; } function min(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); return result; } function partition(iterator, context) { iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; } function pluck(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; } function reject(iterator, context) { var results = []; this.each(function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); return results; } function sortBy(iterator, context) { return this.map(function(value, index) { return { value: value, criteria: iterator.call(context, value, index) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); } function toArray() { return this.map(); } function zip() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { return iterator(collections.pluck(index)); }); } function size() { return this.toArray().length; } function inspect() { return '#'; } return { each: each, eachSlice: eachSlice, all: all, every: all, any: any, some: any, collect: collect, map: collect, detect: detect, findAll: findAll, select: findAll, filter: findAll, grep: grep, include: include, member: include, inGroupsOf: inGroupsOf, inject: inject, invoke: invoke, max: max, min: min, partition: partition, pluck: pluck, reject: reject, sortBy: sortBy, toArray: toArray, entries: toArray, zip: zip, size: size, inspect: inspect, find: detect }; })(); function $A(iterable) { if (!iterable) return []; if ('toArray' in Object(iterable)) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } Array.from = $A; (function() { var arrayProto = Array.prototype, slice = arrayProto.slice, _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available function each(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); } if (!_each) _each = each; function clear() { this.length = 0; return this; } function first() { return this[0]; } function last() { return this[this.length - 1]; } function compact() { return this.select(function(value) { return value != null; }); } function flatten() { return this.inject([], function(array, value) { if (Object.isArray(value)) return array.concat(value.flatten()); array.push(value); return array; }); } function without() { var values = slice.call(arguments, 0); return this.select(function(value) { return !values.include(value); }); } function reverse(inline) { return (inline !== false ? this : this.toArray())._reverse(); } function uniq(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); } function intersect(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); } function clone() { return slice.call(this, 0); } function size() { return this.length; } function inspect() { return '[' + this.map(Object.inspect).join(', ') + ']'; } function toJSON() { var results = []; this.each(function(object) { var value = Object.toJSON(object); if (!Object.isUndefined(value)) results.push(value); }); return '[' + results.join(', ') + ']'; } function indexOf(item, i) { i || (i = 0); var length = this.length; if (i < 0) i = length + i; for (; i < length; i++) if (this[i] === item) return i; return -1; } function lastIndexOf(item, i) { i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; var n = this.slice(0, i).reverse().indexOf(item); return (n < 0) ? n : i - n - 1; } function concat() { var array = slice.call(this, 0), item; for (var i = 0, length = arguments.length; i < length; i++) { item = arguments[i]; if (Object.isArray(item) && !('callee' in item)) { for (var j = 0, arrayLength = item.length; j < arrayLength; j++) array.push(item[j]); } else { array.push(item); } } return array; } Object.extend(arrayProto, Enumerable); if (!arrayProto._reverse) arrayProto._reverse = arrayProto.reverse; Object.extend(arrayProto, { _each: _each, clear: clear, first: first, last: last, compact: compact, flatten: flatten, without: without, reverse: reverse, uniq: uniq, intersect: intersect, clone: clone, toArray: clone, size: size, inspect: inspect, toJSON: toJSON }); var CONCAT_ARGUMENTS_BUGGY = (function() { return [].concat(arguments)[0][0] !== 1; })(1,2) if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; })(); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { function initialize(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); } function _each(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } } function set(key, value) { return this._object[key] = value; } function get(key) { if (this._object[key] !== Object.prototype[key]) return this._object[key]; } function unset(key) { var value = this._object[key]; delete this._object[key]; return value; } function toObject() { return Object.clone(this._object); } function keys() { return this.pluck('key'); } function values() { return this.pluck('value'); } function index(value) { var match = this.detect(function(pair) { return pair.value === value; }); return match && match.key; } function merge(object) { return this.clone().update(object); } function update(object) { return new Hash(object).inject(this, function(result, pair) { result.set(pair.key, pair.value); return result; }); } function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } function toQueryString() { return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) return results.concat(values.map(toQueryPair.curry(key))); } else results.push(toQueryPair(key, values)); return results; }).join('&'); } function inspect() { return '#'; } function toJSON() { return Object.toJSON(this.toObject()); } function clone() { return new Hash(this); } return { initialize: initialize, _each: _each, set: set, get: get, unset: unset, toObject: toObject, toTemplateReplacements: toObject, keys: keys, values: values, index: index, merge: merge, update: update, toQueryString: toQueryString, inspect: inspect, toJSON: toJSON, clone: clone }; })()); Hash.from = $H; Object.extend(Number.prototype, (function() { function toColorPart() { return this.toPaddedString(2, 16); } function succ() { return this + 1; } function times(iterator, context) { $R(0, this, true).each(iterator, context); return this; } function toPaddedString(length, radix) { var string = this.toString(radix || 10); return '0'.times(length - string.length) + string; } function toJSON() { return isFinite(this) ? this.toString() : 'null'; } function abs() { return Math.abs(this); } function round() { return Math.round(this); } function ceil() { return Math.ceil(this); } function floor() { return Math.floor(this); } return { toColorPart: toColorPart, succ: succ, times: times, toPaddedString: toPaddedString, toJSON: toJSON, abs: abs, round: round, ceil: ceil, floor: floor }; })()); function $R(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } var ObjectRange = Class.create(Enumerable, (function() { function initialize(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; } function _each(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } } function include(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } return { initialize: initialize, _each: _each, include: include }; })()); var Ajax = { getTransport: function() { return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, activeRequestCount: 0 }; Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, unregister: function(responder) { this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) { } } }); } }; Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); Ajax.Base = Class.create({ initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '', evalJSON: true, evalJS: true }; Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); else if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); Ajax.Request = Class.create(Ajax.Base, { _complete: false, initialize: function($super, url, options) { $super(options); this.transport = Ajax.getTransport(); this.request(url); }, request: function(url) { this.url = url; this.method = this.options.method; var params = Object.clone(this.options.parameters); if (!['get', 'post'].include(this.method)) { params['_method'] = this.method; this.method = 'post'; } this.parameters = params; if (params = Object.toQueryString(params)) { if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='; } try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); this.body = this.method == 'post' ? (this.options.postBody || params) : null; this.transport.send(this.body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); }, setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, success: function() { var status = this.getStatus(); return !status || (status >= 200 && status < 300); }, getStatus: function() { try { return this.transport.status || 0; } catch (e) { return 0 } }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { this.transport.onreadystatechange = Prototype.emptyFunction; } }, isSameOrigin: function() { var m = this.url.match(/^\s*https?:\/\/[^\/]*/); return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ protocol: location.protocol, domain: document.domain, port: location.port ? ':' + location.port : '' })); }, getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; } catch (e) { return null; } }, evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } if(readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); } }, status: 0, statusText: '', getStatus: Ajax.Request.prototype.getStatus, getStatusText: function() { try { return this.transport.statusText || ''; } catch (e) { return '' } }, getHeader: Ajax.Request.prototype.getHeader, getAllHeaders: function() { try { return this.getAllResponseHeaders(); } catch (e) { return null } }, getResponseHeader: function(name) { return this.transport.getResponseHeader(name); }, getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); }, _getHeaderJSON: function() { var json = this.getHeader('X-JSON'); if (!json) return null; json = decodeURIComponent(escape(json)); try { return json.evalJSON(this.request.options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } }, _getResponseJSON: function() { var options = this.request.options; if (!options.evalJSON || (options.evalJSON != 'force' && !(this.getHeader('Content-type') || '').include('application/json')) || this.responseText.blank()) return null; try { return this.responseText.evalJSON(options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } } }); Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) }; options = Object.clone(options); var onComplete = options.onComplete; options.onComplete = (function(response, json) { this.updateContent(response.responseText); if (Object.isFunction(onComplete)) onComplete(response, json); }).bind(this); $super(url, options); }, updateContent: function(responseText) { var receiver = this.container[this.success() ? 'success' : 'failure'], options = this.options; if (!options.evalScripts) responseText = responseText.stripScripts(); if (receiver = $(receiver)) { if (options.insertion) { if (Object.isString(options.insertion)) { var insertion = { }; insertion[options.insertion] = responseText; receiver.insert(insertion); } else options.insertion(receiver, responseText); } else receiver.update(responseText); } } }); Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { initialize: function($super, container, url, options) { $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = { }; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(response) { if (this.options.decay) { this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = response.responseText; } this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(Element.extend(query.snapshotItem(i))); return results; }; } /*--------------------------------------------------------------------------*/ if (!window.Node) var Node = { }; if (!Node.ELEMENT_NODE) { Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }); } (function(global) { var SETATTRIBUTE_IGNORES_NAME = (function(){ var elForm = document.createElement("form"); var elInput = document.createElement("input"); var root = document.documentElement; elInput.setAttribute("name", "test"); elForm.appendChild(elInput); root.appendChild(elForm); var isBuggy = elForm.elements ? (typeof elForm.elements.test == "undefined") : null; root.removeChild(elForm); elForm = elInput = null; return isBuggy; })(); var element = global.Element; global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(global.Element, element || { }); if (element) global.Element.prototype = element.prototype; })(this); Element.cache = { }; Element.idCounter = 1; Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; }, hide: function(element) { element = $(element); element.style.display = 'none'; return element; }, show: function(element) { element = $(element); element.style.display = ''; return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; }, update: (function(){ var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ var el = document.createElement("select"), isBuggy = true; el.innerHTML = ""; if (el.options && el.options[0]) { isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; } el = null; return isBuggy; })(); var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ try { var el = document.createElement("table"); if (el && el.tBodies) { el.innerHTML = "test"; var isBuggy = typeof el.tBodies[0] == "undefined"; el = null; return isBuggy; } } catch (e) { return true; } })(); var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { var s = document.createElement("script"), isBuggy = false; try { s.appendChild(document.createTextNode("")); isBuggy = !s.firstChild || s.firstChild && s.firstChild.nodeType !== 3; } catch (e) { isBuggy = true; } s = null; return isBuggy; })(); function update(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); var tagName = element.tagName.toUpperCase(); if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { element.text = content; return element; } if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { if (tagName in Element._insertionTranslations.tags) { while (element.firstChild) { element.removeChild(element.firstChild); } Element._getContentFromAnonymousElement(tagName, content.stripScripts()) .each(function(node) { element.appendChild(node) }); } else { element.innerHTML = content.stripScripts(); } } else { element.innerHTML = content.stripScripts(); } content.evalScripts.bind(content).defer(); return element; } return update; })(), replace: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); range.selectNode(element); content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } element.parentNode.replaceChild(content, element); return element; }, insert: function(element, insertions) { element = $(element); if (Object.isString(insertions) || Object.isNumber(insertions) || Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; var content, insert, tagName, childNodes; for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { insert(element, content); continue; } content = Object.toHTML(content); tagName = ((position == 'before' || position == 'after') ? element.parentNode : element).tagName.toUpperCase(); childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); if (position == 'top' || position == 'after') childNodes.reverse(); childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } return element; }, wrap: function(element, wrapper, attributes) { element = $(element); if (Object.isElement(wrapper)) $(wrapper).writeAttribute(attributes || { }); else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); else wrapper = new Element('div', wrapper); if (element.parentNode) element.parentNode.replaceChild(wrapper, element); wrapper.appendChild(element); return wrapper; }, inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { var property = pair.first(), attribute = pair.last(); var value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, recursivelyCollect: function(element, property) { element = $(element); var elements = []; while (element = element[property]) if (element.nodeType == 1) elements.push(Element.extend(element)); return elements; }, ancestors: function(element) { return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { return Element.select(element, "*"); }, firstDescendant: function(element) { element = $(element).firstChild; while (element && element.nodeType != 1) element = element.nextSibling; return $(element); }, immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; while (element && element.nodeType != 1) element = element.nextSibling; if (element) return [element].concat($(element).nextSiblings()); return []; }, previousSiblings: function(element) { return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); return Element.previousSiblings(element).reverse() .concat(Element.nextSiblings(element)); }, match: function(element, selector) { if (Object.isString(selector)) selector = new Selector(selector); return selector.match($(element)); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return Element.firstDescendant(element); return Object.isNumber(expression) ? Element.descendants(element)[expression] : Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = Element.previousSiblings(element); return Object.isNumber(expression) ? previousSiblings[expression] : Selector.findElement(previousSiblings, expression, index); }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = Element.nextSiblings(element); return Object.isNumber(expression) ? nextSiblings[expression] : Selector.findElement(nextSiblings, expression, index); }, select: function(element) { var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element, args); }, adjacent: function(element) { var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element.parentNode, args).without(element); }, identify: function(element) { element = $(element); var id = Element.readAttribute(element, 'id'); if (id) return id; do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); Element.writeAttribute(element, 'id', id); return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; if (name.include(':')) { return (!element.attributes || !element.attributes[name]) ? null : element.attributes[name].value; } } return element.getAttribute(name); }, writeAttribute: function(element, name, value) { element = $(element); var attributes = { }, t = Element._attributeTranslations.write; if (typeof name == 'object') attributes = name; else attributes[name] = Object.isUndefined(value) ? true : value; for (var attr in attributes) { name = t.names[attr] || attr; value = attributes[attr]; if (t.values[attr]) name = t.values[attr](element, value); if (value === false || value === null) element.removeAttribute(name); else if (value === true) element.setAttribute(name, name); else element.setAttribute(name, value); } return element; }, getHeight: function(element) { return Element.getDimensions(element).height; }, getWidth: function(element) { return Element.getDimensions(element).width; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; element.className = element.className.replace( new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; return Element[Element.hasClassName(element, className) ? 'removeClassName' : 'addClassName'](element, className); }, cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; } return element; }, empty: function(element) { return $(element).innerHTML.blank(); }, descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; if (ancestor.contains) return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) if (element == ancestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, getStyle: function(element, style) { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } if (style == 'opacity') return value ? parseFloat(value) : 1.0; return value == 'auto' ? null : value; }, getOpacity: function(element) { return $(element).getStyle('opacity'); }, setStyle: function(element, styles) { element = $(element); var elementStyle = element.style, match; if (Object.isString(styles)) { element.style.cssText += ';' + styles; return styles.include('opacity') ? element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : property] = styles[property]; return element; }, setOpacity: function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }, getDimensions: function(element) { element = $(element); var display = Element.getStyle(element, 'display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; var originalDisplay = els.display; els.visibility = 'hidden'; if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; }, makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } } return element; }, undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } return element; }, makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = Element.getStyle(element, 'overflow') || 'auto'; if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }, positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); }, absolutize: function(element) { element = $(element); if (Element.getStyle(element, 'position') == 'absolute') return element; var offsets = Element.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; return element; }, relativize: function(element) { element = $(element); if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; return element; }, cumulativeScrollOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return Element._returnOffset(valueL, valueT); }, getOffsetParent: function(element) { if (element.offsetParent) return $(element.offsetParent); if (element == document.body) return $(element); while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return $(element); return $(document.body); }, viewportOffset: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return Element._returnOffset(valueL, valueT); }, clonePosition: function(element, source) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || { }); source = $(source); var p = Element.viewportOffset(source); element = $(element); var delta = [0, 0]; var parent = null; if (Element.getStyle(element, 'position') == 'absolute') { parent = Element.getOffsetParent(element); delta = Element.viewportOffset(parent); } if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; if (options.setHeight) element.style.height = source.offsetHeight + 'px'; return element; } }; Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); Element._attributeTranslations = { write: { names: { className: 'class', htmlFor: 'for' }, values: { } } }; if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { case 'left': case 'top': case 'right': case 'bottom': if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': if (!Element.visible(element)) return null; var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) return dim + 'px'; var properties; if (style === 'height') { properties = ['border-top-width', 'padding-top', 'padding-bottom', 'border-bottom-width']; } else { properties = ['border-left-width', 'padding-left', 'padding-right', 'border-right-width']; } return properties.inject(dim, function(memo, property) { var val = proceed(element, property); return val === null ? memo : memo - parseInt(val, 10); }) + 'px'; default: return proceed(element, style); } } ); Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( function(proceed, element, attribute) { if (attribute === 'title') return element.title; return proceed(element, attribute); } ); } else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); try { element.offsetParent } catch(e) { return $(document.body) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); try { element.offsetParent } catch(e) { return Element._returnOffset(0,0) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); var offsetParent = element.getOffsetParent(); if (offsetParent && offsetParent.getStyle('position') === 'fixed') offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); }); Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( function(proceed, element) { try { element.offsetParent } catch(e) { return Element._returnOffset(0,0) } return proceed(element); } ); Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); var value = element.style[style]; if (!value && element.currentStyle) value = element.currentStyle[style]; if (style == 'opacity') { if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if (value[1]) return parseFloat(value[1]) / 100; return 1.0; } if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { function stripAlpha(filter){ return filter.replace(/alpha\([^\)]*\)/gi,''); } element = $(element); var currentStyle = element.currentStyle; if ((currentStyle && !currentStyle.hasLayout) || (!currentStyle && element.style.zoom == 'normal')) element.style.zoom = 1; var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { (filter = stripAlpha(filter)) ? style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; Element._attributeTranslations = (function(){ var classProp = 'className'; var forProp = 'for'; var el = document.createElement('div'); el.setAttribute(classProp, 'x'); if (el.className !== 'x') { el.setAttribute('class', 'x'); if (el.className === 'x') { classProp = 'class'; } } el = null; el = document.createElement('label'); el.setAttribute(forProp, 'x'); if (el.htmlFor !== 'x') { el.setAttribute('htmlFor', 'x'); if (el.htmlFor === 'x') { forProp = 'htmlFor'; } } el = null; return { read: { names: { 'class': classProp, 'className': classProp, 'for': forProp, 'htmlFor': forProp }, values: { _getAttr: function(element, attribute) { return element.getAttribute(attribute); }, _getAttr2: function(element, attribute) { return element.getAttribute(attribute, 2); }, _getAttrNode: function(element, attribute) { var node = element.getAttributeNode(attribute); return node ? node.value : ""; }, _getEv: (function(){ var el = document.createElement('div'); el.onclick = Prototype.emptyFunction; var value = el.getAttribute('onclick'); var f; if (String(value).indexOf('{') > -1) { f = function(element, attribute) { attribute = element.getAttribute(attribute); if (!attribute) return null; attribute = attribute.toString(); attribute = attribute.split('{')[1]; attribute = attribute.split('}')[0]; return attribute.strip(); }; } else if (value === '') { f = function(element, attribute) { attribute = element.getAttribute(attribute); if (!attribute) return null; return attribute.strip(); }; } el = null; return f; })(), _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { return element.title; } } } } })(); Element._attributeTranslations.write = { names: Object.extend({ cellpadding: 'cellPadding', cellspacing: 'cellSpacing' }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; }, style: function(element, value) { element.style.cssText = value ? value : ''; } } }; Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); (function(v) { Object.extend(v, { href: v._getAttr2, src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, checked: v._flag, readonly: v._flag, multiple: v._flag, onload: v._getEv, onunload: v._getEv, onclick: v._getEv, ondblclick: v._getEv, onmousedown: v._getEv, onmouseup: v._getEv, onmouseover: v._getEv, onmousemove: v._getEv, onmouseout: v._getEv, onfocus: v._getEv, onblur: v._getEv, onkeypress: v._getEv, onkeydown: v._getEv, onkeyup: v._getEv, onsubmit: v._getEv, onreset: v._getEv, onselect: v._getEv, onchange: v._getEv }); })(Element._attributeTranslations.read.values); if (Prototype.BrowserFeatures.ElementExtensions) { (function() { function _descendants(element) { var nodes = element.getElementsByTagName('*'), results = []; for (var i = 0, node; node = nodes[i]; i++) if (node.tagName !== "!") // Filter out comment nodes. results.push(node); return results; } Element.Methods.down = function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? _descendants(element)[expression] : Element.select(element, expression)[index || 0]; } })(); } } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : (value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }; } else if (Prototype.Browser.WebKit) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; if (value == 1) if(element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch (e) { } return element; }; Element.Methods.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }; } if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { element.parentNode.replaceChild(content, element); return element; } content = Object.toHTML(content); var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { var nextSibling = element.next(); var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); else fragments.each(function(node) { parent.appendChild(node) }); } else element.outerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } Element._returnOffset = function(l, t) { var result = [l, t]; result.left = l; result.top = t; return result; }; Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; t[2].times(function() { div = div.firstChild }); } else div.innerHTML = html; return $A(div.childNodes); }; Element._insertionTranslations = { before: function(element, node) { element.parentNode.insertBefore(node, element); }, top: function(element, node) { element.insertBefore(node, element.firstChild); }, bottom: function(element, node) { element.appendChild(node); }, after: function(element, node) { element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['', '
    ', 1], TBODY: ['', '
    ', 2], TR: ['', '
    ', 3], TD: ['
    ', '
    ', 4], SELECT: ['', 1] } }; (function() { var tags = Element._insertionTranslations.tags; Object.extend(tags, { THEAD: tags.TBODY, TFOOT: tags.TBODY, TH: tags.TD }); })(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return !!(node && node.specified); } }; Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); (function(div) { if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { window.HTMLElement = { }; window.HTMLElement.prototype = div['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } div = null; })(document.createElement('div')) Element.extend = (function() { function checkDeficiency(tagName) { if (typeof window.Element != 'undefined') { var proto = window.Element.prototype; if (proto) { var id = '_' + (Math.random()+'').slice(2); var el = document.createElement(tagName); proto[id] = 'x'; var isBuggy = (el[id] !== 'x'); delete proto[id]; el = null; return isBuggy; } } return false; } function extendElementWith(element, methods) { for (var property in methods) { var value = methods[property]; if (Object.isFunction(value) && !(property in element)) element[property] = value.methodize(); } } var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); if (Prototype.BrowserFeatures.SpecificElementExtensions) { if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { return function(element) { if (element && typeof element._extendedByPrototype == 'undefined') { var t = element.tagName; if (t && (/^(?:object|applet|embed)$/i.test(t))) { extendElementWith(element, Element.Methods); extendElementWith(element, Element.Methods.Simulated); extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); } } return element; } } return Prototype.K; } var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { if (!element || typeof element._extendedByPrototype != 'undefined' || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), tagName = element.tagName.toUpperCase(); if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); extendElementWith(element, methods); element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); } } }); extend.refresh(); return extend; })(); Element.hasAttribute = function(element, attribute) { if (element.hasAttribute) return element.hasAttribute(attribute); return Element.Methods.Simulated.hasAttribute(element, attribute); }; Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; if (!methods) { Object.extend(Form, Form.Methods); Object.extend(Form.Element, Form.Element.Methods); Object.extend(Element.Methods.ByTag, { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), "TEXTAREA": Object.clone(Form.Element.Methods) }); } if (arguments.length == 2) { var tagName = methods; methods = arguments[1]; } if (!tagName) Object.extend(Element.Methods, methods || { }); else { if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; for (var property in methods) { var value = methods[property]; if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) destination[property] = value.methodize(); } } function findDOMClass(tagName) { var klass; var trans = { "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": "FrameSet", "IFRAME": "IFrame" }; if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; var element = document.createElement(tagName); var proto = element['__proto__'] || element.constructor.prototype; element = null; return proto; } var elementPrototype = window.HTMLElement ? HTMLElement.prototype : Element.prototype; if (F.ElementExtensions) { copy(Element.Methods, elementPrototype); copy(Element.Methods.Simulated, elementPrototype, true); } if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; if (Element.extend.refresh) Element.extend.refresh(); Element.cache = { }; }; document.viewport = { getDimensions: function() { return { width: this.getWidth(), height: this.getHeight() }; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; (function(viewport) { var B = Prototype.Browser, doc = document, element, property = {}; function getRootElement() { if (B.WebKit && !doc.evaluate) return document; if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) return document.body; return document.documentElement; } function define(D) { if (!element) element = getRootElement(); property[D] = 'client' + D; viewport['get' + D] = function() { return element[property[D]] }; return viewport['get' + D](); } viewport.getWidth = define.curry('Width'); viewport.getHeight = define.curry('Height'); })(document.viewport); Element.Storage = { UID: 1 }; Element.addMethods({ getStorage: function(element) { if (!(element = $(element))) return; var uid; if (element === window) { uid = 0; } else { if (typeof element._prototypeUID === "undefined") element._prototypeUID = [Element.Storage.UID++]; uid = element._prototypeUID[0]; } if (!Element.Storage[uid]) Element.Storage[uid] = $H(); return Element.Storage[uid]; }, store: function(element, key, value) { if (!(element = $(element))) return; if (arguments.length === 2) { Element.getStorage(element).update(key); } else { Element.getStorage(element).set(key, value); } return element; }, retrieve: function(element, key, defaultValue) { if (!(element = $(element))) return; var hash = Element.getStorage(element), value = hash.get(key); if (Object.isUndefined(value)) { hash.set(key, defaultValue); value = defaultValue; } return value; }, clone: function(element, deep) { if (!(element = $(element))) return; var clone = element.cloneNode(deep); clone._prototypeUID = void 0; if (deep) { var descendants = Element.select(clone, '*'), i = descendants.length; while (i--) { descendants[i]._prototypeUID = void 0; } } return Element.extend(clone); } }); /* Portions of the Selector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); if (this.shouldUseSelectorsAPI()) { this.mode = 'selectorsAPI'; } else if (this.shouldUseXPath()) { this.mode = 'xpath'; this.compileXPathMatcher(); } else { this.mode = "normal"; this.compileMatcher(); } }, shouldUseXPath: (function() { var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ var isBuggy = false; if (document.evaluate && window.XPathResult) { var el = document.createElement('div'); el.innerHTML = '
    '; var xpath = ".//*[local-name()='ul' or local-name()='UL']" + "//*[local-name()='li' or local-name()='LI']"; var result = document.evaluate(xpath, el, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); isBuggy = (result.snapshotLength !== 2); el = null; } return isBuggy; })(); return function() { if (!Prototype.BrowserFeatures.XPath) return false; var e = this.expression; if (Prototype.Browser.WebKit && (e.include("-of-type") || e.include(":empty"))) return false; if ((/(\[[\w-]*?:|:checked)/).test(e)) return false; if (IS_DESCENDANT_SELECTOR_BUGGY) return false; return true; } })(), shouldUseSelectorsAPI: function() { if (!Prototype.BrowserFeatures.SelectorsAPI) return false; if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; if (!Selector._div) Selector._div = new Element('div'); try { Selector._div.querySelector(this.expression); } catch(e) { return false; } return true; }, compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m, len = ps.length, name; if (Selector._cache[e]) { this.matcher = Selector._cache[e]; return; } this.matcher = ["this.matcher = function(root) {", "var r = root, h = Selector.handlers, c = false, n;"]; while (e && le != e && (/\S/).test(e)) { le = e; for (var i = 0; i"; } }); if (Prototype.BrowserFeatures.SelectorsAPI && document.compatMode === 'BackCompat') { Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ var div = document.createElement('div'), span = document.createElement('span'); div.id = "prototype_test_id"; span.className = 'Test'; div.appendChild(span); var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); div = span = null; return isIgnored; })(); } Object.extend(Selector, { _cache: { }, xpath: { descendant: "//*", child: "/*", adjacent: "/following-sibling::*[1]", laterSibling: '/following-sibling::*', tagName: function(m) { if (m[1] == '*') return ''; return "[local-name()='" + m[1].toLowerCase() + "' or local-name()='" + m[1].toUpperCase() + "']"; }, className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", id: "[@id='#{1}']", attrPresence: function(m) { m[1] = m[1].toLowerCase(); return new Template("[@#{1}]").evaluate(m); }, attr: function(m) { m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; return new Template(Selector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { var h = Selector.xpath.pseudos[m[1]]; if (!h) return ''; if (Object.isFunction(h)) return h(m); return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { '=': "[@#{1}='#{3}']", '!=': "[@#{1}!='#{3}']", '^=': "[starts-with(@#{1}, '#{3}')]", '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", '*=': "[contains(@#{1}, '#{3}')]", '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" }, pseudos: { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 'empty': "[count(*) = 0 and (count(text()) = 0)]", 'checked': "[@checked]", 'disabled': "[(@disabled) and (@type!='hidden')]", 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v, len = p.length, name; var exclusion = []; while (e && le != e && (/\S/).test(e)) { le = e; for (var i = 0; i= 0)]"; return new Template(predicate).evaluate({ fragment: fragment, a: a, b: b }); } } } }, criteria: { tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', className: 'n = h.className(n, r, "#{1}", c); c = false;', id: 'n = h.id(n, r, "#{1}", c); c = false;', attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', attr: function(m) { m[3] = (m[5] || m[6]); return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); }, pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); }, descendant: 'c = "descendant";', child: 'c = "child";', adjacent: 'c = "adjacent";', laterSibling: 'c = "laterSibling";' }, patterns: [ { name: 'laterSibling', re: /^\s*~\s*/ }, { name: 'child', re: /^\s*>\s*/ }, { name: 'adjacent', re: /^\s*\+\s*/ }, { name: 'descendant', re: /^\s/ }, { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } ], assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); }, className: function(element, matches) { return Element.hasClassName(element, matches[1]); }, id: function(element, matches) { return element.id === matches[1]; }, attrPresence: function(element, matches) { return Element.hasAttribute(element, matches[1]); }, attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, handlers: { concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) a.push(node); return a; }, mark: function(nodes) { var _true = Prototype.emptyFunction; for (var i = 0, node; node = nodes[i]; i++) node._countedByPrototype = _true; return nodes; }, unmark: (function(){ var PROPERTIES_ATTRIBUTES_MAP = (function(){ var el = document.createElement('div'), isBuggy = false, propName = '_countedByPrototype', value = 'x' el[propName] = value; isBuggy = (el.getAttribute(propName) === value); el = null; return isBuggy; })(); return PROPERTIES_ATTRIBUTES_MAP ? function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node.removeAttribute('_countedByPrototype'); return nodes; } : function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node._countedByPrototype = void 0; return nodes; } })(), index: function(parentNode, reverse, ofType) { parentNode._countedByPrototype = Prototype.emptyFunction; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { var node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } }, unique: function(nodes) { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); }, descendant: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName('*')); return results; }, child: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); } return results; }, adjacent: function(nodes) { for (var i = 0, results = [], node; node = nodes[i]; i++) { var next = this.nextElementSibling(node); if (next) results.push(next); } return results; }, laterSibling: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.nextSiblings(node)); return results; }, nextElementSibling: function(node) { while (node = node.nextSibling) if (node.nodeType == 1) return node; return null; }, previousElementSibling: function(node) { while (node = node.previousSibling) if (node.nodeType == 1) return node; return null; }, tagName: function(nodes, root, tagName, combinator) { var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { if (combinator == "descendant") { for (var i = 0, node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName(tagName)); return results; } else nodes = this[combinator](nodes); if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; if (root == document) { if (!targetNode) return []; if (!nodes) return [targetNode]; } else { if (!root.sourceIndex || root.sourceIndex < 1) { var nodes = root.getElementsByTagName('*'); for (var j = 0, node; node = nodes[j]; j++) { if (node.id === id) return [node]; } } } if (nodes) { if (combinator) { if (combinator == 'child') { for (var i = 0, node; node = nodes[i]; i++) if (targetNode.parentNode == node) return [targetNode]; } else if (combinator == 'descendant') { for (var i = 0, node; node = nodes[i]; i++) if (Element.descendantOf(targetNode, node)) return [targetNode]; } else if (combinator == 'adjacent') { for (var i = 0, node; node = nodes[i]; i++) if (Selector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; } else nodes = h[combinator](nodes); } for (var i = 0, node; node = nodes[i]; i++) if (node == targetNode) return [targetNode]; return []; } return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; }, className: function(nodes, root, className, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); return Selector.handlers.byClassName(nodes, root, className); }, byClassName: function(nodes, root, className) { if (!nodes) nodes = Selector.handlers.descendant([root]); var needle = ' ' + className + ' '; for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { nodeClassName = node.className; if (nodeClassName.length == 0) continue; if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) results.push(node); } return results; }, attrPresence: function(nodes, root, attr, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); return results; }, attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); var handler = Selector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); if (nodeValue === null) continue; if (handler(nodeValue, value)) results.push(node); } return results; }, pseudo: function(nodes, name, value, root, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); if (!nodes) nodes = root.getElementsByTagName("*"); return Selector.pseudos[name](nodes, value, root); } }, pseudos: { 'first-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.previousElementSibling(node)) continue; results.push(node); } return results; }, 'last-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.nextElementSibling(node)) continue; results.push(node); } return results; }, 'only-child': function(nodes, value, root) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) results.push(node); return results; }, 'nth-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root); }, 'nth-last-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true); }, 'nth-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, false, true); }, 'nth-last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true, true); }, 'first-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, false, true); }, 'last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, true, true); }, 'only-of-type': function(nodes, formula, root) { var p = Selector.pseudos; return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); }, getIndices: function(a, b, total) { if (a == 0) return b > 0 ? [b] : []; return $R(1, total).inject([], function(memo, i) { if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); return memo; }); }, nth: function(nodes, formula, root, reverse, ofType) { if (nodes.length == 0) return []; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { if (!node.parentNode._countedByPrototype) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } } if (formula.match(/^\d+$/)) { // just a number formula = Number(formula); for (var i = 0, node; node = nodes[i]; i++) if (node.nodeIndex == formula) results.push(node); } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (m[1] == "-") m[1] = -1; var a = m[1] ? Number(m[1]) : 1; var b = m[2] ? Number(m[2]) : 0; var indices = Selector.pseudos.getIndices(a, b, nodes.length); for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); } } h.unmark(nodes); h.unmark(indexed); return results; }, 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (node.tagName == '!' || node.firstChild) continue; results.push(node); } return results; }, 'not': function(nodes, selector, root) { var h = Selector.handlers, selectorType, m; var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._countedByPrototype) results.push(node); h.unmark(exclusions); return results; }, 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node.disabled && (!node.type || node.type !== 'hidden')) results.push(node); return results; }, 'disabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.disabled) results.push(node); return results; }, 'checked': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.checked) results.push(node); return results; } }, operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + '-').include('-' + (v || "").toUpperCase() + '-'); } }, split: function(expression) { var expressions = []; expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); return expressions; }, matchElements: function(elements, expression) { var matches = $$(expression), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._countedByPrototype) results.push(element); h.unmark(matches); return results; }, findElement: function(elements, expression, index) { if (Object.isNumber(expression)) { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { expressions = Selector.split(expressions.join(',')); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; } }); if (Prototype.Browser.IE) { Object.extend(Selector.handlers, { concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) if (node.tagName !== "!") a.push(node); return a; } }); } function $$() { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { form = $(form); form.reset(); return form; }, serializeElements: function(elements, options) { if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; var key, value, submitted = false, submit = options.submit; var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { serialize: function(form, options) { return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { var elements = $(form).getElementsByTagName('*'), element, arr = [ ], serializers = Form.Element.Serializers; for (var i = 0; element = elements[i]; i++) { arr.push(element); } return arr.inject([], function(elements, child) { if (serializers[child.tagName.toLowerCase()]) elements.push(Element.extend(child)); return elements; }) }, getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return $A(inputs).map(Element.extend); for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { form = $(form); Form.getElements(form).invoke('disable'); return form; }, enable: function(form) { form = $(form); Form.getElements(form).invoke('enable'); return form; }, findFirstElement: function(form) { var elements = $(form).getElements().findAll(function(element) { return 'hidden' != element.type && !element.disabled; }); var firstByIndex = elements.findAll(function(element) { return element.hasAttribute('tabIndex') && element.tabIndex >= 0; }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, focusFirstElement: function(form) { form = $(form); form.findFirstElement().activate(); return form; }, request: function(form, options) { form = $(form), options = Object.clone(options || { }); var params = options.parameters, action = form.readAttribute('action') || ''; if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; return new Ajax.Request(action, options); } }; /*--------------------------------------------------------------------------*/ Form.Element = { focus: function(element) { $(element).focus(); return element; }, select: function(element) { $(element).select(); return element; } }; Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { var pair = { }; pair[element.name] = value; return Object.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](element); }, setValue: function(element, value) { element = $(element); var method = element.tagName.toLowerCase(); Form.Element.Serializers[method](element, value); return element; }, clear: function(element) { $(element).value = ''; return element; }, present: function(element) { return $(element).value != ''; }, activate: function(element) { element = $(element); try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; }, disable: function(element) { element = $(element); element.disabled = true; return element; }, enable: function(element) { element = $(element); element.disabled = false; return element; } }; /*--------------------------------------------------------------------------*/ var Field = Form.Element; var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element, value); default: return Form.Element.Serializers.textarea(element, value); } }, inputSelector: function(element, value) { if (Object.isUndefined(value)) return element.checked ? element.value : null; else element.checked = !!value; }, textarea: function(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; }, select: function(element, value) { if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; currentValue = this.optionValue(opt); if (single) { if (currentValue == value) { opt.selected = true; return; } } else opt.selected = value.include(currentValue); } } }, selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; }, selectMany: function(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); } return values; }, optionValue: function(opt) { return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }; /*--------------------------------------------------------------------------*/ Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); this.element = $(element); this.lastValue = this.getValue(); }, execute: function() { var value = this.getValue(); if (Object.isString(this.lastValue) && Object.isString(value) ? this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } }); Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks: function() { Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } }); Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); (function() { var Event = { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, KEY_INSERT: 45, cache: {} }; var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; var _isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; _isButton = function(event, code) { return event.button === buttonMap[code]; }; } else if (Prototype.Browser.WebKit) { _isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; case 1: return event.which == 1 && event.metaKey; default: return false; } }; } else { _isButton = function(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); }; } function isLeftClick(event) { return _isButton(event, 0) } function isMiddleClick(event) { return _isButton(event, 1) } function isRightClick(event) { return _isButton(event, 2) } function element(event) { event = Event.extend(event); var node = event.target, type = event.type, currentTarget = event.currentTarget; if (currentTarget && currentTarget.tagName) { if (type === 'load' || type === 'error' || (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' && currentTarget.type === 'radio')) node = currentTarget; } if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; return Element.extend(node); } function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; var elements = [element].concat(element.ancestors()); return Selector.findElement(elements, expression, 0); } function pointer(event) { return { x: pointerX(event), y: pointerY(event) }; } function pointerX(event) { var docElement = document.documentElement, body = document.body || { scrollLeft: 0 }; return event.pageX || (event.clientX + (docElement.scrollLeft || body.scrollLeft) - (docElement.clientLeft || 0)); } function pointerY(event) { var docElement = document.documentElement, body = document.body || { scrollTop: 0 }; return event.pageY || (event.clientY + (docElement.scrollTop || body.scrollTop) - (docElement.clientTop || 0)); } function stop(event) { Event.extend(event); event.preventDefault(); event.stopPropagation(); event.stopped = true; } Event.Methods = { isLeftClick: isLeftClick, isMiddleClick: isMiddleClick, isRightClick: isRightClick, element: element, findElement: findElement, pointer: pointer, pointerX: pointerX, pointerY: pointerY, stop: stop }; var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); if (Prototype.Browser.IE) { function _relatedTarget(event) { var element; switch (event.type) { case 'mouseover': element = event.fromElement; break; case 'mouseout': element = event.toElement; break; default: return null; } return Element.extend(element); } Object.extend(methods, { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, inspect: function() { return '[object Event]' } }); Event.extend = function(event, element) { if (!event) return false; if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; var pointer = Event.pointer(event); Object.extend(event, { target: event.srcElement || element, relatedTarget: _relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); return Object.extend(event, methods); }; } else { Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); Event.extend = Prototype.K; } function _createResponder(element, eventName, handler) { var registry = Element.retrieve(element, 'prototype_event_registry'); if (Object.isUndefined(registry)) { CACHE.push(element); registry = Element.retrieve(element, 'prototype_event_registry', $H()); } var respondersForEvent = registry.get(eventName); if (Object.isUndefined(respondersForEvent)) { respondersForEvent = []; registry.set(eventName, respondersForEvent); } if (respondersForEvent.pluck('handler').include(handler)) return false; var responder; if (eventName.include(":")) { responder = function(event) { if (Object.isUndefined(event.eventName)) return false; if (event.eventName !== eventName) return false; Event.extend(event, element); handler.call(element, event); }; } else { if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === "mouseenter" || eventName === "mouseleave")) { if (eventName === "mouseenter" || eventName === "mouseleave") { responder = function(event) { Event.extend(event, element); var parent = event.relatedTarget; while (parent && parent !== element) { try { parent = parent.parentNode; } catch(e) { parent = element; } } if (parent === element) return; handler.call(element, event); }; } } else { responder = function(event) { Event.extend(event, element); handler.call(element, event); }; } } responder.handler = handler; respondersForEvent.push(responder); return responder; } function _destroyCache() { for (var i = 0, length = CACHE.length; i < length; i++) { Event.stopObserving(CACHE[i]); CACHE[i] = null; } } var CACHE = []; if (Prototype.Browser.IE) window.attachEvent('onunload', _destroyCache); if (Prototype.Browser.WebKit) window.addEventListener('unload', Prototype.emptyFunction, false); var _getDOMEventName = Prototype.K; if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; return eventName in translations ? translations[eventName] : eventName; }; } function observe(element, eventName, handler) { element = $(element); var responder = _createResponder(element, eventName, handler); if (!responder) return element; if (eventName.include(':')) { if (element.addEventListener) element.addEventListener("dataavailable", responder, false); else { element.attachEvent("ondataavailable", responder); element.attachEvent("onfilterchange", responder); } } else { var actualEventName = _getDOMEventName(eventName); if (element.addEventListener) element.addEventListener(actualEventName, responder, false); else element.attachEvent("on" + actualEventName, responder); } return element; } function stopObserving(element, eventName, handler) { element = $(element); var registry = Element.retrieve(element, 'prototype_event_registry'); if (Object.isUndefined(registry)) return element; if (eventName && !handler) { var responders = registry.get(eventName); if (Object.isUndefined(responders)) return element; responders.each( function(r) { Element.stopObserving(element, eventName, r.handler); }); return element; } else if (!eventName) { registry.each( function(pair) { var eventName = pair.key, responders = pair.value; responders.each( function(r) { Element.stopObserving(element, eventName, r.handler); }); }); return element; } var responders = registry.get(eventName); if (!responders) return; var responder = responders.find( function(r) { return r.handler === handler; }); if (!responder) return element; var actualEventName = _getDOMEventName(eventName); if (eventName.include(':')) { if (element.removeEventListener) element.removeEventListener("dataavailable", responder, false); else { element.detachEvent("ondataavailable", responder); element.detachEvent("onfilterchange", responder); } } else { if (element.removeEventListener) element.removeEventListener(actualEventName, responder, false); else element.detachEvent('on' + actualEventName, responder); } registry.set(eventName, responders.without(responder)); return element; } function fire(element, eventName, memo, bubble) { element = $(element); if (Object.isUndefined(bubble)) bubble = true; if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; var event; if (document.createEvent) { event = document.createEvent('HTMLEvents'); event.initEvent('dataavailable', true, true); } else { event = document.createEventObject(); event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; } event.eventName = eventName; event.memo = memo || { }; if (document.createEvent) element.dispatchEvent(event); else element.fireEvent(event.eventType, event); return Event.extend(event); } Object.extend(Event, Event.Methods); Object.extend(Event, { fire: fire, observe: observe, stopObserving: stopObserving }); Element.addMethods({ fire: fire, observe: observe, stopObserving: stopObserving }); Object.extend(document, { fire: fire.methodize(), observe: observe.methodize(), stopObserving: stopObserving.methodize(), loaded: false }); if (window.Event) Object.extend(window.Event, Event); else window.Event = Event; })(); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearTimeout(timer); document.loaded = true; document.fire('dom:loaded'); } function checkReadyState() { if (document.readyState === 'complete') { document.stopObserving('readystatechange', checkReadyState); fireContentLoadedEvent(); } } function pollDoScroll() { try { document.documentElement.doScroll('left'); } catch(e) { timer = pollDoScroll.defer(); return; } fireContentLoadedEvent(); } if (document.addEventListener) { document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { document.observe('readystatechange', checkReadyState); if (window == top) timer = pollDoScroll.defer(); } Event.observe(window, 'load', fireContentLoadedEvent); })(); Element.addMethods(); /*------------------------------- DEPRECATED -------------------------------*/ Hash.toQueryString = Object.toQueryString; var Toggle = { display: Element.toggle }; Element.Methods.childOf = Element.Methods.descendantOf; var Insertion = { Before: function(element, content) { return Element.insert(element, {before:content}); }, Top: function(element, content) { return Element.insert(element, {top:content}); }, Bottom: function(element, content) { return Element.insert(element, {bottom:content}); }, After: function(element, content) { return Element.insert(element, {after:content}); } }; var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); var Position = { includeScrollOffsets: false, prepare: function() { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }, within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x; this.ycomp = y; this.offset = Element.cumulativeOffset(element); return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = Element.cumulativeScrollOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = Element.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, cumulativeOffset: Element.Methods.cumulativeOffset, positionedOffset: Element.Methods.positionedOffset, absolutize: function(element) { Position.prepare(); return Element.absolutize(element); }, relativize: function(element) { Position.prepare(); return Element.relativize(element); }, realOffset: Element.Methods.cumulativeScrollOffset, offsetParent: Element.Methods.getOffsetParent, page: Element.Methods.viewportOffset, clone: function(source, target, options) { options = options || { }; return Element.clonePosition(target, source, options); } }; /*--------------------------------------------------------------------------*/ if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ function iter(name) { return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; } instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? function(element, className) { className = className.toString().strip(); var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); return cond ? document._getElementsByXPath('.//*' + cond, element) : []; } : function(element, className) { className = className.toString().strip(); var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); if (!classNames && !className) return elements; var nodes = $(element).getElementsByTagName('*'); className = ' ' + className + ' '; for (var i = 0, child, cn; child = nodes[i]; i++) { if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || (classNames && classNames.all(function(name) { return !name.toString().blank() && cn.include(' ' + name + ' '); })))) elements.push(Element.extend(child)); } return elements; }; return function(className, parentElement) { return $(parentElement || document.body).getElementsByClassName(className); }; }(Element.Methods); /*--------------------------------------------------------------------------*/ Element.ClassNames = Class.create(); Element.ClassNames.prototype = { initialize: function(element) { this.element = $(element); }, _each: function(iterator) { this.element.className.split(/\s+/).select(function(name) { return name.length > 0; })._each(iterator); }, set: function(className) { this.element.className = className; }, add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; this.set($A(this).concat(classNameToAdd).join(' ')); }, remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set($A(this).without(classNameToRemove).join(' ')); }, toString: function() { return $A(this).join(' '); } }; Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/javascripts/rails.js000066400000000000000000000073431154044152300275250ustar00rootroot00000000000000document.observe("dom:loaded", function() { function handleRemote(element) { var method, url, params; if (element.tagName.toLowerCase() === 'form') { method = element.readAttribute('method') || 'post'; url = element.readAttribute('action'); params = element.serialize(true); } else { method = element.readAttribute('data-method') || 'get'; url = element.readAttribute('href'); params = {}; } var event = element.fire("ajax:before"); if (event.stopped) return false; new Ajax.Request(url, { method: method, parameters: params, asynchronous: true, evalScripts: true, onLoading: function(request) { element.fire("ajax:loading", {request: request}); }, onLoaded: function(request) { element.fire("ajax:loaded", {request: request}); }, onInteractive: function(request) { element.fire("ajax:interactive", {request: request}); }, onComplete: function(request) { element.fire("ajax:complete", {request: request}); }, onSuccess: function(request) { element.fire("ajax:success", {request: request}); }, onFailure: function(request) { element.fire("ajax:failure", {request: request}); } }); element.fire("ajax:after"); } function handleMethod(element) { var method, url, token_name, token; method = element.readAttribute('data-method'); url = element.readAttribute('href'); csrf_param = $$('meta[name=csrf-param]').first(); csrf_token = $$('meta[name=csrf-token]').first(); var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); element.parentNode.appendChild(form); if (method != 'post') { var field = new Element('input', { type: 'hidden', name: '_method', value: method }); form.appendChild(field); } if (csrf_param) { var param = csrf_param.readAttribute('content'); var token = csrf_token.readAttribute('content'); var field = new Element('input', { type: 'hidden', name: param, value: token }); form.appendChild(field); } form.submit(); } $(document.body).observe("click", function(event) { var message = event.findElement().readAttribute('data-confirm'); if (message && !confirm(message)) { event.stop(); return false; } var element = event.findElement("a[data-remote]"); if (element) { handleRemote(element); event.stop(); return true; } var element = event.findElement("a[data-method]"); if (element) { handleMethod(element); event.stop(); return true; } }); // TODO: I don't think submit bubbles in IE $(document.body).observe("submit", function(event) { var element = event.findElement(), message = element.readAttribute('data-confirm'); if (message && !confirm(message)) { event.stop(); return false; } var inputs = element.select("input[type=submit][data-disable-with]"); inputs.each(function(input) { input.disabled = true; input.writeAttribute('data-original-value', input.value); input.value = input.readAttribute('data-disable-with'); }); var element = event.findElement("form[data-remote]"); if (element) { handleRemote(element); event.stop(); } }); $(document.body).observe("ajax:after", function(event) { var element = event.findElement(); if (element.tagName.toLowerCase() === 'form') { var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); inputs.each(function(input) { input.value = input.readAttribute('data-original-value'); input.writeAttribute('data-original-value', null); input.disabled = false; }); } }); });ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/robots.txt000066400000000000000000000003141154044152300255640ustar00rootroot00000000000000# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file # # To ban all spiders from the entire site uncomment the next two lines: # User-Agent: * # Disallow: / ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/stylesheets/000077500000000000000000000000001154044152300260715ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/public/stylesheets/.gitkeep000066400000000000000000000000001154044152300275100ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/script/000077500000000000000000000000001154044152300235435ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/script/rails000077500000000000000000000006031154044152300246020ustar00rootroot00000000000000#!/usr/bin/env ruby # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. ENV_PATH = File.expand_path('../../config/environment', __FILE__) BOOT_PATH = File.expand_path('../../config/boot', __FILE__) APP_PATH = File.expand_path('../../config/application', __FILE__) require BOOT_PATH require 'rails/commands' ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/test/000077500000000000000000000000001154044152300232165ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/test/performance/000077500000000000000000000000001154044152300255175ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/test/performance/browsing_test.rb000066400000000000000000000003451154044152300307370ustar00rootroot00000000000000require 'test_helper' require 'rails/performance_test_help' # Profiling results for each test method are written to tmp/performance. class BrowsingTest < ActionDispatch::PerformanceTest def test_homepage get '/' end end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/test/shoulda_macros/000077500000000000000000000000001154044152300262215ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/test/shoulda_macros/custom_macro.rb000066400000000000000000000001351154044152300312400ustar00rootroot00000000000000module CustomMacro def custom_macro end end ActiveSupport::TestCase.extend(CustomMacro) ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/test/test_helper.rb000066400000000000000000000007061154044152300260640ustar00rootroot00000000000000ENV["RAILS_ENV"] = "test" require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting fixtures :all # Add more helper methods to be used by all tests here... end ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/000077500000000000000000000000001154044152300235345ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/gems/000077500000000000000000000000001154044152300244675ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/gems/gem_with_macro-0.0.1/000077500000000000000000000000001154044152300301055ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/000077500000000000000000000000001154044152300331105ustar00rootroot00000000000000gem_macro.rb000066400000000000000000000001241154044152300353040ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macrosmodule GemMacro def gem_macro end end ActiveSupport::TestCase.extend(GemMacro) ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/plugins/000077500000000000000000000000001154044152300252155ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/plugins/.gitkeep000066400000000000000000000000001154044152300266340ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/plugins/.keep000066400000000000000000000000001154044152300261300ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/plugins/plugin_with_macro/000077500000000000000000000000001154044152300307275ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/plugins/plugin_with_macro/shoulda_macros/000077500000000000000000000000001154044152300337325ustar00rootroot00000000000000plugin_macro.rb000066400000000000000000000001351154044152300366560ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/rails3_root/vendor/plugins/plugin_with_macro/shoulda_macrosmodule PluginMacro def plugin_macro end end ActiveSupport::TestCase.extend(PluginMacro) ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/000077500000000000000000000000001154044152300214365ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/000077500000000000000000000000001154044152300251565ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/assign_to_matcher_spec.rb000066400000000000000000000031721154044152300322110ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::AssignToMatcher do context "a controller that assigns to an instance variable" do before do @controller = build_response { @var = 'value' } end it "should accept assigning to that variable" do @controller.should assign_to(:var) end it "should accept assigning to that variable with the correct class" do @controller.should assign_to(:var).with_kind_of(String) end it "should reject assigning to that variable with another class" do @controller.should_not assign_to(:var).with_kind_of(Fixnum) end it "should accept assigning the correct value to that variable" do @controller.should assign_to(:var).with('value') end it "should reject assigning another value to that variable" do @controller.should_not assign_to(:var).with('other') end it "should reject assigning to another variable" do @controller.should_not assign_to(:other) end it "should accept assigning to the same value in the test context" do @expected = 'value' @controller.should assign_to(:var).in_context(self).with { @expected } end it "should reject assigning to the another value in the test context" do @expected = 'other' @controller.should_not assign_to(:var).in_context(self).with { @expected } end end context "a controller that assigns a nil value to an instance variable" do before do @controller = build_response { @var = nil } end it "should accept assigning to that variable" do @controller.should assign_to(:var) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/filter_param_matcher_spec.rb000066400000000000000000000011241154044152300326630ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::FilterParamMatcher do context "given parameter filters" do before do Rails.application.config.filter_parameters = [:secret] end it "should accept filtering that parameter" do nil.should filter_param(:secret) end it "should reject filtering another parameter" do matcher = filter_param(:other) matcher.matches?(nil).should be_false matcher.failure_message.should include("Expected other to be filtered") matcher.failure_message.should =~ /secret/ end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/redirect_to_matcher_spec.rb000066400000000000000000000022341154044152300325240ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::RedirectToMatcher do context "a controller that redirects" do before do @controller = build_response { redirect_to '/some/url' } end it "accepts redirecting to that url" do @controller.should redirect_to('/some/url') end it "rejects redirecting to a different url" do @controller.should_not redirect_to('/some/other/url') end it "accepts redirecting to that url in a block" do @controller.should redirect_to('somewhere') { '/some/url' } end it "rejects redirecting to a different url in a block" do @controller.should_not redirect_to('somewhere else') { '/some/other/url' } end end context "a controller that doesn't redirect" do before do @controller = build_response { render :text => 'hello' } end it "rejects redirecting to a url" do @controller.should_not redirect_to('/some/url') end end it "provides the correct description when provided a block" do matcher = redirect_to('somewhere else') { '/some/other/url' } matcher.description.should == 'redirect to somewhere else' end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/render_template_matcher_spec.rb000066400000000000000000000020701154044152300333710ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::RenderTemplateMatcher do include ActionController::TemplateAssertions context "a controller that renders a template" do before do @controller = build_response(:action => 'show') { render } end it "should accept rendering that template" do @controller.should render_template(:show) end it "should reject rendering a different template" do @controller.should_not render_template(:index) end it "should accept rendering that template in the given context" do @controller.should render_template(:show).in_context(self) end it "should reject rendering a different template in the given context" do @controller.should_not render_template(:index).in_context(self) end end context "a controller that doesn't render a template" do before do @controller = build_response { render :nothing => true } end it "should reject rendering a template" do @controller.should_not render_template(:show) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/render_with_layout_matcher_spec.rb000066400000000000000000000024431154044152300341320ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::RenderWithLayoutMatcher do include ActionController::TemplateAssertions context "a controller that renders with a layout" do before do create_view('layouts/wide.html.erb', 'some content, <%= yield %>') @controller = build_response { render :layout => 'wide' } end it "should accept rendering with any layout" do @controller.should render_with_layout end it "should accept rendering with that layout" do @controller.should render_with_layout(:wide) end it "should reject rendering with another layout" do @controller.should_not render_with_layout(:other) end end context "a controller that renders without a layout" do before do @controller = build_response { render :layout => false } end it "should reject rendering with a layout" do @controller.should_not render_with_layout end end context "given a context with layouts" do before do @layout = 'happy' @controller = build_response { render :layout => false } @layouts = Hash.new(0) @layouts[@layout] = 1 end it "should accept that layout in that context" do @controller.should render_with_layout(@layout).in_context(self) end end end respond_with_content_type_matcher_spec.rb000066400000000000000000000017401154044152300354430ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controllerrequire 'spec_helper' describe Shoulda::Matchers::ActionController::RespondWithContentTypeMatcher do context "a controller responding with content type :xml" do before do @controller = build_response { render :xml => { :user => "thoughtbot" }.to_xml } end it "should accept responding with content type :xml" do @controller.should respond_with_content_type(:xml) end it "should accept responding with content type 'application/xml'" do @controller.should respond_with_content_type('application/xml') end it "should accept responding with content type /xml/" do @controller.should respond_with_content_type(/xml/) end it "should reject responding with another content type" do @controller.should_not respond_with_content_type(:json) end end it "should generate the correct test name" do respond_with_content_type(:xml).description. should == "respond with content type of application/xml" end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/respond_with_matcher_spec.rb000066400000000000000000000047411154044152300327330ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::RespondWithMatcher do context "a controller responding with success" do before do @controller = build_response { render :text => "text", :status => 200 } end it "should accept responding with 200" do @controller.should respond_with(200) end it "should accept responding with :success" do @controller.should respond_with(:success) end it "should reject responding with another status" do @controller.should_not respond_with(:error) end end context "a controller responding with redirect" do before do @controller = build_response { render :text => "text", :status => 301 } end it "should accept responding with 301" do @controller.should respond_with(301) end it "should accept responding with :redirect" do @controller.should respond_with(:redirect) end it "should reject responding with another status" do @controller.should_not respond_with(:error) end end context "a controller responding with missing" do before do @controller = build_response { render :text => "text", :status => 404 } end it "should accept responding with 404" do @controller.should respond_with(404) end it "should accept responding with :missing" do @controller.should respond_with(:missing) end it "should reject responding with another status" do @controller.should_not respond_with(:success) end end context "a controller responding with error" do before do @controller = build_response { render :text => "text", :status => 500 } end it "should accept responding with 500" do @controller.should respond_with(500) end it "should accept responding with :error" do @controller.should respond_with(:error) end it "should reject responding with another status" do @controller.should_not respond_with(:success) end end context "a controller responding with not implemented" do before do @controller = build_response { render :text => "text", :status => 501 } end it "should accept responding with 501" do @controller.should respond_with(501) end it "should accept responding with :not_implemented" do @controller.should respond_with(:not_implemented) end it "should reject responding with another status" do @controller.should_not respond_with(:success) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/route_matcher_spec.rb000066400000000000000000000040371154044152300313620ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::RouteMatcher do context "given a controller with a defined glob url" do before do @controller = define_controller('Examples').new define_routes do match 'examples/*id', :to => 'examples#example' end end it "should accept glob route" do @controller.should route(:get, '/examples/foo/bar'). to(:action => 'example', :id => 'foo/bar') end end context "given a controller with a defined route" do before do @controller = define_controller('Examples').new define_routes do match 'examples/:id', :to => 'examples#example' end end it "should accept routing the correct path to the correct parameters" do @controller.should route(:get, '/examples/1'). to(:action => 'example', :id => '1') end it "should accept a symbol controller" do Object.new.should route(:get, '/examples/1'). to(:controller => :examples, :action => 'example', :id => '1') end it "should accept a symbol action" do @controller.should route(:get, '/examples/1'). to(:action => :example, :id => '1') end it "should accept a non-string parameter" do @controller.should route(:get, '/examples/1'). to(:action => 'example', :id => 1) end it "should reject an undefined route" do @controller.should_not route(:get, '/bad_route').to(:var => 'value') end it "should reject a route for another controller" do @other = define_controller('Other').new @other.should_not route(:get, '/examples/1'). to(:action => 'example', :id => '1') end it "should reject a route for different parameters" do @controller.should_not route(:get, '/examples/1'). to(:action => 'other', :id => '1') end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/set_session_matcher_spec.rb000066400000000000000000000026061154044152300325620ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::SetSessionMatcher do context "a controller that sets a session variable" do before do @controller = build_response do session[:var] = 'value' session[:false_var] = false end end it "should accept assigning to that variable" do @controller.should set_session(:var) end it "should accept assigning the correct value to that variable" do @controller.should set_session(:var).to('value') end it "should reject assigning another value to that variable" do @controller.should_not set_session(:var).to('other') end it "should reject assigning to another variable" do @controller.should_not set_session(:other) end it "should accept assigning nil to another variable" do @controller.should set_session(:other).to(nil) end it "should accept assigning false to that variable" do @controller.should set_session(:false_var).to(false) end it "should accept assigning to the same value in the test context" do @expected = 'value' @controller.should set_session(:var).in_context(self).to { @expected } end it "should reject assigning to the another value in the test context" do @expected = 'other' @controller.should_not set_session(:var).in_context(self).to { @expected } end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_controller/set_the_flash_matcher_spec.rb000066400000000000000000000051371154044152300330360ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionController::SetTheFlashMatcher do context "a controller that sets a flash message" do before do @controller = build_response { flash[:notice] = 'value' } end it "should accept setting any flash message" do @controller.should set_the_flash end it "should accept setting the exact flash message" do @controller.should set_the_flash.to('value') end it "should accept setting a matched flash message" do @controller.should set_the_flash.to(/value/) end it "should reject setting a different flash message" do @controller.should_not set_the_flash.to('other') end it "should reject setting a different pattern" do @controller.should_not set_the_flash.to(/other/) end end context "a controller that sets a flash.now message" do before do @controller = build_response { flash.now[:notice] = 'value' } end it "should reject setting any flash message" do @controller.should_not set_the_flash end it "should accept setting any flash.now message" do @controller.should set_the_flash.now end it "should accept setting the exact flash.now message" do @controller.should set_the_flash.to('value').now end it "should accept setting a matched flash.now message" do @controller.should set_the_flash.to(/value/).now end it "should reject setting a different flash.now message" do @controller.should_not set_the_flash.to('other').now end it "should reject setting a different flash.now pattern" do @controller.should_not set_the_flash.to(/other/).now end end context "a controller that sets multiple flash messages" do before do @controller = build_response { flash.now[:notice] = 'value' flash[:success] = 'great job' } end it "should accept setting any flash.now message" do @controller.should set_the_flash.now @controller.should set_the_flash end it "should accept setting a matched flash.now message" do @controller.should set_the_flash.to(/value/).now @controller.should set_the_flash.to(/great/) end it "should reject setting a different flash.now message" do @controller.should_not set_the_flash.to('other').now @controller.should_not set_the_flash.to('other') end end context "a controller that doesn't set a flash message" do before do @controller = build_response end it "should reject setting any flash message" do @controller.should_not set_the_flash end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_mailer/000077500000000000000000000000001154044152300242445ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/action_mailer/have_sent_email_spec.rb000066400000000000000000000064451154044152300307370ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActionMailer::HaveSentEmailMatcher do def add_mail_to_deliveries ::ActionMailer::Base.deliveries << Mailer.the_email end context "an email" do before do define_mailer :mailer, [:the_email] do def the_email mail :from => "do-not-reply@example.com", :to => "myself@me.com", :subject => "This is spam", :body => "Every email is spam." end end add_mail_to_deliveries end after { ::ActionMailer::Base.deliveries.clear } it "accepts sent e-mail based on the subject" do should have_sent_email.with_subject(/is spam$/) matcher = have_sent_email.with_subject(/totally safe/) matcher.matches?(nil) matcher.failure_message.should =~ /Expected sent email with subject/ end it "accepts sent e-mail based on a string sender" do should have_sent_email.from('do-not-reply@example.com') matcher = have_sent_email.from('you@example.com') matcher.matches?(nil) matcher.failure_message.should =~ /Expected sent email from/ end it "accepts sent e-mail based on a regexp sender" do should have_sent_email.from(/@example\.com/) matcher = have_sent_email.from(/you@/) matcher.matches?(nil) matcher.failure_message.should =~ /Expected sent email from/ end it "accepts sent e-mail based on the body" do should have_sent_email.with_body(/is spam\./) matcher = have_sent_email.with_body(/totally safe/) matcher.matches?(nil) matcher.failure_message.should =~ /Expected sent email with body/ end it "accept sent e-mail based on the recipient" do should have_sent_email.to('myself@me.com') matcher = have_sent_email.to('you@example.com') matcher.matches?(nil) matcher.failure_message.should =~ /Expected sent email to/ end it "lists all the deliveries within failure message" do add_mail_to_deliveries matcher = have_sent_email.to('you@example.com') matcher.matches?(nil) matcher.failure_message.should =~ /Deliveries:\n"This is spam" to \["myself@me\.com"\]\n"This is spam" to \["myself@me\.com"\]/ end it "allows chaining" do should have_sent_email.with_subject(/spam/).from('do-not-reply@example.com').with_body(/spam/).to('myself@me.com') should_not have_sent_email.with_subject(/ham/).from('you@example.com').with_body(/ham/).to('them@example.com') end end it "provides a detailed description of the e-mail expected to be sent" do matcher = have_sent_email matcher.description.should == 'send an email' matcher = matcher.with_subject("Welcome!") matcher.description.should == 'send an email with a subject of "Welcome!"' matcher = matcher.with_body("Welcome, human!") matcher.description.should == 'send an email with a subject of "Welcome!" containing "Welcome, human!"' matcher = matcher.from("alien@example.com") matcher.description.should == 'send an email with a subject of "Welcome!" containing "Welcome, human!" from "alien@example.com"' matcher = matcher.to("human@example.com") matcher.description.should == 'send an email with a subject of "Welcome!" containing "Welcome, human!" from "alien@example.com" to "human@example.com"' end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/000077500000000000000000000000001154044152300242475ustar00rootroot00000000000000allow_mass_assignment_of_matcher_spec.rb000066400000000000000000000036231154044152300343130ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_recordrequire 'spec_helper' describe Shoulda::Matchers::ActiveRecord::AllowMassAssignmentOfMatcher do context "an attribute that is blacklisted from mass-assignment" do before do define_model :example, :attr => :string do attr_protected :attr end @model = Example.new end it "should reject being mass-assignable" do @model.should_not allow_mass_assignment_of(:attr) end end context "an attribute that is not whitelisted for mass-assignment" do before do define_model :example, :attr => :string, :other => :string do attr_accessible :other end @model = Example.new end it "should reject being mass-assignable" do @model.should_not allow_mass_assignment_of(:attr) end end context "an attribute that is whitelisted for mass-assignment" do before do define_model :example, :attr => :string do attr_accessible :attr end @model = Example.new end it "should accept being mass-assignable" do @model.should allow_mass_assignment_of(:attr) end end context "an attribute not included in the mass-assignment blacklist" do before do define_model :example, :attr => :string, :other => :string do attr_protected :other end @model = Example.new end it "should accept being mass-assignable" do @model.should allow_mass_assignment_of(:attr) end end context "an attribute on a class with no protected attributes" do before do define_model :example, :attr => :string @model = Example.new end it "should accept being mass-assignable" do @model.should allow_mass_assignment_of(:attr) end it "should assign a negative failure message" do matcher = allow_mass_assignment_of(:attr) matcher.matches?(@model).should == true matcher.negative_failure_message.should_not be_nil end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/allow_value_matcher_spec.rb000066400000000000000000000033361154044152300316300ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::AllowValueMatcher do context "an attribute with a format validation" do before do define_model :example, :attr => :string do validates_format_of :attr, :with => /abc/ end @model = Example.new end it "should allow a good value" do @model.should allow_value("abcde").for(:attr) end it "should not allow a bad value" do @model.should_not allow_value("xyz").for(:attr) end end context "an attribute with a format validation and a custom message" do before do define_model :example, :attr => :string do validates_format_of :attr, :with => /abc/, :message => 'bad value' end @model = Example.new end it "should allow a good value" do @model.should allow_value('abcde').for(:attr).with_message(/bad/) end it "should not allow a bad value" do @model.should_not allow_value('xyz').for(:attr).with_message(/bad/) end end context "an attribute with several validations" do before do define_model :example, :attr => :string do validates_presence_of :attr validates_length_of :attr, :within => 1..5 validates_numericality_of :attr, :greater_than_or_equal_to => 1, :less_than_or_equal_to => 50000 end @model = Example.new end it "should allow a good value" do @model.should allow_value("12345").for(:attr) end bad_values = [nil, "", "abc", "0", "50001", "123456"] bad_values.each do |value| it "should not allow a bad value (#{value.inspect})" do @model.should_not allow_value(value).for(:attr) end end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/association_matcher_spec.rb000066400000000000000000000173401154044152300316320ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do context "belong_to" do before do @matcher = belong_to(:parent) end it "should accept a good association with the default foreign key" do define_model :parent define_model :child, :parent_id => :integer do belongs_to :parent end Child.new.should @matcher end it "should reject a nonexistent association" do define_model :child Child.new.should_not @matcher end it "should reject an association of the wrong type" do define_model :parent, :child_id => :integer child_class = define_model :child do has_one :parent end Child.new.should_not @matcher end it "should reject an association that has a nonexistent foreign key" do define_model :parent define_model :child do belongs_to :parent end Child.new.should_not @matcher end it "should accept an association with an existing custom foreign key" do define_model :parent define_model :child, :guardian_id => :integer do belongs_to :parent, :foreign_key => 'guardian_id' end Child.new.should @matcher end it "should accept a polymorphic association" do define_model :child, :parent_type => :string, :parent_id => :integer do belongs_to :parent, :polymorphic => true end Child.new.should @matcher end it "should accept an association with a valid :dependent option" do define_model :parent define_model :child, :parent_id => :integer do belongs_to :parent, :dependent => :destroy end Child.new.should @matcher.dependent(:destroy) end it "should reject an association with a bad :dependent option" do define_model :parent define_model :child, :parent_id => :integer do belongs_to :parent end Child.new.should_not @matcher.dependent(:destroy) end end context "have_many" do before do @matcher = have_many(:children) end it "should accept a valid association without any options" do define_model :child, :parent_id => :integer define_model :parent do has_many :children end Parent.new.should @matcher end it "should accept a valid association with a :through option" do define_model :child define_model :conception, :child_id => :integer, :parent_id => :integer do belongs_to :child end define_model :parent do has_many :conceptions has_many :children, :through => :conceptions end Parent.new.should @matcher end it "should accept a valid association with an :as option" do define_model :child, :guardian_type => :string, :guardian_id => :integer define_model :parent do has_many :children, :as => :guardian end Parent.new.should @matcher end it "should reject an association that has a nonexistent foreign key" do define_model :child define_model :parent do has_many :children end Parent.new.should_not @matcher end it "should reject an association with a bad :as option" do define_model :child, :caretaker_type => :string, :caretaker_id => :integer define_model :parent do has_many :children, :as => :guardian end Parent.new.should_not @matcher end it "should reject an association that has a bad :through option" do define_model :child, :parent_id => :integer define_model :parent do has_many :children end @matcher.through(:conceptions).matches?(Parent.new).should be_false @matcher.failure_message.should =~ /does not have any relationship to conceptions/ end it "should reject an association that has the wrong :through option" do define_model :child define_model :conception, :child_id => :integer, :parent_id => :integer do belongs_to :child end define_model :parent do has_many :conceptions has_many :relationships has_many :children, :through => :conceptions end @matcher.through(:relationships).matches?(Parent.new).should be_false @matcher.failure_message.should =~ /through relationships, but got it through conceptions/ end it "should accept an association with a valid :dependent option" do define_model :child, :parent_id => :integer define_model :parent do has_many :children, :dependent => :destroy end Parent.new.should @matcher.dependent(:destroy) end it "should reject an association with a bad :dependent option" do define_model :child, :parent_id => :integer define_model :parent do has_many :children end Parent.new.should_not @matcher.dependent(:destroy) end end context "have_one" do before do @matcher = have_one(:detail) end it "should accept a valid association without any options" do define_model :detail, :person_id => :integer define_model :person do has_one :detail end Person.new.should @matcher end it "should accept a valid association with an :as option" do define_model :detail, :detailable_id => :integer, :detailable_type => :string define_model :person do has_one :detail, :as => :detailable end Person.new.should @matcher end it "should reject an association that has a nonexistent foreign key" do define_model :detail define_model :person do has_one :detail end Person.new.should_not @matcher end it "should reject an association with a bad :as option" do define_model :detail, :detailable_id => :integer, :detailable_type => :string define_model :person do has_one :detail, :as => :describable end Person.new.should_not @matcher end it "should accept an association with a valid :dependent option" do define_model :detail, :person_id => :integer define_model :person do has_one :detail, :dependent => :destroy end Person.new.should @matcher.dependent(:destroy) end it "should reject an association with a bad :dependent option" do define_model :detail, :person_id => :integer define_model :person do has_one :detail end Person.new.should_not @matcher.dependent(:destroy) end end context "have_and_belong_to_many" do before do @matcher = have_and_belong_to_many(:relatives) end it "should accept a valid association" do define_model :relatives define_model :person do has_and_belongs_to_many :relatives end define_model :people_relative, :person_id => :integer, :relative_id => :integer Person.new.should @matcher end it "should reject a nonexistent association" do define_model :relatives define_model :person define_model :people_relative, :person_id => :integer, :relative_id => :integer Person.new.should_not @matcher end it "should reject an association with a nonexistent join table" do define_model :relatives define_model :person do has_and_belongs_to_many :relatives end Person.new.should_not @matcher end it "should reject an association of the wrong type" do define_model :relatives, :person_id => :integer define_model :person do has_many :relatives end Person.new.should_not @matcher end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/ensure_inclusion_of_matcher_spec.rb000066400000000000000000000040611154044152300333620ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::EnsureInclusionOfMatcher do context "an attribute which must be included in a range" do before do @model = define_model(:example, :attr => :integer) do validates_inclusion_of :attr, :in => 2..5 end.new end it "should accept ensuring the correct range" do @model.should ensure_inclusion_of(:attr).in_range(2..5) end it "should reject ensuring a lower minimum value" do @model.should_not ensure_inclusion_of(:attr).in_range(1..5) end it "should reject ensuring a higher minimum value" do @model.should_not ensure_inclusion_of(:attr).in_range(3..5) end it "should reject ensuring a lower maximum value" do @model.should_not ensure_inclusion_of(:attr).in_range(2..4) end it "should reject ensuring a higher maximum value" do @model.should_not ensure_inclusion_of(:attr).in_range(2..6) end it "should not override the default message with a blank" do @model.should ensure_inclusion_of(:attr).in_range(2..5).with_message(nil) end end context "an attribute with a custom ranged value validation" do before do @model = define_model(:example, :attr => :string) do validates_inclusion_of :attr, :in => 2..4, :message => 'not good' end.new end it "should accept ensuring the correct range" do @model.should ensure_inclusion_of(:attr).in_range(2..4).with_message(/not good/) end end context "an attribute with custom range validations" do before do define_model :example, :attr => :integer do validate :custom_validation def custom_validation if attr < 2 errors.add(:attr, 'too low') elsif attr > 5 errors.add(:attr, 'too high') end end end @model = Example.new end it "should accept ensuring the correct range and messages" do @model.should ensure_inclusion_of(:attr).in_range(2..5).with_low_message(/low/).with_high_message(/high/) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/ensure_length_of_matcher_spec.rb000066400000000000000000000076341154044152300326510ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::EnsureLengthOfMatcher do context "an attribute with a non-zero minimum length validation" do before do @model = define_model(:example, :attr => :string) do validates_length_of :attr, :minimum => 4 end.new end it "should accept ensuring the correct minimum length" do @model.should ensure_length_of(:attr).is_at_least(4) end it "should reject ensuring a lower minimum length with any message" do @model.should_not ensure_length_of(:attr).is_at_least(3).with_short_message(/.*/) end it "should reject ensuring a higher minimum length with any message" do @model.should_not ensure_length_of(:attr).is_at_least(5).with_short_message(/.*/) end it "should not override the default message with a blank" do @model.should ensure_length_of(:attr).is_at_least(4).with_short_message(nil) end end context "an attribute with a minimum length validation of 0" do before do @model = define_model(:example, :attr => :string) do validates_length_of :attr, :minimum => 0 end.new end it "should accept ensuring the correct minimum length" do @model.should ensure_length_of(:attr).is_at_least(0) end end context "an attribute with a maximum length" do before do @model = define_model(:example, :attr => :string) do validates_length_of :attr, :maximum => 4 end.new end it "should accept ensuring the correct maximum length" do @model.should ensure_length_of(:attr).is_at_most(4) end it "should reject ensuring a lower maximum length with any message" do @model.should_not ensure_length_of(:attr).is_at_most(3).with_long_message(/.*/) end it "should reject ensuring a higher maximum length with any message" do @model.should_not ensure_length_of(:attr).is_at_most(5).with_long_message(/.*/) end it "should not override the default message with a blank" do @model.should ensure_length_of(:attr).is_at_most(4).with_long_message(nil) end end context "an attribute with a required exact length" do before do @model = define_model(:example, :attr => :string) do validates_length_of :attr, :is => 4 end.new end it "should accept ensuring the correct length" do @model.should ensure_length_of(:attr).is_equal_to(4) end it "should reject ensuring a lower maximum length with any message" do @model.should_not ensure_length_of(:attr).is_equal_to(3).with_message(/.*/) end it "should reject ensuring a higher maximum length with any message" do @model.should_not ensure_length_of(:attr).is_equal_to(5).with_message(/.*/) end it "should not override the default message with a blank" do @model.should ensure_length_of(:attr).is_equal_to(4).with_message(nil) end end context "an attribute with a custom minimum length validation" do before do @model = define_model(:example, :attr => :string) do validates_length_of :attr, :minimum => 4, :too_short => 'short' end.new end it "should accept ensuring the correct minimum length" do @model.should ensure_length_of(:attr).is_at_least(4).with_short_message(/short/) end end context "an attribute with a custom maximum length validation" do before do @model = define_model(:example, :attr => :string) do validates_length_of :attr, :maximum => 4, :too_long => 'long' end.new end it "should accept ensuring the correct minimum length" do @model.should ensure_length_of(:attr).is_at_most(4).with_long_message(/long/) end end context "an attribute without a length validation" do before do @model = define_model(:example, :attr => :string).new end it "should reject ensuring a minimum length" do @model.should_not ensure_length_of(:attr).is_at_least(1) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/have_db_column_matcher_spec.rb000066400000000000000000000107711154044152300322640ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::HaveDbColumnMatcher do context "have_db_column" do before do @matcher = have_db_column(:nickname) end it "should accept an existing database column" do create_table 'superheros' do |table| table.string :nickname end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a nonexistent database column" do define_model :superhero Superhero.new.should_not @matcher end end context "have_db_column of type string" do before do @matcher = have_db_column(:nickname).of_type(:string) end it "should accept a column of correct type" do create_table 'superheros' do |table| table.string :nickname end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a nonexistent database column" do define_model :superhero Superhero.new.should_not @matcher end it "should reject a column of wrong type" do create_table 'superheros' do |table| table.integer :nickname end define_model_class 'Superhero' Superhero.new.should_not @matcher end end context "have_db_column with precision option" do before do @matcher = have_db_column(:salary).with_options(:precision => 5) end it "should accept a column of correct precision" do create_table 'superheros' do |table| table.decimal :salary, :precision => 5 end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a column of wrong precision" do create_table 'superheros' do |table| table.decimal :salary, :precision => 15 end define_model_class 'Superhero' Superhero.new.should_not @matcher end end context "have_db_column with limit option" do before do @matcher = have_db_column(:email). of_type(:string). with_options(:limit => 255) end it "should accept a column of correct limit" do create_table 'superheros' do |table| table.string :email, :limit => 255 end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a column of wrong limit" do create_table 'superheros' do |table| table.string :email, :limit => 500 end define_model_class 'Superhero' Superhero.new.should_not @matcher end end context "have_db_column with default option" do before do @matcher = have_db_column(:admin). of_type(:boolean). with_options(:default => false) end it "should accept a column of correct default" do create_table 'superheros' do |table| table.boolean :admin, :default => false end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a column of wrong default" do create_table 'superheros' do |table| table.boolean :admin, :default => true end define_model_class 'Superhero' Superhero.new.should_not @matcher end end context "have_db_column with null option" do before do @matcher = have_db_column(:admin). of_type(:boolean). with_options(:null => false) end it "should accept a column of correct null" do create_table 'superheros' do |table| table.boolean :admin, :null => false end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a column of wrong null" do create_table 'superheros' do |table| table.boolean :admin, :null => true end define_model_class 'Superhero' Superhero.new.should_not @matcher end end context "have_db_column with scale option" do before do @matcher = have_db_column(:salary). of_type(:decimal). with_options(:scale => 2) end it "should accept a column of correct scale" do create_table 'superheros' do |table| table.decimal :salary, :precision => 10, :scale => 2 end define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a column of wrong scale" do create_table 'superheros' do |table| table.decimal :salary, :precision => 10, :scale => 4 end define_model_class 'Superhero' Superhero.new.should_not @matcher end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/have_db_index_matcher_spec.rb000066400000000000000000000051641154044152300320760ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::HaveDbIndexMatcher do context "have_db_index" do before do @matcher = have_db_index(:age) end it "should accept an existing index" do db_connection = create_table 'superheros' do |table| table.integer :age end db_connection.add_index :superheros, :age define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject a nonexistent index" do define_model :superhero Superhero.new.should_not @matcher end end context "have_db_index with unique option" do before do @matcher = have_db_index(:ssn).unique(true) end it "should accept an index of correct unique" do db_connection = create_table 'superheros' do |table| table.integer :ssn end db_connection.add_index :superheros, :ssn, :unique => true define_model_class 'Superhero' Superhero.new.should @matcher end it "should reject an index of wrong unique" do db_connection = create_table 'superheros' do |table| table.integer :ssn end db_connection.add_index :superheros, :ssn, :unique => false define_model_class 'Superhero' Superhero.new.should_not @matcher end end context "have_db_index on multiple columns" do before do @matcher = have_db_index([:geocodable_type, :geocodable_id]) end it "should accept an existing index" do db_connection = create_table 'geocodings' do |table| table.integer :geocodable_id table.string :geocodable_type end db_connection.add_index :geocodings, [:geocodable_type, :geocodable_id] define_model_class 'Geocoding' Geocoding.new.should @matcher end it "should reject a nonexistant index" do db_connection = create_table 'geocodings' do |table| table.integer :geocodable_id table.string :geocodable_type end define_model_class 'Geocoding' Geocoding.new.should_not @matcher end end it "should join columns with and describing multiple columns" do have_db_index([:user_id, :post_id]).description.should =~ /on columns user_id and post_id/ end it "should context a unique index as unique" do have_db_index(:user_id).unique(true).description.should =~ /a unique index/ end it "should context a non-unique index as non-unique" do have_db_index(:user_id).unique(false).description.should =~ /a non-unique index/ end it "should not context an index's uniqueness when it isn't important" do have_db_index(:user_id).description.should_not =~ /unique/ end end have_readonly_attributes_matcher_spec.rb000066400000000000000000000023041154044152300343170ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_recordrequire 'spec_helper' describe Shoulda::Matchers::ActiveRecord::HaveReadonlyAttributeMatcher do context "an attribute that cannot be set after being saved" do before do define_model :example, :attr => :string do attr_readonly :attr end @model = Example.new end it "should accept being read-only" do @model.should have_readonly_attribute(:attr) end end context "an attribute not included in the readonly set" do before do define_model :example, :attr => :string, :other => :string do attr_readonly :other end @model = Example.new end it "should not accept being read-only" do @model.should_not have_readonly_attribute(:attr) end end context "an attribute on a class with no readonly attributes" do before do define_model :example, :attr => :string @model = Example.new end it "should not accept being read-only" do @model.should_not have_readonly_attribute(:attr) end it "should assign a failure message" do matcher = have_readonly_attribute(:attr) matcher.matches?(@model).should == false matcher.failure_message.should_not be_nil end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/validate_acceptance_of_matcher_spec.rb000066400000000000000000000022321154044152300337330ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::ValidateAcceptanceOfMatcher do context "an attribute which must be accepted" do before do @model = define_model(:example) do validates_acceptance_of :attr end.new end it "should require that attribute to be accepted" do @model.should validate_acceptance_of(:attr) end it "should not overwrite the default message with nil" do @model.should validate_acceptance_of(:attr).with_message(nil) end end context "an attribute that does not need to be accepted" do before do @model = define_model(:example, :attr => :string).new end it "should not require that attribute to be accepted" do @model.should_not validate_acceptance_of(:attr) end end context "an attribute which must be accepted with a custom message" do before do @model = define_model(:example) do validates_acceptance_of :attr, :message => 'custom' end.new end it "should require that attribute to be accepted with that message" do @model.should validate_acceptance_of(:attr).with_message(/custom/) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/validate_format_of_matcher_spec.rb000066400000000000000000000022021154044152300331320ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::ValidateFormatOfMatcher do context "a postal code" do before do define_model :example, :attr => :string do validates_format_of :attr, :with => /^\d{5}$/ end @model = Example.new end it "should be valid" do @model.should validate_format_of(:attr).with('12345') end it "should not be valid with alpha in zip" do @model.should_not validate_format_of(:attr).with('1234a') @model.should validate_format_of(:attr).not_with('1234a') end it "should not be valid with too few digits" do @model.should_not validate_format_of(:attr).with('1234') @model.should validate_format_of(:attr).not_with('1234') end it "should not be valid with too many digits" do @model.should_not validate_format_of(:attr).with('123456') @model.should validate_format_of(:attr).not_with('123456') end it "should raise error if you try to call both with and not_with" do expect { validate_format_of(:attr).not_with('123456').with('12345') }. to raise_error(RuntimeError) end end end validate_numericality_of_matcher_spec.rb000066400000000000000000000025471154044152300343040ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_recordrequire 'spec_helper' describe Shoulda::Matchers::ActiveRecord::ValidateNumericalityOfMatcher do context "a numeric attribute" do before do define_model :example, :attr => :string do validates_numericality_of :attr end @model = Example.new end it "should only allow numeric values for that attribute" do @model.should validate_numericality_of(:attr) end it "should not override the default message with a blank" do @model.should validate_numericality_of(:attr).with_message(nil) end end context "a numeric attribute with a custom validation message" do before do define_model :example, :attr => :string do validates_numericality_of :attr, :message => 'custom' end @model = Example.new end it "should only allow numeric values for that attribute with that message" do @model.should validate_numericality_of(:attr).with_message(/custom/) end it "should not allow numeric values for that attribute with another message" do @model.should_not validate_numericality_of(:attr) end end context "a non-numeric attribute" do before do @model = define_model(:example, :attr => :string).new end it "should not only allow numeric values for that attribute" do @model.should_not validate_numericality_of(:attr) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/validate_presence_of_matcher_spec.rb000066400000000000000000000054361154044152300334620ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::ValidatePresenceOfMatcher do context "a required attribute" do before do define_model :example, :attr => :string do validates_presence_of :attr end @model = Example.new end it "should require a value" do @model.should validate_presence_of(:attr) end it "should not override the default message with a blank" do @model.should validate_presence_of(:attr).with_message(nil) end end context "a required attribute on a class using ActiveModel::Validations" do before do define_active_model_class("Example", :accessors => [:attr]) do validates_presence_of :attr end @model = Example.new end it "should require a value" do @model.should validate_presence_of(:attr) end it "should not override the default message with a blank" do @model.should validate_presence_of(:attr).with_message(nil) end end context "an optional attribute" do before do @model = define_model(:example, :attr => :string).new end it "should not require a value" do @model.should_not validate_presence_of(:attr) end end context "an optional attribute on a class using ActiveModel::Validations" do before do @model = define_active_model_class("Example", :accessors => [:attr]).new end it "should not require a value" do @model.should_not validate_presence_of(:attr) end end context "a required has_many association" do before do define_model :child @model = define_model :parent do has_many :children validates_presence_of :children end.new end it "should require the attribute to be set" do @model.should validate_presence_of(:children) end end context "an optional has_many association" do before do define_model :child @model = define_model :parent do has_many :children end.new end it "should not require the attribute to be set" do @model.should_not validate_presence_of(:children) end end context "a required has_and_belongs_to_many association" do before do define_model :child @model = define_model :parent do has_and_belongs_to_many :children validates_presence_of :children end.new end it "should require the attribute to be set" do @model.should validate_presence_of(:children) end end context "an optional has_and_belongs_to_many association" do before do define_model :child @model = define_model :parent do has_and_belongs_to_many :children end.new end it "should not require the attribute to be set" do @model.should_not validate_presence_of(:children) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/shoulda/active_record/validate_uniqueness_of_matcher_spec.rb000066400000000000000000000110271154044152300340460ustar00rootroot00000000000000require 'spec_helper' describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher do context "a unique attribute" do before do @model = define_model(:example, :attr => :string, :other => :integer) do validates_uniqueness_of :attr end.new end context "with an existing value" do before do @existing = Example.create!(:attr => 'value', :other => 1) end it "should require a unique value for that attribute" do @model.should validate_uniqueness_of(:attr) end it "should pass when the subject is an existing record" do @existing.should validate_uniqueness_of(:attr) end it "should fail when a scope is specified" do @model.should_not validate_uniqueness_of(:attr).scoped_to(:other) end end context "without an existing value" do before do Example.find(:first).should be_nil @matcher = validate_uniqueness_of(:attr) end it "should fail to require a unique value" do @model.should_not @matcher end it "should alert the tester that an existing value is not present" do @matcher.matches?(@model) @matcher.negative_failure_message.should =~ /^Can't find first .*/ end end end context "a unique attribute with a custom error and an existing value" do before do @model = define_model(:example, :attr => :string) do validates_uniqueness_of :attr, :message => 'Bad value' end.new Example.create! end it "should fail when checking the default message" do @model.should_not validate_uniqueness_of(:attr) end it "should fail when checking a message that doesn't match" do @model.should_not validate_uniqueness_of(:attr).with_message(/abc/i) end it "should pass when checking a message that matches" do @model.should validate_uniqueness_of(:attr).with_message(/bad/i) end end context "a scoped unique attribute with an existing value" do before do @model = define_model(:example, :attr => :string, :scope1 => :integer, :scope2 => :integer) do validates_uniqueness_of :attr, :scope => [:scope1, :scope2] end.new @existing = Example.create!(:attr => 'value', :scope1 => 1, :scope2 => 2) end it "should pass when the correct scope is specified" do @model.should validate_uniqueness_of(:attr).scoped_to(:scope1, :scope2) end it "should pass when the subject is an existing record" do @existing.should validate_uniqueness_of(:attr).scoped_to(:scope1, :scope2) end it "should fail when a different scope is specified" do @model.should_not validate_uniqueness_of(:attr).scoped_to(:scope1) end it "should fail when no scope is specified" do @model.should_not validate_uniqueness_of(:attr) end it "should fail when a non-existent attribute is specified as a scope" do @model.should_not validate_uniqueness_of(:attr).scoped_to(:fake) end end context "a non-unique attribute with an existing value" do before do @model = define_model(:example, :attr => :string).new Example.create!(:attr => 'value') end it "should not require a unique value for that attribute" do @model.should_not validate_uniqueness_of(:attr) end end context "a case sensitive unique attribute with an existing value" do before do @model = define_model(:example, :attr => :string) do validates_uniqueness_of :attr, :case_sensitive => true end.new Example.create!(:attr => 'value') end it "should not require a unique, case-insensitive value for that attribute" do @model.should_not validate_uniqueness_of(:attr).case_insensitive end it "should require a unique, case-sensitive value for that attribute" do @model.should validate_uniqueness_of(:attr) end end context "a case sensitive unique integer attribute with an existing value" do before do @model = define_model(:example, :attr => :integer) do validates_uniqueness_of :attr, :case_sensitive => true end.new Example.create!(:attr => 'value') end it "should require a unique, case-insensitive value for that attribute" do @model.should validate_uniqueness_of(:attr).case_insensitive end it "should require a unique, case-sensitive value for that attribute" do @model.should validate_uniqueness_of(:attr) end end end ruby-shoulda-matchers-1.0.0~beta2/spec/spec_helper.rb000066400000000000000000000017271154044152300226240ustar00rootroot00000000000000ENV['RAILS_ENV'] = 'test' ENV['RAILS_VERSION'] ||= '3.0.3' RAILS_GEM_VERSION = ENV['RAILS_VERSION'] rails_root = File.dirname(__FILE__) + '/rails3_root' ENV['BUNDLE_GEMFILE'] = rails_root + '/Gemfile' require "#{rails_root}/config/environment" require 'rspec' require 'rspec/autorun' PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')).freeze $LOAD_PATH << File.join(PROJECT_ROOT, 'lib') Dir[File.join(PROJECT_ROOT, 'spec', 'support', '**', '*.rb')].each { |file| require(file) } require 'shoulda-matchers' require 'rspec/rails' # Run the migrations ActiveRecord::Migration.verbose = false ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate") RSpec.configure do |config| config.mock_with :mocha config.include Shoulda::Matchers::ActionController, :example_group => { :file_path => /action_controller/ } config.include Shoulda::Matchers::ActionMailer, :example_group => { :file_path => /action_mailer/ } end ruby-shoulda-matchers-1.0.0~beta2/spec/support/000077500000000000000000000000001154044152300215135ustar00rootroot00000000000000ruby-shoulda-matchers-1.0.0~beta2/spec/support/model_builder.rb000066400000000000000000000070251154044152300246520ustar00rootroot00000000000000module ModelBuilder TMP_VIEW_PATH = File.expand_path(File.join(File.dirname(__FILE__), 'rails3_root', 'tmp', 'views')).freeze def self.included(example_group) example_group.class_eval do before do @created_tables ||= [] end after { teardown_defined_constants } end end def create_table(table_name, &block) connection = ActiveRecord::Base.connection begin connection.execute("DROP TABLE IF EXISTS #{table_name}") connection.create_table(table_name, &block) @created_tables << table_name connection rescue Exception => e connection.execute("DROP TABLE IF EXISTS #{table_name}") raise e end end def define_constant(class_name, base, &block) class_name = class_name.to_s.camelize klass = Class.new(base) Object.const_set(class_name, klass) klass.unloadable klass.class_eval(&block) if block_given? klass end def define_model_class(class_name, &block) define_constant(class_name, ActiveRecord::Base, &block) end def define_active_model_class(class_name, options = {}, &block) define_constant(class_name, Object) do include ActiveModel::Validations options[:accessors].each do |column| attr_accessor column.to_sym end class_eval(&block) if block_given? end end def define_model(name, columns = {}, &block) class_name = name.to_s.pluralize.classify table_name = class_name.tableize create_table(table_name) do |table| columns.each do |name, type| table.column name, type end end define_model_class(class_name, &block) end def define_mailer(name, paths, &block) class_name = name.to_s.pluralize.classify klass = define_constant(class_name, ActionMailer::Base, &block) end def define_controller(class_name, &block) class_name = class_name.to_s class_name << 'Controller' unless class_name =~ /Controller$/ define_constant(class_name, ActionController::Base, &block) end def define_routes(&block) Rails.application.routes.draw(&block) @routes = Rails.application.routes class << self include ActionDispatch::Assertions end end def build_response(opts = {}, &block) action = opts[:action] || 'example' klass = define_controller('Examples') block ||= lambda { render :nothing => true } klass.class_eval { layout false; define_method(action, &block) } define_routes do match 'examples', :to => "examples##{action}" end create_view("examples/#{action}.html.erb", "abc") klass.view_paths = [TMP_VIEW_PATH] @controller = klass.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller.send :assign_shortcuts, @request, @response @controller.send :initialize_template_class, @response class << self include ActionController::TestCase::Behavior end @routes = Rails.application.routes get action @controller end def create_view(path, contents) full_path = File.join(TMP_VIEW_PATH, path) FileUtils.mkdir_p(File.dirname(full_path)) File.open(full_path, 'w') { |file| file.write(contents) } end def teardown_defined_constants ActiveSupport::Dependencies.clear @created_tables.each do |table_name| ActiveRecord::Base. connection. execute("DROP TABLE IF EXISTS #{table_name}") end FileUtils.rm_rf(TMP_VIEW_PATH) Rails.application.reload_routes! end end RSpec.configure do |config| config.include ModelBuilder end