fix 1.0.0.beta4 → 1.0.0.beta8

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: 6245525d719965043d4187cbcce446e21c2b946303048159a13cc37161851ada
4
- data.tar.gz: c5b10da14f591df283e832f15f4839b420ecfbcbca4af6ed2974b28c20815d15
3
+ metadata.gz: 9d095777befeb78f0e16a218f3f14c0f8b39955265db3848cd26a0afb0b224f3
4
+ data.tar.gz: d918e8bfd693765450dfb14477f4fe9285642e22a35995446b89acac78662837
5
5
  SHA512:
6
- metadata.gz: 9ba29c713607b3cfe280c7eb82e9eee07adc69b8d0c58c0ab9cf54117befc5a0f35c76c32d93cd0e5c4bf911a5c09786109ecc336d4866ada3e7d79f0296551b
7
- data.tar.gz: 6267c5c89cd4a6cf4a7573bb4866e4855ed10a403cd2cb606fbe9a18f6ff3f3206559e01f8c349f0ca82d6b8a60fed13546dce80744b0967ca9ac3aeda2d381b
6
+ metadata.gz: c063f295f4d133635d70da15659c613018b51931db21d40c3bedb7db9b442fa756e8ed52833fc49333a816749df99c73956d43c237159f7bd6e728ea36e7791d
7
+ data.tar.gz: 1e4317121e610e67d0d396571b86e0a82b07c16122cc0be5c2c7ee5514136d9ec349282b643f06fe6d7f7b33cd04584048cf72ec3d8d88345e93f1135e7d5dee
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2020 Cyril Kato
3
+ Copyright (c) 2014-2021 Cyril Kato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,91 +1,120 @@
1
1
  # Fix
2
2
 
