r_spec 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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"