spec_producer 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +73 -24
  5. data/lib/configuration.rb +41 -0
  6. data/lib/generators/spec_producer/install/install_generator.rb +13 -0
  7. data/lib/generators/spec_producer/install/templates/spec_producer +1 -0
  8. data/lib/spec_producer.rb +140 -47
  9. data/lib/spec_producer/factories_production_module.rb +17 -11
  10. data/lib/spec_producer/missing_files_module.rb +104 -41
  11. data/lib/spec_producer/missing_gems_module.rb +77 -0
  12. data/lib/spec_producer/producers.rb +10 -0
  13. data/lib/spec_producer/producers/base.rb +129 -0
  14. data/lib/spec_producer/producers/controllers_producer.rb +31 -0
  15. data/lib/spec_producer/producers/helpers_producer.rb +26 -0
  16. data/lib/spec_producer/producers/jobs_producer.rb +34 -0
  17. data/lib/spec_producer/producers/mailers_producer.rb +24 -0
  18. data/lib/spec_producer/producers/models_producer.rb +89 -0
  19. data/lib/spec_producer/producers/registry.rb +66 -0
  20. data/lib/spec_producer/producers/routes_producer.rb +44 -0
  21. data/lib/spec_producer/producers/serializers_producer.rb +46 -0
  22. data/lib/spec_producer/producers/views_producer.rb +25 -0
  23. data/lib/spec_producer/railtie.rb +13 -0
  24. data/lib/spec_producer/rspec_builders.rb +1 -0
  25. data/lib/spec_producer/rspec_builders/base.rb +148 -0
  26. data/lib/spec_producer/rspec_builders/builder.rb +220 -0
  27. data/lib/spec_producer/rspec_builders/matchers.rb +256 -0
  28. data/lib/spec_producer/spec_production_module.rb +80 -331
  29. data/lib/spec_producer/spec_runner.rb +14 -0
  30. data/lib/spec_producer/utils.rb +1 -0
  31. data/lib/spec_producer/utils/file_utils.rb +69 -0
  32. data/lib/spec_producer/version.rb +1 -1
  33. data/lib/tasks/spec_producer_tasks.rake +103 -0
  34. data/spec_producer.gemspec +6 -0
  35. metadata +111 -2
@@ -14,7 +14,7 @@ module SpecProducer::FactoriesProductionModule
14
14
  final_text = "FactoryGirl.define do\n"
15
15
  final_text << " factory :#{descendant.name.underscore}, :class => #{descendant.name} do\n"
16
16
 
17
- descendant.columns.each do |column|
17
+ descendant.columns.reject { |column| ['id', 'created_at', 'updated_at'].include? column.name }.each do |column|
18
18
  value = case column.type
19
19
  when :string then "'#{descendant.name.underscore.upcase}_#{column.name.underscore.upcase}'"
20
20
  when :text then "'#{descendant.name.underscore.upcase}_#{column.name.underscore.upcase}'"
@@ -31,28 +31,34 @@ module SpecProducer::FactoriesProductionModule
31
31
  final_text << " #{column.name} #{value}\n"
32
32
  end
33
33
 
34
+ descendant.reflections.each_pair do |key, reflection|
35
+ if reflection.macro == :has_one
36
+ final_text << " association :#{key.to_s}\n"
37
+ end
38
+ end
39
+
34
40
  final_text << " end\n"
35
41
  final_text << "end"
36
42
 
37
43
  if File.exists?(Rails.root.join("spec/factories/#{descendant.name.underscore}.rb"))
38
- puts '#'*100
39
- puts "Please, check whether the following lines are included in: spec/factories/" + descendant.name.underscore + ".rb\n"
40
- puts '#'*100
41
- puts "\n"
44
+ puts ('#'*100).colorize(:light_blue)
45
+ puts ("Please, check whether the following lines are included in: spec/factories/" + descendant.name.underscore + ".rb").colorize(:light_blue)
46
+ puts ('#'*100).colorize(:light_blue)
42
47
  puts final_text
48
+ puts "\n\n"
43
49
  else
44
50
  unless Dir.exists? Rails.root.join("spec")
45
- puts "Generating spec directory"
51
+ puts "Generating spec directory".colorize(:yellow)
46
52
  Dir.mkdir(Rails.root.join("spec"))
47
53
  end
48
54
 
49
55
  unless Dir.exists? Rails.root.join("spec/factories")
50
- puts "Generating spec/factories directory"
56
+ puts "Generating spec/factories directory".colorize(:yellow)
51
57
  Dir.mkdir(Rails.root.join("spec/factories"))
