diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb new file mode 100644 index 00000000..410860a1 --- /dev/null +++ b/spec/models/todo_spec.rb @@ -0,0 +1,196 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Todo do + def valid_attributes(attributes={}) + { + :description => "don't forget the milk", + :context => mock_model(Context, :name => 'errands') + }.merge(attributes) + end + + def create_todo(attributes={}) + todo = Todo.new(valid_attributes(attributes)) + todo.stub!(:user).and_return(mock_model(User, :date => Time.now)) + todo.save! + todo + end + + before(:each) do + @todo = Todo.new + end + + it_should_belong_to :context + it_should_belong_to :project + it_should_belong_to :user + + it_should_validate_presence_of :description + it_should_validate_presence_of :context + it_should_validate_length_of :description, :maximum => 100 + it_should_validate_length_of :notes, :maximum => 60_000 + + it 'validates presence of show_from when deferred' + + it 'ensures that show_from is a date in the future' do + todo = Todo.new(valid_attributes) + todo.stub!(:user).and_return(mock_model(User, :date => Time.now)) + todo.show_from = 3.days.ago + todo.should have(1).error_on(:show_from) + end + + it 'allows show_from to be blank' do + todo = Todo.new(valid_attributes(:show_from => '')) + todo.should_not have(:any).error_on(:show_from) + end + + describe 'states' do + it 'is active on initializing' do + create_todo.should be_active + end + + it 'is deferred when show from is in the future' do + create_todo(:show_from => 1.week.from_now).should be_deferred + end + + describe 'active' do + %w(project_hidden completed deferred).each do |from_state| + it "is activable from `#{from_state}'" do + todo = create_todo + todo.state = from_state + todo.send("#{from_state}?").should be_true + todo.activate! + todo.should be_active + end + end + + it 'clears show_from when entering active state' do + todo = create_todo + todo.show_from = 3.days.from_now + todo.should be_deferred + todo.activate! + todo.should be_active + todo.show_from.should be_nil + end + + it 'clears completed_at when entering active state' do + todo = create_todo + todo.complete! + todo.should be_completed + todo.activate! + todo.should be_active + todo.completed_at.should be_nil + end + end + + describe 'completed' do + %w(active project_hidden deferred).each do |from_state| + it "is completable from `#{from_state}'" do + todo = create_todo + todo.state = from_state + todo.send("#{from_state}?").should be_true + todo.complete! + todo.should be_completed + end + end + + it 'sets complated_at' do + todo = create_todo + todo.complete! + todo.completed_at.should_not be_nil + end + end + + describe 'project_hidden' do + %w(active deferred).each do |from_state| + it "is hiddable from `#{from_state}'" do + todo = create_todo + todo.state = from_state + todo.send("#{from_state}?").should be_true + todo.hide! + todo.should be_project_hidden + end + end + + it 'unhides to deferred when if show_from' do + todo = create_todo(:show_from => 4.days.from_now) + todo.hide! + todo.should be_project_hidden + todo.unhide! + todo.should be_deferred + end + + it 'unhides to active when not show_from' do + todo = create_todo(:show_from => '') + todo.hide! + todo.should be_project_hidden + todo.unhide! + todo.should be_active + end + end + + it "is deferrable from `active'" do + todo = create_todo + todo.activate! + todo.should be_active + todo.defer! + todo.should be_deferred + end + end + + describe 'when toggling completion' do + it 'toggles to active when completed' do + todo = create_todo + todo.complete! + todo.should be_completed + todo.toggle_completion! + todo.should be_active + end + + it 'toggles to completed when not completed' do + todo = create_todo + todo.should_not be_completed + todo.toggle_completion! + todo.should be_completed + end + end + + describe 'when retrieving project' do + it 'returns project if set' do + project = mock_model(Project) + todo = Todo.new(:project => project) + todo.project.should == project + end + + it 'returns a NullProject if not set' do + Todo.new.project.should be_an_instance_of(NullProject) + end + end + + describe('when setting show_from') { it 'is speced' } + + it 'is starred if tag is "starred"' do + todo = create_todo + todo.should_not be_starred + todo.add_tag('starred') + todo.reload + todo.should be_starred + end + + describe 'when toggling star flag' do + it 'toggles to not starred when starred' do + todo = create_todo + todo.add_tag('starred') + todo.should be_starred + todo.toggle_star! + todo.reload + todo.should_not be_starred + end + + it 'toggles to starred when not starred' do + todo = create_todo + todo.should_not be_starred + todo.toggle_star! + todo.reload + todo.should be_starred + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 00000000..7f915fbd --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,181 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe User do + def valid_attributes(attributes={}) + { + :login => 'simon', + :password => 'foobarspam', + :password_confirmation => 'foobarspam' + }.merge(attributes) + end + + before(:each) do + @user = User.new + end + + describe 'associations' do + it 'has many contexts' do + User.should have_many(:contexts). + with_order('position ASC'). + with_dependent(:delete_all) + end + + it 'has many projects' do + User.should have_many(:projects). + with_order('projects.position ASC'). + with_dependent(:delete_all) + end + + # TODO: uses fixtures to test those + it 'has many active_projects' do + User.should have_many(:active_projects). + with_order('projects.position ASC'). + with_conditions('state = ?', 'active'). + with_class_name('Project') + end + + it 'has many active contexts' do + User.should have_many(:active_contexts). + with_order('position ASC'). + with_conditions('hide = ?', 'true'). + with_class_name('Context') + end + + it 'has many todos' do + User.should have_many(:todos). + with_order('todos.completed_at DESC, todos.created_at DESC'). + with_dependent(:delete_all) + end + + it 'has many deferred todos' do + User.should have_many(:deferred_todos). + with_order('show_from ASC, todos.created_at DESC'). + with_conditions('state = ?', 'deferred'). + with_class_name('Todo') + end + + it 'has many completed todos' do + User.should have_many(:completed_todos). + with_order('todos.completed_at DESC'). + with_conditions('todos.state = ? and todos.completed_at is not null', 'completed'). + with_include(:project, :context). + with_class_name('Todo') + end + + it 'has many notes' do + User.should have_many(:notes). + with_order('created_at DESC'). + with_dependent(:delete_all) + end + + it 'has many taggings' do + User.should have_many(:taggings) + end + + it 'has many tags through taggings' do + User.should have_many(:tags).through(:taggings).with_select('DISTINCT tags.*') + end + + it 'has one preference' do + User.should have_one(:preference) + end + end + + it_should_validate_presence_of :login + it_should_validate_presence_of :password + it_should_validate_presence_of :password_confirmation + + it_should_validate_length_of :password, :within => 5..40 + it_should_validate_length_of :login, :within => 3..80 + + it_should_validate_uniqueness_of :login + it_should_validate_confirmation_of :password + + it 'validates presence of password only when password is required' + it 'validates presence of password_confirmation only when password is required' + it 'validates confirmation of password only when password is required' + it 'validates presence of open_id_url only when using openid' + + it 'accepts only allow auth_type authorized by the admin' do + Tracks::Config.should_receive(:auth_schemes).exactly(3).times.and_return(%w(database open_id)) + User.new(valid_attributes(:auth_type => 'database')).should_not have(:any).error_on(:auth_type) + User.new(valid_attributes(:auth_type => 'open_id')).should_not have(:any).error_on(:auth_type) + User.new(valid_attributes(:auth_type => 'ldap')).should have(1).error_on(:auth_type) + end + + it 'returns login for #to_param' do + @user.login = 'john' + @user.to_param.should == 'john' + end + + it 'has a custom finder to find admin' do + User.should_receive(:find).with(:first, :conditions => ['is_admin = ?', true]) + User.find_admin + end + + it 'has a custom finder to find by openid url' + it 'knows if there is any user through #no_users_yet? (TODO: better description)' + + describe 'when choosing what do display as a name' do + it 'displays login when no first name and last name' do + User.new(valid_attributes).display_name.should == 'simon' + end + + it 'displays last name when no first name' do + User.new(valid_attributes(:last_name => 'foo')).display_name.should == 'foo' + end + + it 'displays first name when no last name' do + User.new(valid_attributes(:first_name => 'bar')).display_name.should == 'bar' + end + + it 'displays first name and last name when both specified' do + User.new(valid_attributes(:first_name => 'foo', :last_name => 'bar')).display_name.should == 'foo bar' + end + end + + describe 'authentication' do + before(:each) do + @user = User.create!(valid_attributes) + end + + it 'authenticates user' do + User.authenticate('simon', 'foobarspam').should == @user + end + + it 'resets password' do + @user.update_attributes( + :password => 'new password', + :password_confirmation => 'new password' + ) + User.authenticate('simon', 'new password').should == @user + end + + it 'does not rehash password after update of login' do + @user.update_attribute(:login, 'foobar') + User.authenticate('foobar', 'foobarspam').should == @user + end + + it 'sets remember token' do + @user.remember_me + @user.remember_token.should_not be_nil + @user.remember_token_expires_at.should_not be_nil + end + + it 'unsets remember token' do + @user.remember_me + @user.remember_token.should_not be_nil + @user.forget_me + @user.remember_token.should be_nil + end + + it 'remembers me default two weeks' do + before = 2.weeks.from_now.utc + @user.remember_me + after = 2.weeks.from_now.utc + @user.remember_token.should_not be_nil + @user.remember_token_expires_at.should_not be_nil + @user.remember_token_expires_at.should be_between(before, after) + end + end +end diff --git a/spec/scenarios/contexts_scenario.rb b/spec/scenarios/contexts_scenario.rb new file mode 100644 index 00000000..719da5db --- /dev/null +++ b/spec/scenarios/contexts_scenario.rb @@ -0,0 +1,19 @@ +class ContextsScenario < Scenario::Base + uses :users + + def load + %w(Call Email Errand Someday).each_with_index do |context, index| + create_context context, index+1 + end + end + + def create_context(name, position) + create_model :context, name.downcase.to_sym, + :name => name, + :position => position, + :hide => name == 'Someday' ? true : false, + :created_at => Time.now, + :updated_at => Time.now, + :user_id => user_id(:sean) + end +end diff --git a/spec/scenarios/projects_scenario.rb b/spec/scenarios/projects_scenario.rb new file mode 100644 index 00000000..3b5624f1 --- /dev/null +++ b/spec/scenarios/projects_scenario.rb @@ -0,0 +1,20 @@ +class ProjectsScenario < Scenario::Base + def load + create_project :build_time_machine, 'Build a working time machine' + create_project :make_more_money, 'Make more money than Billy Gates' + create_project :evict_dinosaurs, 'Evict dinosaurs from the garden' + create_project :attend_railsconf, 'Attend RailsConf' + end + + def create_project(identifier, name) + attributes = { + :name => name, + :state => 'active', + :created_at => 4.day.ago, + :updated_at => 1.minute.ago + } + create_model :project, + identifier || attributes[:name].split.first.downcase.to_sym, + attributes + end +end diff --git a/spec/scenarios/todos_scenario.rb b/spec/scenarios/todos_scenario.rb new file mode 100644 index 00000000..ddaa7f93 --- /dev/null +++ b/spec/scenarios/todos_scenario.rb @@ -0,0 +1,30 @@ +class TodosScenario < Scenario::Base + uses :contexts, :projects, :users + + def load + create_todo :bill, + :description => 'Call Bill Gates to find out how much he makes per day', + :user => :sean, + :context => :call, + :project => :make_more_money + create_todo :bank, + :description => 'Call my bank', + :user => :sean, + :context => :call, + :project => :make_more_money + end + + def create_todo(identifier, options={}) + context = options.delete(:context) + project = options.delete(:project) + user = options.delete(:user) + attributes = { + :state => 'active', + :created_at => 1.week.ago, + :context_id => context_id(context), + :project_id => project_id(project), + :user_id => user_id(user) + }.merge(options) + create_model :todo, identifier, attributes + end +end diff --git a/spec/scenarios/users_scenario.rb b/spec/scenarios/users_scenario.rb new file mode 100644 index 00000000..5ed73b83 --- /dev/null +++ b/spec/scenarios/users_scenario.rb @@ -0,0 +1,19 @@ +class UsersScenario < Scenario::Base + def load + create_user :login => 'johnny', :first_name => 'Johnny', :last_name => 'Smith' + create_user :login => 'jane', :first_name => 'Jane', :last_name => 'Pilbeam' + create_user :login => 'sean', :first_name => 'Sean', :last_name => 'Pallmer' + end + + def create_user(attributes={}) + password = attributes[:login] + Time.now.to_s + attributes = { + :password => password, + :password_confirmation => password, + :is_admin => attributes[:is_admin] || false, + }.merge(attributes) + identifier = attributes[:login].downcase.to_sym + user = create_model :user, identifier, attributes + Preference.create(:show_number_completed => 5, :user => user) + end +end diff --git a/vendor/plugins/scenarios/.gitignore b/vendor/plugins/scenarios/.gitignore new file mode 100644 index 00000000..e8edc4ed --- /dev/null +++ b/vendor/plugins/scenarios/.gitignore @@ -0,0 +1,4 @@ +environments +*.log +tmp +vendor \ No newline at end of file diff --git a/vendor/plugins/scenarios/LICENSE b/vendor/plugins/scenarios/LICENSE new file mode 100644 index 00000000..0c9f55a0 --- /dev/null +++ b/vendor/plugins/scenarios/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2007, Adam Williams and John W. Long. + +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. \ No newline at end of file diff --git a/vendor/plugins/scenarios/README b/vendor/plugins/scenarios/README new file mode 100644 index 00000000..971472e8 --- /dev/null +++ b/vendor/plugins/scenarios/README @@ -0,0 +1,262 @@ +== Rails Scenarios Plugin + +Who hasn't experienced the pain of dozens of YAML files filled with hundreds +of inter-related data structures? When do you look at People of an +Organization and not have to look at the organization_id, open the +organizations.yml file, and search for 'id: X'? + +In a nutshell, scenarios are a drop in replacement for YAML fixtures. Instead +of encouraging you to create a mindless amount of raw data in the form of +YAML, scenarios encourage you to create code that populates your tables with +the appropriate records. + +How is it different from other solutions? A few things: + + * It continues to provide a fundamental, fast insertion method using attributes + written directly to a table. This is the + Scenarios::TableMethods#create_record method. + + * It allows you to create records using validations if you prefer, or if it's + important to have all your callbacks be invoked. See + Scenarios::TableMethods#create_model. Both create_record and create_model + allow you to name your instances for retrieval by the instance and id reader + methods (more below). + + * Nothing stops you from simply invoking YouModel.create!, etc. We'll still + keep track of the tables the scenario modifies and clean things up afterward. + + * It allows you to create re-usable scenarios as classes. These classes are + like any other class - they may include modules, subclass, and be composed of + other scenarios. See Scenarios::Base.uses. This also means that you can load + any scenario into any Rails environment. That's what the 'rake + db:scenario:load' task is good for (more below). Very handy for re-using all + that test support code to create populated demos! + +=== Quick Start + +Since Scenarios is a Rails plugin at this time, you should get it installed, +using the appropriate method (script/plugin, svn, piston) into your +vendor/plugins directory. Once you have this, in your spec_helper.rb or +test_helper.rb, add the following line after the spec requires: + + require 'scenarios' + +The Scenarios you write should be placed in the spec/scenarios directory of your +Rails project if you're using RSpec, or the test/scenarios directory of your +Rails project if you're using Test::Unit. Scenario file names always end in +"_scenario.rb" and classes end in "Scenario". + +A simple scenario looks like this: + + # in spec/scenarios/users_scenario.rb or test/scenarios/users_scenario.rb + class UsersScenario < Scenario::Base + def load + create_record :user, :john, :name => 'John', :password => 'doodaht' + create_record :user, :cindy, :name => 'Cindy', :password => 'whoot!' + end + end + +In the example above, I'm using the create_record instance method to +create two users: John and Cindy. Notice the calls to create_record. +There are three parameters. The first is the singular name of the table to +insert the record into, the second is the symbolic name of the record (more on +that later), and the third is a hash of the attributes of the record. + +To use the UsersScenario in a description, you should declare it using +the scenario method. Here it is within a spec file (RSpec): + + # in spec/models/user_spec.rb + describe User do + scenario :users + + it "should allow me to do something with John" do + user = users(:john) + user.password.should == "doodaht" + end + end + +and here it is within a standard Test::Unit test: + + # in test/unit/user_test.rb + class UserTest < Test::Unit::TestCase + + scenario :users + + def test_do_something + user = users(:john) + assert_equal "doodaht", user.password + end + end + +Notice that it is easy to load an instance of a model object using its +symbolic name with a reader method, similar to that of Rails' fixtures. In the +example above, I loaded John with the reader method users and the +symbolic name :john. (Remember that in the Users scenario I declared +that John should be accessible through the symbolic name :john.) + +I could also have retrieved an array of user fixtures by passing in +multiple symbolic names to the reader method: + + # in spec/models/user_spec.rb + describe User do + scenario :users + + it "should allow me to get all admins" do + admins = users(:john, :ryan) + User.admins.should eql(admins) + end + end + +=== Composition + +In real life your scenarios will probably grow quite complicated. The +scenarios plugin allows you to deal with this complexity through composition. + +Here's a simple example: + + # in spec/scenarios/posts_scenario.rb or test/scenarios/posts_scenario.rb + class PostsScenario < Scenario::Base + def load + create_record :post, :first, :title => "First Post" + create_record :post, :second, :title => "Second Post" + end + end + + # in spec/scenarios/comments_scenario.rb or test/scenarios/comments_scenario.rb + class CommentsScenario < Scenario::Base + uses :posts + + def load + create_record :comment, :first, :body => "Nice post!", :post_id => post_id(:first) + create_record :comment, :second, :body => "I like it.", :post_id => post_id(:first) + create_record :comment, :third, :body => "I thoroughly disagree.", :post_id => post_id(:second) + end + end + +In the example above, the CommentsScenario declares that it depends on the +Posts scenario with the uses class method. This means that if you +load the CommentsScenario, the PostsScenario will be loaded first and the +CommentsScenario will have access to all the data loaded by the PostsScenario +in its own load method. Note that inside the load method I'm using +another form of reader methed which simply gives you the id for a symbolic +name (in this case: post_id). This is most useful for making +associations, as done here with comments and posts. + +=== Helper Methods + +Another way of simplifying your scenarios and specs/tests is through helper +methods. The Scenarios plugin provides a handy way to declare helper methods +that are accessible from inside the scenario and also from inside related +RSpec/Test::Unit examples: + + # in spec/scenarios/users_scenario.rb or test/scenarios/users_scenario.rb + class UsersScenario < Scenario::Base + def load + create_user :name => "John" + end + + helpers do + def create_user(attributes={}) + create_record :user, attributes[:name].downcase.intern, attributes + end + def login_as(user) + @request.session[:user_id] = user.id + end + end + end + +Helper methods declared inside the helpers block are mixed into the scenario +when it is instantiated and mixed into examples that declare that they are using +the scenario. Also, in the case where one scenario uses another, the +using scenario will have the helper methods of the used scenario. + + # in spec/controllers/projects_controller_spec.rb + describe "Projects screen" do + scenario :users + + it "should show active projects" do + login_as(users(:john)) + get :projects + @response.should have_tag('#active_projects') + end + end + + # in test/functional/projects_controller_test.rb + class PeopleControllerTest < Test::Unit::TestCase + scenario :users + + def test_index + login_as(users(:john)) + get :projects + assert_tag('#active_projects') + end + end + +Notice that within my specs/tests I have access to the login_as helper method +declared inside the helpers block of the UsersScenario. Scenario +helpers are a great way to share helper methods between specs/tests that use a +specific scenario. + +=== Built-in Scenario + +There is a scenario named 'blank' that comes with the plugin. This scenario is +useful when you want to express, and guarantee, that the database is empty. It +works by using your db/schema.rb, so if the table isn't created in there, it +won't be cleaned up. + +Scenario.load_paths is an array of the locations to look for scenario +definitions. The built-in scenarios directory is consulted last, so if you'd +like to re-define, for instance, the 'blank' scenario, simply create +'blank_scenario.rb' in your spec/scenarios or test/scenarios directory. + +=== Load Rake Task + +The Scenarios plugin provides a single Rake task, db:scenario:load, +which you may use in a fashion similar to Rails fixtures' +db:fixtures:load. + + rake db:scenario:load SCENARIO=comments + +When invoked, this task will populate the development database with the named +scenario. + +If you do not specify SCENARIO, the task will expect to find a default scenario +(a file 'default_scenario.rb' having DefaultScenario defined in it). It is our +practice to have it such that this scenario uses a number of our other +scenarios, thereby: + +* encouraging us to use test data that looks good in the running development + application + +* allowing us to troubleshoot failing tests in the running development + application + +=== More Information + +For more information, be sure to look through the documentation over at RubyForge: + +* http://faithfulcode.rubyforge.org/docs/scenarios + +You might also enjoy taking a look at the specs for the plugin and the example +scenarios: + +* http://faithfulcode.rubyforge.org/svn/plugins/trunk/scenarios/spec/scenarios_spec.rb +* http://faithfulcode.rubyforge.org/svn/plugins/trunk/scenarios/spec/scenarios + +Browse the complete source code: + +* http://faithfulcode.rubyforge.org/svn/plugins/trunk/scenarios + +=== Running Tests + +You should be able to simply run rake. Notice in testing/environment.rb the +revisions under which this project will work. If you intend to test against +HEAD, you will need to delete the directory testing/tmp/trunk/HEAD. At some +point, it would be nice to have the script track the revision of HEAD that we +have, and update the directory automatically. + +=== License + +The Scenarios plugin is released under the MIT-License and is Copyright (c) +2007, Adam Williams and John W. Long. Special thanks to Chris Redinger for his +part in helping us get this plugin ready for the public. \ No newline at end of file diff --git a/vendor/plugins/scenarios/Rakefile b/vendor/plugins/scenarios/Rakefile new file mode 100644 index 00000000..bd0deb95 --- /dev/null +++ b/vendor/plugins/scenarios/Rakefile @@ -0,0 +1,10 @@ +require File.expand_path(File.dirname(__FILE__) + '/testing/plugit_descriptor') + +require 'rake/rdoctask' +Rake::RDocTask.new(:doc) do |r| + r.title = "Rails Scenarios Plugin" + r.main = "README" + r.options << "--line-numbers" + r.rdoc_files.include("README", "LICENSE", "lib/**/*.rb") + r.rdoc_dir = "doc" +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/TODO b/vendor/plugins/scenarios/TODO new file mode 100644 index 00000000..9be8fb9d --- /dev/null +++ b/vendor/plugins/scenarios/TODO @@ -0,0 +1 @@ +Make sure before :all's that use scenario methods work. They don't right now. \ No newline at end of file diff --git a/vendor/plugins/scenarios/helpers.diff b/vendor/plugins/scenarios/helpers.diff new file mode 100644 index 00000000..340068bb --- /dev/null +++ b/vendor/plugins/scenarios/helpers.diff @@ -0,0 +1,127 @@ +Only in /Users/aiwilliams/Workspaces/faithfulcode/scenarios/: .git +Only in scenarios/: .svn +Only in scenarios/: .tm_last_run_ruby +diff -r scenarios/Rakefile /Users/aiwilliams/Workspaces/faithfulcode/scenarios/Rakefile +3c3 +< TESTING_ENVIRONMENTS["rspec_3317_rails_8956"].load +--- +> TESTING_ENVIRONMENTS["rspec_3119_rails_8375"].load +Only in /Users/aiwilliams/Workspaces/faithfulcode/scenarios/: helpers.diff +Only in scenarios/lib: .svn +Only in scenarios/lib/scenarios: .svn +diff -r scenarios/lib/scenarios/base.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/lib/scenarios/base.rb +11,14c11,12 +< # be included into the scenario and all specs that include the scenario. +< # You may also provide names of helpers from your scenarios/helpers +< # directory, or any other module you'd like included in your Scenario. +< def helpers(helper_names_or_modules = [], &block) +--- +> # be included into the scenario and all specs that include the scenario +> def helpers(&block) +17,19d14 +< mod.module_eval do +< [helper_names_or_modules].flatten.each {|h| include h.is_a?(Module) ? h : h.to_scenario_helper} +< end +Only in scenarios/lib/scenarios/builtin: .svn +Only in scenarios/lib/scenarios/extensions: .svn +diff -r scenarios/lib/scenarios/extensions/string.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/lib/scenarios/extensions/string.rb +22,39d21 +< # Convert a string into the associated scenario helper module: +< # +< # "basic".to_scenario_helper #=> BasicScenarioHelper +< # "basic_scenario".to_scenario_helper #=> BasicScenarioHelper +< # +< # Raises Scenario::NameError if the the helper cannot be loacated as +< # 'helpers/_helper' in Scenario.load_paths. +< def to_scenario_helper +< class_name = "#{self.strip.camelize.sub(/ScenarioHelper$/, '')}ScenarioHelper" +< Scenario.load_paths.each do |path| +< filename = "#{path}/#{class_name.underscore}.rb" +< if File.file?(filename) +< require filename +< break +< end +< end +< class_name.constantize rescue raise Scenario::NameError, "Expected to find #{class_name} in #{Scenario.load_paths.inspect}" +< end +diff -r scenarios/lib/scenarios/extensions/symbol.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/lib/scenarios/extensions/symbol.rb +14,23d13 +< # Convert a symbol into the associated scenario helper module: +< # +< # :basic.to_scenario_helper #=> BasicScenarioHelper +< # :basic_scenario.to_scenario_helper #=> BasicScenarioHelper +< # +< # Raises Scenario::NameError if the the helper cannot be loacated as +< # 'helpers/_helper' in Scenario.load_paths. +< def to_scenario_helper +< to_s.to_scenario_helper +< end +Only in scenarios/spec: .svn +Only in scenarios/spec: environments.rb +Only in scenarios/spec/scenarios: .svn +Only in scenarios/spec/scenarios: helpers +diff -r scenarios/spec/scenarios_spec.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/spec/scenarios_spec.rb +23,27d22 +< it 'should allow us to have helpers in scenarios/helpers directory which we can get through the helpers class method' do +< klass = :empty.to_scenario +< klass.helpers :myown +< end +< +diff -r scenarios/spec/spec_helper.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/spec/spec_helper.rb +1,20c1,6 +< $LOAD_PATH << File.dirname(__FILE__) + '/../testing' +< +< require File.dirname(__FILE__) + "/environments" +< +< require 'active_support' +< require 'active_record' +< require 'action_controller' +< require 'action_view' +< +< module Spec +< module Rails +< module Example +< end +< end +< end +< +< require 'spec/rails' +< require 'scenarios' +< +< require 'models' +\ No newline at end of file +--- +> require File.expand_path(File.dirname(__FILE__) + "/../testing/environment") +> TESTING_ENVIRONMENTS[TESTING_ENVIRONMENT].load(DATABASE_ADAPTER) +> require "models" +> require "spec" +> require "spec/rails" +> require "scenarios" +Only in scenarios/tasks: .svn +Only in scenarios/test: .svn +Only in scenarios/testing: .svn +diff -r scenarios/testing/environment.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/testing/environment.rb +15c15 +< TESTING_ENVIRONMENT = "rspec_3317_rails_8956" unless defined?(TESTING_ENVIRONMENT) +--- +> TESTING_ENVIRONMENT = "rspec_3119_rails_8375" unless defined?(TESTING_ENVIRONMENT) +31c31 +< # system "cd #{lib.support_directory} && patch -p0 < #{File.join(TESTING_ROOT, "rspec_on_rails_3119.patch")}" +--- +> system "cd #{lib.support_directory} && patch -p0 < #{File.join(TESTING_ROOT, "rspec_on_rails_3119.patch")}" +36,38c36,38 +< TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3317_rails_8956", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env| +< env.package "rails", "http://svn.rubyonrails.org/rails", "trunk", "8956", &rails_package +< env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3317", &rspec_package +--- +> TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3119_rails_8375", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env| +> env.package "rails", "http://svn.rubyonrails.org/rails", "trunk", "8375", &rails_package +> env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3119", &rspec_package +40c40 +< TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3317_rails_1_2_6", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env| +--- +> TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3119_rails_1_2_6", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env| +42c42 +< env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3317", &rspec_package +--- +> env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3119", &rspec_package diff --git a/vendor/plugins/scenarios/lib/scenarios.rb b/vendor/plugins/scenarios/lib/scenarios.rb new file mode 100644 index 00000000..5950e8ec --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios.rb @@ -0,0 +1,34 @@ +module Scenarios + # Thrown by Scenario.load when it cannot find a specific senario. + class NameError < ::NameError; end + + class << self + # The locations from which scenarios will be loaded. + mattr_accessor :load_paths + self.load_paths = ["#{RAILS_ROOT}/spec/scenarios", "#{RAILS_ROOT}/test/scenarios", "#{File.dirname(__FILE__)}/scenarios/builtin"] + + # Load a scenario by name. scenario_name can be a string, symbol, + # or the scenario class. + def load(scenario_name) + klass = scenario_name.to_scenario + klass.load + klass + end + end +end + +# The Scenario namespace makes for Scenario::Base +Scenario = Scenarios + +# For Rails 1.2 compatibility +unless Class.instance_methods.include?(:superclass_delegating_reader) + require File.dirname(__FILE__) + "/scenarios/extensions/delegating_attributes" +end + +require 'active_record/fixtures' +require 'scenarios/configuration' +require 'scenarios/table_blasting' +require 'scenarios/table_methods' +require 'scenarios/loading' +require 'scenarios/base' +require 'scenarios/extensions' \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/base.rb b/vendor/plugins/scenarios/lib/scenarios/base.rb new file mode 100644 index 00000000..3885af3c --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/base.rb @@ -0,0 +1,73 @@ +module Scenarios + class Base + class << self + # Class method to load the scenario. Used internally by the Scenarios + # plugin. + def load + new.load_scenarios(used_scenarios + [self]) + end + + # Class method for your own scenario to define helper methods that will + # be included into the scenario and all specs that include the scenario + def helpers(&block) + mod = (const_get(:Helpers) rescue const_set(:Helpers, Module.new)) + mod.module_eval(&block) if block_given? + mod + end + + # Class method for your own scenario to define the scenarios that it + # depends on. If your scenario depends on other scenarios those + # scenarios will be loaded before the load method on your scenario is + # executed. + def uses(*scenarios) + names = scenarios.map(&:to_scenario).reject { |n| used_scenarios.include?(n) } + used_scenarios.concat(names) + end + + # Class method that returns the scenarios used by your scenario. + def used_scenarios # :nodoc: + @used_scenarios ||= [] + @used_scenarios = (@used_scenarios.collect(&:used_scenarios) + @used_scenarios).flatten.uniq + end + + # Returns the scenario class. + def to_scenario + self + end + end + + include TableMethods + include Loading + + attr_reader :table_config + + # Initialize a scenario with a Configuration. Used internally by the + # Scenarios plugin. + def initialize(config = Configuration.new) + @table_config = config + table_config.update_scenario_helpers self.class + self.extend table_config.table_readers + self.extend table_config.scenario_helpers + end + + # This method should be implemented in your scenarios. You may also have + # scenarios that simply use other scenarios, so it is not required that + # this be overridden. + def load + end + + # Unload a scenario, sort of. This really only deletes the records, all of + # them, of every table this scenario modified. The goal is to maintain a + # clean database for successive runs. Used internally by the Scenarios + # plugin. + def unload + return if unloaded? + record_metas.each_value { |meta| blast_table(meta.table_name) } + @unloaded = true + end + + def unloaded? + @unloaded == true + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb b/vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb new file mode 100644 index 00000000..1ab74d3c --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb @@ -0,0 +1,18 @@ +class BlankScenario < Scenarios::Base + def load + table_names.each do |table| + blast_table(table) + end + end + + def table_names + self.class.table_names + end + + def self.table_names + @table_names ||= begin + schema = (open(RAILS_ROOT + '/db/schema.rb') { |f| f.read } rescue '') + schema.grep(/create_table\s+(['"])(.+?)\1/m) { $2 } + end + end +end diff --git a/vendor/plugins/scenarios/lib/scenarios/configuration.rb b/vendor/plugins/scenarios/lib/scenarios/configuration.rb new file mode 100644 index 00000000..6becffcf --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/configuration.rb @@ -0,0 +1,55 @@ +module Scenarios + class Configuration # :nodoc: + attr_reader :blasted_tables, :loaded_scenarios, :record_metas, :table_readers, :scenario_helpers, :symbolic_names_to_id + + def initialize + @blasted_tables = Set.new + @record_metas = Hash.new + @table_readers = Module.new + @scenario_helpers = Module.new + @symbolic_names_to_id = Hash.new {|h,k| h[k] = Hash.new} + @loaded_scenarios = Array.new + end + + # Given a created record (currently ScenarioModel or ScenarioRecord), + # update the table readers module appropriately such that this record and + # it's id are findable via methods like 'people(symbolic_name)' and + # 'person_id(symbolic_name)'. + def update_table_readers(record) + ids, record_meta = symbolic_names_to_id, record.record_meta # scoping assignments + + ids[record_meta.table_name][record.symbolic_name] = record.id + table_readers.send :define_method, record_meta.id_reader do |*symbolic_names| + record_ids = symbolic_names.flatten.collect do |symbolic_name| + if symbolic_name.kind_of?(ActiveRecord::Base) + symbolic_name.id + else + record_id = ids[record_meta.table_name][symbolic_name.to_sym] + raise ActiveRecord::RecordNotFound, "No object is associated with #{record_meta.table_name}(:#{symbolic_name})" unless record_id + record_id + end + end + record_ids.size > 1 ? record_ids : record_ids.first + end + + table_readers.send :define_method, record_meta.record_reader do |*symbolic_names| + results = symbolic_names.flatten.collect do |symbolic_name| + symbolic_name.kind_of?(ActiveRecord::Base) ? + symbolic_name : + record_meta.record_class.find(send(record_meta.id_reader, symbolic_name)) + end + results.size > 1 ? results : results.first + end + end + + def update_scenario_helpers(scenario_class) + scenario_helpers.module_eval do + include scenario_class.helpers + end + end + + def scenarios_loaded? + !loaded_scenarios.blank? + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions.rb b/vendor/plugins/scenarios/lib/scenarios/extensions.rb new file mode 100644 index 00000000..f442786e --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions.rb @@ -0,0 +1,5 @@ +require File.dirname(__FILE__) + "/extensions/object" +require File.dirname(__FILE__) + "/extensions/string" +require File.dirname(__FILE__) + "/extensions/symbol" +require File.dirname(__FILE__) + "/extensions/active_record" +require File.dirname(__FILE__) + "/extensions/test_case" rescue nil \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb new file mode 100644 index 00000000..509abd27 --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb @@ -0,0 +1,14 @@ +module ActiveRecord + class Base + cattr_accessor :table_config + include Scenarios::TableBlasting + + # In order to guarantee that tables are tracked when _create_model_ is + # used, and those models cause other models to be created... + def create_with_table_blasting + prepare_table(self.class.table_name) + create_without_table_blasting + end + alias_method_chain :create, :table_blasting + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb new file mode 100644 index 00000000..f5f0ef87 --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb @@ -0,0 +1,40 @@ +# These class attributes behave something like the class +# inheritable accessors. But instead of copying the hash over at +# the time the subclass is first defined, the accessors simply +# delegate to their superclass unless they have been given a +# specific value. This stops the strange situation where values +# set after class definition don't get applied to subclasses. +class Class + def superclass_delegating_reader(*names) + class_name_to_stop_searching_on = self.superclass.name.blank? ? "Object" : self.superclass.name + names.each do |name| + class_eval <<-EOS + def self.#{name} + if defined?(@#{name}) + @#{name} + elsif superclass < #{class_name_to_stop_searching_on} && superclass.respond_to?(:#{name}) + superclass.#{name} + end + end + def #{name} + self.class.#{name} + end + EOS + end + end + + def superclass_delegating_writer(*names) + names.each do |name| + class_eval <<-EOS + def self.#{name}=(value) + @#{name} = value + end + EOS + end + end + + def superclass_delegating_accessor(*names) + superclass_delegating_reader(*names) + superclass_delegating_writer(*names) + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/object.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/object.rb new file mode 100644 index 00000000..efd28197 --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions/object.rb @@ -0,0 +1,5 @@ +class Object + def metaclass + (class << self; self; end) + end unless method_defined?(:metaclass) +end diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/string.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/string.rb new file mode 100644 index 00000000..830aa791 --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions/string.rb @@ -0,0 +1,22 @@ +class String + + # Convert a string into the associated scenario class: + # + # "basic".to_scenario #=> BasicScenario + # "basic_scenario".to_scenario #=> BasicScenario + # + # Raises Scenario::NameError if the the scenario cannot be loacated in + # Scenario.load_paths. + def to_scenario + class_name = "#{self.strip.camelize.sub(/Scenario$/, '')}Scenario" + Scenario.load_paths.each do |path| + filename = "#{path}/#{class_name.underscore}.rb" + if File.file?(filename) + require filename + break + end + end + class_name.constantize rescue raise Scenario::NameError, "Expected to find #{class_name} in #{Scenario.load_paths.inspect}" + end + +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb new file mode 100644 index 00000000..35e5f090 --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb @@ -0,0 +1,14 @@ +class Symbol + + # Convert a symbol into the associated scenario class: + # + # :basic.to_scenario #=> BasicScenario + # :basic_scenario.to_scenario #=> BasicScenario + # + # Raises Scenario::NameError if the the scenario cannot be located in + # Scenario.load_paths. + def to_scenario + to_s.to_scenario + end + +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb new file mode 100644 index 00000000..a9f0ef6a --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb @@ -0,0 +1,77 @@ +module Test #:nodoc: + module Unit #:nodoc: + class TestCase #:nodoc: + superclass_delegating_accessor :scenario_classes + superclass_delegating_accessor :table_config + + # Changing either of these is not supported at this time. + self.use_transactional_fixtures = true + self.use_instantiated_fixtures = false + + include Scenarios::TableMethods + include Scenarios::Loading + + class << self + # This class method is mixed into RSpec and allows you to declare that + # you are using a given scenario or set of scenarios within a spec: + # + # scenario :basic # loads BasicScenario and any dependencies + # scenario :posts, :comments # loads PostsScenario and CommentsScenario + # + # It accepts an array of scenarios (strings, symbols, or classes) and + # will load them roughly in the order that they are specified. + def scenario(*names) + self.scenario_classes = [] + names.each do |name| + scenario_class = name.to_scenario + scenario_classes.concat(scenario_class.used_scenarios + [scenario_class]) + end + scenario_classes.uniq! + end + + # Overridden to provide before all and after all code which sets up and + # tears down scenarios + def suite_with_scenarios + suite = suite_without_scenarios + class << suite + attr_accessor :test_class + def run_with_scenarios(*args, &block) + debugger + run_without_scenarios(*args, &block) + test_class.table_config.loaded_scenarios.each { |s| s.unload } if test_class.table_config + end + alias_method_chain :run, :scenarios + end + suite.test_class = self + suite + end + alias_method_chain :suite, :scenarios + end + + # Hook into fixtures loading lifecycle to instead load scenarios. This + # is expected to be called in a fashion respective of + # use_transactional_fixtures. I feel like a leech. + def load_fixtures + if !scenarios_loaded? || !use_transactional_fixtures? + self.class.table_config = Scenarios::Configuration.new if !use_transactional_fixtures? || table_config.nil? + load_scenarios(scenario_classes) + end + self.extend scenario_helpers + self.extend table_readers + end + + # Here we are counting on existing logic to allow teardown method + # overriding as done in fixtures.rb. Only if transaction fixtures are + # not being used do we unload scenarios after a test. Otherwise, we wait + # until the end of the run of all tests on this test_case (collection of + # tests, right!). See the TestSuite extension done in _suite_ for + # behaviour when using transaction fixtures. + def teardown_with_scenarios + teardown_without_scenarios + loaded_scenarios.each { |s| s.unload } unless use_transactional_fixtures? + end + alias_method_chain :teardown, :scenarios + + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/loading.rb b/vendor/plugins/scenarios/lib/scenarios/loading.rb new file mode 100644 index 00000000..b369196e --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/loading.rb @@ -0,0 +1,51 @@ +module Scenarios + # Provides scenario loading and convenience methods around the Configuration + # that must be made available through a method _table_config_. + module Loading # :nodoc: + def load_scenarios(scenario_classes) + install_active_record_tracking_hook + scenario_classes.each do |scenario_class| + scenario = scenario_class.new(table_config) + scenario.load + table_config.loaded_scenarios << scenario + end if scenario_classes + end + + def loaded_scenarios + table_config.loaded_scenarios + end + + def scenarios_loaded? + table_config && table_config.scenarios_loaded? + end + + # The sum of all the loaded scenario's helper methods. These can be mixed + # into anything you like to gain access to them. + def scenario_helpers + table_config.scenario_helpers + end + + # The sum of all the available table reading methods. These will only + # include readers for which data has been placed into the table. These can + # be mixed into anything you like to gain access to them. + def table_readers + table_config.table_readers + end + + # # This understand nesting descriptions one deep + # def table_config + # on_my_class = self.class.instance_variable_get("@table_config") + # return on_my_class if on_my_class + # + # if self.class.superclass + # on_super_class = self.class.superclass.instance_variable_get("@table_config") + # return on_super_class if on_super_class + # end + # end + + private + def install_active_record_tracking_hook + ActiveRecord::Base.table_config = table_config + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/table_blasting.rb b/vendor/plugins/scenarios/lib/scenarios/table_blasting.rb new file mode 100644 index 00000000..9b2efc3d --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/table_blasting.rb @@ -0,0 +1,20 @@ +module Scenarios + module TableBlasting + def self.included(base) + base.module_eval do + delegate :blasted_tables, :to => :table_config + end + end + + def blast_table(name) # :nodoc: + ActiveRecord::Base.silence do + ActiveRecord::Base.connection.delete "DELETE FROM #{name}", "Scenario Delete" + end + blasted_tables << name + end + + def prepare_table(name) + blast_table(name) unless blasted_tables.include?(name) + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/lib/scenarios/table_methods.rb b/vendor/plugins/scenarios/lib/scenarios/table_methods.rb new file mode 100644 index 00000000..a9f2be34 --- /dev/null +++ b/vendor/plugins/scenarios/lib/scenarios/table_methods.rb @@ -0,0 +1,205 @@ +module Scenarios + # This helper module contains the #create_record method. It is made + # available to all Scenario instances, test and example classes, and test + # and example instances. + module TableMethods + include TableBlasting + + delegate :record_metas, :to => :table_config + + # Insert a record into the database, add the appropriate helper methods + # into the scenario and spec, and return the ID of the inserted record: + # + # create_record :event, :name => "Ruby Hoedown" + # create_record Event, :hoedown, :name => "Ruby Hoedown" + # + # The first form will create a new record in the given class identifier + # and no symbolic name (essentially). + # + # The second form is exactly like the first, except for that it provides a + # symbolic name as the second parameter. The symbolic name will allow you + # to access the record through a couple of helper methods: + # + # events(:hoedown) # The hoedown event + # event_id(:hoedown) # The ID of the hoedown event + # + # These helper methods are only accessible for a particular table after + # you have inserted a record into that table using create_record. + def create_record(class_identifier, *args) + insert(ScenarioRecord, class_identifier, *args) do |record| + meta = record.record_meta + fixture = record.to_fixture + begin + meta.connection.insert_fixture(fixture, record.record_meta.table_name) + rescue # Rails 1.2 compatible! + meta.connection.execute "INSERT INTO #{meta.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})" + end + record.id + end + end + + # Instantiate and save! a model, add the appropriate helper methods into + # the scenario and spec, and return the new model instance: + # + # create_model :event, :name => "Ruby Hoedown" + # create_model Event, :hoedown, :name => "Ruby Hoedown" + # + # The first form will create a new model with no symbolic name + # (essentially). + # + # The second form is exactly like the first, except for that it provides a + # symbolic name as the second parameter. The symbolic name will allow you + # to access the record through a couple of helper methods: + # + # events(:hoedown) # The hoedown event + # event_id(:hoedown) # The ID of the hoedown event + # + # These helper methods are only accessible for a particular table after + # you have inserted a record into that table using create_model. + def create_model(class_identifier, *args) + insert(ScenarioModel, class_identifier, *args) do |record| + model = record.to_model + model.save! + model + end + end + + private + def insert(record_or_model, class_identifier, *args, &insertion) + symbolic_name, attributes = extract_creation_arguments(args) + record_meta = (record_metas[class_identifier] ||= RecordMeta.new(class_identifier)) + record = record_or_model.new(record_meta, attributes, symbolic_name) + return_value = nil + ActiveRecord::Base.silence do + prepare_table(record_meta.table_name) + return_value = insertion.call record + table_config.update_table_readers(record) + self.extend table_config.table_readers + end + return_value + end + + def extract_creation_arguments(arguments) + if arguments.size == 2 && arguments.last.kind_of?(Hash) + arguments + elsif arguments.size == 1 && arguments.last.kind_of?(Hash) + [nil, arguments[0]] + else + [nil, Hash.new] + end + end + + class RecordMeta # :nodoc: + attr_reader :class_name, :record_class, :table_name + + def initialize(class_identifier) + @class_identifier = class_identifier + @class_name = resolve_class_name(class_identifier) + @record_class = class_name.constantize + @table_name = record_class.table_name + end + + def timestamp_columns + @timestamp_columns ||= begin + timestamps = %w(created_at created_on updated_at updated_on) + columns.select do |column| + timestamps.include?(column.name) + end + end + end + + def columns + @columns ||= connection.columns(table_name) + end + + def connection + record_class.connection + end + + def id_reader + @id_reader ||= begin + reader = ActiveRecord::Base.pluralize_table_names ? table_name.singularize : table_name + "#{reader}_id".to_sym + end + end + + def record_reader + table_name.to_sym + end + + def resolve_class_name(class_identifier) + case class_identifier + when Symbol + class_identifier.to_s.singularize.camelize + when Class + class_identifier.name + when String + class_identifier + end + end + + def to_s + "#" + end + end + + class ScenarioModel # :nodoc: + attr_reader :attributes, :model, :record_meta, :symbolic_name + delegate :id, :to => :model + + def initialize(record_meta, attributes, symbolic_name = nil) + @record_meta = record_meta + @attributes = attributes.stringify_keys + @symbolic_name = symbolic_name || object_id + end + + def to_hash + to_model.attributes + end + + def to_model + @model ||= record_meta.record_class.new(attributes) + end + end + + class ScenarioRecord # :nodoc: + attr_reader :record_meta, :symbolic_name + + def initialize(record_meta, attributes, symbolic_name = nil) + @record_meta = record_meta + @attributes = attributes.stringify_keys + @symbolic_name = symbolic_name || object_id + + install_default_attributes! + end + + def id + @attributes['id'] + end + + def to_hash + @attributes + end + + def to_fixture + Fixture.new(to_hash, record_meta.class_name) + end + + def install_default_attributes! + @attributes['id'] ||= symbolic_name.to_s.hash.abs + install_timestamps! + end + + def install_timestamps! + record_meta.timestamp_columns.each do |column| + @attributes[column.name] = now(column) unless @attributes.key?(column.name) + end + end + + def now(column) + now = ActiveRecord::Base.default_timezone == :utc ? column.klass.now.utc : column.klass.now + now.to_s(:db) + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb new file mode 100644 index 00000000..1d3c3318 --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb @@ -0,0 +1,9 @@ +class ComplexCompositeScenario < Scenario::Base + uses :composite, :places + + helpers do + def method_from_complex_composite_scenario + :method_from_complex_composite_scenario + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb new file mode 100644 index 00000000..09903b28 --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb @@ -0,0 +1,9 @@ +class CompositeScenario < Scenario::Base + uses :people, :things + + helpers do + def method_from_composite_scenario + :method_from_composite_scenario + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb new file mode 100644 index 00000000..0ea2ba64 --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb @@ -0,0 +1,4 @@ +class EmptyScenario < Scenario::Base + def load + end +end diff --git a/vendor/plugins/scenarios/spec/scenarios/people_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/people_scenario.rb new file mode 100644 index 00000000..792e0f4a --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios/people_scenario.rb @@ -0,0 +1,26 @@ +class PeopleScenario < Scenario::Base + + def load + create_person "John Long" + create_person "Adam Williams" + end + + helpers do + def create_person(attributes = {}) + if attributes.kind_of?(String) + first, last = attributes.split(/\s+/) + attributes = { :first_name => first, :last_name => last } + end + attributes = person_params(attributes) + create_record(:person, attributes[:first_name].strip.gsub(' ', '_').underscore.to_sym, attributes) + end + + def person_params(attributes = {}) + attributes = { + :first_name => "John", + :last_name => "Q." + }.update(attributes) + end + end + +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/scenarios/places_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/places_scenario.rb new file mode 100644 index 00000000..50d7ddf9 --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios/places_scenario.rb @@ -0,0 +1,22 @@ +class PlacesScenario < Scenario::Base + + def load + create_place "Taj Mahal", "India" + create_place "Whitehouse", "Washington DC" + end + + helpers do + def create_place(name, location) + attributes = place_params(:name => name, :location => location) + create_record(:place, name.strip.gsub(' ', '_').underscore.to_sym, attributes) + end + + def place_params(attributes = {}) + attributes = { + :name => "Noplace", + :location => "Nowhere" + }.update(attributes) + end + end + +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/scenarios/things_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/things_scenario.rb new file mode 100644 index 00000000..d37abd39 --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios/things_scenario.rb @@ -0,0 +1,22 @@ +class ThingsScenario < Scenario::Base + + def load + create_thing "one" + create_thing "two" + end + + helpers do + def create_thing(attributes = {}) + attributes = { :name => attributes } if attributes.kind_of?(String) + attributes = thing_params(attributes) + create_record(:thing, attributes[:name].strip.gsub(' ', '_').underscore.to_sym, attributes) + end + + def thing_params(attributes = {}) + attributes = { + :name => "Unnamed Thing", + :description => "I'm not sure what this is." + }.update(attributes) + end + end +end diff --git a/vendor/plugins/scenarios/spec/scenarios_spec.rb b/vendor/plugins/scenarios/spec/scenarios_spec.rb new file mode 100644 index 00000000..c74fd5e0 --- /dev/null +++ b/vendor/plugins/scenarios/spec/scenarios_spec.rb @@ -0,0 +1,185 @@ +require File.expand_path(File.dirname(__FILE__) + "/spec_helper") + +class ExplodesOnSecondInstantiationScenario < Scenario::Base + cattr_accessor :instance + def initialize(*args) + raise "Should only be created once" if self.class.instance + self.class.instance = super(*args) + end +end + +describe "Scenario loading" do + scenario :explodes_on_second_instantiation + + it "should work" do + end + + it 'should work again' do + end +end + +describe "Scenario loading" do + it "should load from configured directories" do + Scenario.load(:empty) + EmptyScenario + end + + it "should raise Scenario::NameError when the scenario does not exist" do + lambda { Scenario.load(:whatcha_talkin_bout) }.should raise_error(Scenario::NameError) + end + + it "should allow us to add helper methods through the helpers class method" do + klass = :empty.to_scenario + klass.helpers do + def hello + "Hello World" + end + end + klass.new.methods.should include('hello') + end + + it "should provide a built-in scenario named :blank which clears all tables found in schema.rb" do + Scenario.load(:blank) + BlankScenario + end +end + +describe Scenarios::TableMethods do + scenario :things + + it "should understand namespaced models" do + create_record "ModelModule::Model", :raking, :name => "Raking", :description => "Moving leaves around" + models(:raking).should_not be_nil + end + + it "should include record creation methods" do + create_record(:thing, :three, :name => "Three") + things(:three).name.should == "Three" + end + + it "should include other example helper methods" do + create_thing("The Thing") + things(:the_thing).name.should == "The Thing" + end + + describe "for retrieving objects" do + it "should have a pluralized name" do + should respond_to("things") + should_not respond_to("thing") + end + + it "should answer a single object given a single name" do + things(:one).should be_kind_of(Thing) + things("one").should be_kind_of(Thing) + things(:two).name.should == "two" + end + + it "should answer an array of objects given multiple names" do + things(:one, :two).should be_kind_of(Array) + things(:one, :two).should eql([things(:one), things(:two)]) + end + + it "should just return the argument if an AR instance is given" do + thing = things(:one) + things(thing).should eql(thing) + end + end + + describe "for retrieving ids" do + it "should have a singular name" do + should respond_to("thing_id") + should_not respond_to("thing_ids") + should_not respond_to("things_id") + end + + it "should answer a single id given a single name" do + thing_id(:one).should be_kind_of(Fixnum) + thing_id("one").should be_kind_of(Fixnum) + end + + it "should answer an array of ids given multiple names" do + thing_id(:one, :two).should be_kind_of(Array) + thing_id(:one, :two).should eql([thing_id(:one), thing_id(:two)]) + thing_id("one", "two").should eql([thing_id(:one), thing_id(:two)]) + end + + it "should answer the id of the argument if an AR instance id given" do + thing = things(:one) + thing_id(thing).should == thing.id + end + end +end + +describe "it uses people and things scenarios", :shared => true do + it "should have reader helper methods for each used scenario" do + should respond_to(:things) + should respond_to(:people) + end + + it "should allow us to use helper methods from each scenario inside an example" do + should respond_to(:create_thing) + should respond_to(:create_person) + end +end + +describe "A composite scenario" do + scenario :composite + + it_should_behave_like "it uses people and things scenarios" + + it "should allow us to use helper methods scenario" do + should respond_to(:method_from_composite_scenario) + end +end + +describe "Multiple scenarios" do + scenario :things, :people + + it_should_behave_like "it uses people and things scenarios" +end + +describe "A complex composite scenario" do + scenario :complex_composite + + it_should_behave_like "it uses people and things scenarios" + + it "should have correct reader helper methods" do + should respond_to(:places) + end + + it "should allow us to use correct helper methods" do + should respond_to(:create_place) + end +end + +describe "Overlapping scenarios" do + scenario :composite, :things, :people + + it "should not cause scenarios to be loaded twice" do + Person.find_all_by_first_name("John").size.should == 1 + end +end + +describe "create_record table method" do + scenario :empty + + it "should automatically set timestamps" do + create_record :note, :first, :content => "first note" + note = notes(:first) + note.created_at.should be_instance_of(Time) + end +end + +describe "create_model table method" do + scenario :empty + + it "should support symbolic names" do + thing = create_model Thing, :mything, :name => "My Thing", :description => "For testing" + things(:mything).should == thing + end + + it "should blast any table touched as a side effect of creating a model (callbacks, observers, etc.)" do + create_model SideEffectyThing + blasted_tables.should include(Thing.table_name) + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/spec.opts b/vendor/plugins/scenarios/spec/spec.opts new file mode 100644 index 00000000..2144705e --- /dev/null +++ b/vendor/plugins/scenarios/spec/spec.opts @@ -0,0 +1,7 @@ +--colour +--format +progress +--loadby +mtime +--reverse +--backtrace \ No newline at end of file diff --git a/vendor/plugins/scenarios/spec/spec_helper.rb b/vendor/plugins/scenarios/spec/spec_helper.rb new file mode 100644 index 00000000..b641894f --- /dev/null +++ b/vendor/plugins/scenarios/spec/spec_helper.rb @@ -0,0 +1,24 @@ +require File.expand_path(File.dirname(__FILE__) + '/../testing/plugit_descriptor') + +TESTING_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../testing") +TESTING_TMP = "#{TESTING_ROOT}/tmp" + +require 'fileutils' +FileUtils.mkdir_p(TESTING_TMP) +FileUtils.touch("#{TESTING_TMP}/test.log") + +require 'logger' +RAILS_DEFAULT_LOGGER = Logger.new("#{TESTING_TMP}/test.log") +RAILS_DEFAULT_LOGGER.level = Logger::DEBUG + +ActiveRecord::Base.silence do + ActiveRecord::Base.configurations = {'sqlite3' => { + 'adapter' => 'sqlite3', + 'database' => "#{TESTING_TMP}/sqlite3.db" + }} + ActiveRecord::Base.establish_connection 'sqlite3' + load "#{TESTING_ROOT}/schema.rb" +end + +require "models" +require "scenarios" \ No newline at end of file diff --git a/vendor/plugins/scenarios/tasks/scenario.rake b/vendor/plugins/scenarios/tasks/scenario.rake new file mode 100644 index 00000000..37b56ebb --- /dev/null +++ b/vendor/plugins/scenarios/tasks/scenario.rake @@ -0,0 +1,19 @@ +namespace :db do + namespace :scenario do + desc "Load a scenario into the current environment's database using SCENARIO=scenario_name" + task :load => 'db:reset' do + scenario_name = ENV['SCENARIO'] || 'default' + begin + klass = Scenarios.load(scenario_name) + puts "Loaded #{klass.name.underscore.gsub('_', ' ')}." + rescue Scenarios::NameError => e + if scenario_name == 'default' + puts "Error! Set the SCENARIO environment variable or define a DefaultScenario class." + else + puts "Error! Invalid scenario name [#{scenario_name}]." + end + exit(1) + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/testing/application.rb b/vendor/plugins/scenarios/testing/application.rb new file mode 100644 index 00000000..87f91f64 --- /dev/null +++ b/vendor/plugins/scenarios/testing/application.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/testing/models.rb b/vendor/plugins/scenarios/testing/models.rb new file mode 100644 index 00000000..34801db4 --- /dev/null +++ b/vendor/plugins/scenarios/testing/models.rb @@ -0,0 +1,14 @@ +class Person < ActiveRecord::Base; end +class Place < ActiveRecord::Base; end +class Thing < ActiveRecord::Base; end +class Note < ActiveRecord::Base; end + +class SideEffectyThing < ActiveRecord::Base + after_create do + Thing.create! + end +end + +module ModelModule + class Model < ActiveRecord::Base; end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/testing/plugit_descriptor.rb b/vendor/plugins/scenarios/testing/plugit_descriptor.rb new file mode 100644 index 00000000..17b19e67 --- /dev/null +++ b/vendor/plugins/scenarios/testing/plugit_descriptor.rb @@ -0,0 +1,44 @@ +require 'rubygems' +gem 'plugit' +require 'plugit' + +$LOAD_PATH << File.expand_path("#{File.dirname(__FILE__)}/../lib") +$LOAD_PATH << File.expand_path(File.dirname(__FILE__)) +RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/..") + +Plugit.describe do |scenarios| + scenarios.environments_root_path = File.dirname(__FILE__) + '/environments' + vendor_directory = File.expand_path(File.dirname(__FILE__) + '/../vendor/plugins') + + scenarios.environment :default, 'Released versions of Rails and RSpec' do |env| + env.library :rails, :export => "git clone git://github.com/rails/rails.git" do |rails| + rails.after_update { `git co v2.1.0_RC1` } + rails.load_paths = %w{/activesupport/lib /activerecord/lib /actionpack/lib} + rails.requires = %w{active_support active_record action_controller action_view} + end + env.library :rspec, :export => "git clone git://github.com/dchelimsky/rspec.git" do |rspec| + rspec.after_update { `git co 1.1.4 && mkdir -p #{vendor_directory} && ln -sF #{File.expand_path('.')} #{vendor_directory + '/rspec'}` } + rspec.requires = %w{spec} + end + env.library :rspec_rails, :export => "git clone git://github.com/dchelimsky/rspec-rails.git" do |rspec_rails| + rspec_rails.after_update { `git co 1.1.4` } + rspec_rails.requires = %w{spec/rails} + end + end + + scenarios.environment :edge, 'Edge versions of Rails and RSpec' do |env| + env.library :rails, :export => "git clone git://github.com/rails/rails.git --depth 1" do |rails| + rails.before_install { `git pull` } + rails.load_paths = %w{/activesupport/lib /activerecord/lib /actionpack/lib} + rails.requires = %w{active_support active_record action_controller action_view} + end + env.library :rspec, :export => "git clone git://github.com/dchelimsky/rspec.git --depth 1" do |rspec| + rspec.after_update { `git pull && mkdir -p #{vendor_directory} && ln -sF #{File.expand_path('.')} #{vendor_directory + '/rspec'}` } + rspec.requires = %w{spec} + end + env.library :rspec_rails, :export => "git clone git://github.com/dchelimsky/rspec-rails.git --depth 1" do |rspec_rails| + rspec_rails.after_update { `git pull` } + rspec_rails.requires = %w{spec/rails} + end + end +end \ No newline at end of file diff --git a/vendor/plugins/scenarios/testing/schema.rb b/vendor/plugins/scenarios/testing/schema.rb new file mode 100644 index 00000000..30a1326c --- /dev/null +++ b/vendor/plugins/scenarios/testing/schema.rb @@ -0,0 +1,31 @@ +ActiveRecord::Schema.define do + create_table :people, :force => true do |t| + t.column :first_name, :string + t.column :last_name, :string + end + + create_table :places, :force => true do |t| + t.column :name, :string + t.column :location, :string + end + + create_table :things, :force => true do |t| + t.column :name, :string + t.column :description, :string + end + + create_table :side_effecty_things, :force => true do |t| + end + + create_table :models, :force => true do |t| + t.column :name, :string + t.column :description, :string + end + + create_table :notes, :force => true do |t| + t.column :content, :string + t.column :created_at, :datetime + t.column :updated_at, :datetime + end +end + \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/.gitignore b/vendor/plugins/skinny_spec/.gitignore new file mode 100644 index 00000000..28f7b7da --- /dev/null +++ b/vendor/plugins/skinny_spec/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +doc \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/README.rdoc b/vendor/plugins/skinny_spec/README.rdoc new file mode 100644 index 00000000..49cfbf60 --- /dev/null +++ b/vendor/plugins/skinny_spec/README.rdoc @@ -0,0 +1,267 @@ += Skinny Spec + +Skinny Spec is a collection of spec helper methods designed to help trim the fat and DRY up +some of the bloat that sometimes results from properly specing your classes and templates. + +== Requirements and Recommendations + +Obviously you'll need to be using RSpec[http://github.com/dchelimsky/rspec/tree/master] and +Rspec-Rails[http://github.com/dchelimsky/rspec-rails/tree/master] as your testing framework. + +Skinny Spec was originally designed [and best enjoyed] if you're using +Haml[http://github.com/nex3/haml/tree/master] and +make_resourceful[http://github.com/rsl/make_resourceful/tree/master] but will default to +ERb and a facsimile of Rails' default scaffolding [for the views and controllers, respectively] +if Haml and/or make_resourceful are not available. I recommend using them though. :) + +In addition, Skinny Spec uses Ruby2Ruby to make nicer expectation messages and you'll want to +have that installed as well. It's not a dependency or anything but it is highly +recommended. + +== Setup + +Once you've installed the plugin in your app's vendor/plugins folder, you're ready to rock! +Skinny Spec includes itself into the proper RSpec classes so there's no configuration on your +part. Sweet! + +== Usage + +The simplest way to use Skinny Specs is to generate a resource scaffold: + + script/generate skinny_scaffold User + +This command takes the usual complement of attribute definitions like +script/generate scaffold. Then have a look at the generated files (particularly the +specs) to see what's new and different with Skinny Spec. + +=== Controller Specs + +Let's look at the controller specs. + + describe UsersController do + def valid_attributes(args = {}) + { + # Add valid attributes for the your params[:user] here! + }.merge(args) + end + + describe "GET :index" do + before(:each) do + @users = stub_index(User) + end + + it_should_find_and_assign :users + it_should_render :template, "index" + end + + # ... + + describe "POST :create" do + describe "when successful" do + before(:each) do + @user = stub_create(User) + end + + it_should_initialize_and_save :user + it_should_redirect_to { user_url(@user) } + end + + # ... + +First thing you should see is a method definition for +valid_attributes. This will be used later by the create and update +specs to more accurately represent how the controller works in actual practice by supplying +somewhat real data for the params coming from the HTML forms. + +Next we find an example group for GET :index. That stub_index method there +does a lot of work behind the curtain. I'll leave it up to you to check the documentation for it +(and its brothers and sister methods like stub_new) but I will point out that the +methods named stub_controller_method should only be used for stubbing and +mocking the main object of the method. To create mocks for other ancillary objects, please +use stub_find_all, stub_find_one, and stub_initialize. The reason +for this is because the former methods actually save us a step by defining an implicit +controller method request. If you add a new method to your resource routing, you'll want to +use the helper method define_request in those example groups to define an explicit +request. You can also define a method called shared_request to "share a +define_request" across shared describe blocks, like so: + + describe "POST :create" do + def shared_request + post :create + end + + describe "when successful" do + # ... + end + + describe "when unsuccessful" do + # ... + end + end + +Note: When you're adding longer, more complicated controller specs you can still leverage +implicit and shared requests by calling do_request in your spec as in the following +example: + + # Let's assume this controller is _not_ CategoriesController + # and that loading the categories isn't part of the default actions + describe "GET :index" do + before(:each) do + @categories = stub_find_all(Category) + end + + it "should preload categories" do + Category.should_receive(:find).with(:all) + do_request + end + + it "should assign @categories" do + do_request + assigns[:categories].should == @categories + end + end + +Finally we get to the meat of the spec and of Skinny Specs itself: the actual expectations. +The first thing you'll notice is the use of example group (read: "describe" block) level methods +instead of the usual example (read: "it") blocks. Using this helper at the example group level +saves us three lines over using an example block. (If this isn't significant to you, this is +probably the wrong plugin for you as well. Sorry.) Note that none of these methods use the +instance variables defined in the "before" block because they are all nil at the example block +level. Let's look at a sample method to see how it works: + + it_should_find_and_assign :users + +This actually wraps two different expectations: one that User.should_receive(:find).with(:all) +and another that the instance variable @users is assigned with the return value from that finder call. +If you need to add more detailed arguments to the find, you can easily break this into two different +expectations like: + + it_should_find :users, :limit => 2 + it_should_assign :users + +See the documentation for the it_should_find for more information. You might have guessed that +it_should_initialize_assign and it_should_render_template work in a similar +fashion and you'd be right. Again, see the documentation for these individual methods for more +information. Lots of information in those docs. + +A useful helper method that doesn't appear in any of the scaffolding is with_default_restful_actions +which takes a block and evaluates it for each of the RESTful controller actions. Very useful for +spec'ing that these methods redirect to the login page when the user isn't logged in, for example. This +method is designed to be used inside an example like so: + + describe "when not logged in" do + it "should redirect all requests to the login page" do + with_default_restful_actions do + response.should redirect_to(login_url) + end + end + end + +Before we're through with the controller specs, let me point out one more important detail. In +order to use it_should_redirect_to we have to send the routing inside a block argument +there so it can be evaluated in the example context instead of the example group, where it +completely blows up. This methodology is used anywhere routing is referred to in a "skinny", +example group level spec. + +=== View Specs + +Now let's move to the view specs! + + describe "/users/form.html.haml" do + before(:each) do + @user = mock_and_assign(User, :stub => { + :name => "foo", + :birthday => 1.week.ago, + :adult => false + }) + end + + it_should_have_form_for :user + + it_should_allow_editing :user, :name + it_should_allow_editing :user, :birthday + it_should_allow_editing :user, :adult + + it_should_link_to_show :user + it_should_link_to { users_path } + end + +Like the special stub_index methods in the controller +specs, the view specs have a shorthand mock and stub helpers: mock_and_assign and +mock_and_assign_collection. These are well documented so please check them out. + +There are also some really nice helper methods that I'd like point out. First is +it_should_have_form_for. This is a really good convenience wrapper that basically wraps +the much longer: + + it "should use form_for to generate the proper form action and options" do + template.should_receive(:form_for).with(@user) + do_render + end + +Next up is the it_should_allow_editing helper. I love this method the most because it +really helps DRY up that view spec while at the same time being amazingly unbrittle. Instead of +creating an expectation for a specific form element, this method creates a generalized expectation +that there's a form element with the name attribute set in such away that it will +generate the proper params to use in the controller to edit or create the instance. +Check out the docs and the source for more information on this. Also check out +it_should_have_form_element_for which is roughly equivalent for those times when you use +form_tag instead. + +Finally let's look at those it_should_link_to_controller_method helpers. +These methods (and there's one each for the controller methods +new, edit, show, and delete) point to instance variables +which you should be created in the "before" blocks with mock_and_assign. The other is +it_should_allow_editing which is likewise covered extensively in the documentation and +I will just point out here that, like it_should_link_to_edit and such, it takes a +symbol for the name of the instance variable it refers to and additionally takes +a symbol for the name of the attribute to be edited. + +Also note that, when constructing a long form example, instead of defining an instance variable +for the name of the template and calling render @that_template you can simply call +do_render which takes the name of the template from the outermost example group where +it is customarily stated. + +=== Model Specs + +Skinny Spec adds a matcher for the various ActiveRecord associations. On the example group level +you call them like: + + it_should_belong_to :manager + it_should_have_many :clients + +Within an example you can call them on either the class or the instance setup in the +"before" block. These are equivalent: + + @user.should belong_to(:group) + User.should belong_to(:group) + +I've also added some very basic validation helpers like it_should_validate_presence_of, +it_should_validate_uniqueness_of, it_should_not_mass_assign. Please consult +the documentation for more information. + +== Miscellaneous Notes + +In the scaffolding, I have used my own idiomatic Rails usage: + +* All controller actions which use HTML forms [new, edit, etc] use a shared + form and leverage form_for to its fullest by letting it create the appropriate + action and options. +* Some instances where you might expect link_to are button_to. This is to provide a common + interface element which can be styled the same instead of a mishmash of links and buttons and + inputs everywhere. To take full advantage of this, I usually override many of Rails' default + helpers with custom ones that all use actual HTML BUTTON elements which are much + easier to style than "button" typed INPUT. I've provided a text file in the + "additional" folder of this plugin which you can use in your ApplicationHelper. (I also + provide an optional override helper for the label method which uses + #titleize instead of humanize for stylistic reasons). +* Probably more that I can't think of. + +== Credits and Thanks + +Sections of this code were taken from or inspired by Rick Olsen's +rspec_on_rails_on_crack[http://github.com/technoweenie/rspec_on_rails_on_crack/tree/master]. +Also thanks and props to Hampton Catlin and Nathan Weizenbaum for the lovely and imminently useable +Haml and make_resourceful. Also also praises and glory to David Chelimsky and the Rspec crew. + +Also thanks to Don Petersen for his suggestions and fixes. \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/Rakefile b/vendor/plugins/skinny_spec/Rakefile new file mode 100644 index 00000000..b0adbc43 --- /dev/null +++ b/vendor/plugins/skinny_spec/Rakefile @@ -0,0 +1,11 @@ +require 'rake' +require 'rake/rdoctask' + +desc 'Generate documentation for the Skinny Spec plugin' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = 'Skinny Spec' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/additional/helper_overrides.txt b/vendor/plugins/skinny_spec/additional/helper_overrides.txt new file mode 100644 index 00000000..a15ec2d3 --- /dev/null +++ b/vendor/plugins/skinny_spec/additional/helper_overrides.txt @@ -0,0 +1,58 @@ +# Please insert these into your ApplicationHelper + +# Replacement for Rails' default submit_tag helper +# using HTML button element rather than HTML input element +def submit_tag(text, options = {}) + content_tag :button, text, options.merge(:type => :submit) +end + +# Replacement for Rails' default button_to helper +# using HTML button element rather than HTML input element +def button_to(name, options = {}, html_options = {}) + html_options = html_options.stringify_keys + convert_boolean_attributes!(html_options, %w( disabled )) + + method_tag = '' + if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s) + method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s) + end + + form_method = method.to_s == 'get' ? 'get' : 'post' + + request_token_tag = '' + if form_method == 'post' && protect_against_forgery? + request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) + end + + if confirm = html_options.delete("confirm") + html_options["onclick"] = "return #{confirm_javascript_function(confirm)};" + end + + url = options.is_a?(String) ? options : self.url_for(options) + name ||= url + + html_options.merge!("type" => "submit", "value" => name) + + "
" + + method_tag + content_tag("button", name, html_options) + request_token_tag + "
" +end + +# Replacement for Rails' default button_to_function helper +# using HTML button element rather than HTML input element +def button_to_function(name, *args, &block) + html_options = args.extract_options! + function = args[0] || '' + + html_options.symbolize_keys! + function = update_page(&block) if block_given? + content_tag(:button, name, html_options.merge({ + :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" + })) +end + +# Replacement for Rails' default label helper +# using String#titleize rather than String#humanize +def label(object_name, method, text = nil, options = {}) + text ||= method.to_s[].titleize + super +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/skinny_scaffold_generator.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/skinny_scaffold_generator.rb new file mode 100644 index 00000000..7f6f2b25 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/skinny_scaffold_generator.rb @@ -0,0 +1,93 @@ +class SkinnyScaffoldGenerator < Rails::Generator::NamedBase + attr_reader :controller_class_path, :controller_file_path, :controller_class_nesting, + :controller_class_nesting_depth, :controller_class_name, :controller_underscore_name, + :controller_plural_name, :template_language + alias_method :controller_file_name, :controller_underscore_name + alias_method :controller_singular_name, :controller_file_name + alias_method :controller_table_name, :controller_plural_name + + def initialize(runtime_args, runtime_options = {}) + super + + base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@name.pluralize) + @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name) + + if @controller_class_nesting.empty? + @controller_class_name = @controller_class_name_without_nesting + else + @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}" + end + end + + def manifest + record do |m| + # Check for class naming collisions + m.class_collisions controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper" + m.class_collisions class_path, "#{class_name}" + + # # Controller, helper, and views directories + m.directory File.join('app', 'views', controller_class_path, controller_file_name) + m.directory File.join('spec', 'views', controller_class_path, controller_file_name) + m.directory File.join('app', 'helpers', controller_class_path) + m.directory File.join('spec', 'helpers', controller_class_path) + m.directory File.join('app', 'controllers', controller_class_path) + m.directory File.join('spec', 'controllers', controller_class_path) + m.directory File.join('app', 'models', class_path) + m.directory File.join('spec', 'models', class_path) + + # Views + @template_language = defined?(Haml) ? "haml" : "erb" + %w{index show form}.each do |action| + m.template "#{action}.html.#{template_language}", + File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.#{template_language}") + m.template "#{action}.html_spec.rb", + File.join('spec/views', controller_class_path, controller_file_name, "#{action}.html.#{template_language}_spec.rb") + end + m.template "index_partial.html.#{template_language}", + File.join('app/views', controller_class_path, controller_file_name, "_#{file_name}.html.#{template_language}") + m.template 'index_partial.html_spec.rb', + File.join('spec/views', controller_class_path, controller_file_name, "_#{file_name}.html.#{template_language}_spec.rb") + + # Helper + m.template 'helper.rb', + File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb") + m.template 'helper_spec.rb', + File.join('spec/helpers', controller_class_path, "#{controller_file_name}_helper_spec.rb") + + # Controller + m.template 'controller.rb', + File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") + m.template 'controller_spec.rb', + File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb") + + # Model + m.template 'model.rb', + File.join('app/models', class_path, "#{file_name}.rb") + m.template 'model_spec.rb', + File.join('spec/models', class_path, "#{file_name}_spec.rb") + + # Routing + m.route_resources controller_file_name + + unless options[:skip_migration] + m.migration_template( + 'migration.rb', 'db/migrate', + :assigns => { + :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}", + :attributes => attributes + }, + :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}" + ) + end + end + end + +protected + def banner + "Usage: #{$0} skinny_scaffold ModelName [field:type, field:type]" + end + + def model_name + class_name.demodulize + end +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller.rb new file mode 100644 index 00000000..ea9b617d --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller.rb @@ -0,0 +1,105 @@ +class <%= controller_class_name %>Controller < ApplicationController + <%- if defined?(Resourceful::Maker) -%> + make_resourceful do + actions :all + + # Let's get the most use from form_for and share a single form here! + response_for :new, :edit do + render :template => "<%= plural_name %>/form" + end + + response_for :create_fails, :update_fails do + flash[:error] = "There was a problem!" + render :template => "<%= plural_name %>/form" + end + end + <%- else -%> + # GET /<%= table_name %> + # GET /<%= table_name %>.xml + def index + @<%= table_name %> = <%= class_name %>.find(:all) + + respond_to do |format| + format.html # index.html.erb + format.xml { render :xml => @<%= table_name %> } + end + end + + # GET /<%= table_name %>/1 + # GET /<%= table_name %>/1.xml + def show + @<%= file_name %> = <%= class_name %>.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.xml { render :xml => @<%= file_name %> } + end + end + + # GET /<%= table_name %>/new + # GET /<%= table_name %>/new.xml + def new + @<%= file_name %> = <%= class_name %>.new + + respond_to do |format| + format.html { render :template => "<%= plural_name %>/form" } + format.xml { render :xml => @<%= file_name %> } + end + end + + # GET /<%= table_name %>/1/edit + def edit + @<%= file_name %> = <%= class_name %>.find(params[:id]) + render :template => "<%= plural_name %>/form" + end + + # POST /<%= table_name %> + # POST /<%= table_name %>.xml + def create + @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>]) + + respond_to do |format| + if @<%= file_name %>.save + flash[:notice] = '<%= class_name %> was successfully created.' + format.html { redirect_to(@<%= file_name %>) } + format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> } + else + flash.now[:error] = '<%= class_name %> could not be created.' + format.html { render :template => "<%= plural_name %>/form" } + format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity } + end + end + end + + # PUT /<%= table_name %>/1 + # PUT /<%= table_name %>/1.xml + def update + @<%= file_name %> = <%= class_name %>.find(params[:id]) + + respond_to do |format| + if @<%= file_name %>.update_attributes(params[:<%= file_name %>]) + flash[:notice] = '<%= class_name %> was successfully updated.' + format.html { redirect_to(@<%= file_name %>) } + format.xml { head :ok } + else + flash.now[:error] = '<%= class_name %> could not be created.' + format.html { render :template => "<%= plural_name %>/form" } + format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity } + end + end + end + + # DELETE /<%= table_name %>/1 + # DELETE /<%= table_name %>/1.xml + def destroy + @<%= file_name %> = <%= class_name %>.find(params[:id]) + @<%= file_name %>.destroy + + respond_to do |format| + flash[:notice] = '<%= class_name %> was successfully deleted.' + format.html { redirect_to(<%= table_name %>_url) } + format.xml { head :ok } + end + end + <%- end -%> +end diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller_spec.rb new file mode 100644 index 00000000..dc6d5599 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller_spec.rb @@ -0,0 +1,116 @@ +require File.dirname(__FILE__) + '/../spec_helper' + +describe <%= controller_class_name %>Controller do + def valid_attributes(args = {}) + { +<% if attributes.empty? -%> + # Add valid attributes for the your params[:<%= singular_name %>] here! +<% else -%> + <%- attributes.each_with_index do |attribute, index| -%> + <%- case attribute.type -%> + <%- when :string, :text -%> + "<%= attribute.name %>" => "foo"<%= index < attributes.size - 1 ? "," : "" %> + <%- when :integer, :float, :decimal -%> + "<%= attribute.name %>" => 815<%= index < attributes.size - 1 ? "," : "" %> + <%- when :boolean -%> + "<%= attribute.name %>" => false<%= index < attributes.size - 1 ? "," : "" %> + <%- when :date, :datetime, :time, :timestamp -%> + "<%= attribute.name %>" => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %> + <%- else -%> + "<%= attribute.name %>" => nil<%= index < attributes.size - 1 ? "," : "" %> # Could not determine valid attribute + <%- end -%> + <%- end -%> +<% end -%> + }.merge(args) + end + + describe "GET :index" do + before(:each) do + @<%= plural_name %> = stub_index(<%= class_name %>) + end + + it_should_find_and_assign :<%= plural_name %> + it_should_render_template "index" + end + + describe "GET :new" do + before(:each) do + @<%= singular_name %> = stub_new(<%= class_name %>) + end + + it_should_initialize_and_assign :<%= singular_name %> + it_should_render_template "form" + end + + describe "POST :create" do + describe "when successful" do + before(:each) do + @<%= singular_name %> = stub_create(<%= class_name %>) + end + + it_should_initialize_and_save :<%= singular_name %> + it_should_set_flash :notice + it_should_redirect_to { <%= singular_name %>_url(@<%= singular_name %>) } + end + + describe "when unsuccessful" do + before(:each) do + @<%= singular_name %> = stub_create(<%= class_name %>, :return => :false) + end + + it_should_initialize_and_assign :<%= singular_name %> + it_should_set_flash :error + it_should_render_template "form" + end + end + + describe "GET :show" do + before(:each) do + @<%= singular_name %> = stub_show(<%= class_name %>) + end + + it_should_find_and_assign :<%= singular_name %> + it_should_render_template "show" + end + + describe "GET :edit" do + before(:each) do + @<%= singular_name %> = stub_edit(<%= class_name %>) + end + + it_should_find_and_assign :<%= singular_name %> + it_should_render_template "form" + end + + describe "PUT :update" do + describe "when successful" do + before(:each) do + @<%= singular_name %> = stub_update(<%= class_name %>) + end + + it_should_find_and_update :<%= singular_name %> + it_should_set_flash :notice + it_should_redirect_to { <%= singular_name %>_url(@<%= singular_name %>) } + end + + describe "when unsuccessful" do + before(:each) do + @<%= singular_name %> = stub_update(<%= class_name %>, :return => :false) + end + + it_should_find_and_assign :<%= singular_name %> + it_should_set_flash :error + it_should_render_template "form" + end + end + + describe "DELETE :destroy" do + before(:each) do + @<%= singular_name %> = stub_destroy(<%= class_name %>) + end + + it_should_find_and_destroy :<%= singular_name %> + it_should_set_flash :notice + it_should_redirect_to { <%= plural_name %>_url } + end +end diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.erb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.erb new file mode 100644 index 00000000..ac30dc1b --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.erb @@ -0,0 +1,25 @@ +

