diff --git a/README.markdown b/README.markdown index 4e767e83d6..79cde55bdd 100644 --- a/README.markdown +++ b/README.markdown @@ -50,6 +50,21 @@ Note that the generators are there to help you get started, but they are no substitute for writing your own examples, and they are only guaranteed to work out of the box for the default scenario (`ActiveRecord` + `Webrat`). +#### Custom Generator Workflow + +To plug in a custom generator for one or more example types: +In config/application.rb: + + config.app_generators do |g| + g.routing_specs = :my_fancy_routing_specs + g.controller_specs = :my_fancy_controller_specs + g.view_specs = :my_fancy_view_specs + end + +These generators should all live in lib/generators/rspec and the classes should be +declared as `Rspec::Generators::MyFancyRoutingSpecsGenerator`, and so on. Make copies +of the ones from this git repo to use as templates. + ### Autotest The `rspec:install` generator creates an `./autotest/discover.rb` file, which diff --git a/lib/generators/rspec/controller_specs/controller_specs_generator.rb b/lib/generators/rspec/controller_specs/controller_specs_generator.rb new file mode 100644 index 0000000000..1f1658ddaf --- /dev/null +++ b/lib/generators/rspec/controller_specs/controller_specs_generator.rb @@ -0,0 +1,81 @@ +require 'generators/rspec' + +module Rspec + module Generators + class ControllerSpecsGenerator < Base + class_option :controller_specs, :type => :boolean, :default => true + class_option :orm, :desc => "ORM used to generate the controller" + class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller" + def generate_controller_spec + return unless options[:controller_specs] + + template 'controller_spec.rb', + File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb") + end + protected + def params + "{'these' => 'params'}" + end + + # Returns the name of the mock. For example, if the file name is user, + # it returns mock_user. + # + # If a hash is given, it uses the hash key as the ORM method and the + # value as response. So, for ActiveRecord and file name "User": + # + # mock_file_name(:save => true) + # #=> mock_user(:save => true) + # + # If another ORM is being used and another method instead of save is + # called, it will be the one used. + # + def mock_file_name(hash=nil) + if hash + method, and_return = hash.to_a.first + method = orm_instance.send(method).split('.').last.gsub(/\(.*?\)/, '') + "mock_#{file_name}(:#{method} => #{and_return})" + else + "mock_#{file_name}" + end + end + + # Receives the ORM chain and convert to expects. For ActiveRecord: + # + # should! orm_class.find(User, "37") + # #=> User.should_receive(:find).with(37) + # + # For Datamapper: + # + # should! orm_class.find(User, "37") + # #=> User.should_receive(:get).with(37) + # + def should_receive!(chain) + stub_or_should_chain(:should_receive, chain) + end + + # Receives the ORM chain and convert to stub. For ActiveRecord: + # + # stub! orm_class.find(User, "37") + # #=> User.stub!(:find).with(37) + # + # For Datamapper: + # + # stub! orm_class.find(User, "37") + # #=> User.stub!(:get).with(37) + # + def stub!(chain) + stub_or_should_chain(:stub, chain) + end + + def stub_or_should_chain(mode, chain) + receiver, method = chain.split(".") + method.gsub!(/\((.*?)\)/, '') + + response = "#{receiver}.#{mode}(:#{method})" + response << ".with(#{$1})" unless $1.blank? + response + end + + end + end +end diff --git a/lib/generators/rspec/scaffold/templates/controller_spec.rb b/lib/generators/rspec/controller_specs/templates/controller_spec.rb similarity index 100% rename from lib/generators/rspec/scaffold/templates/controller_spec.rb rename to lib/generators/rspec/controller_specs/templates/controller_spec.rb diff --git a/lib/generators/rspec/routing_specs/routing_specs_generator.rb b/lib/generators/rspec/routing_specs/routing_specs_generator.rb new file mode 100644 index 0000000000..1abfdc1afd --- /dev/null +++ b/lib/generators/rspec/routing_specs/routing_specs_generator.rb @@ -0,0 +1,17 @@ +require 'generators/rspec' + +module Rspec + module Generators + class RoutingSpecsGenerator < Base + include Rails::Generators::ResourceHelpers + class_option :routing_specs, :type => :boolean, :default => true + + def generate_routing_spec + return unless options[:routing_specs] + + template 'routing_spec.rb', + File.join('spec/routing', controller_class_path, "#{controller_file_name}_routing_spec.rb") + end + end + end +end diff --git a/lib/generators/rspec/scaffold/templates/routing_spec.rb b/lib/generators/rspec/routing_specs/templates/routing_spec.rb similarity index 100% rename from lib/generators/rspec/scaffold/templates/routing_spec.rb rename to lib/generators/rspec/routing_specs/templates/routing_spec.rb diff --git a/lib/generators/rspec/scaffold/scaffold_generator.rb b/lib/generators/rspec/scaffold/scaffold_generator.rb index 15cf2787c8..20dba732f4 100644 --- a/lib/generators/rspec/scaffold/scaffold_generator.rb +++ b/lib/generators/rspec/scaffold/scaffold_generator.rb @@ -18,123 +18,23 @@ class ScaffoldGenerator < Base class_option :helper_specs, :type => :boolean, :default => true, :desc => "Generate helper specs" class_option :routing_specs, :type => :boolean, :default => true, :desc => "Generate routing specs" - def generate_controller_spec - return unless options[:controller_specs] + hook_for :controller_specs + hook_for :view_specs - template 'controller_spec.rb', - File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb") - end - - def generate_view_specs - return unless options[:view_specs] - - copy_view :edit - copy_view :index unless options[:singleton] - copy_view :new - copy_view :show - end # Invoke the helper using the controller name (pluralized) hook_for :helper, :as => :scaffold do |invoked| invoke invoked, [ controller_name ] end - def generate_routing_spec - return unless options[:routing_specs] - - template 'routing_spec.rb', - File.join('spec/routing', controller_class_path, "#{controller_file_name}_routing_spec.rb") - end + hook_for :routing_specs hook_for :integration_tool, :as => :integration protected - - def webrat? - options[:webrat_matchers] || @webrat_matchers_requested - end - - def copy_view(view) - template "#{view}_spec.rb", - File.join("spec/views", controller_file_path, "#{view}.html.#{options[:template_engine]}_spec.rb") - end - - def params - "{'these' => 'params'}" - end - - # Returns the name of the mock. For example, if the file name is user, - # it returns mock_user. - # - # If a hash is given, it uses the hash key as the ORM method and the - # value as response. So, for ActiveRecord and file name "User": - # - # mock_file_name(:save => true) - # #=> mock_user(:save => true) - # - # If another ORM is being used and another method instead of save is - # called, it will be the one used. - # - def mock_file_name(hash=nil) - if hash - method, and_return = hash.to_a.first - method = orm_instance.send(method).split('.').last.gsub(/\(.*?\)/, '') - "mock_#{file_name}(:#{method} => #{and_return})" - else - "mock_#{file_name}" - end - end - - # Receives the ORM chain and convert to expects. For ActiveRecord: - # - # should! orm_class.find(User, "37") - # #=> User.should_receive(:find).with(37) - # - # For Datamapper: - # - # should! orm_class.find(User, "37") - # #=> User.should_receive(:get).with(37) - # - def should_receive!(chain) - stub_or_should_chain(:should_receive, chain) - end - - # Receives the ORM chain and convert to stub. For ActiveRecord: - # - # stub! orm_class.find(User, "37") - # #=> User.stub!(:find).with(37) - # - # For Datamapper: - # - # stub! orm_class.find(User, "37") - # #=> User.stub!(:get).with(37) - # - def stub!(chain) - stub_or_should_chain(:stub, chain) - end - - def stub_or_should_chain(mode, chain) - receiver, method = chain.split(".") - method.gsub!(/\((.*?)\)/, '') - - response = "#{receiver}.#{mode}(:#{method})" - response << ".with(#{$1})" unless $1.blank? - response - end - - def value_for(attribute) - case attribute.type - when :string - "#{attribute.name.titleize}".inspect - else - attribute.default.inspect - end - end - def banner self.class.banner end - end end end diff --git a/lib/generators/rspec/scaffold/templates/edit_spec.rb b/lib/generators/rspec/view_specs/templates/edit_spec.rb similarity index 100% rename from lib/generators/rspec/scaffold/templates/edit_spec.rb rename to lib/generators/rspec/view_specs/templates/edit_spec.rb diff --git a/lib/generators/rspec/scaffold/templates/index_spec.rb b/lib/generators/rspec/view_specs/templates/index_spec.rb similarity index 100% rename from lib/generators/rspec/scaffold/templates/index_spec.rb rename to lib/generators/rspec/view_specs/templates/index_spec.rb diff --git a/lib/generators/rspec/scaffold/templates/new_spec.rb b/lib/generators/rspec/view_specs/templates/new_spec.rb similarity index 100% rename from lib/generators/rspec/scaffold/templates/new_spec.rb rename to lib/generators/rspec/view_specs/templates/new_spec.rb diff --git a/lib/generators/rspec/scaffold/templates/show_spec.rb b/lib/generators/rspec/view_specs/templates/show_spec.rb similarity index 100% rename from lib/generators/rspec/scaffold/templates/show_spec.rb rename to lib/generators/rspec/view_specs/templates/show_spec.rb diff --git a/lib/generators/rspec/view_specs/view_specs_generator.rb b/lib/generators/rspec/view_specs/view_specs_generator.rb new file mode 100644 index 0000000000..fb9e5b64e1 --- /dev/null +++ b/lib/generators/rspec/view_specs/view_specs_generator.rb @@ -0,0 +1,47 @@ +require 'generators/rspec' + +module Rspec + module Generators + class ViewSpecsGenerator < Base + include Rails::Generators::ResourceHelpers + class_option :view_specs, :type => :boolean, :default => true + argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + + class_option :orm, :desc => "ORM used to generate the controller" + class_option :template_engine, :desc => "Template engine to generate view files" + class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller" + + class_option :controller_specs, :type => :boolean, :default => true, :desc => "Generate controller specs" + class_option :view_specs, :type => :boolean, :default => true, :desc => "Generate view specs" + class_option :webrat_matchers, :type => :boolean, :default => false, :desc => "Use webrat matchers in view specs" + class_option :helper_specs, :type => :boolean, :default => true, :desc => "Generate helper specs" + class_option :routing_specs, :type => :boolean, :default => true, :desc => "Generate routing specs" + + def generate_view_specs + return unless options[:view_specs] + + copy_view :edit + copy_view :index unless options[:singleton] + copy_view :new + copy_view :show + end + protected + def webrat? + options[:webrat_matchers] || @webrat_matchers_requested + end + + def copy_view(view) + template "#{view}_spec.rb", + File.join("spec/views", controller_file_path, "#{view}.html.#{options[:template_engine]}_spec.rb") + end + def value_for(attribute) + case attribute.type + when :string + "#{attribute.name.titleize}".inspect + else + attribute.default.inspect + end + end + end + end +end diff --git a/lib/rspec-rails.rb b/lib/rspec-rails.rb index 004457b01e..85d2eb35c6 100644 --- a/lib/rspec-rails.rb +++ b/lib/rspec-rails.rb @@ -3,8 +3,11 @@ module Rails class Railtie < ::Rails::Railtie # Rails-3.0.1 requires config.app_generators instead of 3.0.0's config.generators generators = config.respond_to?(:app_generators) ? config.app_generators : config.generators + generators.test_framework :rspec generators.integration_tool :rspec - generators.test_framework :rspec + generators.routing_specs = :rspec unless generators.view_specs + generators.controller_specs = :rspec unless generators.view_specs + generators.view_specs = :rspec unless generators.view_specs rake_tasks do load "rspec/rails/tasks/rspec.rake"