52
58
  end
53
59
 
54
60
  path = "spec/factories/#{descendant.name.underscore}.rb"
55
- puts "Producing factory file for: #{path}"
61
+ puts "Producing factory file for: #{path}".colorize(:green)
56
62
  f = File.open("#{Rails.root.join(path)}", 'wb+')
57
63
  f.write(final_text)
58
64
  f.close
@@ -61,8 +67,8 @@ module SpecProducer::FactoriesProductionModule
61
67
 
62
68
  nil
63
69
  rescue NameError => e
64
- puts "NameError '#{e}' was raised. Can't produce factories for this project."
70
+ puts "NameError '#{e}' was raised. Can't produce factories for this project.".colorize(:red)
65
71
  rescue Exception => e
66
- puts "Exception '#{e}' was raised. Skipping factories production."
72
+ puts "Exception '#{e}' was raised. Skipping factories production.".colorize(:red)
67
73
  end
68
- end
74
+ end
@@ -1,3 +1,5 @@
1
+ require 'colorize'
2
+
1
3
  module SpecProducer::MissingFilesModule
2
4
  def self.print_missing_model_specs(options = {})
3
5
  if options[:specific_namespace]
@@ -6,14 +8,16 @@ module SpecProducer::MissingFilesModule
6
8
  files_list = Dir["app/models/**/*.rb"]
7
9
  end
8
10
 
