Skip to content

Commit

Permalink
feat: add Ruby 3.1 support and enhance code documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
cyril committed Jan 1, 2025
1 parent 133bbda commit 27890cb
Show file tree
Hide file tree
Showing 25 changed files with 775 additions and 174 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
strategy:
matrix:
ruby:
- 3.1
- 3.2
- 3.3
- 3.4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
ruby-version: 3.1
bundler-cache: true

- name: Run the RuboCop task
Expand Down
8 changes: 7 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
AllCops:
NewCops: enable
TargetRubyVersion: 3.2
TargetRubyVersion: 3.1

Exclude:
- '**/*.md'
Expand Down Expand Up @@ -54,3 +54,9 @@ Naming/FileName:
Style/ClassAndModuleChildren:
Exclude:
- README.md

Naming/BlockForwarding:
Enabled: false

Style/ArgumentsForwarding:
Enabled: false
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.3
3.1.6
25 changes: 12 additions & 13 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
PATH
remote: .
specs:
fix (1.0.0.beta12)
defi (~> 3.0.0)
matchi (~> 4.1.0)
spectus (~> 5.0.1)
fix (0.19)
defi (~> 3.0.1)
matchi (~> 4.1.1)
spectus (~> 5.0.2)

GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
defi (3.0.0)
defi (3.0.1)
docile (1.4.1)
expresenter (1.5.0)
expresenter (1.5.1)
json (2.9.1)
language_server-protocol (3.17.0.3)
matchi (4.1.0)
matchi (4.1.1)
parallel (1.26.3)
parser (3.3.6.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -52,12 +52,11 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
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)
spectus (5.0.2)
expresenter (~> 1.5.1)
test_tube (~> 4.0.1)
test_tube (4.0.1)
defi (~> 3.0.1)
unicode-display_width (3.1.3)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The MIT License

Copyright (c) 2014-2024 Cyril Kato
Copyright (c) 2014-2025 Cyril Kato

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
177 changes: 153 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ Fix is a modern Ruby testing framework that emphasizes clear separation between

## Installation

### Prerequisites

- Ruby >= 3.1.0

### Setup

Add to your Gemfile:

```ruby
gem "fix", ">= 1.0.0.beta12"
gem "fix"
```

Then execute:
Expand All @@ -26,7 +32,7 @@ bundle install
Or install it yourself:

```sh
gem install fix --pre
gem install fix
```

## Core Principles
Expand Down Expand Up @@ -171,13 +177,36 @@ Fix :UserAccount do
end
```

This example demonstrates:
- Using `let` to define test fixtures
- Context-specific testing with `with`
- Method behavior testing with `on`
- Different requirement levels with `MUST`/`MUST_NOT`
- Testing state changes with the `change` matcher
- Nested contexts for complex scenarios
The implementation might look like this:

```ruby
class User
attr_reader :role, :password_hash

def initialize(role:)
@role = role
@password_hash = nil
end

def admin?
role == "admin"
end

def can_access?(resource)
return true if admin?
false
end

def full_name
"#{@first_name} #{@last_name}"
end

