r_spec 1.0.0.beta1 → 1.0.0.beta2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ffdec1b2b30ba9271129d9332872597dbc00204d92495e4cbfa1f3b00896d8f
4
- data.tar.gz: 59c6a96b46b865ea72ecd3b84f80189f8173550447bed8820e1156130ba02c67
3
+ metadata.gz: d95780dc86d2b0c2295f22fe18ed889298716d8a817ba4a74fbd1c9c1ec71c03
4
+ data.tar.gz: a954787c7ad5a50c1d3a49d5b1be21515dc0cad0ecd5f7fa618ee736014add2f
5
5
  SHA512:
6
- metadata.gz: 60c275a1bbe617d37623707f45cd7d8cb0673354a7e1751873ecf08e84160f77030530a284b0d759cb32705599a64d5a0bb2287acd10c8dbb3bd0f8b0a1328fa
7
- data.tar.gz: 19f8fa7e5727b596a11d2865fedf26953198d09604cdc2150ef037e88d78913305c5a257d4289e270a3697f4555524973df46ea081da1502082c25b864704a4d
6
+ metadata.gz: 338b22198f2f09e9ae3a36184027f23f728ebdc7b691e0ef3f05d111bf3d753c829d21388dba52fdc8be0e8ba452deea5cc08d38c848e932d67fb1e448769600
7
+ data.tar.gz: 6601841bac1ed4603c327539fc5b123b9990cfa483b42504ed18dc8680d9c304636dbff10a05ebdc6f76de6d11ac3190b9ad77453aaeb978f853360f9330e04e
data/README.md CHANGED
@@ -18,10 +18,10 @@ This clone attempts to provide most of RSpec's DSL without magic power, so that
18
18
 
19
19
  * Less features and an implementation with much less code complexity.
20
20
  * Spec files can also be executed directly with the `ruby` executable.
21
- * There cannot be more than one expectation per example.
22
21
  * There is no option to activate monkey-patching.
23
22
  * Does not rely on hacks such as `at_exit` hook to trigger the tests.
24
23
  * Built-in matchers do not trust _actual_ and do not send it any message.
24
+ * The subject must be explicitly defined, otherwise it is not implemented.
25
25
 
26
26
  ## Important ⚠️
27
27
 
@@ -40,7 +40,7 @@ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-ge
40
40
  Add this line to your application's Gemfile:
41
41
 
42
42
  ```ruby
43
- gem "r_spec", ">= 1.0.0.beta1"
43
+ gem "r_spec", ">= 1.0.0.beta2"
44
44
  ```
45
45
 
46
46
  And then execute:
@@ -57,22 +57,32 @@ gem install r_spec --pre
57
57
 
58
58
  ## Usage
59
59
 
60
- Given this `greeting_spec.rb` spec:
60
+ Let's test an array:
61
61
 
62
62
  ```ruby
63
- require "r_spec"
63
+ # array_spec.rb
64
64
 
65
- greeting = "Hello, world!"
65
+ require "r_spec"
66
66
 
67
- RSpec.describe String do
68
- context "Alice" do
69
- before { greeting.gsub!("world", "Alice") }
70
- it { expect(greeting).to eql "Hello, Alice!" }
67
+ RSpec.describe Array do
68
+ before do
69
+ @elements = described_class.new
71
70
  end
72
71
 
73
- context "Bob" do
74
- before { greeting.gsub!("world", "Bob") }
75
- it { expect(greeting).to eql "Hello, Bob!" }
72
+ describe "#count" do
73
+ subject do
74
+ @elements.count
75
+ end
76
+
77
+ it { is_expected.to be 0 }
78
+
79
+ context "when a new element is added" do
80
+ before do
81
+ @elements << 1
82
+ end
83
+
84
+ it { is_expected.to be 1 }
85
+ end
76
86
  end
77
87
  end
78
88
  ```