9
- puts "\n" << "## Searching for missing model specs..."
10
- files_list.each do |file|
11
- unless FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb'))
12
- puts "Missing model spec file for: #{file}"
13
- end
11
+ puts "\n" << "## Searching for missing model specs...".colorize(:light_blue)
12
+ missing = files_list.select { |file| !FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb')) }
13
+
14
+ missing.each do |file|
15
+ puts "Missing model spec file for: #{file}".colorize(:red)
14
16
  end
15
17
 
16
- nil
18
+ if missing.size == 0
19
+ puts "No missing model spec files found!".colorize(:green)
20
+ end
17
21
  end
18
22
 
19
23
  def self.print_missing_controller_specs(options = {})
@@ -23,14 +27,16 @@ module SpecProducer::MissingFilesModule
23
27
  files_list = Dir["app/controllers/**/*.rb"]
24
28
  end
25
29
 
26
- puts "\n" << "## Searching for missing controller specs..."
27
- files_list.each do |file|
28
- unless FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb'))
29
- puts "Missing controller spec file for: #{file}"
30
- end
30
+ puts "\n" << "## Searching for missing controller specs...".colorize(:light_blue)
31
+ missing = files_list.select { |file| !FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb')) }
32
+
33
+ missing.each do |file|
34
+ puts "Missing controller spec file for: #{file}".colorize(:red)
31
35
  end
32
36
 
33
- nil
37
+ if missing.size == 0
38
+ puts "No missing controller spec files found!".colorize(:green)
39
+ end
34
40
  end
35
41
 
36
42
  def self.print_missing_job_specs(options = {})
@@ -40,14 +46,16 @@ module SpecProducer::MissingFilesModule
40
46
  files_list = Dir["app/jobs/**/*.rb"]
41
47
  end
42
48
 
43
- puts "\n" << "## Searching for missing jobs specs..."
44
- files_list.each do |file|
45
- unless FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb'))
46
- puts "Missing job spec file for: #{file}"
47
- end
49
+ puts "\n" << "## Searching for missing jobs specs...".colorize(:light_blue)
50
+ missing = files_list.select { |file| !FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb')) }
51
+
52
+ missing.each do |file|
53
+ puts "Missing job spec file for: #{file}".colorize(:red)
48
54
  end
49
55
 
50
- nil
56
+ if missing.size == 0
57
+ puts "No missing job spec files found!".colorize(:green)
58
+ end
51
59
  end
52
60
 
53
61
  def self.print_missing_mailer_specs(options = {})
@@ -57,47 +65,102 @@ module SpecProducer::MissingFilesModule
57
65
  files_list = Dir["app/mailers/**/*.rb"]
58
66
  end
59
67
 
60
- puts "\n" << "## Searching for missing mailers specs..."
61
- files_list.each do |file|
62
- unless FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb'))
63
- puts "Missing mailer spec file for: #{file}"
64
- end
68
+ puts "\n" << "## Searching for missing mailers specs...".colorize(:light_blue)
69
+ missing = files_list.select { |file| !FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb')) }
70
+
71
+ missing.each do |file|
72
+ puts "Missing mailer spec file for: #{file}".colorize(:red)
65
73
  end
66
74
 
67
- nil
75
+ if missing.size == 0
76
+ puts "No missing mailer spec files found!".colorize(:green)
77
+ end
68
78
  end
69
79
 
70
80
  def self.print_missing_helper_specs(options = {})(options = {})
81
+ if options[:specific_namespace]
82
+ files_list = Dir["app/helpers/**/#{options[:specific_namespace].gsub('app/helpers', '')}/**/*.rb"]
83
+ else
84
+ files_list = Dir["app/helpers/**/*.rb"]
85
+ end
86
+
87
+ puts "\n" << "## Searching for missing helper specs...".colorize(:light_blue)
88
+ missing = files_list.select { |file| !FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb')) }
89
+
90
+ missing.each do |file|
91
+ puts "Missing helper spec file for: #{file}".colorize(:red)
92
+ end
93
+
94
+ if missing.size == 0
95
+ puts "No missing helper spec files found!".colorize(:green)
96
+ end
97
+ end
98
+
99
+ def self.print_missing_view_specs(options = {})
71
100
  if options[:specific_namespace]
72
- files_list = Dir["app/helpers/**/#{options[:specific_namespace].gsub('app/helpers', '')}/**/*.rb"]
101
+ files_list = Dir["app/views/**/#{options[:specific_namespace].gsub('app/views', '')}/**/*.erb"]
73
102
  else
74
- files_list = Dir["app/helpers/**/*.rb"]
103
+ files_list = Dir["app/views/**/*.erb"]
75
104
  end
76
105
 
77
- puts "\n" << "## Searching for missing helper specs..."
78
- files_list.each do |file|
79
- unless FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb'))
80
- puts "Missing helper spec file for: #{file}"
81
- end
106
+ puts "\n" << "## Searching for missing view specs...".colorize(:light_blue)
107
+ missing = files_list.select { |file| !FileTest.exists?("#{file.gsub('app/', 'spec/')}_spec.rb") }
108
+
109
+ missing.each do |file|
110
+ puts "Missing view spec file for: #{file}".colorize(:red)
82
111
  end
83
112
 
84
- nil
113
+ if missing.size == 0
114
+ puts "No missing view spec files found!".colorize(:green)
115
+ end
85
116
  end
86
117
 
87
- def self.print_missing_view_specs(options = {})
118
+ def self.print_missing_serializer_specs(options = {})
88
119
  if options[:specific_namespace]
89
- files_list = Dir["app/views/**/#{options[:specific_namespace].gsub('app/views', '')}/**/*.erb"]
120
+ files_list = Dir["app/serializers/**/#{options[:specific_namespace].gsub('app/serializers', '')}/**/*.erb"]
90
121
  else
91
- files_list = Dir["app/views/**/*.erb"]
122
+ files_list = Dir["app/serializers/**/*.erb"]
123
+ end
124
+
125
+ puts "\n" << "## Searching for missing serializers specs...".colorize(:light_blue)
126
+ missing = files_list.select { |file| !FileTest.exists?(file.gsub('app/', 'spec/').gsub('.rb', '_spec.rb')) }
127
+
128
+ missing.each do |file|
129
+ puts "Missing serializer spec file for: #{file}".colorize(:red)
92
130
  end
93
131
 
94
- puts "\n" << "## Searching for missing view specs..."
95
- files_list.each do |file|
96
- unless FileTest.exists?("#{file.gsub('app/', 'spec/')}_spec.rb")
97
- puts "Missing spec file for: #{file}"
132
+ if missing.size == 0
133
+ puts "No missing serializer spec files found!".colorize(:green)
134
+ end
135
+ end
136
+
137
+ def self.print_missing_route_specs(options = {})
138
+ routes = Rails.application.routes.routes.map do |route|
139
+ path = route.path.spec.to_s.gsub(/\(\.:format\)/, "")
140
+ verb = %W{ GET POST PUT PATCH DELETE }.grep(route.verb).first.downcase.to_sym
141
+ controller = route.defaults[:controller]
142
+ action = route.defaults[:action]
143
+
144
+ if controller.present? && !/^rails/.match(controller)
145
+ { :path => path, :verb => verb, :controller => controller, :action => action }
146
+ end
147
+ end.compact
148
+
149
+ puts "\n" << "## Searching for missing route specs...".colorize(:light_blue)
150
+ missing = []
151
+
152
+ routes.group_by { |route| route[:controller] }.each do |route_group|
153
+ unless File.exists?(Rails.root.join("spec/routing/#{route_group[0]}_routing_spec.rb"))
154
+ missing << "spec/routing/#{route_group[0]}_routing_spec.rb"
98
155
  end
99
156
  end
100
157
 
101
- nil
158
+ missing.each do |file|
159
+ puts "Missing route spec file for: #{file}".colorize(:red)
160
+ end
161
+
162
+ if missing.size == 0
163
+ puts "No missing route spec files found!".colorize(:green)
164
+ end
102
165
  end
103
- end
166
+ end
@@ -0,0 +1,77 @@
1
+ require 'colorize'
2
+
3
+ module SpecProducer::MissingGemsModule
4
+ def self.set_up_necessities
5
+ # TODO: Update spec_helper or rails helper with proper configurations
6
+ gemfiles = Dir.glob(Rails.root.join('Gemfile'))
7
+
8
+ if gemfiles.size > 0
9
+ contents = File.read(gemfiles.first)
10
+ gems = contents.scan(/gem \'(?<gem>\S*)\'/).flatten.uniq
11
+ missing_gems = []
12
+
13
+ missing_gems << 'rspec-rails' unless (gems.include? 'rspec-rails')
14
+ missing_gems << 'factory_girl_rails' unless (gems.include? 'factory_girl_rails')
15
+ missing_gems << 'shoulda-matchers' unless (gems.include? 'shoulda-matchers')
16
+ missing_gems << 'webmock' unless (gems.include? 'webmock')
17
+ missing_gems << 'rubocop' unless (gems.include? 'rubocop')
18
+
19
+ # No need for capybara if there are no views to parse
20
+ missing_gems << 'capybara' unless ((gems.include? 'capybara') && Dir["app/views/**/*.erb"] != [])
21
+
22
+ if missing_gems.size > 0
23
+ actions_needed = ""
24
+ useful_info = "\nGems installed:\n"
25
+ contents << "\n\ngroup :test do\n"
26
+
27
+ missing_gems.each do |gem|
28
+ contents << " gem '#{gem}'\n"
29
+
30
+ if gem == 'rspec-rails'
31
+ useful_info << "# Rspec: https://github.com/rspec/rspec-rails\n"
32
+ elsif gem == 'factory_girl_rails'
33
+ useful_info << "# FactoryGirl: https://github.com/thoughtbot/factory_girl_rails\n"
34
+ elsif gem == 'shoulda-matchers'
35
+ useful_info << "# Shoulda Matchers: https://github.com/thoughtbot/shoulda-matchers\n"
36
+ elsif gem == 'webmock'
37
+ actions_needed << "# Add 'require \'webmock/rspec\'' in your spec_helper or rails_helper\n"
38
+ useful_info << "# Webmock: https://github.com/bblimke/webmock\n"
39
+ elsif gem == 'rubocop'
40
+ useful_info << "# RuboCop: https://github.com/bbatsov/rubocop\n"
41
+ end
42
+ end
43
+
44
+ contents << "end"
45
+
46
+ f = File.open(gemfiles.first, 'wb+')
47
+ f.write(contents)
48
+ f.close
49
+
50
+ if defined?(Bundler)
51
+ Bundler.with_clean_env do
52
+ puts "\n\nRunning bundle after setting list of gems in you Gemfile.".colorize(:yellow)
53
+
54
+ system 'bundle install'
55
+ end
56
+ end
57
+
58
+ if 'rspec-rails'.in? missing_gems
59
+ puts "\n\nInitializing Rspec files and helpers.".colorize(:yellow)
60
+
61
+ system 'rails generate rspec:install'
62
+ end
63
+
64
+ if actions_needed != ''
65
+ puts "\n\nYou will additionally need to:\n".colorize(:green)
66
+ puts actions_needed.colorize(:green)
67
+ end
68
+
69
+ puts useful_info.colorize(:light_blue)
70
+ else
71
+ puts 'Could not find anything missing!'.colorize(:light_blue)
72
+ end
73
+ else
74
+ puts "We couldn't find a Gemfile and setting up halted!".colorize(:red)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_producer/producers/base'
2
+ require 'spec_producer/producers/models_producer'
3
+ require 'spec_producer/producers/views_producer'
4
+ require 'spec_producer/producers/helpers_producer'
5
+ require 'spec_producer/producers/controllers_producer'
6
+ require 'spec_producer/producers/mailers_producer'
7
+ require 'spec_producer/producers/jobs_producer'
8
+ require 'spec_producer/producers/routes_producer'
9
+ require 'spec_producer/producers/serializers_producer'
10
+ require 'spec_producer/producers/registry'
@@ -0,0 +1,129 @@
1
+ require 'spec_producer/utils'
2
+ # Base module that needs to be included and implemented by
3
+ # corresponding producers. Note: This module should be prepended and
4
+ # not included / extended.
5
+ #
6
+ # Concrete subclasses need to implement 2 methods: call and resources
7
+ # We use the prepend and prepended callback in order to call Base#call
8
+ # method first and the invoke super which will call the corresponding
9
+ # concrete implementation to be executed. The #call method on every concrete
10
+ # implementation will receive the resource that we currently iterate over.
11
+ #
12
+ # Conrete implementation need to prepend this module and to provide
13
+ # #resources method which returns a collection of resources that
14
+ # will be iterated in order to generate the specs for. the resource
15
+ # object should respond to #name (ex. Some::User) and #type ('models', 'controllers' etc)
16
+ # We need these values in order to generate the Describe 'Some::Person' in rspec files.
17
+ #
18
+ # A minimal implementation could be
19
+ #
20
+ # class SomeSpecProducer
21
+ # prepend Base
22
+ #
23
+ # def resources
24
+ # [Clas.new(Object), Class.new(Object)].map { |obj|
25
+ # Resource.new(obj, obj.name, 'models')
26
+ # }
27
+ # end
28
+ #
29
+ #
30
+ # def call(resource)
31
+ # builder.context 'Some context' do |b|
32
+ # resource.attributes.each do |attr|
33
+ # b.responds_to(attr)
34
+ # end
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # The base class is also responsible to write for each spec file that we generate
40
+ # the top static lines requiring 'spec_helper' and the beginning of spec file:
41
+ #
42
+ # require 'spec_helper'
43
+ # describe User, type: :model do
44
+ # # Other code omitted
45
+ # end
46
+ #
47
+ # Each concrete instance is responsible to create the `body` of the spec file which includes
48
+ # its basic validations if any or any other info that we might get from ActiveRecord, ActionController
49
+ # etc.
50
+ #
51
+ # Finally this class is responsible to write the generated spec to the corresponsing
52
+ # path (ex spec/models/user_spec.rb)
53
+ #
54
+ module SpecProducer
55
+ module Producers
56
+ module Base
57
+ class Resource < Struct.new(:obj, :name, :type)
58
+ end
59
+
60
+ module ClassMethods
61
+ def call(args)
62
+ new(args).call
63
+ end
64
+ end
65
+
66
+ def self.prepended(base)
67
+ base.extend(ClassMethods)
68
+ end
69
+
70
+ attr_reader :type
71
+ attr_reader :builder
72
+
73
+ def initialize(type)
74
+ @type = type
75
+ @builder = RspecBuilders::Base.new
76
+ end
77
+
78
+ def call
79
+ raise(NotImplementedError.new('Abstract method.')) unless defined?(super)
80
+ warn "No resources available" if resources.empty?
81
+
82
+ resources.each do |resource|
83
+ builder.build do |b|
84
+ b.write(helper_spec_file)
85
+ b.spec resource.name, resource.type do |b|
86
+ super(resource)
87
+ end
88
+ end
89
+
90
+ # Footer / Fils Close etc
91
+ if resource.type == 'routing'
92
+ Utils::FileUtils.try_to_create_spec_file(resource.type, "#{resource.name}_routing", builder)
93
+ else
94
+ Utils::FileUtils.try_to_create_spec_file(resource.type.pluralize, resource.name.underscore, builder)
95
+ end
96
+
97
+ builder.flush!
98
+ end
99
+
100
+ self
101
+ rescue StandardError => e
102
+ handle_exception(e)
103
+ end
104
+
105
+ # @Abstract method
106
+ def resources
107
+ raise(NotImplementedError.new('Abstract method.')) unless defined?(super)
108
+ super
109
+ end
110
+
111
+ #######
112
+ private
113
+ #######
114
+
115
+ def handle_exception(e)
116
+ raise e if SpecProducer.configuration.raise_errors
117
+ puts "Exception '#{e}' was raised. Skipping model specs production.".colorize(:red)
118
+ end
119
+
120
+ def helper_spec_file
121
+ "require \'#{require_helper_string}\'\n"
122
+ end
123
+
124
+ def rspec_describe(klass, type)
125
+ "describe #{klass}, :type => :#{type} do"
126
+ end
127
+ end
128
+ end
129
+ end