Skip to content

Commit e623b5c

Browse files
committed
refactor: simplify Fix module API and enhance documentation
- Streamline public API by removing unnecessary abstraction layers - Rename methods for better clarity (specification_names -> keys) - Add comprehensive documentation with detailed matcher examples - Improve error handling for missing specification blocks - Remove redundant name normalization methods
1 parent 48ab78e commit e623b5c

File tree

7 files changed

+435
-323
lines changed

7 files changed

+435
-323
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
fix (0.20)
4+
fix (0.21)
55
defi (~> 3.0.1)
66
matchi (~> 4.1.1)
77
spectus (~> 5.0.2)

VERSION.semver

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.20
1+
0.21

lib/fix.rb

Lines changed: 156 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,179 @@
11
# frozen_string_literal: true
22

33
require_relative "fix/doc"
4+
require_relative "fix/error/missing_specification_block"
45
require_relative "fix/error/specification_not_found"
56
require_relative "fix/set"
67
require_relative "kernel"
78

8-
# Namespace for the Fix framework.
9+
# The Fix framework namespace provides core functionality for managing and running test specifications.
10+
# Fix offers a unique approach to testing by clearly separating specifications from their implementations.
911
#
10-
# Provides core functionality for managing and running test specifications.
11-
# Fix supports two modes of operation:
12-
# 1. Named specifications that can be referenced later
13-
# 2. Anonymous specifications for immediate testing
12+
# Fix supports two primary modes of operation:
13+
# 1. Named specifications that can be stored and referenced later
14+
# 2. Anonymous specifications for immediate one-time testing
1415
#
15-
# @example Creating and running a named specification
16-
# Fix :Answer do
17-
# it MUST equal 42
16+
# Available matchers through the Matchi library include:
17+
# - Basic Comparison: eq, eql, be, equal
18+
# - Type Checking: be_an_instance_of, be_a_kind_of
19+
# - State & Changes: change(object, method).by(n), by_at_least(n), by_at_most(n), from(old).to(new), to(new)
20+
# - Value Testing: be_within(delta).of(value), match(regex), satisfy { |value| ... }
21+
# - Exceptions: raise_exception(class)
22+
# - State Testing: be_true, be_false, be_nil
23+
# - Predicate Matchers: be_*, have_* (e.g., be_empty, have_key)
24+
#
25+
# @example Creating and running a named specification with various matchers
26+
# Fix :Calculator do
27+
# on(:add, 0.1, 0.2) do
28+
# it SHOULD be 0.3 # Technically true but fails due to floating point precision
29+
# it MUST be_an_instance_of(Float) # Type checking
30+
# it MUST be_within(0.0001).of(0.3) # Proper floating point comparison
31+
# end
32+
#
33+
# on(:divide, 1, 0) do
34+
# it MUST raise_exception ZeroDivisionError # Exception testing
35+
# end
36+
# end
37+
#
38+
# Fix[:Calculator].test { Calculator.new }
39+
#
40+
# @example Using state change matchers
41+
# Fix :UserAccount do
42+
# on(:deposit, 100) do
43+
# it MUST change(account, :balance).by(100)
44+
# it SHOULD change(account, :updated_at)
45+
# end
46+
#
47+
# on(:update_status, :premium) do
48+
# it MUST change(account, :status).from(:basic).to(:premium)
49+
# end
50+
# end
51+
#
52+
# @example Using predicate matchers
53+
# Fix :Collection do
54+
# with items: [] do
55+
# it MUST be_empty # Tests empty?
56+
# it MUST_NOT have_errors # Tests has_errors?
57+
# end
1858
# end
1959
#
20-
# Fix[:Answer].test { 42 }
60+
# @example Complete specification with multiple matchers
61+
# Fix :Product do
62+
# let(:price) { 42.99 }
2163
#
22-
# @example Creating and running an anonymous specification
23-
# Fix do
24-
# it MUST be_positive
25-
# end.test { 42 }
64+
# it MUST be_an_instance_of Product # Type checking
65+
# it MUST_NOT be_nil # Nil checking
2666
#
27-
# @see Fix::Set
28-
# @see Fix::Builder
67+
# on(:price) do
68+
# it MUST be_within(0.01).of(42.99) # Floating point comparison
69+
# end
70+
#
71+
# with category: "electronics" do
72+
# it MUST satisfy { |p| p.valid? } # Custom validation
73+
#
74+
# on(:save) do
75+
# it MUST change(product, :updated_at) # State change
76+
# it SHOULD_NOT raise_exception # Exception checking
77+
# end
78+
# end
79+
# end
80+
#
81+
# @see Fix::Set For managing collections of specifications
82+
# @see Fix::Doc For storing and retrieving specifications
83+
# @see Fix::Dsl For the domain-specific language used in specifications
84+
# @see Fix::Matcher For the complete list of available matchers
2985
#
3086
# @api public
3187
module Fix
32-
class << self
33-
# Retrieves and loads a built specification for testing.
34-
#
35-
# @example Run a named specification
36-
# Fix[:Answer].test { 42 }
37-
#
38-
# @param name [String, Symbol] The constant name of the specification
39-
# @return [Fix::Set] The loaded specification set ready for testing
40-
# @raise [Fix::Error::SpecificationNotFound] If the named specification doesn't exist
41-
def [](name)
42-
name = normalize_name(name)
43-
validate_specification_exists!(name)
44-
Set.load(name)
45-
end
88+
# Creates a new specification set, optionally registering it under a name.
89+
#
90+
# @param name [Symbol, nil] Optional name to register the specification under.
91+
# If nil, creates an anonymous specification for immediate use.
92+
# @yieldreturn [void] Block containing the specification definition using Fix DSL
93+
# @return [Fix::Set] A new specification set ready for testing
94+
# @raise [Fix::Error::MissingSpecificationBlock] If no block is provided
95+
#
96+
# @example Create a named specification
97+
# Fix :StringValidator do
98+
# on(:validate, "hello@example.com") do
99+
# it MUST be_valid_email
100+
# it MUST satisfy { |result| result.errors.empty? }
101+
# end
102+
# end
103+
#
104+
# @example Create an anonymous specification
105+
# Fix do
106+
# it MUST be_positive
107+
# it MUST be_within(0.1).of(42.0)
108+
# end.test { 42 }
109+
#
110+
# @api public
111+
def self.spec(name = nil, &block)
112+
raise Error::MissingSpecificationBlock if block.nil?
46113

