rspec-jumpstart 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/rspec-jumpstart +88 -0
- data/lib/rspec_jumpstart.rb +30 -0
- data/lib/rspec_jumpstart/config.rb +16 -0
- data/lib/rspec_jumpstart/erb_factory.rb +85 -0
- data/lib/rspec_jumpstart/erb_templates.rb +126 -0
- data/lib/rspec_jumpstart/generator.rb +392 -0
- data/lib/rspec_jumpstart/rdoc_factory.rb +71 -0
- data/lib/rspec_jumpstart/version.rb +8 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 72abb831cb7e020319ad9069bcdfaba93eecb595
|
4
|
+
data.tar.gz: 88b785a7ee71f18bfa1c6c0a4dd291d34b58001e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d81a980d209b1d09931775637a75bfb121ef15e9f3872960f5ec864c000ca441fa73855ae87084e52f3336be612103ab2ffad26ae8c7295bf58ae3831b5abc2
|
7
|
+
data.tar.gz: e52935530d0bc8bdca3b44b00f57477eb2cecb0e3d2c59b177da8f9384b85df210f4e8164935283a4a53d8b711f8b29b564e2b77ebf67b3da19e104f886c154b
|
data/bin/rspec-jumpstart
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# rspec-jumpstart [dir/*.rb]
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'rspec_jumpstart/version'
|
8
|
+
require 'rspec_jumpstart/generator'
|
9
|
+
|
10
|
+
print_version = false
|
11
|
+
force_write = false
|
12
|
+
dry_run = false
|
13
|
+
rails_mode = false
|
14
|
+
spec_dir = './spec'
|
15
|
+
delta_path = nil
|
16
|
+
exc_spec_dir = nil
|
17
|
+
full_path = nil
|
18
|
+
|
19
|
+
opt = OptionParser.new
|
20
|
+
opt.on('-f', 'Create if absent or append to the existing spec') { |_| force_write = true }
|
21
|
+
opt.on('--force', '') { |_| force_write = true }
|
22
|
+
|
23
|
+
opt.on('-n', 'Dry run mode (shows generated code to console)') { |_| dry_run = true }
|
24
|
+
opt.on('--dry-run', '') { |_| dry_run = true }
|
25
|
+
|
26
|
+
opt.on('-r', 'Run in Rails mode') { |_| rails_mode = true }
|
27
|
+
opt.on('--rails', '') { |_| rails_mode = true }
|
28
|
+
|
29
|
+
opt.on('-o VAL', 'Output directory (default: ./spec)') { |dir| spec_dir = dir }
|
30
|
+
opt.on('--output-dir VAL', '') { |dir| spec_dir = dir }
|
31
|
+
|
32
|
+
opt.on('-e VAL', 'Exclude directory (default: ./spec/no-run/**)') { |dir| exc_spec_dir = dir }
|
33
|
+
opt.on('--exc-dir VAL', '') { |dir| exc_spec_dir = dir }
|
34
|
+
|
35
|
+
opt.on('-D VAL', 'Delta template path (e.g. ./rails_controller_delta.erb)') { |path| delta_path = path }
|
36
|
+
opt.on('--delta-template VAL', '') { |path| delta_path = path }
|
37
|
+
|
38
|
+
opt.on('-F VAL', 'Full template path (e.g. ./rails_controller_full.erb)') { |path| full_path = path }
|
39
|
+
opt.on('--full-template VAL', '') { |path| full_path = path }
|
40
|
+
|
41
|
+
opt.on('-v', 'Print version') { |_| print_version = true }
|
42
|
+
opt.on('--version', '') { |_| print_version = true }
|
43
|
+
|
44
|
+
args = opt.parse(ARGV)
|
45
|
+
|
46
|
+
if print_version
|
47
|
+
puts "rspec-jumpstart #{RSpecJumpstart::VERSION}"
|
48
|
+
exit 0
|
49
|
+
end
|
50
|
+
|
51
|
+
dir_or_file = args.first
|
52
|
+
if dir_or_file.nil?
|
53
|
+
puts "Usage: rspec-jumpstart [dir/*.rb] -options"
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
unless dir_or_file.match(/.rb$/)
|
57
|
+
dir_or_file = dir_or_file.gsub(/\/$/, '') + "/**/*.rb"
|
58
|
+
end
|
59
|
+
|
60
|
+
delta_template = nil
|
61
|
+
if delta_path
|
62
|
+
if File.exist?(delta_path)
|
63
|
+
delta_template = File.read(delta_path)
|
64
|
+
else
|
65
|
+
puts "'#{delta_path}' is not found."
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
full_template = nil
|
71
|
+
if full_path
|
72
|
+
if File.exist?(full_path)
|
73
|
+
full_template = File.read(full_path)
|
74
|
+
else
|
75
|
+
puts "'#{full_path}' is not found."
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
generator = RSpecJumpstart::Generator.new(spec_dir, delta_template, full_template)
|
81
|
+
|
82
|
+
files = Dir.glob(dir_or_file)
|
83
|
+
files -= Dir.glob(exc_spec_dir)
|
84
|
+
|
85
|
+
files.each do |file_path|
|
86
|
+
generator.write_spec(file_path, force_write, dry_run, rails_mode)
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec_jumpstart/generator'
|
4
|
+
require 'rspec_jumpstart/config'
|
5
|
+
require 'rspec_jumpstart/version'
|
6
|
+
|
7
|
+
#
|
8
|
+
# RSpecJumpstart Facade
|
9
|
+
#
|
10
|
+
module RSpecJumpstart
|
11
|
+
class << self
|
12
|
+
# Returns RSpecJumpstart's configuration object.
|
13
|
+
# @api private
|
14
|
+
def config
|
15
|
+
@config ||= RSpecJumpstart::Config.instance
|
16
|
+
yield @config if block_given?
|
17
|
+
@config
|
18
|
+
end
|
19
|
+
alias configure config
|
20
|
+
|
21
|
+
def version
|
22
|
+
VERSION
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.write_spec(file_path, spec_dir = './spec', force_write = false, dry_run = false)
|
27
|
+
generator = RSpecJumpstart::Generator.new(spec_dir)
|
28
|
+
generator.write_spec(file_path, force_write, dry_run)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module RSpecJumpstart
|
6
|
+
# Global configuration affecting all threads.
|
7
|
+
class Config
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
attr_accessor :behaves_like_exclusions
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@behaves_like_exclusions = []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'rspec_jumpstart'
|
5
|
+
require 'rspec_jumpstart/erb_templates'
|
6
|
+
|
7
|
+
#
|
8
|
+
# ERB instance provider
|
9
|
+
#
|
10
|
+
module RSpecJumpstart
|
11
|
+
class ERBFactory
|
12
|
+
|
13
|
+
def initialize(custom_template)
|
14
|
+
@custom_template = custom_template
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Returns ERB instance for creating new spec
|
19
|
+
#
|
20
|
+
def get_instance_for_new_spec(rails_mode, target_path)
|
21
|
+
template = get_erb_template(@custom_template, true, rails_mode, target_path)
|
22
|
+
ERB.new(template, nil, '-', '_new_spec_code')
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Returns ERB instance for appending lacking tests
|
27
|
+
#
|
28
|
+
def get_instance_for_appending(rails_mode, target_path)
|
29
|
+
template = get_erb_template(@custom_template, false, rails_mode, target_path)
|
30
|
+
ERB.new(template, nil, '-', '_additional_spec_code')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
#
|
36
|
+
# Returns ERB template
|
37
|
+
#
|
38
|
+
def get_erb_template(custom_template, is_full, rails_mode, target_path)
|
39
|
+
if custom_template
|
40
|
+
custom_template
|
41
|
+
elsif rails_mode && target_path.match(/controllers/)
|
42
|
+
get_rails_controller_template(is_full)
|
43
|
+
elsif rails_mode && target_path.match(/models/)
|
44
|
+
get_rails_model_template(is_full)
|
45
|
+
elsif rails_mode && target_path.match(/helpers/)
|
46
|
+
get_rails_helper_template(is_full)
|
47
|
+
else
|
48
|
+
get_basic_template(is_full)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_rails_controller_template(is_full)
|
53
|
+
if is_full
|
54
|
+
RSpecJumpstart::ERBTemplates::RAILS_CONTROLLER_NEW_SPEC_TEMPLATE
|
55
|
+
else
|
56
|
+
RSpecJumpstart::ERBTemplates::RAILS_CONTROLLER_METHODS_PART_TEMPLATE
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_rails_model_template(is_full)
|
61
|
+
if is_full
|
62
|
+
RSpecJumpstart::ERBTemplates::RAILS_MODEL_NEW_SPEC_TEMPLATE
|
63
|
+
else
|
64
|
+
RSpecJumpstart::ERBTemplates::RAILS_MODEL_METHODS_PART_TEMPLATE
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_rails_helper_template(is_full)
|
69
|
+
if is_full
|
70
|
+
RSpecJumpstart::ERBTemplates::RAILS_HELPER_NEW_SPEC_TEMPLATE
|
71
|
+
else
|
72
|
+
RSpecJumpstart::ERBTemplates::RAILS_HELPER_METHODS_PART_TEMPLATE
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_basic_template(is_full)
|
77
|
+
if is_full
|
78
|
+
RSpecJumpstart::ERBTemplates::BASIC_NEW_SPEC_TEMPLATE
|
79
|
+
else
|
80
|
+
RSpecJumpstart::ERBTemplates::BASIC_METHODS_PART_TEMPLATE
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'rspec_jumpstart'
|
5
|
+
|
6
|
+
#
|
7
|
+
# ERB templates
|
8
|
+
#
|
9
|
+
module RSpecJumpstart
|
10
|
+
module ERBTemplates
|
11
|
+
|
12
|
+
BASIC_METHODS_PART_TEMPLATE = <<~SPEC
|
13
|
+
<%- methods_to_generate.map { |method| %>
|
14
|
+
# TODO: auto-generated
|
15
|
+
describe '<%= decorated_name(method) %>' do
|
16
|
+
it '<%= method.name %>' do
|
17
|
+
<%- if get_instantiation_code(c, method) -%><%= get_instantiation_code(c, method) %><%- end -%>
|
18
|
+
<%- if get_params_initialization_code(method) -%><%= get_params_initialization_code(method) %><%- end -%>
|
19
|
+
result = <%= get_method_invocation_code(c, method).sub(c.to_s,'described_class') %>
|
20
|
+
|
21
|
+
expect(result).not_to be_nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
<% } %>
|
25
|
+
SPEC
|
26
|
+
|
27
|
+
BASIC_NEW_SPEC_TEMPLATE = <<~SPEC
|
28
|
+
# frozen_string_literal: true
|
29
|
+
|
30
|
+
<% if rails_mode then %>require 'rails_helper'
|
31
|
+
<% else -%>require 'spec_helper'
|
32
|
+
<% end -%>
|
33
|
+
<% unless rails_mode then %>require '<%= self_path %>'
|
34
|
+
<% end -%>
|
35
|
+
|
36
|
+
RSpec.describe <%= to_string_namespaced_path_whole(file_path) %> do
|
37
|
+
<%= ERB.new(BASIC_METHODS_PART_TEMPLATE, nil, '-').result(binding) -%>
|
38
|
+
end
|
39
|
+
SPEC
|
40
|
+
|
41
|
+
RAILS_CONTROLLER_METHODS_PART_TEMPLATE = <<~SPEC
|
42
|
+
<%- methods_to_generate.map { |method| %>
|
43
|
+
# TODO: auto-generated
|
44
|
+
describe '<%= decorated_name(method) %>' do
|
45
|
+
it '<%= get_rails_http_method(method.name).upcase %> <%= method.name %>' do
|
46
|
+
<%= get_rails_http_method(method.name) %> :<%= method.name %>, {}, {}
|
47
|
+
|
48
|
+
expect(response).to have_http_status(:ok)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
<% } %>
|
52
|
+
SPEC
|
53
|
+
|
54
|
+
RAILS_CONTROLLER_NEW_SPEC_TEMPLATE = <<~SPEC
|
55
|
+
# frozen_string_literal: true
|
56
|
+
|
57
|
+
require 'rails_helper'
|
58
|
+
|
59
|
+
RSpec.describe <%= (to_string_namespaced_path(self_path) + get_complete_class_name(c)).split('::').uniq.join('::') %>, type: :controller do
|
60
|
+
<%= ERB.new(RAILS_CONTROLLER_METHODS_PART_TEMPLATE, nil, '-').result(binding) -%>
|
61
|
+
end
|
62
|
+
SPEC
|
63
|
+
|
64
|
+
RAILS_MODEL_METHODS_PART_TEMPLATE = <<~SPEC
|
65
|
+
<%= ERB.new(RAILS_MODEL_SCOPE_PART_TEMPLATE, nil, '-').result(binding) -%>
|
66
|
+
<%- methods_to_generate.map { |method| %>
|
67
|
+
# TODO: auto-generated
|
68
|
+
describe '<%= decorated_name(method) %>' do
|
69
|
+
it '<%= method.name %>' do
|
70
|
+
<%- if get_instantiation_code(c, method) -%><%= get_instantiation_code(c, method) %><%- end -%>
|
71
|
+
<%- if get_params_initialization_code(method) -%><%= get_params_initialization_code(method) %><%- end -%>
|
72
|
+
result = <%= get_method_invocation_code(c, method).sub(c.to_s,'described_class') %>
|
73
|
+
|
74
|
+
expect(result).not_to be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
<% } %>
|
78
|
+
SPEC
|
79
|
+
|
80
|
+
RAILS_MODEL_SCOPE_PART_TEMPLATE = <<~SPEC
|
81
|
+
<%- scope_methods_to_generate.map { |method| %>
|
82
|
+
# TODO: auto-generated
|
83
|
+
describe '.<%= method %>' do # scope test
|
84
|
+
it 'supports named scope <%= method %>' do
|
85
|
+
expect(described_class.limit(3).<%= method %>).to all(be_a(described_class))
|
86
|
+
end
|
87
|
+
end<% } %>
|
88
|
+
SPEC
|
89
|
+
|
90
|
+
RAILS_MODEL_NEW_SPEC_TEMPLATE = <<~SPEC
|
91
|
+
# frozen_string_literal: true
|
92
|
+
|
93
|
+
require 'rails_helper'
|
94
|
+
require 'shared_model_stuff'
|
95
|
+
|
96
|
+
RSpec.describe <%= (to_string_namespaced_path(self_path) + get_complete_class_name(c)).split('::').uniq.join('::') %>, type: :model do
|
97
|
+
it_behaves_like 'real_model'
|
98
|
+
<%= ERB.new(RAILS_MODEL_METHODS_PART_TEMPLATE, nil, '-').result(binding) -%>
|
99
|
+
end
|
100
|
+
SPEC
|
101
|
+
|
102
|
+
RAILS_HELPER_METHODS_PART_TEMPLATE = <<~SPEC
|
103
|
+
<%- methods_to_generate.map { |method| %>
|
104
|
+
# TODO: auto-generated
|
105
|
+
describe '<%= decorated_name(method) %>' do
|
106
|
+
it 'works' do
|
107
|
+
result = <%= get_rails_helper_method_invocation_code(method) %>
|
108
|
+
|
109
|
+
expect(result).not_to be_nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
<% } %>
|
113
|
+
SPEC
|
114
|
+
|
115
|
+
RAILS_HELPER_NEW_SPEC_TEMPLATE = <<~SPEC
|
116
|
+
# frozen_string_literal: true
|
117
|
+
|
118
|
+
require 'rails_helper'
|
119
|
+
|
120
|
+
RSpec.describe <%= (to_string_namespaced_path(self_path) + get_complete_class_name(c)).split('::').uniq.join('::') %>, type: :helper do
|
121
|
+
<%= ERB.new(RAILS_HELPER_METHODS_PART_TEMPLATE, nil, '-').result(binding) -%>
|
122
|
+
end
|
123
|
+
SPEC
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,392 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rdoc'
|
4
|
+
require 'rspec_jumpstart'
|
5
|
+
require 'rspec_jumpstart/erb_factory'
|
6
|
+
require 'rspec_jumpstart/erb_templates'
|
7
|
+
require 'rspec_jumpstart/rdoc_factory'
|
8
|
+
|
9
|
+
#
|
10
|
+
# RSpec Code Generator
|
11
|
+
#
|
12
|
+
module RSpecJumpstart
|
13
|
+
class Generator
|
14
|
+
include RSpecJumpstart::ERBTemplates
|
15
|
+
|
16
|
+
attr_accessor :spec_dir, :delta_template, :full_template
|
17
|
+
|
18
|
+
def initialize(spec_dir = './spec', delta_template = nil, full_template = nil)
|
19
|
+
@spec_dir = spec_dir.gsub(/\/$/, '')
|
20
|
+
@delta_template = delta_template
|
21
|
+
@full_template = full_template
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Writes new spec or appends to the existing spec.
|
26
|
+
#
|
27
|
+
def write_spec(file_path, force_write = false, dry_run = false, rails_mode = false)
|
28
|
+
begin
|
29
|
+
code = ''
|
30
|
+
class_or_module = RSpecJumpstart::RDocFactory.get_rdoc_class_or_module(file_path)
|
31
|
+
if class_or_module
|
32
|
+
spec_path = get_spec_path(file_path)
|
33
|
+
|
34
|
+
code = if force_write && File.exist?(spec_path)
|
35
|
+
append_to_existing_spec(class_or_module, dry_run, rails_mode, file_path, spec_path)
|
36
|
+
else
|
37
|
+
create_new_spec(class_or_module, dry_run, rails_mode, file_path, spec_path)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
puts red("#{file_path} skipped (Class/Module not found).")
|
41
|
+
end
|
42
|
+
rescue Exception => e
|
43
|
+
puts red("#{file_path} aborted - #{e.message}")
|
44
|
+
end
|
45
|
+
|
46
|
+
code
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Gets the complete class name from RDoc::NormalClass/RDoc::NormalModule instance.
|
51
|
+
#
|
52
|
+
def get_complete_class_name(class_or_module, name = class_or_module.name)
|
53
|
+
if class_or_module.parent.name && class_or_module.parent.is_a?(RDoc::NormalModule)
|
54
|
+
get_complete_class_name(class_or_module.parent, "#{class_or_module.parent.name}::#{name}")
|
55
|
+
else
|
56
|
+
name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_string_namespaced_path(self_path)
|
61
|
+
path = self_path.split('/').map { |x| camelize(x) }[1..-2].uniq.join('::')
|
62
|
+
path.empty? ? '' : path + '::'
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_string_namespaced_path_whole(self_path)
|
66
|
+
self_path.
|
67
|
+
sub('.rb', '').
|
68
|
+
split('/').
|
69
|
+
map { |x| camelize(x) }[2..-1].
|
70
|
+
uniq.
|
71
|
+
join('::')
|
72
|
+
end
|
73
|
+
|
74
|
+
def decorated_name(method)
|
75
|
+
(method.singleton ? '.' : '#') + method.name
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Returns spec file path.
|
80
|
+
# e.g. "lib/foo/bar_baz.rb" -> "spec/foo/bar_baz_spec.rb"
|
81
|
+
#
|
82
|
+
def get_spec_path(file_path)
|
83
|
+
spec_dir + '/' + file_path.
|
84
|
+
gsub(/^\.\//, '').
|
85
|
+
gsub(%r{^(lib/)|(app/)}, '').
|
86
|
+
sub(/\.rb$/, '_spec.rb')
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Returns string value to require.
|
91
|
+
# e.g. "lib/foo/bar_baz.rb" -> "foo/bar_baz"
|
92
|
+
#
|
93
|
+
def to_string_value_to_require(file_path)
|
94
|
+
file_path.gsub(%r{^(lib/)|(app/)}, '').gsub(/\.rb$/, '')
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Returns snake_case name.
|
99
|
+
# e.g. FooBar -> "foo_bar"
|
100
|
+
#
|
101
|
+
def instance_name(c)
|
102
|
+
c.name.
|
103
|
+
gsub(/::/, '/').
|
104
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
105
|
+
gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
106
|
+
tr('-', '_').
|
107
|
+
downcase
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Extracts parameter names as an *Array*.
|
112
|
+
# e.g. "()" -> []
|
113
|
+
# e.g. "(a, b = 'foo')" -> ["a", "b"]
|
114
|
+
#
|
115
|
+
def to_param_names_array(params)
|
116
|
+
params.
|
117
|
+
split(',').
|
118
|
+
map { |p| p.gsub(/[()\s]/, '').gsub(/=.+$/, '') }.
|
119
|
+
reject { |p| p.nil? || p.empty? }
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Returns params part
|
124
|
+
# e.g. ["a","b"] -> "(a, b)"
|
125
|
+
# e.g. [] -> ""
|
126
|
+
#
|
127
|
+
def to_params_part(params)
|
128
|
+
param_csv = to_param_names_array(params).join(', ')
|
129
|
+
param_csv.empty? ? '' : "(#{param_csv})"
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Creates new spec.
|
134
|
+
#
|
135
|
+
# rubocop:disable Metrics/AbcSize
|
136
|
+
def create_new_spec(class_or_module, dry_run, rails_mode, file_path, spec_path)
|
137
|
+
# These names are used in ERB template, don't delete.
|
138
|
+
methods_to_generate = public_methods_found(class_or_module)
|
139
|
+
scope_methods_to_generate = scopes(class_or_module, file_path, spec_path)
|
140
|
+
c = class_or_module
|
141
|
+
self_path = to_string_value_to_require(file_path)
|
142
|
+
# rubocop:enable Lint/UselessAssignment
|
143
|
+
|
144
|
+
erb = RSpecJumpstart::ERBFactory.
|
145
|
+
new(@full_template).
|
146
|
+
get_instance_for_new_spec(rails_mode, file_path)
|
147
|
+
code = erb.result(binding)
|
148
|
+
|
149
|
+
if dry_run
|
150
|
+
puts "----- #{spec_path} -----"
|
151
|
+
puts code
|
152
|
+
elsif File.exist?(spec_path)
|
153
|
+
# puts yellow("#{spec_path} already exists.")
|
154
|
+
else
|
155
|
+
FileUtils.mkdir_p(File.dirname(spec_path))
|
156
|
+
File.open(spec_path, 'w') { |f| f.write(code) }
|
157
|
+
puts green("#{spec_path} created.")
|
158
|
+
end
|
159
|
+
|
160
|
+
code
|
161
|
+
end
|
162
|
+
|
163
|
+
def public_methods_found(class_or_module)
|
164
|
+
class_or_module.method_list.select do |m|
|
165
|
+
m.visibility.equal?(:public) && m.name != 'new'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# rubocop:enable Metrics/AbcSize
|
170
|
+
|
171
|
+
#
|
172
|
+
# Appends new tests to the existing spec.
|
173
|
+
#
|
174
|
+
# rubocop:disable Metrics/AbcSize
|
175
|
+
def append_to_existing_spec(class_or_module, dry_run, rails_mode, file_path, spec_path)
|
176
|
+
existing_spec = File.read(spec_path)
|
177
|
+
if skip?(existing_spec)
|
178
|
+
return
|
179
|
+
end
|
180
|
+
lacking_methods = public_methods_found(class_or_module).
|
181
|
+
reject { |m| existing_spec.match(signature(m)) }
|
182
|
+
|
183
|
+
scope_methods_to_generate = scopes(class_or_module, file_path, spec_path)
|
184
|
+
if lacking_methods.empty? && scope_methods_to_generate.empty?
|
185
|
+
# puts yellow("#{spec_path} skipped.")
|
186
|
+
else
|
187
|
+
# These names are used in ERB template, don't delete.
|
188
|
+
methods_to_generate = lacking_methods
|
189
|
+
c = class_or_module
|
190
|
+
# rubocop:enable Lint/UselessAssignment
|
191
|
+
|
192
|
+
erb = RSpecJumpstart::ERBFactory.new(@delta_template).get_instance_for_appending(rails_mode, spec_path)
|
193
|
+
additional_spec = erb.result(binding)
|
194
|
+
|
195
|
+
last_end_not_found = true
|
196
|
+
code = existing_spec.split("\n").reverse.reject do |line|
|
197
|
+
before_modified = last_end_not_found
|
198
|
+
last_end_not_found = line.gsub(/#.+$/, '').strip != 'end' if before_modified
|
199
|
+
before_modified
|
200
|
+
end.reverse.join("\n") + "\n" + additional_spec + "\nend\n"
|
201
|
+
|
202
|
+
if dry_run
|
203
|
+
puts "----- #{spec_path} -----"
|
204
|
+
puts code
|
205
|
+
else
|
206
|
+
File.open(spec_path, 'w') { |f| f.write(code) }
|
207
|
+
end
|
208
|
+
puts green("#{spec_path} modified.")
|
209
|
+
end
|
210
|
+
|
211
|
+
code
|
212
|
+
end
|
213
|
+
|
214
|
+
def skip?(text)
|
215
|
+
RSpecJumpstart.config.behaves_like_exclusions.each do |exclude_pattern|
|
216
|
+
return true if text.match(exclude_pattern)
|
217
|
+
end
|
218
|
+
|
219
|
+
false
|
220
|
+
end
|
221
|
+
|
222
|
+
# rubocop:enable Metrics/AbcSize
|
223
|
+
|
224
|
+
# -----
|
225
|
+
# Code generation
|
226
|
+
# -----
|
227
|
+
|
228
|
+
#
|
229
|
+
# e.g.
|
230
|
+
# a = double('a')
|
231
|
+
# b = double('b')
|
232
|
+
# bar_baz = BarBaz.new(a, b)
|
233
|
+
#
|
234
|
+
def get_instantiation_code(c, method)
|
235
|
+
if method.singleton
|
236
|
+
''
|
237
|
+
else
|
238
|
+
constructor = c.method_list.find { |m| m.name == 'new' }
|
239
|
+
if constructor.nil?
|
240
|
+
" #{instance_name(c)} = described_class.new\n"
|
241
|
+
else
|
242
|
+
get_params_initialization_code(constructor) +
|
243
|
+
" #{instance_name(c)} = described_class.new#{to_params_part(constructor.params)}\n"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# e.g.
|
250
|
+
# a = double('a')
|
251
|
+
# b = double('b')
|
252
|
+
#
|
253
|
+
def get_params_initialization_code(method)
|
254
|
+
code = to_param_names_array(method.params).map do |p|
|
255
|
+
x = p.sub('*', '').sub('&', '')
|
256
|
+
" #{x} = double('#{x}')" unless x.empty?
|
257
|
+
end.compact.join("\n")
|
258
|
+
code.empty? ? '' : "#{code}\n"
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# e.g. BarBaz.do_something(a, b) { |c| }
|
263
|
+
#
|
264
|
+
def get_method_invocation_code(c, method)
|
265
|
+
target = method.singleton ? 'described_class' : instance_name(c)
|
266
|
+
"#{target}.#{method.name}#{to_params_part(method.params)}#{get_block_code(method)}"
|
267
|
+
end
|
268
|
+
|
269
|
+
#
|
270
|
+
# e.g. do_something(a, b) { |c| }
|
271
|
+
#
|
272
|
+
def get_rails_helper_method_invocation_code(method)
|
273
|
+
"#{method.name}#{to_params_part(method.params)}#{get_block_code(method)}"
|
274
|
+
end
|
275
|
+
|
276
|
+
#
|
277
|
+
# e.g. { |a, b| }
|
278
|
+
#
|
279
|
+
def get_block_code(method)
|
280
|
+
if method.block_params.nil? || method.block_params.empty?
|
281
|
+
''
|
282
|
+
else
|
283
|
+
" { |#{method.block_params}| }"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def get_rails_http_method(method_name)
|
288
|
+
RAILS_RESOURCE_METHOD_AND_HTTP_METHOD[method_name] || 'get'
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
|
293
|
+
def parse_sexp(sexp, scopes, methods, stack = [])
|
294
|
+
case sexp[0]
|
295
|
+
when :module
|
296
|
+
parse_sexp(sexp[2], scopes, methods, stack + [sexp[0], sexp[1][1][1]])
|
297
|
+
|
298
|
+
when :vcall
|
299
|
+
name = sexp[1][1]
|
300
|
+
return if name.eql?('private')
|
301
|
+
|
302
|
+
when :command
|
303
|
+
if sexp[1][0] == :@ident && sexp[1][1] == 'scope'
|
304
|
+
name = sexp[2][1][0][1][1][1]
|
305
|
+
scopes << name
|
306
|
+
end
|
307
|
+
|
308
|
+
when :class
|
309
|
+
parse_sexp(sexp[3], scopes, methods, stack + [sexp[0], sexp[1][1][1]])
|
310
|
+
|
311
|
+
when :def
|
312
|
+
name = sexp[1][1]
|
313
|
+
# line_number = sexp[1][2][0]
|
314
|
+
|
315
|
+
parse_sexp(sexp[3], scopes, methods, stack + [sexp[0], sexp[1][1]])
|
316
|
+
|
317
|
+
# puts "#{line_number}: Method: #{stack.last}##{name}\n"
|
318
|
+
methods << name
|
319
|
+
else
|
320
|
+
if sexp.is_a?(Array)
|
321
|
+
sexp.each { |s| parse_sexp(s, scopes, methods, stack) if s.is_a?(Array) }
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
require 'ripper'
|
327
|
+
|
328
|
+
def scopes(_klass, file_path, spec_path)
|
329
|
+
content = File.read(file_path)
|
330
|
+
spec_content = (
|
331
|
+
begin
|
332
|
+
File.read(spec_path)
|
333
|
+
rescue
|
334
|
+
''
|
335
|
+
end)
|
336
|
+
sexp = Ripper.sexp(content)
|
337
|
+
methods = []
|
338
|
+
scopes = []
|
339
|
+
|
340
|
+
parse_sexp(sexp, scopes, methods)
|
341
|
+
|
342
|
+
scope_methods = []
|
343
|
+
scopes.each do |method|
|
344
|
+
unless spec_content.include?("'.#{method}'")
|
345
|
+
scope_methods << method
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
scope_methods
|
350
|
+
end
|
351
|
+
|
352
|
+
def signature(method)
|
353
|
+
"'#{decorated_name(method).sub('?', '\?').gsub('[', '\[').gsub(']', '\]')}'"
|
354
|
+
end
|
355
|
+
|
356
|
+
def colorize(text, color_code)
|
357
|
+
"#{color_code}#{text}\033[0m"
|
358
|
+
end
|
359
|
+
|
360
|
+
def red(text)
|
361
|
+
colorize(text, "\033[31m")
|
362
|
+
end
|
363
|
+
|
364
|
+
def green(text)
|
365
|
+
colorize(text, "\033[32m")
|
366
|
+
end
|
367
|
+
|
368
|
+
def blue(text)
|
369
|
+
colorize(text, "\033[34m")
|
370
|
+
end
|
371
|
+
|
372
|
+
def yellow(text)
|
373
|
+
colorize(text, "\033[33m")
|
374
|
+
end
|
375
|
+
|
376
|
+
def camelize(str)
|
377
|
+
str.split('_').map { |w| w.capitalize }.join
|
378
|
+
end
|
379
|
+
|
380
|
+
RAILS_RESOURCE_METHOD_AND_HTTP_METHOD = {
|
381
|
+
'index' => 'get',
|
382
|
+
'new' => 'get',
|
383
|
+
'create' => 'post',
|
384
|
+
'show' => 'get',
|
385
|
+
'edit' => 'get',
|
386
|
+
'update' => 'patch',
|
387
|
+
'destroy' => 'delete'
|
388
|
+
}.freeze
|
389
|
+
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rdoc'
|
4
|
+
require 'rdoc/generator'
|
5
|
+
require 'rdoc/options'
|
6
|
+
require 'rdoc/parser/ruby'
|
7
|
+
require 'rdoc/stats'
|
8
|
+
require 'rspec_jumpstart'
|
9
|
+
|
10
|
+
#
|
11
|
+
# RDoc instance factory
|
12
|
+
#
|
13
|
+
module RSpecJumpstart
|
14
|
+
class RDocFactory
|
15
|
+
|
16
|
+
#
|
17
|
+
# Returns RDoc::NormalClass/RDoc::NormalModule instance.
|
18
|
+
#
|
19
|
+
def self.get_rdoc_class_or_module(file_path)
|
20
|
+
top_level = get_ruby_parser(file_path).scan
|
21
|
+
extract_target_class_or_module(top_level)
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Creates new RDoc::Parser::Ruby instance.
|
26
|
+
#
|
27
|
+
def self.get_ruby_parser(file_path)
|
28
|
+
top_level = RDoc::TopLevel.new(file_path)
|
29
|
+
if RUBY_VERSION.to_f < 2.0
|
30
|
+
# reset is removed since 2.0
|
31
|
+
RDoc::TopLevel.reset
|
32
|
+
end
|
33
|
+
|
34
|
+
# RDoc::Stats initialization
|
35
|
+
if defined?(RDoc::Store)
|
36
|
+
# RDoc 4.0.0 requires RDoc::Store internally.
|
37
|
+
store = RDoc::Store.new
|
38
|
+
top_level.store = store
|
39
|
+
stats = RDoc::Stats.new(store, 1)
|
40
|
+
else
|
41
|
+
stats = RDoc::Stats.new(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
RDoc::Parser::Ruby.new(
|
45
|
+
top_level,
|
46
|
+
file_path,
|
47
|
+
File.read(file_path),
|
48
|
+
RDoc::Options.new,
|
49
|
+
stats
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Extracts RDoc::NormalClass/RDoc::NormalModule from RDoc::TopLevel.
|
55
|
+
#
|
56
|
+
def self.extract_target_class_or_module(top_level)
|
57
|
+
c = top_level.classes.first
|
58
|
+
if c.nil?
|
59
|
+
m = top_level.modules.first
|
60
|
+
if m.nil?
|
61
|
+
top_level.is_a?(RDoc::NormalModule) ? top_level : nil
|
62
|
+
else
|
63
|
+
extract_target_class_or_module(m)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
c
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-jumpstart
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Timothy Chambers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: rspec-jumpstart supports you writing tests for existing code.
|
14
|
+
email:
|
15
|
+
- tim@possibilogy.com
|
16
|
+
executables:
|
17
|
+
- rspec-jumpstart
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/rspec-jumpstart
|
22
|
+
- lib/rspec_jumpstart.rb
|
23
|
+
- lib/rspec_jumpstart/config.rb
|
24
|
+
- lib/rspec_jumpstart/erb_factory.rb
|
25
|
+
- lib/rspec_jumpstart/erb_templates.rb
|
26
|
+
- lib/rspec_jumpstart/generator.rb
|
27
|
+
- lib/rspec_jumpstart/rdoc_factory.rb
|
28
|
+
- lib/rspec_jumpstart/version.rb
|
29
|
+
homepage: https://github.com/tjchambers/rspec-jumpstart
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 2.5.1
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: rspec-jumpstart supports you writing tests for existing code.
|
53
|
+
test_files: []
|