rspec-jumpstart 1.1.1
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.
- 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: []
|