<%= singular_name %>.new_record? ? "New" : "Edit" %> <%= model_name %>

+<%% form_for(@<%= singular_name %>) do |f| %> +
+ <%%= f.error_messages %> +
+ <%- if attributes.blank? -%> +

Add your form elements here, please!

+ <%- else -%> + <%- attributes.each do |attribute| -%> +

+ <%%= f.label :<%= attribute.name %> %>
+ <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> +

+ <%- end -%> + <%- end -%> +
+ <%%= submit_tag "Save" %> + +
+<%% end -%> \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.haml b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.haml new file mode 100644 index 00000000..d97eabb9 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.haml @@ -0,0 +1,18 @@ +%h1== #{@<%= singular_name %>.new_record? ? "New" : "Edit"} #{<%= model_name %>} +- form_for @<%= singular_name %> do |f| + #form_errors= f.error_messages +<% if attributes.blank? -%> + %p Add your form elements here, please! +<% else -%> + <%- attributes.each do |attribute| -%> + %p + = f.label :<%= attribute.name %> + = f.<%= attribute.field_type %> :<%= attribute.name %> + <%- end -%> +<% end -%> + #commands + = submit_tag "Save" +#navigation_commands + - unless @<%= singular_name %>.new_record? + = button_to "Show", <%= singular_name %>_path(@<%= singular_name %>), :method => "get", :title => "Show <%= singular_name %>. Unsaved changes will be lost." + = button_to "Back to List", <%= plural_name %>_path, :class => "cancel", :method => "get", :title => "Return to <%= singular_name %> list without saving changes" \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html_spec.rb new file mode 100644 index 00000000..acdf2d01 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html_spec.rb @@ -0,0 +1,41 @@ +require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper' + +describe "<%= File.join(controller_class_path, controller_singular_name) %>/form.html.<%= template_language %>" do + before(:each) do + @<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => { +<% if attributes.blank? -%> + # Add your stub attributes and return values here like: + # :name => "Foo", :created_at => 1.week.ago, :updated_at => nil +<% else -%> + <%- attributes.each_with_index do |attribute, index| -%> + <%- case attribute.type -%> + <%- when :string, :text -%> + :<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %> + <%- when :integer, :float, :decimal -%> + :<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %> + <%- when :boolean -%> + :<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %> + <%- when :date, :datetime, :time, :timestamp -%> + :<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %> + <%- else -%> + :<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %> # Could not determine valid attribute + <%- end -%> + <%- end -%> +<% end -%> + }) + end + + it_should_have_form_for :<%= singular_name %> +<% if attributes.blank? -%> + # Add specs for editing attributes here, please! Like this: + # + # it_should_allow_editing :<%= singular_name %>, :foo +<% else -%> + <%- attributes.each do |attribute| -%> + it_should_allow_editing :<%= singular_name %>, :<%= attribute.name %> + <%- end -%> +<% end -%> + + it_should_link_to_show :<%= singular_name %> + it_should_link_to { <%= plural_name %>_path } +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/helper.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/helper.rb new file mode 100644 index 00000000..9bd821b1 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/helper.rb @@ -0,0 +1,2 @@ +module <%= controller_class_name %>Helper +end diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/helper_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/helper_spec.rb new file mode 100644 index 00000000..6a34ca2a --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/helper_spec.rb @@ -0,0 +1,5 @@ +require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../spec_helper' + +describe <%= controller_class_name %>Helper do + # Add your specs here or remove this file completely, please! +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html.erb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html.erb new file mode 100644 index 00000000..318f94e3 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html.erb @@ -0,0 +1,31 @@ +