@@ -80,22 +90,25 @@ end
80
90
  It can be tested in the console with the command:
81
91
 
82
92
  ```sh
83
- ruby greeting_spec.rb
93
+ ruby array_spec.rb
84
94
  ```
85
95
 
86
- > ..
87
- >
88
- > Ran 2 tests in 0.010994 seconds
89
- > 100% compliant - 0 infos, 0 failures, 0 errors
96
+ array_spec.rb:15 Success: expected to be 0.
97
+ array_spec.rb:22 Success: expected to be 1.
98
+
99
+ ## Test suite
100
+
101
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
90
102
 
91
103
  ## Contact
92
104
 
93
- * Home page: https://r-spec.dev/
105
+ * Home page: https://r-spec.dev
94
106
  * Source code: https://github.com/cyril/r_spec.rb
107
+ * Twitter: https://twitter.com/cyri_
95
108
 
96
109
  ## Versioning
97
110
 
98
- __R_Spec__ follows [Semantic Versioning 2.0](https://semver.org/).
111
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
99
112
 
100
113
  ## License
101
114
 
data/lib/r_spec.rb CHANGED
@@ -1,11 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Namespace for the RSpec framework.
3
+ # Top level namespace for the RSpec clone.
4
4
  #
5
- # @api public
5
+ # @example
6
+ # require "r_spec"
7
+ #
8
+ # RSpec.describe Integer do
9
+ # it { expect(41.next).to be 42 }
10
+ # end
6
11
  #
12
+ # @api public
7
13
  module RSpec
8
14
  # Specs are built with this method.
15
+ #
16
+ # @param const [Module] A module to include in block context.
17
+ # @param block [Proc] The block to define the specs.
18
+ #
19
+ # @api public
9
20
  def self.describe(const, &block)
10
21
  raise ::TypeError, const.class.inspect unless const.is_a?(::Module)
11
22
 
data/lib/r_spec/dsl.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "expresenter"
4
3
  require "matchi/rspec"
5
4
  require "securerandom"
6
5
 
7
6
  module RSpec
7
+ # Abstract class for handling the domain-specific language.
8
8
  class DSL
9
+ # @param block [Proc] The content to execute at the class initialization.
9
10
  def self.before(&block)
10
11
  define_method(:initialize) do |*args, **kwargs|
11
12
  super()
@@ -13,14 +14,22 @@ module RSpec
13
14
  end
14
15
  end
15
16
 
17
+ # @param block [Proc] The content of the method to define.
18
+ # @return [Symbol] A protected method that define the block content.
16
19
  def self.let(name, &block)
17
20
  protected define_method(name.to_sym, &block)
18
21
  end
19
22
 
23
+ # @param block [Proc] The subject to set.
24
+ # @return [Symbol] A `subject` method that define the block content.
20
25
  def self.subject(&block)
21
26
  let(__method__, &block)
22
27
  end
23
28
 
29
+ # Describe a set of expectations.
30
+ #
31
+ # @param const [Module, #object_id] A module to include in block context.
32
+ # @param block [Proc] The block to define the specs.
24
33
  def self.describe(const, &block)
25
34
  desc = Test.const_set("Test#{random_str}", ::Class.new(self))
26
35
 
@@ -35,46 +44,71 @@ module RSpec
35
44
 
36
45
  singleton_class.send(:alias_method, :context, :describe)
37
46
 
38
- def self.it(name = "test_#{random_str.downcase}", &block)
39
- raise ::ArgumentError, "Missing block for #{name.inspect} test" unless block
47
+ # Evaluate an expectation.
48
+ #
49
+ # @param block [Proc] An expectation to evaluate.
50
+ #
51
+ # @raise (see ExpectationTarget#result)
52
+ # @return (see ExpectationTarget#result)
53
+ def self.it(_name = nil, &block)
54
+ raise ::ArgumentError, "Missing block" unless block
40
55
 
41
- path_info = block.source_location.join(":")
42
- print "\e[3m#{path_info}\e[23m "
56
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
43
57
 
44
58
  i = example.new
45
59
  i.instance_eval(&block)
46
60
  end
47
61
 
62
+ # @private
63
+ #
64
+ # @return [Class<DSL>] The class of the example to be tested.
48
65
  private_class_method def self.example
49
66
  ::Class.new(self) do
50
67
  include ::Matchi::Helper
51
68
 
52
69
  private
53
70
 
71
+ # Wraps the target of an expectation with the actual value.
72
+ #
73
+ # @param actual [#object_id] The actual value.
74
+ #
75
+ # @return [ExpectationTarget] The target of the expectation.
54
76
  def expect(actual)
55
- undef expect
56
- undef is_expected
57
-
58
- Expect.new(actual)
77
+ ExpectationTarget.new(actual)
59
78
  end
60
79
 
61
- # rubocop:disable Naming/PredicateName
80
+ # Wraps the target of an expectation with the subject as actual value.
81
+ #
82
+ # @return [ExpectationTarget] (see #expect)
62
83
  def is_expected
63
84
  expect(subject)
64
85
  end
65
- # rubocop:enable Naming/PredicateName
66
86
 
87
+ def log(message)
88
+ Log.result(message)
89
+ end
90
+
91
+ # Indicate that an example is disabled pending some action.
92
+ #
93
+ # @param description [String] The reason why the example is pending.
94
+ #
95
+ # @return [nil] Write a message to STDOUT.
67
96
  def pending(description)
68
- puts Requirement.pending(description).colored_string
97
+ Pending.result(description)
69
98
  end
70
99
  end
71
100
  end
72
101
 
102
+ # @private
103
+ #
104
+ # @return [String] A random string.
73
105
  private_class_method def self.random_str
74
106
  ::SecureRandom.alphanumeric(5)
75
107
  end
76
108
  end
77
109
  end
78
110
 
79
- require_relative "expect"
111
+ require_relative "expectation_target"
112
+ require_relative "log"
113
+ require_relative "pending"
80
114
  require_relative "test"
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter"
4
+
5
+ module RSpec
6
+ # Wraps the target of an expectation.
7
+ #
8
+ # @example
9
+ # expect(something) # => ExpectationTarget wrapping something
10
+ #
11
+ # # used with `to`
12
+ # expect(actual).to be(42)
13
+ #
14
+ # # with `not_to`
15
+ # expect(actual).not_to be(4)
16
+ #
17
+ # @note `ExpectationTarget` is not intended to be instantiated directly by
18
+ # users. Use `expect` instead.
19
+ class ExpectationTarget
20
+ # Instantiate a new expectation target.
21
+ #
22
+ # @param actual [#object_id] The actual value.
23
+ #
24
+ # @api private
25
+ def initialize(actual)
26
+ @actual = actual
27
+ end
28
+
29
+ # Runs the given expectation, passing if `matcher` returns true.
30
+ #
31
+ # @example _Absolute requirement_ definition
32
+ # expect("foo".upcase).to eq("foo")
33
+ #
34
+ # @param matcher [#matches?] The matcher.
35
+ #
36
+ # @raise (see #result)
37
+ # @return (see #result)
38
+ def to(matcher)
39
+ absolute_requirement(matcher: matcher, negate: false)
40
+ end
41
+
42
+ # Runs the given expectation, passing if `matcher` returns false.
43
+ #
44
+ # @example _Absolute prohibition_ definition
45
+ # expect("foo".size).not_to be(4)
46
+ #
47
+ # @param (see #to)
48
+ #
49
+ # @raise (see #result)
50
+ # @return (see #result)
51
+ def not_to(matcher)
52
+ absolute_requirement(matcher: matcher, negate: true)
53
+ end
54
+
55
+ protected
56
+
57
+ # @param matcher [#matches?] The matcher.
58
+ # @param negate [Boolean] Positive or negative assertion?
59
+ #
60
+ # @raise (see #result)
61
+ # @return (see #result)
62
+ def absolute_requirement(matcher:, negate:)
63
+ result(
64
+ matcher: matcher,
65
+ negate: negate,
66
+ passed: negate ^ matcher.matches? { @actual }
67
+ )
68
+ end
69
+
70
+ # @param matcher [#matches?] The matcher.
71
+ # @param negate [Boolean] Positive or negative assertion?
72
+ # @param passed [Boolean] The result of an expectation.
73
+ #
74
+ # @return [nil] Write a message to STDOUT.
75
+ #
76
+ # @raise [SystemExit] Terminate execution immediately by calling
77
+ # `Kernel.exit(false)` with a failure message written to STDERR.
78
+ def result(matcher:, negate:, passed:)
79
+ puts " " + ::Expresenter.call(passed).with(
80
+ actual: @actual,
81
+ error: nil,
82
+ expected: matcher.expected,
83
+ got: passed,
84
+ negate: negate,
85
+ valid: passed,
86
+ matcher: matcher.class.to_sym,
87
+ level: :MUST
88
+ ).colored_string
89
+ rescue ::Expresenter::Fail => e
90
+ abort " #{e.colored_string}"
91
+ end
92
+ end
93
+ end
data/lib/r_spec/log.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter"
4
+
5
+ module RSpec
6
+ # Exception for debugging purpose.
7
+ class Log < ::NoMethodError
8
+ # @param message [String] A message to log to the console.
9
+ #
10
+ # @return [nil] Write a log message to STDOUT.
11
+ def self.result(message)
12
+ puts " " + ::Expresenter.call(true).with(
13
+ actual: nil,
14
+ error: new(message),
15
+ expected: 42,
16
+ got: nil,
17
+ matcher: :be,
18
+ negate: false,
19
+ level: :MAY,
20
+ valid: false
21
+ ).colored_string
22
+ end
23
+ end
24
+ end
@@ -1,8 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "expresenter"
4
+
3
5
  module RSpec
4
- # rubocop:disable Lint/InheritException
6
+ # Exception for pending expectations.
5
7
  class Pending < ::NotImplementedError
8
+ # @param message [String] The not implemented expectation description.
9
+ #
10
+ # @return [nil] Write a pending expectation to STDOUT.
11
+ def self.result(message)
12
+ warn " " + ::Expresenter.call(true).with(
13
+ actual: new(message),
14
+ error: nil,
15
+ expected: self,
16
+ got: false,
17
+ matcher: :raise_exception,
18
+ negate: true,
19
+ level: :SHOULD,
20
+ valid: false
21
+ ).colored_string
22
+ end
6
23
  end
7
- # rubocop:enable Lint/InheritException
8
24
  end
data/lib/r_spec/test.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpec
4
- # Set of tests.
5
- #
6
- # rubocop:disable Lint/EmptyClass
7
- class Test
4
+ # Namespace to collect test classes.
5
+ module Test
8
6
  end
9
- # rubocop:enable Lint/EmptyClass
10
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: r_spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-30 00:00:00.000000000 Z
11
+ date: 2021-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expresenter
@@ -174,10 +174,9 @@ files:
174
174
  - README.md
175
175
  - lib/r_spec.rb
176
176
  - lib/r_spec/dsl.rb
177
- - lib/r_spec/exam.rb
178
- - lib/r_spec/expect.rb
177
+ - lib/r_spec/expectation_target.rb
178
+ - lib/r_spec/log.rb
179
179
  - lib/r_spec/pending.rb
180
- - lib/r_spec/requirement.rb
181
180
  - lib/r_spec/test.rb
182
181
  homepage: https://r-spec.dev/
183
182
  licenses:
data/lib/r_spec/exam.rb DELETED
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpec
4
- # This class evaluate the expectation with the given value.
5
- class Exam
6
- # Execute the untested code from the given value against the matcher.
7
- #
8
- # @param actual [#object_id] The actual object to test.
9
- # @param negate [Boolean] Positive or negative assertion?
10
- # @param matcher [#matches?] The matcher.
11
- def initialize(actual:, negate:, matcher:)
12
- @actual = actual
13
- @got = negate ^ matcher.matches? { actual }
14
- end
15
- # @return [#object_id] The actual value.
16
- attr_reader :actual
17
-
18
- # @return [Boolean] Report to the spec requirement level if the
19
- # expectation is true or false.
20
- attr_reader :got
21
-
22
- # Report to the spec requirement if the test pass or fail.
23
- #
24
- # @return [Boolean] Report if the test pass or fail?
25
- def valid?
26
- got
27
- end
28
- end
29
- end
data/lib/r_spec/expect.rb DELETED
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpec
4
- class Expect
5
- def initialize(actual)
6
- @actual = actual
7
- end
8
-
9
- def to(matcher)
10
- Requirement.new(actual: @actual, matcher: matcher, negate: false).call
11
- end
12
-
13
- def not_to(matcher)
14
- Requirement.new(actual: @actual, matcher: matcher, negate: true).call
15
- end
16
- end
17
- end
18
-
19
- require_relative "requirement"
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "expresenter"
4
-
5
- module RSpec
6
- class Requirement
7
- def self.pending(description)
8
- ::Expresenter.call(true).new(
9
- actual: nil,
10
- error: Pending.new(description),
11
- expected: nil,
12
- got: nil,
13
- matcher: :eql,
14
- negate: false,
15
- level: :MAY,
16
- valid: false
17
- )
18
- end
19
-
20
- # Initialize the requirement class.
21
- #
22
- # @param actual [#object_id] The actual object to test.
23
- # @param matcher [#matches?] The matcher.
24
- # @param negate [Boolean] Positive or negative assertion?
25
- def initialize(actual:, matcher:, negate:)
26
- @exam = Exam.new(actual: actual, negate: negate, matcher: matcher)
27
- @matcher = matcher
28
- @negate = negate
29
- @result = expectation_result
30
- end
31
-
32
- # @return [Exam] The exam.
33
- attr_reader :exam
34
-
35
- # @return [#matches?] The matcher that performed a boolean comparison
36
- # between the actual value and the expected value.
37
- attr_reader :matcher
38
-
39
- # @return [Expresenter::Fail, Expresenter::Pass] The test result.
40
- attr_reader :result
41
-
42
- # Evaluate the expectation.
43
- #
44
- # @return [Boolean] Report if the expectation pass or fail?
45
- def pass?
46
- exam.valid?
47
- end
48
-
49
- # The consequence of the expectation.
50
- #
51
- # @return [nil] The test passed.
52
- # @raise [SystemExit] The test failed.
53
- def call
54
- if result.passed?
55
- puts result.colored_string
56
- else
57
- abort result.colored_string
58
- end
59
- end
60
-
61
- protected
62
-
63
- # @note The boolean comparison between the actual value and the expected
64
- # value can be evaluated to a negative assertion.
65
- #
66
- # @return [Boolean] Positive or negative assertion?
67
- def negate?
68
- @negate
69
- end
70
-
71
- # The result of the expectation.
72
- #
73
- # @return [Expresenter::Fail, Expresenter::Pass] The test result.
74
- def expectation_result
75
- ::Expresenter.call(pass?).new(
76
- actual: exam.actual,
77
- error: nil,
78
- expected: matcher.expected,
79
- got: exam.got,
80
- negate: negate?,
81
- valid: exam.valid?,
82
- matcher: matcher.class.to_sym,
83
- level: :MUST
84
- )
85
- end
86
- end
87
- end
88
-
89
- require_relative "exam"
90
- require_relative "pending"