rspec_magic 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +3 -0
  4. data/.yardopts +7 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +36 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README-ru.md +328 -0
  9. data/README.md +328 -0
  10. data/Rakefile +15 -0
  11. data/lib/rspec_magic/config.rb +17 -0
  12. data/lib/rspec_magic/stable/alias_method.rb +26 -0
  13. data/lib/rspec_magic/stable/context_when.rb +101 -0
  14. data/lib/rspec_magic/stable/described_sym.rb +62 -0
  15. data/lib/rspec_magic/stable/use_letset.rb +95 -0
  16. data/lib/rspec_magic/stable/use_method_discovery.rb +75 -0
  17. data/lib/rspec_magic/stable.rb +13 -0
  18. data/lib/rspec_magic/unstable/include_dir_context.rb +39 -0
  19. data/lib/rspec_magic/unstable.rb +13 -0
  20. data/lib/rspec_magic/version.rb +5 -0
  21. data/lib/rspec_magic.rb +11 -0
  22. data/rspec_magic.gemspec +17 -0
  23. data/spec/lib/rspec_magic/alias_method_spec.rb +19 -0
  24. data/spec/lib/rspec_magic/context_when_spec.rb +45 -0
  25. data/spec/lib/rspec_magic/described_sym_spec.rb +39 -0
  26. data/spec/lib/rspec_magic/include_dir_context/README.md +2 -0
  27. data/spec/lib/rspec_magic/include_dir_context/_context.rb +4 -0
  28. data/spec/lib/rspec_magic/include_dir_context/app/_context.rb +4 -0
  29. data/spec/lib/rspec_magic/include_dir_context/app/idc_consumer_spec.rb +15 -0
  30. data/spec/lib/rspec_magic/include_dir_context/app/models/_context.rb +4 -0
  31. data/spec/lib/rspec_magic/include_dir_context/app/models/idc_consumer_spec.rb +15 -0
  32. data/spec/lib/rspec_magic/include_dir_context/idc_consumer_spec.rb +15 -0
  33. data/spec/lib/rspec_magic/use_letset_spec.rb +134 -0
  34. data/spec/lib/rspec_magic/use_method_discovery_spec.rb +24 -0
  35. data/spec/spec_helper.rb +9 -0
  36. data/spec/support/self.rb +6 -0
  37. data/spec/support/simplecov.rb +11 -0
  38. metadata +80 -0
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ "LODoc"
4
+
5
+ module RSpecMagic; module Stable
6
+ # Create an automatic +let+ variable containing the method or action name,
7
+ # computed from the description of the parent +describe+.
8
+ #
9
+ # describe do
10
+ # use_method_discovery :m
11
+ #
12
+ # subject { m }
13
+ #
14
+ # describe "#first_name" do
15
+ # it { is_expected.to eq :first_name }
16
+ # end
17
+ #
18
+ # describe ".some_stuff" do
19
+ # it { is_expected.to eq :some_stuff }
20
+ # end
21
+ #
22
+ # describe "GET some_action" do
23
+ # describe "intermediate context" do
24
+ # it { is_expected.to eq :some_action }
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ module UseMethodDiscovery
30
+ module Exports
31
+ # Enable the discovery mechanics.
32
+ # @param [Symbol] method_let
33
+ def use_method_discovery(method_let)
34
+ # This context and all sub-contexts will respond to A and return B. "Signature" is based on
35
+ # invocation arguments which can vary as we use the feature more intensively. Signature method
36
+ # is the same, thus it shadows higher level definitions completely.
37
+ signature = { method_let: method_let }
38
+ define_singleton_method(:_umd_signature) { signature }
39
+
40
+ let(method_let) do
41
+ # NOTE: `self.class` responds to signature method, no need to probe and rescue.
42
+ if (sig = (klass = self.class)._umd_signature) != signature
43
+ raise "`#{method_let}` is shadowed by `#{sig.fetch(:method_let)}` in this context"
44
+ end
45
+
46
+ # NOTE: Better not `return` from the loop to keep it debuggable in case logic changes.
47
+ found = nil
48
+ while (klass._umd_signature rescue nil) == signature
49
+ found = self.class.send(:_use_method_discovery_parser, klass.description.to_s) and break
50
+ klass = klass.superclass
51
+ end
52
+
53
+ found or raise "No method-like descriptions found to use as `#{method_let}`"
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # The parser used by {.use_method_discovery}.
60
+ # @param [String] input
61
+ # @return [Symbol] Method name, if parsed okay.
62
+ # @return [nil] If input isn't method-like.
63
+ def _use_method_discovery_parser(input)
64
+ if (mat = input.match(/^(?:(?:#|\.|::)(\w+(?:\?|!|=|)|\[\])|(?:DELETE|GET|PUT|POST) (\w+))$/))
65
+ (mat[1] || mat[2]).to_sym
66
+ end
67
+ end
68
+ end # Exports
69
+ end # module
70
+
71
+ # Activate.
72
+ defined?(RSpec) && RSpec.respond_to?(:configure) and RSpec.configure do |config|
73
+ config.extend UseMethodDiscovery::Exports
74
+ end
75
+ end; end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecMagic
4
+ # "Stable" feature set.
5
+ #
6
+ # # `spec/spec_helper.rb`.
7
+ # require "rspec_magic/stable"
8
+ #
9
+ module Stable
10
+ end
11
+ end
12
+
13
+ Dir[File.expand_path("../stable/**/*.rb", __FILE__)].each { |fn| require fn }
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../config"
4
+
5
+ module RSpecMagic; module Unstable
6
+ # Organize shared contexts in a hierarchy.
7
+ # Import relevant shared contexts into the given test.
8
+ #
9
+ # describe … do
10
+ # include_dir_context __dir__
11
+ # …
12
+ # end
13
+ #
14
+ # See the README file for more information.
15
+ module IncludeDirContext
16
+ module Exports
17
+ # Include the relevant shared contexts hierarchy.
18
+ # @param [String] dir
19
+ def include_dir_context(dir)
20
+ root = Config.spec_path
21
+
22
+ d, steps = dir, []
23
+ while d.start_with?(root)
24
+ steps << d
25
+ d = File.split(d).first
26
+ end
27
+
28
+ steps.reverse.each do |d|
29
+ begin; include_context d; rescue ArgumentError; end
30
+ end
31
+ end
32
+ end # Exports
33
+ end # module
34
+
35
+ # Activate.
36
+ defined?(RSpec) && RSpec.respond_to?(:configure) and RSpec.configure do |config|
37
+ config.extend IncludeDirContext::Exports
38
+ end
39
+ end; end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecMagic
4
+ # "Unstable" feature set.
5
+ #
6
+ # # `spec/spec_helper.rb`.
7
+ # require "rspec_magic/unstable"
8
+ #
9
+ module Unstable
10
+ end
11
+ end
12
+
13
+ Dir[File.expand_path("../unstable/**/*.rb", __FILE__)].each { |fn| require fn }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecMagic
4
+ VERSION = "0.1.2"
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load the "world" features.
4
+ require_relative "rspec_magic/config"
5
+ require_relative "rspec_magic/version"
6
+
7
+ # Actual features are NOT loaded automatically.
8
+
9
+ # A set of extensions for writing compact and expressive tests.
10
+ module RSpecMagic
11
+ end
@@ -0,0 +1,17 @@
1
+
2
+ require_relative "lib/rspec_magic/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "rspec_magic"
6
+ s.summary = "A set of extensions for writing compact and expressive tests"
7
+ s.version = RSpecMagic::VERSION
8
+
9
+ s.authors = ["Alex Fortuna"]
10
+ s.email = ["fortunadze@gmail.com"]
11
+ s.homepage = "https://github.com/dadooda/rspec_magic"
12
+ s.license = "MIT"
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.require_paths = ["lib"]
16
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
17
+ end
@@ -0,0 +1,19 @@
1
+
2
+ describe "`alias_method` matcher" do
3
+ describe "klass" do
4
+ let(:klass) do
5
+ Class.new do
6
+ def one; end
7
+ alias_method :one1, :one
8
+
9
+ def two; end
10
+ alias two2 two
11
+ end
12
+ end
13
+
14
+ subject { klass.new }
15
+
16
+ it { is_expected.to alias_method(:one1, :one) }
17
+ it { is_expected.to alias_method(:two2, :two) }
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+
2
+ require "json"
3
+
4
+ describe ".context_when" do
5
+ context "when default" do
6
+ context_when a: 1, "b" => 2, x: "y" do
7
+ description = self.description
8
+ it { expect([a, b, x]).to eq [1, 2, "y"] }
9
+ it { expect { c }.to raise_error(NameError, /^undefined local variable or method `c'/) }
10
+ it { expect(description).to eq 'when { a: 1, "b" => 2, x: "y" }' }
11
+ end
12
+ end
13
+
14
+ context "when customized" do
15
+ def self._context_when_formatter(h)
16
+ "when #{h.to_json}"
17
+ end
18
+
19
+ describe "intermediate context" do
20
+ context_when a: 1, x: "y" do
21
+ description = self.description
22
+ it { expect([a, x]).to eq [1, "y"] }
23
+ it { expect(description).to eq 'when {"a":1,"x":"y"}' }
24
+ end
25
+ end
26
+ end # context "when customized"
27
+
28
+ describe "doc examples" do
29
+ describe "*" do
30
+ context_when name: "Joe", age: 25 do
31
+ it do
32
+ expect([name, age]).to eq ["Joe", 25]
33
+ end
34
+ end
35
+
36
+ context "when { name: \"Joe\", age: 25 }" do
37
+ let(:name) { "Joe" }
38
+ let(:age) { 25 }
39
+ it do
40
+ expect([name, age]).to eq ["Joe", 25]
41
+ end
42
+ end
43
+ end
44
+ end # describe "doc examples"
45
+ end
@@ -0,0 +1,39 @@
1
+
2
+ describe "`#described_sym` and friends" do
3
+ use_method_discovery :m
4
+
5
+ # WARNING! These are real, global classes we create here.
6
+
7
+ class CSVRow; end
8
+
9
+ class PageTitle
10
+ class Style
11
+ end
12
+ end
13
+
14
+ subject { public_send(m) }
15
+
16
+ describe "#described_sym" do
17
+ context "when the class is named like …" do
18
+ describe CSVRow do
19
+ it { is_expected.to eq :csv_row }
20
+ end
21
+
22
+ describe PageTitle do
23
+ it { is_expected.to eq :page_title }
24
+ end
25
+
26
+ describe PageTitle::Style do
27
+ # NOTE: That's how it works. Not sure if that's utterly useful though.
28
+ it { is_expected.to eq :"page_title/style" }
29
+ end
30
+ end # context "when the class is named like …"
31
+ end
32
+
33
+ # Just ping it briefly.
34
+ describe "#me" do
35
+ describe CSVRow do
36
+ it { is_expected.to eq :csv_row }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,2 @@
1
+
2
+ This directory with all subdirectories is a single test.
@@ -0,0 +1,4 @@
1
+
2
+ shared_context __dir__ do
3
+ def global_sign; "root"; end
4
+ end
@@ -0,0 +1,4 @@
1
+
2
+ shared_context __dir__ do
3
+ def local_sign; "root/app"; end
4
+ end
@@ -0,0 +1,15 @@
1
+
2
+ describe "`.include_dir_context` at root/app" do
3
+ include_dir_context __dir__
4
+ use_method_discovery :m
5
+
6
+ subject { public_send(m) }
7
+
8
+ describe "#global_sign" do
9
+ it { is_expected.to eq "root" }
10
+ end
11
+
12
+ describe "#local_sign" do
13
+ it { is_expected.to eq "root/app" }
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+
2
+ shared_context __dir__ do
3
+ def local_sign; "root/app/models"; end
4
+ end
@@ -0,0 +1,15 @@
1
+
2
+ describe "`.include_dir_context` at root/app/models" do
3
+ include_dir_context __dir__
4
+ use_method_discovery :m
5
+
6
+ subject { public_send(m) }
7
+
8
+ describe "#global_sign" do
9
+ it { is_expected.to eq "root" }
10
+ end
11
+
12
+ describe "#local_sign" do
13
+ it { is_expected.to eq "root/app/models" }
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+
2
+ describe "`.include_dir_context` at root" do
3
+ include_dir_context __dir__
4
+ use_method_discovery :m
5
+
6
+ subject { public_send(m) }
7
+
8
+ describe "#global_sign" do
9
+ it { is_expected.to eq "root" }
10
+ end
11
+
12
+ describe "#local_sign" do
13
+ it { expect { subject }.to raise_error(NoMethodError) }
14
+ end
15
+ end
@@ -0,0 +1,134 @@
1
+
2
+ describe ".use_letset" do
3
+ describe "straight usage" do
4
+ use_letset :let_a, :attrs
5
+ use_letset :let_d, :data
6
+
7
+ # Same-level definition. Important case.
8
+ let_a(:name) { "Joe" }
9
+
10
+ let_d(:number) { 12 }
11
+
12
+ describe "attrs" do
13
+ let_a(:age) { 25 }
14
+
15
+ describe "sub-context" do
16
+ # Redefinition via `let`.
17
+ let_a(:age) { 36 }
18
+ let_a(:gender) { :male }
19
+ it do
20
+ expect(name).to eq "Joe"
21
+ expect(age).to eq 36
22
+ expect(gender).to eq :male
23
+ expect(attrs).to eq(name: "Joe", age: 36, gender: :male)
24
+ end
25
+ end
26
+
27
+ it do
28
+ expect(name).to eq "Joe"
29
+ expect(age).to eq 25
30
+ expect(attrs).to eq(name: "Joe", age: 25)
31
+ end
32
+ end
33
+
34
+ describe "data" do
35
+ it do
36
+ expect(number).to eq 12
37
+ expect(data).to eq(number: 12)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "declarative (no block) usage" do
43
+ use_letset :let_a, :attrs
44
+
45
+ let_a(:name)
46
+
47
+ subject { attrs }
48
+
49
+ context "when no `let` value" do
50
+ context "when additional other `let` value" do
51
+ let_a(:age) { 25 }
52
+ it { is_expected.to eq(age: 25) }
53
+ end
54
+
55
+ it do
56
+ expect { name }.to raise_error RSpec::Core::ExampleGroup::WrongScopeError
57
+ expect(attrs).to eq({})
58
+ end
59
+ end
60
+
61
+ context "when `let`" do
62
+ let(:name) { "Joe" }
63
+ it { is_expected.to eq(name: "Joe") }
64
+ end
65
+
66
+ context "when `let_a`" do
67
+ let_a(:name) { "Joe" }
68
+ it { is_expected.to eq(name: "Joe") }
69
+ end
70
+
71
+ # Real-life thing follows, that's what we need declarative mode for.
72
+ context_when name: "Joe" do
73
+ context_when name: "Moe" do
74
+ it { is_expected.to eq(name: "Moe") }
75
+ end
76
+
77
+ it { is_expected.to eq(name: "Joe") }
78
+ end
79
+ end # describe "declarative (no block) usage"
80
+
81
+ describe "doc examples" do
82
+ describe "EN" do
83
+ describe do
84
+ # Method is `let_a`. Collection is `attrs`.
85
+ use_letset :let_a, :attrs
86
+
87
+ # Declare `attrs` elements.
88
+ let_a(:age)
89
+ let_a(:name)
90
+
91
+ subject { attrs }
92
+
93
+ # None of the elements is set yet.
94
+ it { is_expected.to eq({}) }
95
+
96
+ # Set `name` and see it in the collection.
97
+ context_when name: "Joe" do
98
+ it { is_expected.to eq(name: "Joe") }
99
+
100
+ # Add `age` and see both in the collection.
101
+ context_when age: 25 do
102
+ it { is_expected.to eq(name: "Joe", age: 25) }
103
+ end
104
+ end
105
+ end
106
+ end # describe "EN"
107
+
108
+ describe "RU" do
109
+ describe do
110
+ # Метод -- `let_a`. Коллекция -- `attrs`.
111
+ use_letset :let_a, :attrs
112
+
113
+ # Декларируем переменные, которые составляют коллекцию `attrs`.
114
+ let_a(:age)
115
+ let_a(:name)
116
+
117
+ subject { attrs }
118
+
119
+ # Ни одна переменная пока не задана, поэтому коллекция будет пустой.
120
+ it { is_expected.to eq({}) }
121
+
122
+ # Задаём `name` и видим его в коллекции.
123
+ context_when name: "Joe" do
124
+ it { is_expected.to eq(name: "Joe") }
125
+
126
+ # Задаём `age` и видим обе переменные в коллекции.
127
+ context_when age: 25 do
128
+ it { is_expected.to eq(name: "Joe", age: 25) }
129
+ end
130
+ end
131
+ end
132
+ end # describe "RU"
133
+ end # describe "doc examples"
134
+ end
@@ -0,0 +1,24 @@
1
+
2
+ describe ".use_method_discovery" do
3
+ describe "doc examples" do
4
+ describe do
5
+ use_method_discovery :m
6
+
7
+ subject { m }
8
+
9
+ describe "#first_name" do
10
+ it { is_expected.to eq :first_name }
11
+ end
12
+
13
+ describe ".some_stuff" do
14
+ it { is_expected.to eq :some_stuff }
15
+ end
16
+
17
+ describe "GET some_action" do
18
+ describe "intermediate context" do
19
+ it { is_expected.to eq :some_action } # (1)
20
+ end
21
+ end
22
+ end
23
+ end # describe "doc examples"
24
+ end
@@ -0,0 +1,9 @@
1
+
2
+ # Must go before all.
3
+ require_relative "support/simplecov"
4
+
5
+ # Load support files.
6
+ Dir[File.expand_path("support/**/*.rb", __dir__)].each { |fn| require fn }
7
+
8
+ # Load shared context hierarchy.
9
+ Dir[File.expand_path("**/_context.rb", __dir__)].each { |fn| require fn }
@@ -0,0 +1,6 @@
1
+
2
+ require "rspec_magic/stable"
3
+ require "rspec_magic/unstable"
4
+
5
+ # Required by some features.
6
+ RSpecMagic::Config.spec_path = File.expand_path("..", __dir__)
@@ -0,0 +1,11 @@
1
+
2
+ #
3
+ # See https://github.com/simplecov-ruby/simplecov#getting-started.
4
+ #
5
+
6
+ require "simplecov"
7
+
8
+ SimpleCov.start do
9
+ add_filter "libx/"
10
+ add_filter "spec/"
11
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec_magic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Alex Fortuna
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - fortunadze@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".yardopts"
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - MIT-LICENSE
26
+ - README-ru.md
27
+ - README.md
28
+ - Rakefile
29
+ - lib/rspec_magic.rb
30
+ - lib/rspec_magic/config.rb
31
+ - lib/rspec_magic/stable.rb
32
+ - lib/rspec_magic/stable/alias_method.rb
33
+ - lib/rspec_magic/stable/context_when.rb
34
+ - lib/rspec_magic/stable/described_sym.rb
35
+ - lib/rspec_magic/stable/use_letset.rb
36
+ - lib/rspec_magic/stable/use_method_discovery.rb
37
+ - lib/rspec_magic/unstable.rb
38
+ - lib/rspec_magic/unstable/include_dir_context.rb
39
+ - lib/rspec_magic/version.rb
40
+ - rspec_magic.gemspec
41
+ - spec/lib/rspec_magic/alias_method_spec.rb
42
+ - spec/lib/rspec_magic/context_when_spec.rb
43
+ - spec/lib/rspec_magic/described_sym_spec.rb
44
+ - spec/lib/rspec_magic/include_dir_context/README.md
45
+ - spec/lib/rspec_magic/include_dir_context/_context.rb
46
+ - spec/lib/rspec_magic/include_dir_context/app/_context.rb
47
+ - spec/lib/rspec_magic/include_dir_context/app/idc_consumer_spec.rb
48
+ - spec/lib/rspec_magic/include_dir_context/app/models/_context.rb
49
+ - spec/lib/rspec_magic/include_dir_context/app/models/idc_consumer_spec.rb
50
+ - spec/lib/rspec_magic/include_dir_context/idc_consumer_spec.rb
51
+ - spec/lib/rspec_magic/use_letset_spec.rb
52
+ - spec/lib/rspec_magic/use_method_discovery_spec.rb
53
+ - spec/spec_helper.rb
54
+ - spec/support/self.rb
55
+ - spec/support/simplecov.rb
56
+ homepage: https://github.com/dadooda/rspec_magic
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.5.2
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: A set of extensions for writing compact and expressive tests
80
+ test_files: []