<%= model_name %> List

+ + <%%- if @<%= plural_name %>.empty? -%> + + + + + + <%%- else -%> + + + <%- if attributes.blank? -%> + + <%- else -%> + <%- attributes.each do |attribute| -%> + + <%- end -%> + <%- end -%> + + + + + + + <%%= render :partial => @<%= plural_name %> %> + + <%%- end -%> +
There are no <%= plural_name.humanize.downcase %>
<%= attribute.name.titleize %>
+
+ <%%= button_to "New <%= singular_name.titleize %>", new_<%= singular_name %>_path, :method => "get" %> +
\ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html.haml b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html.haml new file mode 100644 index 00000000..b0c78b28 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html.haml @@ -0,0 +1,23 @@ +%h1 <%= model_name %> List +%table + - if @<%= plural_name %>.empty? + %tbody + %tr.empty + %td== There are no <%= plural_name.humanize.downcase %> + - else + %thead + %tr +<% if attributes.blank? -%> + %th= # Generic display column +<% else -%> + <%- attributes.each do |attribute| -%> + %th <%= attribute.name.titleize %> + <%- end -%> +<% end -%> + %th.show= # 'Show' link column + %th.edit= # 'Edit' link column + %th.delete= # 'Delete' link column + %tbody + = render :partial => @<%= plural_name %> +#commands + = button_to "New <%= singular_name.titleize %>", new_<%= singular_name %>_path, :method => "get" \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html_spec.rb new file mode 100644 index 00000000..1b3e09d0 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index.html_spec.rb @@ -0,0 +1,15 @@ +require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper' + +describe "<%= File.join(controller_class_path, controller_singular_name) %>/index.html.<%= template_language %>" do + before(:each) do + @<%= plural_name %> = mock_and_assign_collection(<%= model_name %>) + template.stub_render :partial => @<%= plural_name %> + end + + it "should render :partial => @<%= plural_name %>" do + template.expect_render :partial => @<%= plural_name %> + do_render + end + + it_should_link_to_new :<%= singular_name %> +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html.erb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html.erb new file mode 100644 index 00000000..ecdca836 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html.erb @@ -0,0 +1,12 @@ +"> +<% if attributes.blank? -%> + <%= model_name %> #<%%= <%= singular_name %>.id %> +<% else -%> + <%- attributes.each do |attribute| -%> + <%%=h <%= singular_name %>.<%= attribute.name %> %> + <%- end %> +<% end -%> + <%%= button_to "Show", <%= singular_name %>, :method => "get" %> + <%%= button_to "Edit", edit_<%= singular_name %>_path(<%= singular_name %>), :method => "get" %> + <%%= button_to "Delete", <%= singular_name %>, :method => "delete" %> + \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html.haml b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html.haml new file mode 100644 index 00000000..08b3b383 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html.haml @@ -0,0 +1,11 @@ +%tr{:class => cycle("odd", "even")} +<% if attributes.blank? -%> + %td== <%= model_name %> #{<%= singular_name %>.id} +<% else -%> + <%- attributes.each do |attribute| -%> + %td=h <%= singular_name %>.<%= attribute.name %> + <%- end -%> +<% end -%> + %td.show= button_to "Show", <%= singular_name %>_path(<%= singular_name %>), :method => "get" + %td.edit= button_to "Edit", edit_<%= singular_name %>_path(<%= singular_name %>), :method => "get" + %td.delete= button_to "Delete", <%= singular_name %>, :method => "delete" \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html_spec.rb new file mode 100644 index 00000000..3f112e5e --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/index_partial.html_spec.rb @@ -0,0 +1,32 @@ +require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper' + +describe "<%= File.join(controller_class_path, controller_singular_name) %>/_<%= singular_name %>.html.<%= template_language %>" do + before(:each) do + @<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => { +<% if attributes.blank? -%> + # Add your stub attributes and return values here like: + # :name => "Foo", :created_at => 1.week.ago, :updated_at => nil +<% else -%> + <%- attributes.each_with_index do |attribute, index| -%> + <%- case attribute.type -%> + <%- when :string, :text -%> + :<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %> + <%- when :integer, :float, :decimal -%> + :<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %> + <%- when :boolean -%> + :<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %> + <%- when :date, :datetime, :time, :timestamp -%> + :<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %> + <%- else -%> + :<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %> + <%- end -%> + <%- end -%> +<% end -%> + }) + template.stub!(:<%= singular_name %>).and_return(@<%= singular_name %>) + end + + it_should_link_to_show :<%= singular_name %> + it_should_link_to_edit :<%= singular_name %> + it_should_link_to_delete :<%= singular_name %> +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/migration.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/migration.rb new file mode 100644 index 00000000..2e4c29c8 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/migration.rb @@ -0,0 +1,14 @@ +class <%= migration_name %> < ActiveRecord::Migration + def self.up + create_table :<%= table_name %>, :force => true do |t| +<% attributes.each do |attribute| -%> + t.column :<%= attribute.name %>, :<%= attribute.type %> +<% end -%> + t.timestamps + end + end + + def self.down + drop_table :<%= table_name %> + end +end diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/model.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/model.rb new file mode 100644 index 00000000..202f8b30 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/model.rb @@ -0,0 +1,2 @@ +class <%= class_name %> < ActiveRecord::Base +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/model_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/model_spec.rb new file mode 100644 index 00000000..119349fc --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/model_spec.rb @@ -0,0 +1,25 @@ +require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../spec_helper' + +describe <%= class_name %> do + def valid_attributes(args = {}) + { + # Add valid attributes for building your model instances here! + }.merge(args) + end + + before(:each) do + @<%= singular_name %> = <%= class_name %>.new + end + + after(:each) do + @<%= singular_name %>.destroy unless @<%= singular_name %>.new_record? + end + + # Add your model specs here, please! + # And don't forget about the association matchers built-in to skinny_spec like: + # + # it_should_have_many :foos + # it_should_validate_presence_of :bar + # + # Check out the docs for more information. +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.erb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.erb new file mode 100644 index 00000000..5db36d56 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.erb @@ -0,0 +1,15 @@ +