47-
# Lists all defined specification names.
48-
#
49-
# @example Get all specification names
50-
# Fix.specification_names #=> [:Answer, :Calculator, :UserProfile]
51-
#
52-
# @return [Array<Symbol>] Sorted array of specification names
53-
def specification_names
54-
Doc.constants.sort
55-
end
56-
57-
# Checks if a specification is defined.
58-
#
59-
# @example Check for specification existence
60-
# Fix.spec_defined?(:Answer) #=> true
61-
#
62-
# @param name [String, Symbol] Name of the specification to check
63-
# @return [Boolean] true if specification exists, false otherwise
64-
def spec_defined?(name)
65-
specification_names.include?(normalize_name(name))
66-
end
114+
Set.build(name, &block)
115+
end
67116

68-
private
117+
# Retrieves a previously registered specification by name.
118+
#
119+
# @param name [Symbol] The constant name of the specification to retrieve
120+
# @return [Fix::Set] The loaded specification set ready for testing
121+
# @raise [Fix::Error::SpecificationNotFound] If the named specification doesn't exist
122+
#
123+
# @example
124+
# # Define a specification with multiple matchers
125+
# Fix :EmailValidator do
126+
# on(:validate, "test@example.com") do
127+
# it MUST be_valid
128+
# it MUST_NOT raise_exception
129+
# it SHOULD satisfy { |result| result.score > 0.8 }
130+
# end
131+
# end
132+
#
133+
# # Later, retrieve and use it
134+
# Fix[:EmailValidator].test { MyEmailValidator }
135+
#
136+
# @see #spec For creating new specifications
137+
# @see Fix::Set#test For running tests against a specification
138+
# @see Fix::Matcher For available matchers
139+
#
140+
# @api public
141+
def self.[](name)
142+
raise Error::SpecificationNotFound, name unless key?(name)
69143

70-
# Converts any specification name into a symbol.
71-
# This allows for consistent name handling regardless of input type.
72-
#
73-
# @param name [String, Symbol] The name to normalize
74-
# @return [Symbol] The normalized name
75-
# @example
76-
# normalize_name("Answer") #=> :Answer
77-
# normalize_name(:Answer) #=> :Answer
78-
def normalize_name(name)
79-
String(name).to_sym
80-
end
144+
Set.load(name)
145+
end
81146

82-
# Verifies the existence of a specification and raises an error if not found.
83-
# This ensures early failure when attempting to use undefined specifications.
84-
#
85-
# @param name [Symbol] The specification name to validate
86-
# @raise [Fix::Error::SpecificationNotFound] If specification doesn't exist
87-
def validate_specification_exists!(name)
88-
return if spec_defined?(name)
147+
# Lists all defined specification names.
148+
#
149+
# @return [Array<Symbol>] Sorted array of registered specification names
150+
#
151+
# @example
152+
# Fix :First do; end
153+
# Fix :Second do; end
154+
#
155+
# Fix.keys #=> [:First, :Second]
156+
#
157+
# @api public
158+
def self.keys
159+
Doc.constants.sort
160+
end
89161

90-
raise Error::SpecificationNotFound, name
91-
end
162+
# Checks if a specification is registered under the given name.
163+
#
164+
# @param name [Symbol] The name to check for
165+
# @return [Boolean] true if a specification exists with this name, false otherwise
166+
#
167+
# @example
168+
# Fix :Example do
169+
# it MUST be_an_instance_of(Example)
170+
# end
171+
#
172+
# Fix.key?(:Example) #=> true
173+
# Fix.key?(:Missing) #=> false
174+
#
175+
# @api public
176+
def self.key?(name)
177+
keys.include?(name)
92178
end
93179
end

lib/fix/builder.rb

Lines changed: 0 additions & 101 deletions
This file was deleted.

0 commit comments

Comments
 (0)