rspec_magic 0.1.2

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 (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: []