Skip to content

Commit

Permalink
feat: support block syntax for examples
Browse files Browse the repository at this point in the history
  • Loading branch information
cyril committed Dec 30, 2024
1 parent 684ca06 commit a9c165f
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 66 deletions.
19 changes: 10 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
PATH
remote: .
specs:
fix (1.0.0.beta10)
fix (1.0.0.beta11)
defi (~> 3.0.0)
matchi (~> 3.3.2)
spectus (~> 5.0.0)
matchi (~> 4.0.0)
spectus (~> 5.0.1)

GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
defi (3.0.0)
docile (1.4.1)
expresenter (1.4.1)
expresenter (1.5.0)
json (2.9.1)
language_server-protocol (3.17.0.3)
matchi (3.3.2)
matchi (4.0.0)
parallel (1.26.3)
parser (3.3.6.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -52,10 +52,11 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
spectus (5.0.0)
expresenter (~> 1.4.1)
test_tube (~> 3.0.0)
test_tube (3.0.0)
spectus (5.0.1)
expresenter (~> 1.5.0)
matchi (~> 4.0)
test_tube (~> 4.0.0)
test_tube (4.0.0)
defi (~> 3.0.0)
unicode-display_width (3.1.3)
unicode-emoji (~> 4.0, >= 4.0.4)
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
[![Home](https://img.shields.io/badge/Home-fixrb.dev-00af8b)](https://fixrb.dev/)
[![Version](https://img.shields.io/github/v/tag/fixrb/fix?label=Version&logo=github)](https://github.com/fixrb/fix/tags)
[![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/fix/main)
[![Ruby](https://github.com/fixrb/fix/workflows/Ruby/badge.svg?branch=main)](https://github.com/fixrb/fix/actions?query=workflow%3Aruby+branch%3Amain)
[![RuboCop](https://github.com/fixrb/fix/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/fix/actions?query=workflow%3Arubocop+branch%3Amain)
[![License](https://img.shields.io/github/license/fixrb/fix?label=License&logo=github)](https://github.com/fixrb/fix/raw/main/LICENSE.md)

## Introduction
Expand All @@ -16,7 +14,7 @@ Fix is a modern Ruby testing framework that emphasizes clear separation between
Add to your Gemfile:

```ruby
gem "fix", ">= 1.0.0.beta10"
gem "fix", ">= 1.0.0.beta11"
```

Then execute:
Expand Down
2 changes: 1 addition & 1 deletion VERSION.semver
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0.beta10
1.0.0.beta11
4 changes: 2 additions & 2 deletions fix.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ Gem::Specification.new do |spec|
}

spec.add_dependency "defi", "~> 3.0.0"
spec.add_dependency "matchi", "~> 3.3.2"
spec.add_dependency "spectus", "~> 5.0.0"
spec.add_dependency "matchi", "~> 4.0.0"
spec.add_dependency "spectus", "~> 5.0.1"
end
17 changes: 17 additions & 0 deletions fix/it_with_a_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require_relative File.join("..", "lib", "fix")

Fix :ItWithABlock do
it MUST_NOT be 4
it { MUST_NOT be 4 }

it MUST be 42
it { MUST be 42 }

on :-, 42 do
it MUST be 0

it { MUST be 0 }
end
end
13 changes: 10 additions & 3 deletions lib/fix/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,20 @@ def self.on(method_name, *args, **kwargs, &block)
#
# Fix { it MUST be 42 }
#
# Fix do
# it { MUST be 42 }
# end
#
# @api public
def self.it(requirement)
def self.it(requirement = nil, &block)
raise ::ArgumentError, "Must provide either requirement or block, not both" if requirement && block
raise ::ArgumentError, "Must provide either requirement or block" unless requirement || block

location = caller_locations(1, 1).fetch(0)
location = [location.path, location.lineno].join(":")

define_method(:"test_#{requirement.object_id}") do
[location, requirement, self.class.challenges]
define_method(:"test_#{(requirement || block).object_id}") do
[location, requirement || singleton_class.class_eval(&block), self.class.challenges]
end
end

Expand Down
86 changes: 43 additions & 43 deletions lib/fix/matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ module Matcher
#
# @example
# matcher = eq("foo")
# matcher.matches? { "foo" } # => true
# matcher.matches? { "bar" } # => false
# matcher.match? { "foo" } # => true
# matcher.match? { "bar" } # => false
#
# @param expected [#eql?] An expected equivalent object.
#
# @return [#matches?] An equivalence matcher.
# @return [#match?] An equivalence matcher.
#
# @api public
def eq(expected)
Expand All @@ -30,12 +30,12 @@ def eq(expected)
# @example
# object = "foo"
# matcher = be(object)
# matcher.matches? { object } # => true
# matcher.matches? { "foo" } # => false
# matcher.match? { object } # => true
# matcher.match? { "foo" } # => false
#
# @param expected [#equal?] The expected identical object.
#
# @return [#matches?] An identity matcher.
# @return [#match?] An identity matcher.
#
# @api public
def be(expected)
Expand All @@ -48,12 +48,12 @@ def be(expected)
#
# @example
# matcher = be_within(1).of(41)
# matcher.matches? { 42 } # => true
# matcher.matches? { 43 } # => false
# matcher.match? { 42 } # => true
# matcher.match? { 43 } # => false
#
# @param delta [Numeric] A numeric value.
#
# @return [#matches?] A comparison matcher.
# @return [#match?] A comparison matcher.
#
# @api public
def be_within(delta)
Expand All @@ -64,12 +64,12 @@ def be_within(delta)
#
# @example
# matcher = match(/^foo$/)
# matcher.matches? { "foo" } # => true
# matcher.matches? { "bar" } # => false
# matcher.match? { "foo" } # => true
# matcher.match? { "bar" } # => false
#
# @param expected [#match] A regular expression.
#
# @return [#matches?] A regular expression matcher.
# @return [#match?] A regular expression matcher.
#
# @api public
def match(expected)
Expand All @@ -80,12 +80,12 @@ def match(expected)
#
# @example
# matcher = raise_exception(NameError)
# matcher.matches? { Boom } # => true
# matcher.matches? { true } # => false
# matcher.match? { Boom } # => true
# matcher.match? { true } # => false
#
# @param expected [Exception, #to_s] The expected exception name.
#
# @return [#matches?] An error matcher.
# @return [#match?] An error matcher.
#
# @api public
def raise_exception(expected)
Expand All @@ -96,12 +96,12 @@ def raise_exception(expected)
#
# @example
# matcher = be_true
# matcher.matches? { true } # => true
# matcher.matches? { false } # => false
# matcher.matches? { nil } # => false
# matcher.matches? { 4 } # => false
# matcher.match? { true } # => true
# matcher.match? { false } # => false
# matcher.match? { nil } # => false
# matcher.match? { 4 } # => false
#
# @return [#matches?] A `true` matcher.
# @return [#match?] A `true` matcher.
#
# @api public
def be_true
Expand All @@ -112,12 +112,12 @@ def be_true
#
# @example
# matcher = be_false
# matcher.matches? { false } # => true
# matcher.matches? { true } # => false
# matcher.matches? { nil } # => false
# matcher.matches? { 4 } # => false
# matcher.match? { false } # => true
# matcher.match? { true } # => false
# matcher.match? { nil } # => false
# matcher.match? { 4 } # => false
#
# @return [#matches?] A `false` matcher.
# @return [#match?] A `false` matcher.
#
# @api public
def be_false
Expand All @@ -128,12 +128,12 @@ def be_false
#
# @example
# matcher = be_nil
# matcher.matches? { nil } # => true
# matcher.matches? { false } # => false
# matcher.matches? { true } # => false
# matcher.matches? { 4 } # => false
# matcher.match? { nil } # => true
# matcher.match? { false } # => false
# matcher.match? { true } # => false
# matcher.match? { 4 } # => false
#
# @return [#matches?] A `nil` matcher.
# @return [#match?] A `nil` matcher.
#
# @api public
def be_nil
Expand All @@ -144,12 +144,12 @@ def be_nil
#
# @example
# matcher = be_an_instance_of(String)
# matcher.matches? { "foo" } # => true
# matcher.matches? { 4 } # => false
# matcher.match? { "foo" } # => true
# matcher.match? { 4 } # => false
#
# @param expected [Class, #to_s] The expected class name.
#
# @return [#matches?] A type/class matcher.
# @return [#match?] A type/class matcher.
#
# @api public
def be_an_instance_of(expected)
Expand All @@ -161,28 +161,28 @@ def be_an_instance_of(expected)
# @example
# object = []
# matcher = change(object, :length).by(1)
# matcher.matches? { object << 1 } # => true
# matcher.match? { object << 1 } # => true
#
# object = []
# matcher = change(object, :length).by_at_least(1)
# matcher.matches? { object << 1 } # => true
# matcher.match? { object << 1 } # => true
#
# object = []
# matcher = change(object, :length).by_at_most(1)
# matcher.matches? { object << 1 } # => true
# matcher.match? { object << 1 } # => true
#
# object = "foo"
# matcher = change(object, :to_s).from("foo").to("FOO")
# matcher.matches? { object.upcase! } # => true
# matcher.match? { object.upcase! } # => true
#
# object = "foo"
# matcher = change(object, :to_s).to("FOO")
# matcher.matches? { object.upcase! } # => true
# matcher.match? { object.upcase! } # => true
#
# @param object [#object_id] An object.
# @param method [Symbol] The name of a method.
#
# @return [#matches?] A change matcher.
# @return [#match?] A change matcher.
#
# @api public
def change(object, method, ...)
Expand All @@ -193,13 +193,13 @@ def change(object, method, ...)
#
# @example
# matcher = satisfy { |value| value == 42 }
# matcher.matches? { 42 } # => true
# matcher.match? { 42 } # => true
#
# @yield [value] A block that defines the satisfaction criteria
# @yieldparam value The value to test
# @yieldreturn [Boolean] true if the value satisfies the criteria
#
# @return [#matches?] A satisfy matcher.
# @return [#match?] A satisfy matcher.
#
# @api public
def satisfy(&)
Expand All @@ -212,8 +212,8 @@ def satisfy(&)
#
# @example Empty predicate matcher
# matcher = be_empty
# matcher.matches? { [] } # => true
# matcher.matches? { [4] } # => false
# matcher.match? { [] } # => true
# matcher.match? { [4] } # => false
def method_missing(name, ...)
return super unless predicate_matcher_name?(name)

Expand Down
10 changes: 5 additions & 5 deletions lib/fix/requirement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Requirement
# This method mean that the definition is an absolute requirement of the
# specification.
#
# @param matcher [#matches?] The matcher.
# @param matcher [#match?] The matcher.
#
# @return [Requirement::Required] An absolute requirement level instance.
#
Expand All @@ -25,7 +25,7 @@ def MUST(matcher)

# This method mean that the definition is an absolute prohibition of the specification.
#
# @param matcher [#matches?] The matcher.
# @param matcher [#match?] The matcher.
#
# @return [Requirement::Required] An absolute prohibition level instance.
#
Expand All @@ -38,7 +38,7 @@ def MUST_NOT(matcher)
# circumstances to ignore a particular item, but the full implications must be
# understood and carefully weighed before choosing a different course.
#
# @param matcher [#matches?] The matcher.
# @param matcher [#match?] The matcher.
#
# @return [Requirement::Recommended] A recommended requirement level instance.
#
Expand All @@ -52,7 +52,7 @@ def SHOULD(matcher)
# the full implications should be understood and the case carefully weighed
# before implementing any behavior described with this label.
#
# @param matcher [#matches?] The matcher.
# @param matcher [#match?] The matcher.
#
# @return [Requirement::Recommended] A not recommended requirement level
# instance.
Expand All @@ -73,7 +73,7 @@ def SHOULD_NOT(matcher)
# implementation which does not include the option (except, of course, for the
# feature the option provides).
#
# @param matcher [#matches?] The matcher.
# @param matcher [#match?] The matcher.
#
# @return [Requirement::Optional] An optional requirement level instance.
#
Expand Down
5 changes: 5 additions & 0 deletions test/it_with_a_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

require_relative File.join("..", "fix", "it_with_a_block")

Fix[:ItWithABlock].test { 42 }

0 comments on commit a9c165f

Please sign in to comment.