Show <%= model_name %>

+<% if attributes.blank? -%> +

Add your customized markup here, please!

+<% else -%> + <%- attributes.each do |attribute| -%> +

+ + <%%=h @<%= singular_name %>.<%= attribute.name %> %> +

+ <%- end -%> +<% end -%> +
+ <%%= button_to "Edit", edit_<%= singular_name %>_path(@<%= singular_name %>), :method => "get" %> + <%%= button_to "Back to List", <%= plural_name %>_path, :method => "get" %> +
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.haml b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.haml new file mode 100644 index 00000000..d8afe80a --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.haml @@ -0,0 +1,13 @@ +%h1== Show #{<%= model_name %>} +<% if attributes.blank? -%> +%p Add your customized markup here, please! +<% else -%> + <%- attributes.each do |attribute| -%> +%p + %label <%= attribute.name.titleize %>: + =h @<%= singular_name %>.<%= attribute.name %> + <%- end -%> +<% end -%> +#commands + = button_to "Edit", edit_<%= singular_name %>_path(@<%= singular_name %>), :method => "get" + = button_to "Back to List", <%= plural_name %>_path, :method => "get" \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html_spec.rb new file mode 100644 index 00000000..1c6c7aa8 --- /dev/null +++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html_spec.rb @@ -0,0 +1,32 @@ +require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper' + +describe "<%= File.join(controller_class_path, controller_singular_name) %>/show.html.<%= template_language %>" do + before(:each) do +<% if attributes.blank? -%> + @<%= singular_name %> = mock_and_assign(<%= model_name %>) +<% else -%> + @<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => { + <%- attributes.each_with_index do |attribute, index| -%> + <%- case attribute.type -%> + <%- when :string, :text -%> + :<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %> + <%- when :integer, :float, :decimal -%> + :<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %> + <%- when :boolean -%> + :<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %> + <%- when :date, :datetime, :time, :timestamp -%> + :<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %> + <%- else -%> + :<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %> + <%- end -%> + <%- end -%> + }) +<% end -%> + end + + # Add your specs here, please! But remember not to make them brittle + # by specing specing specific HTML elements and classes. + + it_should_link_to_edit :<%= singular_name %> + it_should_link_to { <%= plural_name %>_path } +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/init.rb b/vendor/plugins/skinny_spec/init.rb new file mode 100644 index 00000000..4906d276 --- /dev/null +++ b/vendor/plugins/skinny_spec/init.rb @@ -0,0 +1,3 @@ +if RAILS_ENV == "test" + require "skinny_spec" +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/common_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/common_spec_helpers.rb new file mode 100644 index 00000000..2506accb --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/common_spec_helpers.rb @@ -0,0 +1,46 @@ +module LuckySneaks + # These methods are mostly just called internally by various other spec helper + # methods but you're welcome to use them as needed in your own specs. + module CommonSpecHelpers + # Returns class for the specified name. Example: + # + # class_for("foo") # => Foo + def class_for(name) + name.to_s.constantize + rescue NameError + name.to_s.classify.constantize + # Let any other error rise! + end + + # Returns instance variable for the specified name. Example: + # + # instance_for("foo") # => @foo + def instance_for(name) + instance_variable_get("@#{name.to_s.underscore}") + end + + # Wraps a matcher that checks if the receiver contains an A element (link) + # whose href attribute is set to the specified path. + def have_link_to(path) + have_tag("a[href='#{path}']") + end + + # Returns dummy value for specified attribute based on the datatype expected for that + # attribute. + def dummy_value_for(instance, attribute) + if datatype = instance.column_for_attribute(attribute) + actual = instance.send(attribute) + case datatype.type + when :string, :text + actual == "foo" ? "bar" : "food" + when :integer, :float, :decimal + actual == 108 ? 815 : 108 + when :boolean + actual ? false : true + when :date, :datetime, :time, :timestamp + actual == 1.week.ago ? 2.years.ago : 1.week.ago + end + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_request_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_request_helpers.rb new file mode 100644 index 00000000..a7cd5b94 --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_request_helpers.rb @@ -0,0 +1,67 @@ +module LuckySneaks + module ControllerRequestHelpers # :nodoc: + def self.included(base) + base.extend ExampleGroupMethods + end + + private + def define_implicit_request(method) + @controller_method = method + @implicit_request = case method + when :index, :new, :show, :edit + proc { get method, params } + when :create + proc { post :create, params } + when :update + proc { put :update, params } + when :destroy + proc { put :destroy, params } + end + end + + def eval_request + instance_eval &self.class.instance_variable_get("@the_request") + rescue ArgumentError # missing block + try_shared_request_definition + end + alias do_request eval_request + + def try_shared_request_definition + shared_request + rescue NameError + if @implicit_request + try_implicit_request + else + error_message = "Could not determine request definition for 'describe' context. " + error_message << "Please use define_request or define a shared_request." + raise ArgumentError, error_message + end + end + + def try_implicit_request + @implicit_request.call + end + + def get_response(&block) + eval_request + block.call(response) if block_given? + response + end + + module ExampleGroupMethods + # Defines a request at the example group ("describe") level to be evaluated in the examples. Example: + # + # define_request { get :index, params } + # + # Note: The following methods all define implicit requests: stub_index, stub_new, + # stub_create, stub_show, stub_edit, stub_update, and + # stub_destroy. Using them in your before blocks will allow you to forego + # defining explicit requests using define_request. See + # LuckySneaks::ControllerStubHelpers for information on these methods. + def define_request(&block) + raise ArgumentError, "Must provide a block to define a request!" unless block_given? + @the_request = block + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_spec_helpers.rb new file mode 100644 index 00000000..43210f93 --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_spec_helpers.rb @@ -0,0 +1,435 @@ +$:.unshift File.join(File.dirname(__FILE__), "..") +require "skinny_spec" + +module LuckySneaks + module ControllerSpecHelpers # :nodoc: + include LuckySneaks::CommonSpecHelpers + include LuckySneaks::ControllerRequestHelpers + include LuckySneaks::ControllerStubHelpers + + def self.included(base) + base.extend ExampleGroupMethods + base.extend ControllerRequestHelpers::ExampleGroupMethods + end + + # Evaluates the specified block for each of the RESTful controller methods. + # This is useful to spec that all controller methods redirect when no user is + # logged in. + def with_default_restful_actions(params = {}, &block) + { + :get => :index, + :get => :new, + :post => :create + }.each do |method_id, message| + self.send method_id, message, params + block.call + end + { + :get => :edit, + :put => :update, + :delete => :destroy + }.each do |method_id, message| + if params[:before] + params.delete(:before).call + end + # Presuming any id will do + self.send method_id, message, params.merge(:id => 1) + block.call + end + end + + private + def create_ar_class_expectation(name, method, argument = nil, options = {}) + args = [] + if [:create, :update].include?(@controller_method) + args << (argument.nil? ? valid_attributes : argument) + else + args << argument unless argument.nil? + end + args << options unless options.empty? + if args.empty? + return_value = class_for(name).send(method) + class_for(name).should_receive(method).and_return(return_value) + else + return_value = class_for(name).send(method, *args) + class_for(name).should_receive(method).with(*args).and_return(return_value) + end + end + + def create_positive_ar_instance_expectation(name, method, *args) + instance = instance_for(name) + if args.empty? + return_value = instance.send(method) + instance.should_receive(method).and_return(true) + else + return_value = instance.send(method, *args) + instance.should_receive(method).with(*args).and_return(true) + end + end + + # These methods are designed to be used at the example group [read: "describe"] level + # to simplify and DRY up common expectations. + module ExampleGroupMethods + # Creates an expectation that the controller method calls ActiveRecord::Base.find. + # Examples: + # + # it_should_find :foos # => Foo.should_receive(:find).with(:all) + # it_should_find :foos, :all # An explicit version of the above + # it_should_find :foos, :conditions => {:foo => "bar"} # => Foo.should_receive(:find).with(:all, :conditions => {"foo" => "bar"} + # it_should_find :foo # => Foo.should_recieve(:find).with(@foo.id.to_s) + # it_should_find :foo, :params => "id" # => Foo.should_receive(:find).with(params[:id].to_s) + # it_should_find :foo, 2 # => Foo.should_receive(:find).with("2") + # + # Note: All params (key and value) will be strings if they come from a form element and are handled + # internally with this expectation. + def it_should_find(name, *args) + name_string = name.to_s + name_message = if name_string == name_string.singularize + "a #{name}" + else + name + end + it "should find #{name_message}" do + options = args.extract_options! + # Blech! + argument = if param = params[options.delete(:params)] + param.to_s + else + if args.first + args.first + elsif (instance = instance_variable_get("@#{name}")).is_a?(ActiveRecord::Base) + instance.id.to_s + else + :all + end + end + create_ar_class_expectation name, :find, argument, options + eval_request + end + end + + # Creates an expectation that the controller method calls ActiveRecord::Base.new. + # Takes optional params for the initialization arguments. Example + # + # it_should_initialize :foo # => Foo.should_receive(:new) + # it_should_initialize :foo, :params => :bar # => Foo.should_receive(:new).with(params[:bar]) + # it_should_initialize :foo, :bar => "baz" # => Foo.should_receive(:new).with(:bar => "baz") + def it_should_initialize(name, options = {}) + it "should initialize a #{name}" do + create_ar_class_expectation name, :new, params[options.delete(:params)], options + eval_request + end + end + + # Creates an expectation that the controller method calls ActiveRecord::Base#save on the + # named instance. Example: + # + # it_should_save :foo # => @foo.should_receive(:save).and_return(true) + # + # Note: This helper should not be used to spec a failed save call. Use it_should_assign + # instead, to verify that the instance is captured in an instance variable for the inevitable re-rendering + # of the form template. + def it_should_save(name) + it "should save the #{name}" do + create_positive_ar_instance_expectation name, :save + eval_request + end + end + + # Creates an expectation that the controller method calls ActiveRecord::Base#update_attributes + # on the named instance. Takes optional argument for params to specify in the + # expectation. Examples: + # + # it_should_update :foo # => @foo.should_receive(:update_attributes).and_return(true) + # it_should_update :foo, :params => :bar # => @foo.should_receive(:update_attributes).with(params[:bar]).and_return(true) + # + # Note: This helper should not be used to spec a failed update_attributes call. Use + # it_should_assign instead, to verify that the instance is captured in an instance variable + # for the inevitable re-rendering of the form template. + def it_should_update(name, options = {}) + it "should update the #{name}" do + create_positive_ar_instance_expectation name, :update_attributes, params[options[:params]] + eval_request + end + end + + # Creates an expectation that the controller method calls ActiveRecord::Base#destroy on the named + # instance. Example: + # + # it_should_destroy :foo # => @foo.should_receive(:destroy).and_return(true) + # + # Note: This helper should not be used to spec a failed destroy call. Use + # it_should_assign instead, if you need to verify that the instance is captured in an instance + # variable if it is re-rendered somehow. This is probably a really edge use case. + def it_should_destroy(name, options = {}) + it "should delete the #{name}" do + create_positive_ar_instance_expectation name, :destroy + eval_request + end + end + + # Creates expectation[s] that the controller method should assign the specified + # instance variables along with any specified values. Examples: + # + # it_should_assign :foo # => assigns[:foo].should == @foo + # it_should_assign :foo => "bar" # => assigns[:foo].should == "bar" + # it_should_assign :foo => :nil # => assigns[:foo].should be_nil + # it_should_assign :foo => :not_nil # => assigns[:foo].should_not be_nil + # it_should_assign :foo => :undefined # => controller.send(:instance_variables).should_not include("@foo") + # + # Very special thanks to Rick Olsen for the basis of this code. The only reason I even + # redefine it at all is purely an aesthetic choice for specs like "it should foo" + # over ones like "it foos". + def it_should_assign(*names) + names.each do |name| + if name.is_a?(Symbol) + it_should_assign name => name + elsif name.is_a?(Hash) + name.each do |key, value| + it_should_assign_instance_variable key, value + end + end + end + end + + # Wraps the separate expectations it_should_find and it_should_assign + # for simple cases. If you need more control over the parameters of the find, this + # isn't the right helper method and you should write out the two expectations separately. + def it_should_find_and_assign(*names) + names.each do |name| + it_should_find name + it_should_assign name + end + end + + # Wraps the separate expectations it_should_initialize and it_should_assign + # for simple cases. If you need more control over the parameters of the initialization, this + # isn't the right helper method and you should write out the two expectations separately. + # + # Note: This method is used for controller methods like new, where the instance + # is initialized without being saved (this includes failed create requests). + # If you want to spec that the controller method successfully saves the instance, + # please use it_should_initialize_and_save. + def it_should_initialize_and_assign(*names) + names.each do |name| + it_should_initialize name + it_should_assign name + end + end + + # Wraps the separate expectations it_should_initialize and it_should_save + # for simple cases. If you need more control over the parameters of the initialization, this + # isn't the right helper method and you should write out the two expectations separately. + # + # Note: This method is used for controller methods like create, where the instance + # is initialized and successfully saved. If you want to spec that the instance is created + # but not saved, just use it_should_initialize_and_assign. + def it_should_initialize_and_save(*names) + names.each do |name| + it_should_initialize name + it_should_save name + end + end + + # Wraps the separate expectations it_should_find and it_should_update + # for simple cases. If you need more control over the parameters of the find, this + # isn't the right helper method and you should write out the two expectations separately. + # + # Note: This method is used for controller methods like update, where the + # instance is loaded from the database and successfully saved. If you want to spec that the + # instance is found but not saved, just use it_should_find_and_assign. + def it_should_find_and_update(*names) + names.each do |name| + it_should_find name + it_should_update name + end + end + + # Wraps the separate expectations it_should_find and it_should_destroy + # for simple cases. If you need more control over the parameters of the find, this + # isn't the right helper method and you should write out the two expectations separately. + def it_should_find_and_destroy(*names) + names.each do |name| + it_should_find name + it_should_destroy name + end + end + + # Creates an expectation that the specified collection (flash or session) + # contains the specified key and value. To specify that the collection should be set + # to nil, specify the value as :nil instead. + def it_should_set(collection, key, value = nil, &block) + it "should set #{collection}[:#{key}]" do + # Allow flash.now[:foo] to remain in the flash + flash.stub!(:sweep) if collection == :flash + eval_request + if value + if value == :nil + self.send(collection)[key].should be_nil + else + self.send(collection)[key].should == value + end + elsif block_given? + self.send(collection)[key].should == block.call + else + self.send(collection)[key].should_not be_nil + end + end + end + + # Wraps it_should_set :flash. To specify that the collection should be set + # to nil, specify the value as :nil instead. + def it_should_set_flash(name, value = nil, &block) + it_should_set :flash, name, value, &block + end + + # Wraps it_should_set :session. To specify that the collection should be set + # to nil, specify the value as :nil instead. + def it_should_set_session(name, value = nil, &block) + it_should_set :session, name, value, &block + end + + # Wraps the various it_should_render_foo methods: + # it_should_render_template, it_should_render_partial, + # it_should_render_xml, it_should_render_json, + # it_should_render_formatted, and it_should_render_nothing. + def it_should_render(render_method, *args) + send "it_should_render_#{render_method}", *args + end + + # Creates an expectation that the controller method renders the specified template. + # Accepts the following options which create additional expectations. + # + # :content_type:: Creates an expectation that the Content-Type header for the response + # matches the one specified + # :status:: Creates an expectation that the HTTP status for the response + # matches the one specified + def it_should_render_template(name, options = {}) + create_status_expectation options[:status] if options[:status] + it "should render '#{name}' template" do + eval_request + response.should render_template(name) + end + create_content_type_expectation(options[:content_type]) if options[:content_type] + end + + # Creates an expectation that the controller method renders the specified partial. + # Accepts the following options which create additional expectations. + # + # :content_type:: Creates an expectation that the Content-Type header for the response + # matches the one specified + # :status:: Creates an expectation that the HTTP status for the response + # matches the one specified + def it_should_render_partial(name, options = {}) + create_status_expectation options[:status] if options[:status] + it "should render '#{name}' partial" do + controller.expect_render(:partial => name) + eval_request + end + create_content_type_expectation(options[:content_type]) if options[:content_type] + end + + # Creates an expectation that the controller method renders the specified record via to_xml. + # Accepts the following options which create additional expectations. + # + # :content_type:: Creates an expectation that the Content-Type header for the response + # matches the one specified + # :status:: Creates an expectation that the HTTP status for the response + # matches the one specified + def it_should_render_xml(record = nil, options = {}, &block) + it_should_render_formatted :xml, record, options, &block + end + + # Creates an expectation that the controller method renders the specified record via to_json. + # Accepts the following options which create additional expectations. + # + # :content_type:: Creates an expectation that the Content-Type header for the response + # matches the one specified + # :status:: Creates an expectation that the HTTP status for the response + # matches the one specified + def it_should_render_json(record = nil, options = {}, &block) + it_should_render_formatted :json, record, options, &block + end + + # Called internally by it_should_render_xml and it_should_render_json + # but should not really be called much externally unless you have defined your own + # formats with a matching to_foo method on the record. + # + # Which is probably never. + def it_should_render_formatted(format, record = nil, options = {}, &block) + create_status_expectation options[:status] if options[:status] + it "should render #{format.inspect}" do + if record.is_a?(Hash) + options = record + record = nil + end + if record.nil? && !block_given? + raise ArgumentError, "it_should_render must be called with either a record or a block and neither was given." + else + if record + pieces = record.to_s.split(".") + record = instance_variable_get("@#{pieces.shift}") + record = record.send(pieces.shift) until pieces.empty? + end + block ||= proc { record.send("to_#{format}") } + get_response do |response| + response.should have_text(block.call) + end + end + end + create_content_type_expectation(options[:content_type]) if options[:content_type] + end + + # Creates an expectation that the controller method returns a blank page. You'd already + # know when and why to use this so I'm not typing it out. + def it_should_render_nothing(options = {}) + create_status_expectation options[:status] if options[:status] + it "should render :nothing" do + get_response do |response| + response.body.strip.should be_blank + end + end + end + + # Creates an expectation that the controller method redirects to the specified destination. Example: + # + # it_should_redirect_to { foos_url } + # + # Note: This method takes a block to evaluate the route in the example + # context rather than the example group context. + def it_should_redirect_to(hint = nil, &route) + if hint.nil? && route.respond_to?(:to_ruby) + hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip + end + it "should redirect to #{(hint || route)}" do + eval_request + response.should redirect_to(instance_eval(&route)) + end + end + + private + def it_should_assign_instance_variable(name, value) + expectation_proc = case value + when :nil + proc { assigns[name].should be_nil } + when :not_nil + proc { assigns[name].should_not be_nil } + when :undefined + proc { controller.send(:instance_variables).should_not include("@{name}") } + when Symbol + if (instance_variable = instance_variable_get("@#{name}")).nil? + proc { assigns[name].should_not be_nil } + else + proc { assigns[name].should == instance_variable } + end + else + proc { assigns[name].should == value } + end + it "should #{value == :nil ? 'not ' : ''}assign @#{name}" do + eval_request + instance_eval &expectation_proc + end + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_stub_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_stub_helpers.rb new file mode 100644 index 00000000..950c7a17 --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_stub_helpers.rb @@ -0,0 +1,199 @@ +module LuckySneaks # :nodoc: + # These methods are designed to be used in your example before blocks to accomplish + # a whole lot of functionality with just a tiny bit of effort. The methods which correspond + # to the controller methods perform the most duties as they create the mock_model instances, + # stub out all the necessary methods, and also create implicit requests to DRY up your spec + # file even more. You are encouraged to use these methods to setup the basic calls for your + # resources and only resort to the other methods when mocking and stubbing secondary objects + # and calls. + # + # Both stub_create and stub_update benefit from having a valid_attributes + # method defined at the top level of your example groups, ie the top-most "describe" block + # of the spec file. If you did not generate your specs with skinny_scaffold or + # skinny_resourceful generators, you can simply write a method like the following + # for yourself: + # + # def valid_attributes + # { + # "foo" => "bar", + # "baz" => "quux" + # } + # end + # + # Note this method employs strings as both the key and values to best replicate the way + # they are used in actual controllers where the params will come from a form. + module ControllerStubHelpers + # Stubs out find :all and returns a collection of mock_model + # instances of that class. Accepts the following options: + # + # :format:: Format of the request. Used to only add to_xml and + # to_json when actually needed. + # :size:: Number of instances to return in the result. Default is 3. + # :stub:: Additional methods to stub on the instances + # + # Any additional options will be passed as arguments to the class find. + # You will want to make sure to pass those arguments to the it_should_find spec as well. + def stub_find_all(klass, options = {}) + returning(Array.new(options[:size] || 3){mock_model(klass)}) do |collection| + stub_out klass, options.delete(:stub) + if format = options.delete(:format) + stub_formatted collection, format + params[:format] = format + end + if options.empty? + klass.stub!(:find).with(:all).and_return(collection) + else + klass.stub!(:find).with(:all, options).and_return(collection) + end + end + end + + # Alias for stub_find_all but additionally defines an implicit request get :index. + def stub_index(klass, options = {}) + define_implicit_request :index + stub_find_all klass, options + end + + # Stubs out new method and returns a mock_model instance marked as a new record. + # Accepts the following options: + # + # :format:: Format of the request. Used to only add to_xml and + # to_json when actually needed. + # :stub:: Additional methods to stub on the instances + # + # It also accepts some options used to stub out save with a specified true + # or false but you should be using stub_create in that case. + def stub_initialize(klass, options = {}) + returning mock_model(klass) do |member| + stub_out member, options.delete(:stub) + if format = options[:format] + stub_formatted member, format + params[:format] = format + end + klass.stub!(:new).and_return(member) + if options[:stub_save] + stub_ar_method member, :save, options[:return] + klass.stub!(:new).with(params[options[:params]]).and_return(member) + else + member.stub!(:new_record?).and_return(true) + member.stub!(:id).and_return(nil) + end + end + end + + # Alias for stub_initialize which additionally defines an implicit request get :new. + def stub_new(klass, options = {}) + define_implicit_request :new + stub_initialize klass, options + end + + # Alias for stub_initialize which additionally defines an implicit request post :create. + # + # Note: If stub_create is provided an optional :params hash + # or the method valid_attributes is defined within its scope, + # those params will be added to the example's params object. If neither + # are provided an ArgumentError will be raised. + def stub_create(klass, options = {}) + define_implicit_request :create + if options[:params].nil? + if self.respond_to?(:valid_attributes) + params[klass.name.underscore.to_sym] = valid_attributes + options[:params] = valid_attributes + else + error_message = "Params for creating #{klass} could not be determined. " + error_message << "Please define valid_attributes method in the base 'describe' block " + error_message << "or manually set params in the before block." + raise ArgumentError, error_message + end + end + stub_initialize klass, options.merge(:stub_save => true) + end + + # Stubs out find and returns a single mock_model + # instances of that class. Accepts the following options: + # + # :format:: Format of the request. Used to only add to_xml and + # to_json when actually needed. + # :stub:: Additional methods to stub on the instances + # + # Any additional options will be passed as arguments to find.You will want + # to make sure to pass those arguments to the it_should_find spec as well. + # + # Note: The option :stub_ar is used internally by stub_update + # and stub_destroy. If you need to stub update_attributes or + # destroy you should be using the aforementioned methods instead. + def stub_find_one(klass, options = {}) + returning mock_model(klass) do |member| + stub_out member, options.delete(:stub) + if options[:format] + stub_formatted member, options[:format] + params[:format] = options[:format] + end + if options[:current_object] + params[:id] = member.id + if options[:stub_ar] + stub_ar_method member, options[:stub_ar], options[:return] + end + end + klass.stub!(:find).with(member.id.to_s).and_return(member) + end + end + + # Alias for stub_find_one which additionally defines an implicit request get :show. + def stub_show(klass, options = {}) + define_implicit_request :show + stub_find_one klass, options.merge(:current_object => true) + end + + # Alias for stub_find_one which additionally defines an implicit request get :edit. + def stub_edit(klass, options = {}) + define_implicit_request :edit + stub_find_one klass, options.merge(:current_object => true) + end + + # Alias for stub_find_one which additionally defines an implicit request put :update + # and stubs out the update_attribute method on the instance as well. + # + # Note: If stub_update is provided an optional :params hash + # or the method valid_attributes is defined within its scope, + # those params will be added to the example's params object. If neither + # are provided an ArgumentError will be raised. + def stub_update(klass, options = {}) + define_implicit_request :update + stub_find_one klass, options.merge(:current_object => true, :stub_ar => :update_attributes) + end + + # Alias for stub_find_one which additionally defines an implicit request delete :destroy + # and stubs out the destroy method on the instance as well. + def stub_destroy(klass, options = {}) + define_implicit_request :destroy + stub_find_one klass, options.merge(:current_object => true, :stub_ar => :destroy) + end + + # Stubs to_xml or to_json respectively based on format argument. + def stub_formatted(object, format) + return unless format + object.stub!("to_#{format}").and_return("#{object.class} formatted as #{format}") + end + + private + # Stubs out multiple methods. You shouldn't be calling this yourself and if you do + # you should be able to understand the code yourself, right? + def stub_out(object, stubs = {}) + return if stubs.nil? + stubs.each do |method, value| + if value + object.stub!(method).and_return(value) + else + object.stub!(method) + end + end + end + + # Stubs out ActiveRecord::Base methods like #save, #update_attributes, etc + # that may be called on a found or instantiated mock_model instance. + def stub_ar_method(object, method, return_value) + object.stub!(method).and_return(return_value ? false : true) + end + end +end diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/model_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/model_spec_helpers.rb new file mode 100644 index 00000000..4d65f276 --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/model_spec_helpers.rb @@ -0,0 +1,326 @@ +$:.unshift File.join(File.dirname(__FILE__), "..") +require "skinny_spec" + +module LuckySneaks + # These methods are designed to be used in your example [read: "it"] blocks + # to make your model specs a little more DRY. You might also be interested + # in checking out the example block [read: "describe"] level versions in of these + # methods which can DRY things up even more: + # LuckySneaks::ModelSpecHelpers::ExampleGroupLevelMethods + # + # Note: The validation matchers are only meant to be used for simple validation checking + # not as a one-size-fits-all solution. + module ModelSpecHelpers + include LuckySneaks::CommonSpecHelpers + + def self.included(base) # :nodoc: + base.extend ExampleGroupLevelMethods + end + + class AssociationMatcher # :nodoc: + def initialize(associated, macro) + @associated = associated + @macro = macro + @options = {} + end + + def matches?(main_model) + unless main_model.respond_to?(:reflect_on_association) + if main_model.class.respond_to?(:reflect_on_association) + main_model = main_model.class + else + @not_model = main_model + return false + end + end + if @association = main_model.reflect_on_association(@associated) + @options.all?{|k, v| @association.options[k] == v || + [@association.options[k]] == v} # Stupid to_a being obsoleted! + end + end + + def failure_message + if @not_model + " expected: #{@not_model} to be a subclass of ActiveRecord::Base class, but was not" + elsif @association + " expected: #{association_with(@options)}\n got: #{association_with(@association.options)}" + else + " expected: #{association_with(@options)}, but the association does not exist" + end + end + + def negative_failure_message + if @association + " expected: #{association_with(@options)}\n got: #{association_with(@association.options)}" + else + " expected: #{association_with(@options)} to not occur but it does" + end + end + + # The following public methods are chainable extensions on the main matcher + # Examples: + # + # Foo.should have_many(:bars).through(:foobars).with_dependent(:destroy) + # Bar.should belong_to(:baz).with_class_name("Unbaz") + def through(through_model) + @options[:through] = through_model + self + end + + def and_includes(included_models) + @options[:include] = included_models + self + end + + def and_extends(*modules) + @options[:extends] = modules + self + end + + def with_counter_cache(counter_cache = false) + if counter_cache + @options[:counter_cache] = counter_cache + end + self + end + + def uniq(*irrelevant_args) + @options[:uniq] = true + self + end + alias and_is_unique uniq + alias with_unique uniq + + def polymorphic(*irrelevant_args) + @options[:polymorphic] = true + self + end + alias and_is_polymorphic polymorphic + alias with_polymorphic polymorphic + + def as(interface) + @options[:as] = interface + end + + # Use this to just specify the options as a hash. + # Note: It will completely override any previously set options + def with_options(options = {}) + options.each{|k, v| @options[k] = v} + self + end + + private + # Takes care of methods like with_dependent(:destroy) + def method_missing(method_id, *args, &block) + method_name = method_id.to_s + if method_name =~ /^with_(.*)/ + @options[$1.to_sym] = args + self + else + super method_id, *args, &block + end + end + + def association_with(options) + option_string = (options.nil? || options.empty?) ? "" : options.inspect + unless option_string.blank? + option_string.sub! /^\{(.*)\}$/, ', \1' + option_string.gsub! /\=\>/, ' => ' + end + "#{@macro} :#{@associated}#{option_string}" + end + end + + # Creates matcher that checks if the receiver has a belongs_to association + # with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def belong_to(model) + AssociationMatcher.new model, :belongs_to + end + + # Creates matcher that checks if the receiver has a have_one association + # with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def have_one(model) + AssociationMatcher.new model, :has_one + end + + # Creates matcher that checks if the receiver has a have_many association + # with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def have_many(models) + AssociationMatcher.new models, :has_many + end + + # Creates matcher that checks if the receiver has a have_and_belong_to_many association + # with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def have_and_belong_to_many(models) + AssociationMatcher.new models, :has_and_belongs_to_many + end + + private + def class_or_instance + @model_spec_class_or_instance ||= class_for(self.class.description_text) || instance + end + + def instance + @model_spec_instance ||= instance_for(self.class.description_text) + end + + # These methods are designed to be used at the example group [read: "describe"] level + # to simplify and DRY up common expectations. Most of these methods are wrappers for + # matchers which can also be used on the example level [read: within an "it" block]. See + # LuckySneaks::ModelSpecHelpers for more information. + module ExampleGroupLevelMethods + # Creates an expectation that the current model being spec'd has a belongs_to + # association with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def it_should_belong_to(model) + it "should belong to a #{model}" do + class_or_instance.should belong_to(model) + end + end + + # Creates an expectation that the current model being spec'd has a have_one + # association with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def it_should_have_one(model) + it "should have one #{model}" do + class_or_instance.should have_one(model) + end + end + + # Creates an expectation that the current model being spec'd has a have_many + # association with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def it_should_have_many(models) + it "should have many #{models}" do + class_or_instance.should have_many(models) + end + end + + # Creates an expectation that the current model being spec'd has a have_and_belong_to_many + # association with the specified model. + # + # Note: The argument should be a symbol as in the model's association definition + # and not the model's class name. + def it_should_have_and_belong_to_many(models) + it "should have and belong to many #{models}" do + class_or_instance.should have_and_belong_to_many(models) + end + end + + # Creates an expectation that the current model being spec'd validates_presence_of + # the specified attribute. Takes an optional custom message to match the one in the model's + # validation. + def it_should_validate_presence_of(attribute, message = ActiveRecord::Errors.default_error_messages[:blank]) + it "should not be valid if #{attribute} is blank" do + instance.send "#{attribute}=", nil + instance.errors_on(attribute).should include(message) + end + end + + # Creates an expectation that the current model being spec'd validates_numericality_of + # the specified attribute. Takes an optional custom message to match the one in the model's + # validation. + def it_should_validate_numericality_of(attribute, message = ActiveRecord::Errors.default_error_messages[:not_a_number]) + it "should validate #{attribute} is a numeric" do + instance.send "#{attribute}=", "NaN" + instance.errors_on(attribute).should include(message) + end + end + + # Creates an expectation that the current model being spec'd validates_confirmation_of + # the specified attribute. Takes an optional custom message to match the one in the model's + # validation. + def it_should_validate_confirmation_of(attribute, message = ActiveRecord::Errors.default_error_messages[:confirmation]) + it "should validate confirmation of #{attribute}" do + dummy_value = dummy_value_for(instance, attribute) || "try a string" + instance.send "#{attribute}=", dummy_value + instance.send "#{attribute}_confirmation=", dummy_value.succ + instance.errors_on(attribute).should include(message) + end + end + + # Creates an expectation that the current model being spec'd validates_uniqueness_of + # the specified attribute. Takes an optional custom message to match the one in the model's + # validation. + # + # Note: This method will fail completely if valid_attributes + # does not provide all the attributes needed to create a valid record. + def it_should_validate_uniqueness_of(attribute, message = ActiveRecord::Errors.default_error_messages[:taken]) + it "should validate #{attribute} confirmation" do + previous_instance = class_for(self.class.description_text).create!(valid_attributes) + instance.attributes = valid_attributes + instance.errors_on(attribute).should include(message) + previous_instance.destroy + end + end + + # Creates an expectation that the current model being spec'd accepts the specified values as + # valid for the specified attribute. This is most likely used with validates_format_of + # but there's nothing saying it couldn't be another validation. + def it_should_accept_as_valid(attribute, *values) + values.each do |value| + value_inspect = case value + when String : "'#{value}'" + when NilClass : "nil" + else value + end + it "should accept #{value_inspect} as a valid #{attribute}" do + instance.send "#{attribute}=", value + instance.errors_on(attribute).should == [] + end + end + end + + # Creates an expectation that the current model being spec'd does not accept the specified + # values as valid for the specified attribute. This is most likely used with + # validates_format_of but there's nothing saying it couldn't be another validation. + # Takes an optional argument :message => "some custom error messsage" for + # spec'ing the actual error message. + def it_should_not_accept_as_valid(attribute, *values) + options = values.extract_options! + values.each do |value| + value_inspect = case value + when String : "'#{value}'" + when NilClass : "nil" + else value + end + it "should not accept #{value_inspect} as a valid #{attribute}" do + instance.send "#{attribute}=", value + if options[:message] + instance.errors_on(attribute).should include(options[:message]) + else + instance.should have_at_least(1).errors_on(attribute) + end + end + end + end + # Creates an expectation that the current model being spec'd doesn't allow mass-assignment + # of the specified attribute. + def it_should_not_mass_assign(attribute) + it "should not allow mass-assignment of #{attribute}" do + lambda { + instance.send :attributes=, {attribute => dummy_value_for(instance, attribute)} + }.should_not change(instance, attribute) + end + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/view_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/view_spec_helpers.rb new file mode 100644 index 00000000..eb532554 --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/view_spec_helpers.rb @@ -0,0 +1,460 @@ +$:.unshift File.join(File.dirname(__FILE__), "..") +require "skinny_spec" + +module LuckySneaks + # These methods are designed to be used in your example [read: "it"] blocks + # to make your view specs less brittle and more DRY. You might also be interested + # in checking out the example block [read: "describe"] level versions in of these + # methods which can DRY things up even more: + # LuckySneaks::ViewSpecHelpers::ExampleGroupLevelMethods + module ViewSpecHelpers + include LuckySneaks::CommonSpecHelpers + include ActionController::PolymorphicRoutes + + def self.included(base) # :nodoc: + base.extend ExampleGroupLevelMethods + end + + # Wraps a matcher that checks if the receiver contains a FORM element with + # its action attribute set to the specified path. + def submit_to(path) + have_tag("form[action=#{path}]") + end + + # Wraps a matcher that checks is the receiver contains any of several form elements + # that would return sufficient named parameters to allow editing of the specified + # attribute on the specified instance. Example: + # + # response.should allow_editing(@foo, "bar") + # + # can be satisfied by any of the following HTML elements: + # + # + # + # + # + # + def allow_editing(instance, attribute) + instance_name = instance.class.name.underscore.downcase + if instance.send(attribute).is_a?(Time) + have_tag( + "input[name='#{instance_name}[#{attribute}]'], + select[name=?]", /#{instance_name}\[#{attribute}\(.*\)\]/ + ) + else + have_tag( + "input[type='text'][name='#{instance_name}[#{attribute}]'], + input[type='password'][name='#{instance_name}[#{attribute}]'], + select[name='#{instance_name}[#{attribute}]'], + textarea[name='#{instance_name}[#{attribute}]'], + input[type='checkbox'][name='#{instance_name}[#{attribute}]'], + input[type='checkbox'][name='#{instance_name}[#{attribute.to_s.tableize.singularize}_ids][]']" + ) + end + end + + # Wraps a matcher that checks if the receiver contains an A element (link) + # whose href attribute is set to the specified path or a FORM + # element whose action attribute is set to the specified path. + def have_link_or_button_to(path) + have_tag( + "a[href='#{path}'], + form[action='#{path}'] input, + form[action='#{path}'] button" + ) + end + alias have_link_to have_link_or_button_to + alias have_button_to have_link_or_button_to + + # Wraps have_link_or_button_to new_polymorphic_path for the specified class which + # corresponds with the new method of the controller. + # + # Note: This method may takes a string or symbol representing the model's name + # to send to have_link_or_button_to_show or the model's name itself. + def have_link_or_button_to_new(name) + have_link_or_button_to new_polymorphic_path(name.is_a?(ActiveRecord::Base) ? name : class_for(name)) + end + + # Wraps have_link_or_button_to polymorphic_path(instance) which + # corresponds with the show method of the controller. + def have_link_or_button_to_show(instance) + have_link_or_button_to polymorphic_path(instance) + end + alias have_link_to_show have_link_or_button_to_show + alias have_button_to_show have_link_or_button_to_show + + # Wraps have_link_or_button_to edit_polymorphic_path(instance) which + # corresponds with the edit method of the controller. + def have_link_or_button_to_edit(instance) + have_link_or_button_to edit_polymorphic_path(instance) + end + alias have_link_to_edit have_link_or_button_to_edit + alias have_button_to_edit have_link_or_button_to_edit + + # Wraps a matcher that checks if the receiver contains the HTML created by Rails' + # button_to helper: to wit, a FORM element whose action + # attribute is pointed at the polymorphic_path of the instance + # and contains an INPUT named "_method" with a value of "delete". + def have_button_to_delete(instance) + path = polymorphic_path(instance) + have_tag( + "form[action='#{path}'] input[name='_method'][value='delete'] + input, + form[action='#{path}'] input[name='_method'][value='delete'] + button" + ) + end + + # Creates a mock_model instance and adds it to the assigns collection + # using either the name passed as the first argument or the underscore version + # of its class name. Accepts optional arguments to stub out additional methods + # (and their return values) on the mock_model instance. Example: + # + # mock_and_assign(Foo, :stub => {:bar => "bar"}) + # + # is the same as running assigns[:foo] = mock_model(Foo, :bar => "bar"). + # + # mock_and_assign(Foo, "special_foo", :stub => {:bar => "baz"}) + # + # is the same as running assigns[:special_foo] = mock_model(Foo, :bar => "baz"). + # + # Note: Adding to the assigns collection returns the object added, so this can + # be chained a la @foo = mock_and_assign(Foo). + def mock_and_assign(klass, *args) + options = args.extract_options! + mocked = if options[:stub] + mock_model(klass, options[:stub]) + else + mock_model(klass) + end + yield mocked if block_given? + self.assigns[args.first || "#{klass}".underscore] = mocked + end + + # Creates an array of mock_model instances in the manner of + # mock_and_assign. Accepts option[:size] which sets the size + # of the array (default is 3). + def mock_and_assign_collection(klass, *args) + options = args.dup.extract_options! + return_me = Array.new(options[:size] || 3) do + mocked = if options[:stub] + mock_model(klass, options[:stub]) + else + mock_model(klass) + end + yield mocked if block_given? + mocked + end + self.assigns[args.first || "#{klass}".tableize] = return_me + end + + private + def do_render + if @the_template + render @the_template + elsif File.exists?(File.join(RAILS_ROOT, "app/views", self.class.description_text)) + render self.class.description_text + else + error_message = "Cannot determine template for render. " + error_message << "Please define @the_template in the before block " + error_message << "or name your describe block so that it indicates the correct template." + raise NameError, error_message + end + end + + # These methods are designed to be used at the example group [read: "describe"] level + # to simplify and DRY up common expectations. Most of these methods are wrappers for + # matchers which can also be used on the example level [read: within an "it" block]. See + # LuckySneaks::ViewSpecHelpers for more information. + module ExampleGroupLevelMethods + include LuckySneaks::CommonSpecHelpers + + # Creates an expectation which calls submit_to on the response + # from rendering the template. See that method for more details. + # + # Note: This method takes a Proc to evaluate the route not simply a named route + # helper, which would be undefined in the scope of the example block. + def it_should_submit_to(hint = nil, &route) + if hint.nil? && route.respond_to?(:to_ruby) + hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip + end + it "should submit to #{(hint || route)}" do + do_render + response.should submit_to(instance_eval(&route)) + end + end + + # Creates an expectation that the template uses Rails' form_for to generate + # the proper form action and method to create or update the specified object. + # + # Note: This method takes a string or symbol representing the instance + # variable's name to create the expectation for form_for + # not an instance variable, which would be nil in the scope of the example block. + # If you use namespacing for your form_for, you'll have to manually write out + # a similar spec. + def it_should_have_form_for(name) + it "should have a form_for(@#{name})" do + template.should_receive(:form_for).with(instance_for(name)) + do_render + end + end + + # Negative version of it_should_have_form_for. See that method for more + # details. + def it_should_not_have_form_for(name) + it "should not have a form_for(@#{name})" do + template.should_not_receive(:form_for).with(instance_for(name)) + do_render + end + end + + # Creates an expectation which calls allow_editing on the response + # from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name to send to allow_editing + # not an instance variable, which would be nil in the scope of the example block. + def it_should_allow_editing(name, method) + it "should allow editing of @#{name}##{method}" do + do_render + response.should allow_editing(instance_for(name), method) + end + end + + # Negative version of it_should_allow_editing. See that method for more + # details. + def it_should_not_allow_editing(name, method) + it "should not allow editing of @#{name}##{method}" do + do_render + response.should_not allow_editing(instance_for(name), method) + end + end + + # Creates an expectation that the rendered template contains a FORM element + # (INPUT, TEXTAREA, or SELECT) with the specified name. + def it_should_have_form_element_for(name) + it "should have a form element named '#{name}'" do + do_render + response.should have_tag( + "form input[name='#{name}'], + form textarea[name='#{name}'], + form select[name='#{name}']" + ) + end + end + + # Negative version of it_should_have_form_element_for. See that method + # for more details. + def it_should_not_have_form_element_for(name) + it "should not have a form element named '#{name}'" do + do_render + response.should_not have_tag( + "form input[name='#{name}'], + form textarea[name='#{name}'], + form select[name='#{name}']" + ) + end + end + + # Creates an expectation which calls have_link_or_button_to on the response + # from rendering the template. See that method for more details. + # + # Note: This method takes a block to evaluate the route in the example context + # instead of the example group context. + def it_should_link_to(hint = nil, &route) + if hint.nil? && route.respond_to?(:to_ruby) + hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip + end + it "should have a link/button to #{(hint || route)}" do + do_render + response.should have_link_or_button_to(instance_eval(&route)) + end + end + alias it_should_have_link_to it_should_link_to + alias it_should_have_button_to it_should_link_to + alias it_should_have_button_or_link_to it_should_link_to + + # Negative version of it_should_link_to. See that method + # for more details. + def it_should_not_link_to(hint = nil, &route) + if hint.nil? && route.respond_to?(:to_ruby) + hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip + end + it "should have a link/button to #{(hint || route)}" do + do_render + response.should_not have_link_or_button_to(instance_eval(&route)) + end + end + alias it_should_not_have_link_to it_should_not_link_to + alias it_should_not_have_button_to it_should_not_link_to + alias it_should_not_have_button_or_link_to it_should_not_link_to + + # Creates an expectation which calls have_link_or_button_to_new on the response + # from rendering the template. See that method for more details. + # + # Note: This method may takes a string or symbol representing the model's name + # to send to have_link_or_button_to_show or the model's name itself. + def it_should_link_to_new(name) + it "should have a link/button to create a new #{name}" do + do_render + response.should have_link_or_button_to_new(name) + end + end + alias it_should_have_link_to_new it_should_link_to_new + alias it_should_have_button_to_new it_should_link_to_new + alias it_should_have_button_or_link_to_new it_should_link_to_new + + # Negative version of it_should_link_to_show. See that method + # for more details. + def it_should_not_link_to_new(name) + it "should have a link/button to create a new #{name}" do + do_render + response.should_not have_link_or_button_to_new(name) + end + end + alias it_should_not_have_link_to_new it_should_not_link_to_new + alias it_should_not_have_button_to_new it_should_not_link_to_new + alias it_should_not_have_button_or_link_to_new it_should_not_link_to_new + + # Creates an expectation which calls have_link_or_button_to_show on the response + # from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name to send to have_link_or_button_to_show + # not an instance variable, which would be nil in the scope of the example block. + def it_should_link_to_show(name) + it "should have a link/button to show @#{name}" do + do_render + response.should have_link_or_button_to_show(instance_for(name)) + end + end + alias it_should_have_link_to_show it_should_link_to_show + alias it_should_have_button_to_show it_should_link_to_show + alias it_should_have_button_or_link_to_show it_should_link_to_show + + # Negative version of it_should_link_to_show. See that method + # for more details. + def it_should_not_link_to_show(name) + it "should have a link/button to show @#{name}" do + do_render + response.should_not have_link_or_button_to_show(instance_for(name)) + end + end + alias it_should_not_have_link_to_show it_should_not_link_to_show + alias it_should_not_have_button_to_show it_should_not_link_to_show + alias it_should_not_have_button_or_link_to_show it_should_not_link_to_show + + # Creates an expectation which calls have_link_or_button_to_show + # for each member of the instance variable matching the specified name + # on the response from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name and not an instance variable, which would be nil + # in the scope of the example block. + def it_should_link_to_show_each(name) + it "should have a link/button to show each member of @#{name}" do + do_render + instance_for(name).each do |member| + response.should have_link_or_button_to_show(member) + end + end + end + alias it_should_have_link_to_show_each it_should_link_to_show_each + alias it_should_have_button_to_show_each it_should_link_to_show_each + alias it_should_have_button_or_link_to_show_each it_should_link_to_show_each + + # Creates an expectation which calls have_link_or_button_to_edit on the response + # from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name to send to have_link_or_button_to_edit + # not an instance variable, which would be nil in the scope of the example block. + def it_should_link_to_edit(name) + it "should have a link/button to edit @#{name}" do + do_render + response.should have_link_or_button_to_edit(instance_for(name)) + end + end + alias it_should_have_link_to_edit it_should_link_to_edit + alias it_should_have_button_to_edit it_should_link_to_edit + alias it_should_have_button_or_link_to_edit it_should_link_to_edit + + # Negative version of it_should_link_to_edit. See that method + # for more details. + def it_should_not_link_to_edit(name) + it "should have a link/button to edit @#{name}" do + do_render + response.should_not have_link_or_button_to_edit(instance_for(name)) + end + end + alias it_should_not_have_link_to_edit it_should_not_link_to_edit + alias it_should_not_have_button_to_edit it_should_not_link_to_edit + alias it_should_not_have_button_or_link_to_edit it_should_not_link_to_edit + + + # Creates an expectation which calls have_link_or_button_to_edit + # for each member of the instance variable matching the specified name + # on the response from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name and not an instance variable, which would be nil + # in the scope of the example block. + def it_should_link_to_edit_each(name) + it "should have a link/button to edit each member of @#{name}" do + do_render + instance_for(name).each do |member| + response.should have_link_or_button_to_edit(member) + end + end + end + alias it_should_have_link_to_edit_each it_should_link_to_edit_each + alias it_should_have_button_to_edit_each it_should_link_to_edit_each + alias it_should_have_button_or_link_to_edit_each it_should_link_to_edit_each + + # Creates an expectation which calls have_link_or_button_to_delete on the response + # from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name to send to have_link_or_button_to_delete + # not an instance variable, which would be nil in the scope of the example block. + def it_should_link_to_delete(name) + it "should have a link/button to delete @#{name}" do + do_render + response.should have_button_to_delete(instance_for(name)) + end + end + alias it_should_have_link_to_delete it_should_link_to_delete + alias it_should_have_button_to_delete it_should_link_to_delete + alias it_should_have_button_or_link_to_delete it_should_link_to_delete + + # Negative version of it_should_link_to_delete. See that method + # for more details. + def it_should_not_link_to_delete(name) + it "should not have a link/button to delete @#{name}" do + do_render + response.should_not have_button_to_delete(instance_for(name)) + end + end + alias it_should_not_have_link_to_delete it_should_not_link_to_delete + alias it_should_not_have_button_to_delete it_should_not_link_to_delete + alias it_should_not_have_button_or_link_to_delete it_should_not_link_to_delete + + # Creates an expectation which calls have_link_or_button_to_delete + # for each member of the instance variable matching the specified name + # on the response from rendering the template. See that method for more details. + # + # Note: This method takes a string or symbol representing the instance + # variable's name and not an instance variable, which would be nil + # in the scope of the example block. + def it_should_link_to_delete_each(name) + it "should have a link/button to delete each member of @#{name}" do + do_render + instance_for(name).each do |member| + response.should have_button_to_delete(member) + end + end + end + alias it_should_have_link_to_delete_each it_should_link_to_delete_each + alias it_should_have_button_to_delete_each it_should_link_to_delete_each + alias it_should_have_button_or_link_to_delete_each it_should_link_to_delete_each + end + end +end \ No newline at end of file diff --git a/vendor/plugins/skinny_spec/lib/skinny_spec.rb b/vendor/plugins/skinny_spec/lib/skinny_spec.rb new file mode 100644 index 00000000..c7883884 --- /dev/null +++ b/vendor/plugins/skinny_spec/lib/skinny_spec.rb @@ -0,0 +1,26 @@ +# Let's make sure everyone else is loaded +require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment") +require 'spec' +require 'spec/rails' +begin + require 'ruby2ruby' +rescue + puts "-----" + puts "Attention: skinny_spec requires ruby2ruby for nicer route descriptions" + puts "It is highly recommended that you install it: sudo gem install ruby2ruby" + puts "-----" +end + +# Let's load our family now +require "lucky_sneaks/common_spec_helpers" +require "lucky_sneaks/controller_request_helpers" +require "lucky_sneaks/controller_spec_helpers" +require "lucky_sneaks/controller_stub_helpers" +require "lucky_sneaks/model_spec_helpers" +require "lucky_sneaks/view_spec_helpers" + +# Let's all come together +Spec::Rails::Example::ViewExampleGroup.send :include, LuckySneaks::ViewSpecHelpers +Spec::Rails::Example::HelperExampleGroup.send :include, LuckySneaks::CommonSpecHelpers +Spec::Rails::Example::ControllerExampleGroup.send :include, LuckySneaks::ControllerSpecHelpers +Spec::Rails::Example::ModelExampleGroup.send :include, LuckySneaks::ModelSpecHelpers \ No newline at end of file