fix 0.21 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/fix/it.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spectus/expectation_target'
4
+ require 'matchi/helper'
5
+
6
+ module Fix
7
+ # Wraps the target of an expectation.
8
+ class It < ::Spectus::ExpectationTarget
9
+ include ::Matchi::Helper
10
+
11
+ # Create a new expection target
12
+ #
13
+ # @param callable [#call] The object to test.
14
+ def initialize(callable, **lets)
15
+ raise unless callable.respond_to?(:call)
16
+
17
+ @callable = callable
18
+ @lets = lets
19
+ end
20
+
21
+ private
22
+
23
+ def method_missing(name, *args, &block)
24
+ @lets.fetch(name) { super }
25
+ end
26
+
27
+ def respond_to_missing?(name, include_private = false)
28
+ @lets.key?(name) || super
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fix
4
+ class SuspiciousSuccessError < ::StandardError; end
5
+ end
data/lib/fix.rb CHANGED
@@ -1,179 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "fix/doc"
4
- require_relative "fix/error/missing_specification_block"
5
- require_relative "fix/error/specification_not_found"
6
- require_relative "fix/set"
7
- require_relative "kernel"
8
-
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.
11
- #
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
15
- #
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
58
- # end
59
- #
60
- # @example Complete specification with multiple matchers
61
- # Fix :Product do
62
- # let(:price) { 42.99 }
3
+ # Namespace for the Fix framework.
63
4
  #
64
- # it MUST be_an_instance_of Product # Type checking
65
- # it MUST_NOT be_nil # Nil checking
66
- #
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
85
- #
86
- # @api public
87
5
  module Fix
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?
113
-
114
- Set.build(name, &block)
115
- end
116
-
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)
143
-
144
- Set.load(name)
145
- end
146
-
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]
6
+ # Specs are built with this method.
156
7
  #
157
- # @api public
158
- def self.keys
159
- Doc.constants.sort
160
- end
161
-
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)
8
+ # @example 42 must be equal to 42
9
+ # describe(42) do
10
+ # it { MUST equal 42 }
170
11
  # end
171
12
  #
172
- # Fix.key?(:Example) #=> true
173
- # Fix.key?(:Missing) #=> false
13
+ # @param front_object [BasicObject] The front object.
14
+ # @param options [Hash] Some options.
15
+ # @param specs [Proc] The set of specs.
174
16
  #
175
- # @api public
176
- def self.key?(name)
177
- keys.include?(name)
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)
178
21
  end
179
22
  end
23
+
24
+ require_relative 'kernel'
25
+ require_relative File.join('fix', 'context')
data/lib/kernel.rb CHANGED
@@ -1,103 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Extension of the global Kernel module to provide the Fix method.
4
- # This module provides a global entry point to the Fix testing framework,
5
- # allowing specifications to be defined and managed from anywhere in the application.
6
- #
7
- # The Fix method can be used in two main ways:
8
- # 1. Creating named specifications for reuse
9
- # 2. Creating anonymous specifications for immediate testing
10
- #
11
- # @api public
12
3
  module Kernel
13
4
  # rubocop:disable Naming/MethodName
14
-
15
- # This rule is disabled because Fix is intentionally capitalized to act as
16
- # both a namespace and a method name, following Ruby conventions for DSLs.
17
-
18
- # Defines a new test specification or creates an anonymous specification set.
19
- # When a name is provided, the specification is registered globally and can be
20
- # referenced later using Fix[name]. Anonymous specifications are executed
21
- # immediately and cannot be referenced later.
22
- #
23
- # Specifications can use three levels of requirements, following RFC 2119:
24
- # - MUST/MUST_NOT: Absolute requirements or prohibitions
25
- # - SHOULD/SHOULD_NOT: Strong recommendations that can be ignored with good reason
26
- # - MAY: Optional features that can be implemented or not
27
- #
28
- # Available matchers include:
29
- # - Basic Comparison: eq(expected), eql(expected), be(expected), equal(expected)
30
- # - Type Checking: be_an_instance_of(class), be_a_kind_of(class)
31
- # - State & Changes: change(object, method).by(n), by_at_least(n), by_at_most(n),
32
- # from(old).to(new), to(new)
33
- # - Value Testing: be_within(delta).of(value), match(regex),
34
- # satisfy { |value| ... }
35
- # - Exceptions: raise_exception(class)
36
- # - State Testing: be_true, be_false, be_nil
37
- # - Predicate Matchers: be_* (e.g., be_empty), have_* (e.g., have_key)
38
- #
39
- # @example Creating a named specification with multiple contexts and matchers
40
- # Fix :Calculator do
41
- # on(:add, 2, 3) do
42
- # it MUST eq 5
43
- # it MUST be_an_instance_of(Integer)
44
- # end
45
- #
46
- # with precision: :high do
47
- # on(:divide, 10, 3) do
48
- # it MUST be_within(0.001).of(3.333)
49
- # end
50
- # end
51
- #
52
- # on(:divide, 1, 0) do
53
- # it MUST raise_exception ZeroDivisionError
54
- # end
55
- # end
56
- #
57
- # @example Creating and immediately testing an anonymous specification
58
- # Fix do
59
- # it MUST be_positive
60
- # it SHOULD be_even
61
- # it MAY be_prime
62
- # end.test { 42 }
63
- #
64
- # @example Testing state changes
65
- # Fix :Account do
66
- # on(:deposit, 100) do
67
- # it MUST change(account, :balance).by(100)
68
- # it SHOULD change(account, :updated_at)
69
- # end
70
- #
71
- # on(:withdraw, 50) do
72
- # it MUST change(account, :balance).by(-50)
73
- # it MUST_NOT change(account, :status)
74
- # end
75
- # end
76
- #
77
- # @example Using predicates and custom matchers
78
- # Fix :Collection do
79
- # with items: [] do
80
- # it MUST be_empty
81
- # it MUST_NOT have_errors
82
- # it SHOULD satisfy { |c| c.valid? && c.initialized? }
83
- # end
84
- # end
85
- #
86
- # @param name [Symbol, nil] Optional name to register the specification under
87
- # @yield Block containing the specification definition using Fix DSL
88
- # @return [Fix::Set] A specification set ready for testing
89
- # @raise [Fix::Error::MissingSpecificationBlock] If no block is provided
90
- # @raise [Fix::Error::InvalidSpecificationName] If name is not a valid constant name
91
- #
92
- # @see Fix::Set For managing collections of specifications
93
- # @see Fix::Dsl For the domain-specific language used in specifications
94
- # @see Fix::Matcher For the complete list of available matchers
95
- # @see https://tools.ietf.org/html/rfc2119 For details about requirement levels
96
- #
97
- # @api public
98
- def Fix(name = nil, &block)
99
- ::Fix.spec(name, &block)
5
+ def Fix(subject, **lets, &block)
6
+ ::Fix.describe(subject, **lets, &block)
100
7
  end