def update_password(new_password)
@password_hash = Digest::SHA256.hexdigest(new_password)
true
end
end
```

### Example 2: Duck Specification

Expand Down Expand Up @@ -225,47 +254,140 @@ Running the test:
```ruby
Fix[:Duck].test { Duck.new }
```

## Available Matchers

Fix includes a comprehensive set of matchers through its integration with the [Matchi library](https://github.com/fixrb/matchi):

### Basic Comparison Matchers
<details>
<summary><strong>Basic Comparison Matchers</strong></summary>

- `eq(expected)` - Tests equality using `eql?`
```ruby
it MUST eq(42) # Passes if value.eql?(42)
it MUST eq("hello") # Passes if value.eql?("hello")
```
- `eql(expected)` - Alias for eq
- `be(expected)` - Tests object identity using `equal?`
```ruby
string = "test"
it MUST be(string) # Passes only if it's exactly the same object
```
- `equal(expected)` - Alias for be
</details>

<details>
<summary><strong>Type Checking Matchers</strong></summary>

### Type Checking Matchers
- `be_an_instance_of(class)` - Verifies exact class match
```ruby
it MUST be_an_instance_of(Array) # Passes if value.instance_of?(Array)
it MUST be_an_instance_of(User) # Passes if value.instance_of?(User)
```
- `be_a_kind_of(class)` - Checks class inheritance and module inclusion
```ruby
it MUST be_a_kind_of(Enumerable) # Passes if value.kind_of?(Enumerable)
it MUST be_a_kind_of(Animal) # Passes if value inherits from Animal
```
</details>

<details>
<summary><strong>Change Testing Matchers</strong></summary>

### Change Testing Matchers
- `change(object, method)` - Base matcher for state changes
- `.by(n)` - Expects exact change by n
```ruby
it MUST change(user, :points).by(5) # Exactly +5 points
```
- `.by_at_least(n)` - Expects minimum change by n
```ruby
it MUST change(counter, :value).by_at_least(10) # At least +10
```
- `.by_at_most(n)` - Expects maximum change by n
```ruby
it MUST change(account, :balance).by_at_most(100) # No more than +100
```
- `.from(old).to(new)` - Expects change from old to new value
```ruby
it MUST change(user, :status).from("pending").to("active")
```
- `.to(new)` - Expects change to new value
```ruby
it MUST change(post, :title).to("Updated")
```
</details>

<details>
<summary><strong>Numeric Matchers</strong></summary>

### Numeric Matchers
- `be_within(delta).of(value)` - Tests if a value is within ±delta of expected value
```ruby
it MUST be_within(0.1).of(3.14) # Passes if value is between 3.04 and 3.24
it MUST be_within(5).of(100) # Passes if value is between 95 and 105
```
</details>

<details>
<summary><strong>Pattern Matchers</strong></summary>

### Pattern Matchers
- `match(regex)` - Tests string against regular expression pattern
```ruby
it MUST match(/^\d{3}-\d{2}-\d{4}$/) # SSN format
it MUST match(/^[A-Z][a-z]+$/) # Capitalized word
```
- `satisfy { |value| ... }` - Custom matching with block
```ruby
it MUST satisfy { |num| num.even? && num > 0 }
it MUST satisfy { |user| user.valid? && user.active? }
```
</details>

### State Matchers
- `be_true` - Tests for true
- `be_false` - Tests for false
- `be_nil` - Tests for nil
<details>
<summary><strong>Exception Matchers</strong></summary>

### Exception Matchers
- `raise_exception(class)` - Tests if code raises specified exception
```ruby
it MUST raise_exception(ArgumentError)
it MUST raise_exception(CustomError, "specific message")
```
</details>

### Dynamic Predicate Matchers
- `be_*` - Dynamically matches `object.*?` methods (e.g., `be_empty` calls `empty?`)
- `have_*` - Dynamically matches `object.has_*?` methods (e.g., `have_key` calls `has_key?`)
<details>
<summary><strong>State Matchers</strong></summary>

Example usage:
- `be_true` - Tests for true
```ruby
it MUST be_true # Only passes for true, not truthy values
```
- `be_false` - Tests for false
```ruby
it MUST be_false # Only passes for false, not falsey values
```
- `be_nil` - Tests for nil
```ruby
it MUST be_nil # Passes only for nil
```
</details>

<details>
<summary><strong>Dynamic Predicate Matchers</strong></summary>

- `be_*` - Dynamically matches `object.*?` method
```ruby
it MUST be_empty # Calls empty?
it MUST be_valid # Calls valid?
it MUST be_frozen # Calls frozen?
```
- `have_*` - Dynamically matches `object.has_*?` method
```ruby
it MUST have_key(:id) # Calls has_key?
it MUST have_errors # Calls has_errors?
it MUST have_permission # Calls has_permission?
```
</details>

### Complete Example

Here's an example using various matchers together:
```ruby
Fix :Calculator do
Expand All @@ -284,6 +406,13 @@ Fix :Calculator do
it MUST_NOT be_empty
it MUST satisfy { |result| result.all? { |n| n.positive? } }
end
with string_input: "123" do
on :parse do
it MUST be_a_kind_of Numeric
it MUST satisfy { |n| n > 0 }
end
end
end
```
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.beta12
0.19
33 changes: 30 additions & 3 deletions docs/_config.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
title: Fix specing framework
title: "Fix: Happy Path to Ruby Testing"
author:
name: Cyril Kato
url: "https://github.com/cyril"

description: >
Fix is a modern Ruby testing framework built around a key architectural principle:
the complete separation between specifications and tests. It allows you to write
pure specification documents that define expected behaviors, and then independently
challenge any implementation against these specifications.
remote_theme: jekyll/minima
plugins:
- jekyll-feed
- jekyll-remote-theme
- jekyll-sitemap
- jekyll-seo-tag

minima:
skin: auto
social_links:
- { platform: github, user_url: "https://github.com/fixrb/fix" }
- { platform: x, user_url: "https://x.com/fix_rb" }
- { platform: github, user_url: "https://github.com/fixrb/fix" }
- { platform: x, user_url: "https://x.com/fix_rb" }
- { platform: bsky, user_url: "https://bsky.app/profile/fixrb.dev" }
- { platform: rss, user_url: "/feed.xml" }

# Feed configuration
feed:
icon: /favicon.png
logo: /favicon.png
posts_limit: 100

# SEO
twitter:
username: fix_rb
card: summary

social:
name: Fix Framework
links:
- https://github.com/fixrb/fix
- https://x.com/fix_rb
- https://bsky.app/profile/fixrb.dev

# GitHub Pages specific
github:
is_project_page: true
repository_url: "https://github.com/fixrb/fix"
Loading

0 comments on commit 27890cb

Please sign in to comment.