3
- [![Build Status](https://api.travis-ci.org/fixrb/fix.svg?branch=master)][travis]
4
- [![Code Climate](https://codeclimate.com/github/fixrb/fix/badges/gpa.svg)][codeclimate]
5
- [![Gem Version](https://badge.fury.io/rb/fix.svg)][gem]
6
- [![Inline docs](https://inch-ci.org/github/fixrb/fix.svg?branch=master)][inchpages]
7
- [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
8
- [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
3
+ [![Home](https://img.shields.io/badge/Home-fixrb.dev-00af8b)](https://fixrb.dev/)
4
+ [![Version](https://img.shields.io/github/v/tag/fixrb/fix?label=Version&logo=github)](https://github.com/fixrb/fix/releases)
5
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/fix/main)
6
+ [![CI](https://github.com/fixrb/fix/workflows/CI/badge.svg?branch=main)](https://github.com/fixrb/fix/actions?query=workflow%3Aci+branch%3Amain)
7
+ [![RuboCop](https://github.com/fixrb/fix/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/fix/actions?query=workflow%3Arubocop+branch%3Amain)
8
+ [![License](https://img.shields.io/github/license/fixrb/fix?label=License&logo=github)](https://github.com/fixrb/fix/raw/main/LICENSE.md)
9
9
 
10
- > Specing framework for Ruby.
10
+ ⚠️ This project is still in the experimental phase. May be used at your own risk.
11
+
12
+ ![Fix specing framework for Ruby](https://fixrb.dev/fix.webp)
13
+
14
+ ## Project goals
15
+
16
+ * Extract specs from the tests.
17
+ * Look like English documents.
18
+ * Be minimalist and easy to use.
19
+ * Run tests quickly.
11
20
 
12
21
  ## Installation
13
22
 
14
23
  Add this line to your application's Gemfile:
15
24
 
16
25
  ```ruby
17
- gem 'fix', '>= 1.0.0.beta4'
26
+ gem "fix", ">= 1.0.0.beta8"
18
27
  ```
19
28
 
20
29
  And then execute:
21
30
 
22
- $ bundle
31
+ ```sh
32
+ bundle
33
+ ```
23
34
 
24
35
  Or install it yourself as:
25
36
 
26
- $ gem install fix --pre
37
+ ```sh
38
+ gem install fix --pre
39
+ ```
27
40
 
28
41
  ## Example
29
42
 
30
- Given this app:
43
+ Given these specifications:
31
44
 
32
45
  ```ruby
33
- # examples/duck/app.rb
34
- class Duck
35
- def walks
36
- 'Klop klop!'
46
+ # examples/duck/fix.rb
47
+
48
+ require "fix"
49
+
50
+ Fix :Duck do
51
+ it SHOULD be_an_instance_of :Duck
52
+
53
+ on :swims do
54
+ it MUST be_an_instance_of :String
55
+ it MUST eql "Swoosh..."
37
56
  end
38
57
 
39
- def swims
40
- 'Swoosh...'
58
+ on :speaks do
59
+ it MUST raise_exception NoMethodError
41
60
  end
42
61
 
43
- def quacks
44
- puts 'Quaaaaaack!'
62
+ on :sings do
63
+ it MAY eql "♪... ♫..."
45
64
  end
46
65
  end
47
66
  ```
48
67
 
49
- When you run this:
68
+ When we load this `Duck` application:
50
69
 
51
70
  ```ruby
52
- # examples/duck/fix.rb
53
- require_relative 'app'
54
- require_relative '../../lib/fix'
55
-
56
- @bird = Duck.new
57
-
58
- Fix(@bird) do
59
- on :swims do
60
- it { MUST eql 'Swoosh...' }
71
+ # examples/duck/app.rb
72
+ class Duck
73
+ def walks
74
+ "Klop klop!"
61
75
  end
62
76
 
63
- on :speaks do
64
- it { MUST raise_exception NoMethodError }
77
+ def swims
78
+ "Swoosh..."
65
79
  end
66
80
 
67
- on :sings do
68
- it { MAY eql '♪... ♫...' }
81
+ def quacks
82
+ puts "Quaaaaaack!"
69
83
  end
70
84
  end
71
85
  ```
72
86
 
73
- Then the output should look like this:
87
+ And we run this test:
88
+
89
+ ```ruby
90
+ # examples/duck/test.rb
91
+
92
+ require_relative "app"
93
+ require_relative "fix"
94
+
95
+ Fix[:Duck].against { Duck.new }
96
+ ```
74
97
 
75
98
  ```sh
76
- ruby examples/duck/fix.rb
99
+ ruby examples/duck/test.rb
77
100
  ```
78
101
 
102
+ We should see this output:
103
+
79
104
  ```txt
80
- examples/duck/fix.rb:8: Success: expected to eql "Swoosh...".
81
- examples/duck/fix.rb:12: Success: undefined method `speaks' for #<Duck:0x00007fe3be868ea0>.
82
- examples/duck/fix.rb:16: NoMethodError: undefined method `sings' for #<Duck:0x00007fe3be868ea0>.
105
+ (irb):3 Success: expected #<Duck:0x00007fb2fa208708> to be an instance of Duck.
106
+ (irb):7 Success: expected to eq "Swoosh...".
107
+ (irb):15 NoMethodError: undefined method `sings' for #<Duck:0x00007fb2fd8371d0>.
108
+ (irb):6 Success: expected "Swoosh..." to be an instance of String.
109
+ (irb):11 Success: undefined method `speaks' for #<Duck:0x00007fb2fcc79258>.
83
110
  ```
84
111
 
85
112
  ## Contact
86
113
 
87
- * Home page: https://fixrb.dev/
88
- * Source code: https://github.com/fixrb/fix
114
+ * Home page: [https://fixrb.dev/](https://fixrb.dev/)
115
+ * Source code: [https://github.com/fixrb/fix](https://github.com/fixrb/fix)
116
+ * API Doc: [https://rubydoc.info/gems/fix](https://rubydoc.info/gems/fix)
117
+ * Twitter: [https://twitter.com/fix\_rb](https://twitter.com/fix\_rb)
89
118
 
90
119
  ## Versioning
91
120
 
@@ -93,20 +122,13 @@ __Fix__ follows [Semantic Versioning 2.0](https://semver.org/).
93
122
 
94
123
  ## License
95
124
 
96
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
125
+ The [gem](https://rubygems.org/gems/fix) is available as open source under the terms of the [MIT License](https://github.com/fixrb/fix/raw/main/LICENSE.md).
97
126
 
98
127
  ***
99
128
 
100
129
  <p>
101
130
  This project is sponsored by:<br />
102
131
  <a href="https://sashite.com/"><img
103
- src="https://github.com/fixrb/fix/raw/master/img/sashite.png"
132
+ src="https://github.com/fixrb/fix/raw/main/img/sashite.png"
104
133
  alt="Sashite" /></a>
105
134
  </p>
106
-
107
- [travis]: https://travis-ci.org/fixrb/fix
108
- [codeclimate]: https://codeclimate.com/github/fixrb/fix
109
- [gem]: https://rubygems.org/gems/fix
110
- [inchpages]: https://inch-ci.org/github/fixrb/fix
111
- [rubydoc]: https://rubydoc.info/gems/fix/frames
112
- [gitter]: https://gitter.im/fixrb/fix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
data/lib/fix.rb CHANGED
@@ -1,25 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("fix", "set")
4
+
5
+ require_relative "kernel"
6
+
3
7
  # Namespace for the Fix framework.
4
8
  #
9
+ # @api public
5
10
  module Fix
6
- # Specs are built with this method.
11
+ # Test a built specification.
7
12
  #
8
- # @example 42 must be equal to 42
9
- # describe(42) do
10
- # it { MUST equal 42 }
11
- # end
13
+ # @example Run _Answer_ specification against `42`.
14
+ # Fix[:Answer].against(42)
12
15
  #
13
- # @param front_object [BasicObject] The front object.
14
- # @param options [Hash] Some options.
15
- # @param specs [Proc] The set of specs.
16
+ # @param name [String, Symbol] The constant name of the specifications.
16
17
  #
17
- # @raise [SystemExit] The result of the test.
18
- def self.describe(front_object, **lets, &block)
19
- c = Context.new(front_object, ::Defi.send(:itself), **lets)
20
- c.instance_eval(&block)
18
+ # @return [::Fix::Test] The specification document.
19
+ def self.[](name)
20
+ ::Fix::Set.load(name)
21
21
  end
22
22
  end
23
-
24
- require_relative 'kernel'
25
- require_relative File.join('fix', 'context')
data/lib/fix/doc.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fix
4
+ # Module for storing spec documents.
5
+ #
6
+ # @api private
7
+ module Doc
8
+ # @param name [String, Symbol] The constant name of the specifications.
9
+ def self.fetch(name)
10
+ const_get("#{name}::CONTEXTS")
11
+ end
12
+
13
+ # @param contexts [Array<::Fix::Dsl>] The list of contexts document.
14
+ def self.specs(*contexts)
15
+ contexts.flat_map do |context|
16
+ env = context.new
17
+
18
+ env.public_methods(false).map do |public_method|
19
+ [env] + env.public_send(public_method)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/fix/dsl.rb ADDED
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "defi"
4
+
5
+ require_relative "matcher"
6
+ require_relative "requirement"
7
+
8
+ module Fix
9
+ # Abstract class for handling the domain-specific language.
10
+ #
11
+ # @api private
12
+ class Dsl
13
+ extend Matcher
14
+ extend Requirement
15
+
16
+ # Sets a user-defined property.
17
+ #
18
+ # @example
19
+ # require "fix"
20
+ #
21
+ # Fix do
22
+ # let(:name) { "Bob" }
23
+ # end
24
+ #
25
+ # @param name [String, Symbol] The name of the property.
26
+ # @param block [Proc] The content of the method to define.
27
+ #
28
+ # @return [Symbol] A private method that define the block content.
29
+ #
30
+ # @api public
31
+ def self.let(name, &block)
32
+ private define_method(name, &block)
33
+ end
34
+
35
+ # Defines an example group with user-defined properties that describes a
36
+ # unit to be tested.
37
+ #
38
+ # @example
39
+ # require "fix"
40
+ #
41
+ # Fix do
42
+ # with :password, "secret" do
43
+ # it MUST be true
44
+ # end
45
+ # end
46
+ #
47
+ # @param kwargs [Hash] The list of propreties.
48
+ # @param block [Proc] The block to define the specs.
49
+ #
50
+ # @api public
51
+ def self.with(**kwargs, &block)
52
+ klass = ::Class.new(self)
53
+ klass.const_get(:CONTEXTS) << klass
54
+ kwargs.each { |name, value| klass.let(name) { value } }
55
+ klass.instance_eval(&block)
56
+ klass
57
+ end
58
+
59
+ # Defines an example group that describes a unit to be tested.
60
+ #
61
+ # @example
62
+ # require "fix"
63
+ #
64
+ # Fix do
65
+ # on :+, 2 do
66
+ # it MUST be 42
67
+ # end
68
+ # end
69
+ #
70
+ # @param method_name [String, Symbol] The method to send to the subject.
71
+ # @param block [Proc] The block to define the specs.
72
+ #
73
+ # @api public
74
+ def self.on(method_name, *args, **kwargs, &block)
75
+ klass = ::Class.new(self)
76
+ klass.const_get(:CONTEXTS) << klass
77
+
78
+ const_set("Child#{block.object_id}", klass)
79
+
80
+ klass.define_singleton_method(:challenges) do
81
+ challenge = ::Defi.send(method_name, *args, **kwargs)
82
+ super() + [challenge]
83
+ end
84
+
85
+ klass.instance_eval(&block)
86
+ klass
87
+ end
88
+
89
+ # Defines a concrete spec definition.
90
+ #
91
+ # @example
92
+ # require "fix"
93
+ #
94
+ # Fix { it MUST be 42 }
95
+ #
96
+ # @api public
97
+ def self.it(requirement)
98
+ location = caller_locations(1, 1).fetch(0)
99
+ location = [location.path, location.lineno].join(":")
100
+
101
+ define_method("test_#{requirement.object_id}") do
102
+ [location, requirement, self.class.challenges]
103
+ end
104
+ end
105
+
106
+ # The list of challenges to be addressed to the object to be tested.
107
+ #
108
+ # @return [Array<Defi::Challenge>] A list of challenges.
109
+ def self.challenges
110
+ []
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "matchi"
4
+
5
+ module Fix
6
+ # Collection of expectation matchers.
7
+ #
8
+ # @api private
9
+ module Matcher
10
+ # Equivalence matcher
11
+ #
12
+ # @example
13
+ # matcher = eq("foo")
14
+ # matcher.matches? { "foo" } # => true
15
+ # matcher.matches? { "bar" } # => false
16
+ #
17
+ # @param expected [#eql?] An expected equivalent object.
18
+ #
19
+ # @return [#matches?] An equivalence matcher.
20
+ #
21
+ # @api public
22
+ def eq(expected)
23
+ ::Matchi::Eq.new(expected)
24
+ end
25
+
26
+ alias eql eq
27
+
28
+ # Identity matcher
29
+ #
30
+ # @example
31
+ # object = "foo"
32
+ # matcher = be(object)
33
+ # matcher.matches? { object } # => true
34
+ # matcher.matches? { "foo" } # => false
35
+ #
36
+ # @param expected [#equal?] The expected identical object.
37
+ #
38
+ # @return [#matches?] An identity matcher.
39
+ #
40
+ # @api public
41
+ def be(expected)
42
+ ::Matchi::Be.new(expected)
43
+ end
44
+
45
+ alias equal be
46
+
47
+ # Comparisons matcher
48
+ #
49
+ # @example
50
+ # matcher = be_within(1).of(41)
51
+ # matcher.matches? { 42 } # => true
52
+ # matcher.matches? { 43 } # => false
53
+ #
54
+ # @param delta [Numeric] A numeric value.
55
+ #
56
+ # @return [#matches?] A comparison matcher.
57
+ #
58
+ # @api public
59
+ def be_within(delta)
60
+ ::Matchi::BeWithin.new(delta)
61
+ end
62
+
63
+ # Regular expressions matcher
64
+ #
65
+ # @example
66
+ # matcher = match(/^foo$/)
67
+ # matcher.matches? { "foo" } # => true
68
+ # matcher.matches? { "bar" } # => false
69
+ #
70
+ # @param expected [#match] A regular expression.
71
+ #
72
+ # @return [#matches?] A regular expression matcher.
73
+ #
74
+ # @api public
75
+ def match(expected)
76
+ ::Matchi::Match.new(expected)
77
+ end
78
+
79
+ # Expecting errors matcher
80
+ #
81
+ # @example
82
+ # matcher = raise_exception(NameError)
83
+ # matcher.matches? { Boom } # => true
84
+ # matcher.matches? { true } # => false
85
+ #
86
+ # @param expected [Exception, #to_s] The expected exception name.
87
+ #
88
+ # @return [#matches?] An error matcher.
89
+ #
90
+ # @api public
91
+ def raise_exception(expected)
92
+ ::Matchi::RaiseException.new(expected)
93
+ end
94
+
95
+ # True matcher
96
+ #
97
+ # @example
98
+ # matcher = be_true
99
+ # matcher.matches? { true } # => true
100
+ # matcher.matches? { false } # => false
101
+ # matcher.matches? { nil } # => false
102
+ # matcher.matches? { 4 } # => false
103
+ #
104
+ # @return [#matches?] A `true` matcher.
105
+ #
106
+ # @api public
107
+ def be_true
108
+ be(true)
109
+ end
110
+
111
+ # False matcher
112
+ #
113
+ # @example
114
+ # matcher = be_false
115
+ # matcher.matches? { false } # => true
116
+ # matcher.matches? { true } # => false
117
+ # matcher.matches? { nil } # => false
118
+ # matcher.matches? { 4 } # => false
119
+ #
120
+ # @return [#matches?] A `false` matcher.
121
+ #
122
+ # @api public
123
+ def be_false
124
+ be(false)
125
+ end
126
+
127
+ # Nil matcher
128
+ #
129
+ # @example
130
+ # matcher = be_nil
131
+ # matcher.matches? { nil } # => true
132
+ # matcher.matches? { false } # => false
133
+ # matcher.matches? { true } # => false
134
+ # matcher.matches? { 4 } # => false
135
+ #
136
+ # @return [#matches?] A `nil` matcher.
137
+ #
138
+ # @api public
139
+ def be_nil
140
+ be(nil)
141
+ end
142
+
143
+ # Type/class matcher
144
+ #
145
+ # @example
146
+ # matcher = be_an_instance_of(String)
147
+ # matcher.matches? { "foo" } # => true
148
+ # matcher.matches? { 4 } # => false
149
+ #
150
+ # @param expected [Class, #to_s] The expected class name.
151
+ #
152
+ # @return [#matches?] A type/class matcher.
153
+ #
154
+ # @api public
155
+ def be_an_instance_of(expected)
156
+ ::Matchi::BeAnInstanceOf.new(expected)
157
+ end
158
+
159
+ # Change matcher
160
+ #
161
+ # @example
162
+ # object = []
163
+ # matcher = change(object, :length).by(1)
164
+ # matcher.matches? { object << 1 } # => true
165
+ #
166
+ # object = []
167
+ # matcher = change(object, :length).by_at_least(1)
168
+ # matcher.matches? { object << 1 } # => true
169
+ #
170
+ # object = []
171
+ # matcher = change(object, :length).by_at_most(1)
172
+ # matcher.matches? { object << 1 } # => true
173
+ #
174
+ # object = "foo"
175
+ # matcher = change(object, :to_s).from("foo").to("FOO")
176
+ # matcher.matches? { object.upcase! } # => true
177
+ #
178
+ # object = "foo"
179
+ # matcher = change(object, :to_s).to("FOO")
180
+ # matcher.matches? { object.upcase! } # => true
181
+ #
182
+ # @param object [#object_id] An object.
183
+ # @param method [Symbol] The name of a method.
184
+ # @param args [Array] A list of arguments.
185
+ # @param kwargs [Hash] A list of keyword arguments.
186
+ #
187
+ # @return [#matches?] A change matcher.
188
+ #
189
+ # @api public
190
+ def change(object, method, *args, **kwargs, &block)
191
+ ::Matchi::Change.new(object, method, *args, **kwargs, &block)
192
+ end
193
+
194
+ # Satisfy matcher
195
+ #
196
+ # @example
197
+ # matcher = satisfy { |value| value == 42 }
198
+ # matcher.matches? { 42 } # => true
199
+ #
200
+ # @param expected [Proc] A block of code.
201
+ #
202
+ # @return [#matches?] A satisfy matcher.
203
+ #
204
+ # @api public
205
+ def satisfy(&expected)
206
+ ::Matchi::Satisfy.new(&expected)
207
+ end
208
+
209
+ private
210
+
211
+ # Predicate matcher, or default method missing behavior.
212
+ #
213
+ # @example Empty predicate matcher
214
+ # matcher = be_empty
215
+ # matcher.matches? { [] } # => true
216
+ # matcher.matches? { [4] } # => false
217
+ def method_missing(name, *args, **kwargs, &block)
218
+ return super unless predicate_matcher_name?(name)
219
+
220
+ ::Matchi::Predicate.new(name, *args, **kwargs, &block)
221
+ end
222
+
223
+ # :nocov:
224
+
225
+ # Hook method to return whether the obj can respond to id method or not.
226
+ def respond_to_missing?(name, include_private = false)
227
+ predicate_matcher_name?(name) || super
228
+ end
229
+
230
+ # :nocov:
231
+
232
+ # Predicate matcher name detector.
233
+ #
234
+ # @param name [Array, Symbol] The name of a potential predicate matcher.
235
+ #
236
+ # @return [Boolean] Indicates if it is a predicate matcher name or not.
237
+ def predicate_matcher_name?(name)
238
+ name.start_with?("be_", "have_") && !name.end_with?("!", "?")
239
+ end
240
+ end
241
+ end