101
-
102
8
  # rubocop:enable Naming/MethodName
103
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fix
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.21'
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-03 00:00:00.000000000 Z
11
+ date: 2020-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: defi
@@ -16,47 +16,113 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 3.0.1
19
+ version: 2.0.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 3.0.1
26
+ version: 2.0.3
27
27
  - !ruby/object:Gem::Dependency
28
- name: matchi
28
+ name: spectus
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 4.1.1
33
+ version: 3.1.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 4.1.1
40
+ version: 3.1.1
41
41
  - !ruby/object:Gem::Dependency
42
- name: spectus
42
+ name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 5.0.2
48
- type: :runtime
47
+ version: '2.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
49
91
  prerelease: false
50
92
  version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
51
100
  requirements:
52
101
  - - "~>"
53
102
  - !ruby/object:Gem::Version
54
- version: 5.0.2
55
- description: |
56
- Fix is a modern Ruby testing framework built around a key architectural principle:
57
- the complete separation between specifications and tests. It allows you to write
58
- pure specification documents that define expected behaviors, and then independently
59
- challenge any implementation against these specifications.
103
+ version: '0.17'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.17'
111
+ - !ruby/object:Gem::Dependency
112
+ name: yard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.9'
125
+ description: Specing framework for Ruby.
60
126
  email: contact@cyril.email
61
127
  executables: []
62
128
  extensions: []
@@ -65,28 +131,17 @@ files:
65
131
  - LICENSE.md
66
132
  - README.md
67
133
  - lib/fix.rb
68
- - lib/fix/doc.rb
69
- - lib/fix/dsl.rb
70
- - lib/fix/error/invalid_specification_name.rb
71
- - lib/fix/error/missing_specification_block.rb
72
- - lib/fix/error/missing_subject_block.rb
73
- - lib/fix/error/specification_not_found.rb
74
- - lib/fix/matcher.rb
75
- - lib/fix/requirement.rb
76
- - lib/fix/run.rb
77
- - lib/fix/set.rb
134
+ - lib/fix/context.rb
135
+ - lib/fix/expectation_result_not_found_error.rb
136
+ - lib/fix/it.rb
137
+ - lib/fix/suspicious_success_error.rb
78
138
  - lib/kernel.rb
79
139
  homepage: https://fixrb.dev/
80
140
  licenses:
81
141
  - MIT
82
142
  metadata:
83
- bug_tracker_uri: https://github.com/fixrb/fix/issues
84
- changelog_uri: https://github.com/fixrb/fix/blob/main/CHANGELOG.md
85
- documentation_uri: https://rubydoc.info/gems/fix
86
- homepage_uri: https://fixrb.dev
87
143
  source_code_uri: https://github.com/fixrb/fix
88
- rubygems_mfa_required: 'true'
89
- post_install_message:
144
+ post_install_message:
90
145
  rdoc_options: []
91
146
  require_paths:
92
147
  - lib
@@ -94,15 +149,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
149
  requirements:
95
150
  - - ">="
96
151
  - !ruby/object:Gem::Version
97
- version: 3.1.0
152
+ version: 2.7.0
98
153
  required_rubygems_version: !ruby/object:Gem::Requirement
