fix 0.21 → 1.0.0.beta1

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: a61d6e74ba8d919c935036f602b4ac3bdf4ec121a9880cb6168d16893384e194
4
- data.tar.gz: 1060db9d560d9288af75686c51bf6acbc38fb29606b3d451493f39548795744f
3
+ metadata.gz: 3f307018b701019985c925b9a244c7b6813462faac51b8fef26488070a564f30
4
+ data.tar.gz: c09cc676e5394ef581f5fa7d0ae57beafc4a28686661c5d66b0bdd0006481864
5
5
  SHA512:
6
- metadata.gz: 19f6f8612289b9e23bcda11fd20ee039591a8270b1dbec962474261f45e72784ed6e4ab414ecb2e13280095ca349017eb56c3076f4bddf8bc1b5144a44a6880a
7
- data.tar.gz: 1a8a4b2181814fbd064559a726cd7a41f6fa1079c9f58fc8babc4d54f539ead621ede997a24754e404e5dc64a37a2e138db07e52df1a9bfb9c6d8c939815abcd
6
+ metadata.gz: 35d0e3bafa2f2b612a039c9892cf6024eb42bc52923a551c49b96d3b0efb2abcd9a6e5022b8086c69c351e15f5b789f460e2c938b1a82a4f7148c18a93ca4898
7
+ data.tar.gz: 9c6b388b420a98226aeb4fb59746725f4ea36824ea0d78065b01c4bd9d7c341a10b84dcb42351ca6a0bf865bf754404ad263cfd2b29de1cdd5916e97fd89fbf5
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
- # The MIT License
1
+ The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2025 Cyril Kato
3
+ Copyright (c) 2014-2020 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,452 +1,119 @@
1
- # Fix Framework
1
+ # Fix
2
2
 
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/tags)
5
- [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/fix/main)
6
- [![License](https://img.shields.io/github/license/fixrb/fix?label=License&logo=github)](https://github.com/fixrb/fix/raw/main/LICENSE.md)
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]
7
9
 
8
- ## Introduction
9
-
10
- Fix is a modern Ruby testing framework that emphasizes clear separation between specifications and examples. Unlike traditional testing frameworks, Fix focuses on creating pure specification documents that define expected behaviors without mixing in implementation details.
10
+ > Specing framework for Ruby.
11
11
 
12
12
  ## Installation
13
13
 
14
- ### Prerequisites
15
-
16
- - Ruby >= 3.1.0
17
-
18
- ### Setup
19
-
20
- Add to your Gemfile:
21
-
22
- ```ruby
23
- gem "fix"
24
- ```
25
-
26
- Then execute:
27
-
28
- ```sh
29
- bundle install
30
- ```
31
-
32
- Or install it yourself:
33
-
34
- ```sh
35
- gem install fix
36
- ```
37
-
38
- ## Core Principles
39
-
40
- - **Specifications vs Examples**: Fix makes a clear distinction between specifications (what is expected) and examples (how it's demonstrated). This separation leads to cleaner, more maintainable test suites.
41
-
42
- - **Logic-Free Specifications**: Your specification documents remain pure and focused on defining behaviors, without getting cluttered by implementation logic.
43
-
44
- - **Rich Semantic Language**: Following RFC 2119 conventions, Fix uses precise language with keywords like MUST, SHOULD, and MAY to clearly define different requirement levels in specifications.
45
-
46
- - **Fast Individual Testing**: Tests execute quickly and independently, providing rapid feedback on specification compliance.
47
-
48
- ## Framework Features
49
-
50
- ### Property Definition with `let`
51
-
52
- Define reusable properties across your specifications:
53
-
54
- ```ruby
55
- Fix do
56
- let(:name) { "Bob" }
57
- let(:age) { 42 }
58
-
59
- it MUST eq name
60
- end
61
- ```
62
-
63
- ### Context Creation with `with`
64
-
65
- Test behavior under different conditions:
14
+ Add this line to your application's Gemfile:
66
15
 
67
16
  ```ruby
68
- Fix do
69
- with name: "Alice", role: "admin" do
70
- it MUST be_allowed
71
- end
72
-
73
- with name: "Bob", role: "guest" do
74
- it MUST_NOT be_allowed
75
- end
76
- end
17
+ gem 'fix', '>= 1.0.0.beta1'
77
18
  ```
78
19
 
79
- ### Method Testing with `on`
20
+ And then execute:
80
21
 
81
- Test how objects respond to specific messages:
22
+ $ bundle
82
23
 
83
- ```ruby
84
- Fix do
85
- on :upcase do
86
- it MUST eq "HELLO"
87
- end
24
+ Or install it yourself as:
88
25
 
89
- on :+, 2 do
90
- it MUST eq 42
91
- end
92
- end
93
- ```
26
+ $ gem install fix --pre
94
27
 
95
- ### Requirement Levels
28
+ ## Usage
96
29
 
97
- Fix provides three levels of requirements, each with clear semantic meaning:
98
-
99
- - **MUST/MUST_NOT**: Absolute requirements or prohibitions
100
- - **SHOULD/SHOULD_NOT**: Recommended practices with valid exceptions
101
- - **MAY**: Optional features
30
+ Given this app:
102
31
 
103
32
  ```ruby
104
- Fix do
105
- it MUST be_valid # Required
106
- it SHOULD be_optimized # Recommended
107
- it MAY include_metadata # Optional
108
- end
109
- ```
110
-
111
- ## Quick Start
112
-
113
- Create your first test file:
114
-
115
- ```ruby
116
- # first_test.rb
117
- require "fix"
118
-
119
- Fix :HelloWorld do
120
- it MUST eq "Hello, World!"
121
- end
122
-
123
- Fix[:HelloWorld].test { "Hello, World!" }
124
- ```
125
-
126
- Run it:
127
-
128
- ```sh
129
- ruby first_test.rb
130
- ```
131
-
132
- ## Real-World Examples
133
-
134
- Fix is designed to work with real-world applications of any complexity. Here are some examples demonstrating how Fix can be used in different scenarios:
135
-
136
- ### Example 1: User Account Management
137
-
138
- Here's a comprehensive example showing how to specify a user account system:
139
-
140
- ```ruby
141
- Fix :UserAccount do
142
- # Define reusable properties
143
- let(:admin) { User.new(role: "admin") }
144
- let(:guest) { User.new(role: "guest") }
145
-
146
- # Test basic instance properties
147
- it MUST be_an_instance_of User
148
-
149
- # Test with different contexts
150
- with role: "admin" do
151
- it MUST be_admin
152
-
153
- on :can_access?, "settings" do
154
- it MUST be_true
155
- end
156
- end
157
-
158
- with role: "guest" do
159
- it MUST_NOT be_admin
160
-
161
- on :can_access?, "settings" do
162
- it MUST be_false
163
- end
33
+ # examples/duck/app.rb
34
+ class Duck
35
+ def walks
36
+ 'Klop klop!'
164
37
  end
165
38
 
166
- # Test specific methods
167
- on :full_name do
168
- with first_name: "John", last_name: "Doe" do
169
- it MUST eq "John Doe"
170
- end
39
+ def swims
40
+ 'Swoosh...'
171
41
  end
172
42
 
173
- on :update_password, "new_password" do
174
- it MUST change(admin, :password_hash)
175
- it MUST be_true # Return value check
43
+ def quacks
44
+ puts 'Quaaaaaack!'
176
45
  end
177
46
  end
178
47
  ```
179
48
 
180
- The implementation might look like this:
49
+ When you run this:
181
50
 
182
51
  ```ruby
183
- class User
184
- attr_reader :role, :password_hash
52
+ # examples/duck/fix.rb
53
+ require_relative 'app'
54
+ require_relative '../../lib/fix'
185
55
 
186
- def initialize(role:)
187
- @role = role
188
- @password_hash = nil
189
- end
190
-
191
- def admin?
192
- role == "admin"
193
- end
194
-
195
- def can_access?(resource)
196
- return true if admin?
197
- false
198
- end
199
-
200
- def full_name
201
- "#{@first_name} #{@last_name}"
202
- end
203
-
204
- def update_password(new_password)
205
- @password_hash = Digest::SHA256.hexdigest(new_password)
206
- true
207
- end
208
- end
209
- ```
210
-
211
- ### Example 2: Duck Specification
212
-
213
- Here's how Fix can be used to specify a Duck class:
214
-
215
- ```ruby
216
- Fix :Duck do
217
- it SHOULD be_an_instance_of :Duck
56
+ @bird = Duck.new
218
57
 
58
+ Fix(@bird) do
219
59
  on :swims do
220
- it MUST be_an_instance_of :String
221
- it MUST eql "Swoosh..."
60
+ it { MUST eql 'Swoosh...' }
222
61
  end
223
62
 
224
63
  on :speaks do
225
- it MUST raise_exception NoMethodError
64
+ it { MUST raise_exception NoMethodError }
226
65
  end
227
66
 
228
67
  on :sings do
229
- it MAY eql "♪... ♫..."
68
+ it { MAY eql '♪... ♫...' }
230
69
  end
231
70
  end
232
71
  ```
233
72
 
234
- The implementation:
73
+ Then the output should look like this:
235
74
 
236
- ```ruby
237
- class Duck
238
- def walks
239
- "Klop klop!"
240
- end
241
-
242
- def swims
243
- "Swoosh..."
244
- end
245
-
246
- def quacks
247
- puts "Quaaaaaack!"
248
- end
249
- end
250
- ```
251
-
252
- Running the test:
253
-
254
- ```ruby
255
- Fix[:Duck].test { Duck.new }
75
+ ```sh
76
+ ruby examples/duck/fix.rb
256
77
  ```
257
- ## Available Matchers
258
-
259
- Fix includes a comprehensive set of matchers through its integration with the [Matchi library](https://github.com/fixrb/matchi):
260
-
261
- <details>
262
- <summary><strong>Basic Comparison Matchers</strong></summary>
263
-
264
- - `eq(expected)` - Tests equality using `eql?`
265
- ```ruby
266
- it MUST eq(42) # Passes if value.eql?(42)
267
- it MUST eq("hello") # Passes if value.eql?("hello")
268
- ```
269
- - `eql(expected)` - Alias for eq
270
- - `be(expected)` - Tests object identity using `equal?`
271
- ```ruby
272
- string = "test"
273
- it MUST be(string) # Passes only if it's exactly the same object
274
- ```
275
- - `equal(expected)` - Alias for be
276
- </details>
277
-
278
- <details>
279
- <summary><strong>Type Checking Matchers</strong></summary>
280
-
281
- - `be_an_instance_of(class)` - Verifies exact class match
282
- ```ruby
283
- it MUST be_an_instance_of(Array) # Passes if value.instance_of?(Array)
284
- it MUST be_an_instance_of(User) # Passes if value.instance_of?(User)
285
- ```
286
- - `be_a_kind_of(class)` - Checks class inheritance and module inclusion
287
- ```ruby
288
- it MUST be_a_kind_of(Enumerable) # Passes if value.kind_of?(Enumerable)
289
- it MUST be_a_kind_of(Animal) # Passes if value inherits from Animal
290
- ```
291
- </details>
292
-
293
- <details>
294
- <summary><strong>Change Testing Matchers</strong></summary>
295
-
296
- - `change(object, method)` - Base matcher for state changes
297
- - `.by(n)` - Expects exact change by n
298
- ```ruby
299
- it MUST change(user, :points).by(5) # Exactly +5 points
300
- ```
301
- - `.by_at_least(n)` - Expects minimum change by n
302
- ```ruby
303
- it MUST change(counter, :value).by_at_least(10) # At least +10
304
- ```
305
- - `.by_at_most(n)` - Expects maximum change by n
306
- ```ruby
307
- it MUST change(account, :balance).by_at_most(100) # No more than +100
308
- ```
309
- - `.from(old).to(new)` - Expects change from old to new value
310
- ```ruby
311
- it MUST change(user, :status).from("pending").to("active")
312
- ```
313
- - `.to(new)` - Expects change to new value
314
- ```ruby
315
- it MUST change(post, :title).to("Updated")
316
- ```
317
- </details>
318
-
319
- <details>
320
- <summary><strong>Numeric Matchers</strong></summary>
321
-
322
- - `be_within(delta).of(value)` - Tests if a value is within ±delta of expected value
323
- ```ruby
324
- it MUST be_within(0.1).of(3.14) # Passes if value is between 3.04 and 3.24
325
- it MUST be_within(5).of(100) # Passes if value is between 95 and 105
326
- ```
327
- </details>
328
-
329
- <details>
330
- <summary><strong>Pattern Matchers</strong></summary>
331
-
332
- - `match(regex)` - Tests string against regular expression pattern
333
- ```ruby
334
- it MUST match(/^\d{3}-\d{2}-\d{4}$/) # SSN format
335
- it MUST match(/^[A-Z][a-z]+$/) # Capitalized word
336
- ```
337
- - `satisfy { |value| ... }` - Custom matching with block
338
- ```ruby
339
- it MUST satisfy { |num| num.even? && num > 0 }
340
- it MUST satisfy { |user| user.valid? && user.active? }
341
- ```
342
- </details>
343
-
344
- <details>
345
- <summary><strong>Exception Matchers</strong></summary>
346
-
347
- - `raise_exception(class)` - Tests if code raises specified exception
348
- ```ruby
349
- it MUST raise_exception(ArgumentError)
350
- it MUST raise_exception(CustomError, "specific message")
351
- ```
352
- </details>
353
-
354
- <details>
355
- <summary><strong>State Matchers</strong></summary>
356
-
357
- - `be_true` - Tests for true
358
- ```ruby
359
- it MUST be_true # Only passes for true, not truthy values
360
- ```
361
- - `be_false` - Tests for false
362
- ```ruby
363
- it MUST be_false # Only passes for false, not falsey values
364
- ```
365
- - `be_nil` - Tests for nil
366
- ```ruby
367
- it MUST be_nil # Passes only for nil
368
- ```
369
- </details>
370
-
371
- <details>
372
- <summary><strong>Dynamic Predicate Matchers</strong></summary>
373
-
374
- - `be_*` - Dynamically matches `object.*?` method
375
- ```ruby
376
- it MUST be_empty # Calls empty?
377
- it MUST be_valid # Calls valid?
378
- it MUST be_frozen # Calls frozen?
379
- ```
380
- - `have_*` - Dynamically matches `object.has_*?` method
381
- ```ruby
382
- it MUST have_key(:id) # Calls has_key?
383
- it MUST have_errors # Calls has_errors?
384
- it MUST have_permission # Calls has_permission?
385
- ```
386
- </details>
387
-
388
- ### Complete Example
389
-
390
- Here's an example using various matchers together:
391
78
 
392
- ```ruby
393
- Fix :Calculator do
394
- it MUST be_an_instance_of Calculator
395
-
396
- on :add, 2, 3 do
397
- it MUST eq 5
398
- it MUST be_within(0.001).of(5.0)
399
- end
400
-
401
- on :divide, 1, 0 do
402
- it MUST raise_exception ZeroDivisionError
403
- end
404
-
405
- with numbers: [1, 2, 3] do
406
- it MUST_NOT be_empty
407
- it MUST satisfy { |result| result.all? { |n| n.positive? } }
408
- end
409
-
410
- with string_input: "123" do
411
- on :parse do
412
- it MUST be_a_kind_of Numeric
413
- it MUST satisfy { |n| n > 0 }
414
- end
415
- end
416
- end
79
+ ```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>.
417
83
  ```
418
84
 
419
- ## Why Choose Fix?
420
-
421
- Fix brings several unique advantages to Ruby testing that set it apart from traditional testing frameworks:
85
+ ## Contact
422
86
 
423
- - **Clear Separation of Concerns**: Keep your specifications clean and your examples separate
424
- - **Semantic Precision**: Express requirements with different levels of necessity
425
- - **Fast Execution**: Get quick feedback on specification compliance
426
- - **Pure Specifications**: Write specification documents that focus on behavior, not implementation
427
- - **Rich Matcher Library**: Comprehensive set of matchers for different testing needs
428
- - **Modern Ruby**: Takes advantage of modern Ruby features and practices
87
+ * Home page: https://fixrb.dev/
88
+ * Bugs/issues: https://github.com/fixrb/fix/issues
89
+ * Support: https://stackoverflow.com/questions/tagged/fixrb
429
90
 
430
- ## Get Started
91
+ ## Rubies
431
92
 
432
- Ready to write better specifications? Visit our [GitHub repository](https://github.com/fixrb/fix) to start using Fix in your Ruby projects.
433
-
434
- ## Community & Resources
435
-
436
- - [Blog](https://fixrb.dev/) - Related articles
437
- - [Bluesky](https://bsky.app/profile/fixrb.dev) - Latest updates and discussions
438
- - [Documentation](https://www.rubydoc.info/gems/fix) - Comprehensive guides and API reference
439
- - [Source Code](https://github.com/fixrb/fix) - Contribute and report issues
440
- - [asciinema](https://asciinema.org/~fix) - Watch practical examples in action
93
+ * [MRI](https://www.ruby-lang.org/)
94
+ * [Rubinius](https://rubinius.com/)
95
+ * [JRuby](https://www.jruby.org/)
441
96
 
442
97
  ## Versioning
443
98
 
444
- __Fix__ follows [Semantic Versioning 2.0](https://semver.org/).
99
+ __Fix__ follows [Semantic Versioning 2.0](http://semver.org/).
445
100
 
446
101
  ## License
447
102
 
448
- 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).
103
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
104
+
105
+ ***
449
106
 
450
- ## Sponsors
107
+ <p>
108
+ This project is sponsored by:<br />
109
+ <a href="https://sashite.com/"><img
110
+ src="https://github.com/fixrb/fix/raw/master/img/sashite.png"
111
+ alt="Sashite" /></a>
112
+ </p>
451
113
 
452
- This project is sponsored by [Sashité](https://sashite.com/)
114
+ [travis]: https://travis-ci.org/fixrb/fix
115
+ [codeclimate]: https://codeclimate.com/github/fixrb/fix
116
+ [gem]: https://rubygems.org/gems/fix
117
+ [inchpages]: http://inch-ci.org/github/fixrb/fix
118
+ [rubydoc]: http://rubydoc.info/gems/fix/frames
119
+ [gitter]: https://gitter.im/fixrb/fix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aw'
4
+ require 'defi'
5
+
6
+ module Fix
7
+ # Wraps the target of challenge.
8
+ class Context
9
+ RESERVED_KEYWORDS = %i[
10
+ alias
11
+ and
12
+ begin
13
+ break
14
+ case
15
+ catch
16
+ class
17
+ def
18
+ defined?
19
+ do
20
+ else
21
+ elsif
22
+ end
23
+ ensure
24
+ fail
25
+ false
26
+ for
27
+ if
28
+ in
29
+ module
30
+ next
31
+ nil
32
+ not
33
+ or
34
+ raise
35
+ redo
36
+ rescue
37
+ retry
38
+ return
39
+ self
40
+ super
41
+ then
42
+ throw
43
+ true
44
+ undef
45
+ unless
46
+ until
47
+ when
48
+ while
49
+ yield
50
+ ].freeze
51
+
52
+ attr_reader :callable
53
+
54
+ def initialize(subject, challenge, before_hooks_counter = 0, *hooks, **lets)
55
+ @subject = subject
56
+ @callable = challenge.to(subject)
57
+ @before_hooks = hooks[0, before_hooks_counter]
58
+ @after_hooks = hooks[before_hooks_counter..-1]
59
+ @lets = lets
60
+ end
61
+
62
+ def before(&block)
63
+ @before_hooks << block
64
+ end
65
+
66
+ def after(&block)
67
+ @after_hooks << block
68
+ end
69
+
70
+ def let(name, &block)
71
+ raise ::TypeError, "expected a Symbol, got #{name.class}" unless name.is_a?(::Symbol)
72
+ raise ::NameError, "wrong method name `#{name}'" unless name.match?(/\A[a-z][a-z0-9_]+[?!]?\z/)
73
+ raise ::NameError, "reserved keyword name `#{name}'" if RESERVED_KEYWORDS.include?(name)
74
+ raise ::NameError, "reserved method name `#{name}'" if respond_to?(name, true) && !@lets.key?(name)
75
+
76
+ @lets.update(name => block.call)
77
+ rescue ::SystemExit => e
78
+ raise SuspiciousSuccessError, "attempt `#{name}' to bypass the tests" if e.success?
79
+ raise e
80
+ end
81
+
82
+ def let!(name, &block)
83
+ raise ::TypeError, "expected a Symbol, got #{name.class}" unless name.is_a?(::Symbol)
84
+ raise ::NameError, "wrong method name `#{name}'" unless name.match?(/\A[a-z][a-z0-9_]+[?!]?\z/)
85
+ raise ::NameError, "reserved keyword name `#{name}'" if RESERVED_KEYWORDS.include?(name)
86
+ raise ::NameError, "reserved method name `#{name}'" if respond_to?(name, true) && !@lets.key?(name)
87
+
88
+ @lets.update(name => ::Aw.fork! { block.call })
89
+ rescue ::SystemExit => e
90
+ raise SuspiciousSuccessError, "attempt `#{name}' to bypass the tests" if e.success?
91
+ raise e
92
+ end
93
+
94
+ # Verify the expectation.
95
+ #
96
+ # @param block [Proc] A spec to compare against the computed actual value.
97
+ #
98
+ # @return [::Spectus::Result::Pass, ::Spectus::Result::Fail] Pass or fail.
99
+ def it(_message = nil, &block)
100
+ print "#{block.source_location.join(':')}: "
101
+ i = It.new(callable, **@lets)
102
+ @before_hooks.each { |hook| i.instance_eval(&hook) }
103
+ result = i.instance_eval(&block)
104
+ puts result.colored_string
105
+ rescue ::Spectus::Result::Fail => result
106
+ abort result.colored_string
107
+ ensure
108
+ @after_hooks.each { |hook| i.instance_eval(&hook) }
109
+ raise ExpectationResultNotFoundError unless result.is_a?(::Spectus::Result::Common)
110
+ end
111
+
112
+ def on(name, *args, **options, &block)
113
+ if callable.raised?
114
+ actual = callable
115
+ challenge = ::Defi.send(:call)
116
+ else
117
+ actual = callable.object
118
+ challenge = ::Defi.send(name, *args, **options)
119
+ end
120
+
121
+ o = Context.new(actual, challenge, @before_hooks.length, *@before_hooks + @after_hooks, **@lets)
122
+ o.instance_eval(&block)
123
+ end
124
+
125
+ def with(_message = nil, **new_lets, &block)
126
+ actual = callable.object
127
+ challenge = ::Defi.send(:itself)
128
+
129
+ c = Context.new(actual, challenge, @before_hooks.length, *@before_hooks + @after_hooks, **@lets.merge(new_lets))
130
+ c.instance_eval(&block)
131
+ end
132
+
133
+ private
134
+
135
+ def method_missing(name, *args, &block)
136
+ @lets.fetch(name) { super }
137
+ end
138
+
139
+ def respond_to_missing?(name, include_private = false)
140
+ @lets.key?(name) || super
141
+ end
142
+ end
143
+ end
144
+
145
+ require_relative 'it'
146
+ require_relative 'expectation_result_not_found_error'
147
+ require_relative 'suspicious_success_error'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fix
4
+ class ExpectationResultNotFoundError < ::RuntimeError; end
5
+ end