99
154
  requirements:
100
- - - ">="
155
+ - - ">"
101
156
  - !ruby/object:Gem::Version
102
- version: '0'
157
+ version: 1.3.1
103
158
  requirements: []
104
- rubygems_version: 3.3.27
105
- signing_key:
159
+ rubygems_version: 3.1.2
160
+ signing_key:
106
161
  specification_version: 4
107
- summary: Happy Path to Ruby Testing
162
+ summary: Specing framework for Ruby.
108
163
  test_files: []
data/lib/fix/doc.rb DELETED
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "error/invalid_specification_name"
4
-
5
- module Fix
6
- # The Doc module serves as a central registry for storing and managing test specifications.
7
- # It provides functionality for:
8
- # - Storing specification classes in a structured way
9
- # - Managing the lifecycle of specification documents
10
- # - Extracting test specifications from context objects
11
- # - Validating specification names
12
- #
13
- # The module acts as a namespace for specifications, allowing them to be:
14
- # - Registered with unique names
15
- # - Retrieved by name when needed
16
- # - Protected from name collisions
17
- # - Organized in a hierarchical structure
18
- #
19
- # @example Registering a new specification
20
- # Fix::Doc.add(:Calculator, calculator_specification_class)
21
- #
22
- # @example Retrieving specification contexts
23
- # contexts = Fix::Doc.fetch(:Calculator)
24
- # specifications = Fix::Doc.extract_specifications(*contexts)
25
- #
26
- # @api private
27
- module Doc
28
- # Retrieves the array of test contexts associated with a named specification.
29
- # These contexts define the test environment and requirements for the specification.
30
- #
31
- # @param name [Symbol] The name of the specification to retrieve
32
- # @return [Array<Fix::Dsl>] Array of context classes for the specification
33
- # @raise [NameError] If the specification name doesn't exist in the registry
34
- #
35
- # @example Retrieving contexts for a calculator specification
36
- # contexts = Fix::Doc.fetch(:Calculator)
37
- # contexts.each do |context|
38
- # # Process each context...
39
- # end
40
- #
41
- # @api private
42
- def self.fetch(name)
43
- const_get("#{name}::CONTEXTS")
44
- end
45
-
46
- # Extracts complete test specifications from a list of context classes.
47
- # This method processes contexts to build a list of executable test specifications.
48
- #
49
- # Each extracted specification contains:
50
- # - The test environment
51
- # - The source file location
52
- # - The requirement level (MUST, SHOULD, or MAY)
53
- # - The list of challenges to execute
54
- #
55
- # @param contexts [Array<Fix::Dsl>] List of context classes to process
56
- # @return [Array<Array>] Array of specification arrays where each contains:
57
- # - [0] environment [Fix::Dsl] The test environment instance
58
- # - [1] location [String] The test file location ("path:line")
59
- # - [2] requirement [Object] The test requirement (MUST, SHOULD, or MAY)
60
- # - [3] challenges [Array] Array of test challenges to execute
61
- #
62
- # @example Extracting specifications from contexts
63
- # contexts = Fix::Doc.fetch(:Calculator)
64
- # specifications = Fix::Doc.extract_specifications(*contexts)
65
- # specifications.each do |env, location, requirement, challenges|
66
- # # Process each specification...
67
- # end
68
- #
69
- # @api private
70
- def self.extract_specifications(*contexts)
71
- contexts.flat_map do |context|
72
- extract_context_specifications(context)
73
- end
74
- end
75
-
76
- # Registers a new specification class under the given name in the registry.
77
- # The name must be a valid Ruby constant name to ensure proper namespace organization.
78
- #
79
- # @param name [Symbol] The name to register the specification under
80
- # @param klass [Class] The specification class to register
81
- # @raise [Fix::Error::InvalidSpecificationName] If name is not a valid constant name
82
- # @return [void]
83
- #
84
- # @example Adding a new specification
85
- # class CalculatorSpec < Fix::Dsl
86
- # # specification implementation...
87
- # end
88
- # Fix::Doc.add(:Calculator, CalculatorSpec)
89
- #
90
- # @example Invalid name handling
91
- # # This will raise Fix::Error::InvalidSpecificationName
92
- # Fix::Doc.add(:"invalid-name", some_class)
93
- #
94
- # @api private
95
- def self.add(name, klass)
96
- const_set(name, klass)
97
- rescue ::NameError => _e
98
- raise Error::InvalidSpecificationName, name
99
- end
100
-
101
- # Extracts test specifications from a single context class.
102
- # This method processes public methods in the context to build
103
- # a list of executable test specifications.
104
- #
105
- # @param context [Fix::Dsl] The context class to process
106
- # @return [Array<Array>] Array of specification arrays
107
- #
108
- # @api private
109
- def self.extract_context_specifications(context)
110
- env = context.new
111
- env.public_methods(false).map do |public_method|
112
- [env] + env.public_send(public_method)
113
- end
114
- end
115
-
116
- private_class_method :extract_context_specifications
117
- end
118
- end