matchi 4.2.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/README.md +96 -227
- data/lib/matchi/be.rb +10 -65
- data/lib/matchi/be_a_kind_of.rb +21 -116
- data/lib/matchi/be_an_instance_of.rb +24 -113
- data/lib/matchi/be_within/of.rb +57 -0
- data/lib/matchi/be_within.rb +17 -134
- data/lib/matchi/change/delta.rb +56 -0
- data/lib/matchi/change/from.rb +21 -61
- data/lib/matchi/change/from_to.rb +46 -0
- data/lib/matchi/change/to.rb +17 -81
- data/lib/matchi/change.rb +73 -148
- data/lib/matchi/constant.rb +55 -0
- data/lib/matchi/eq.rb +24 -65
- data/lib/matchi/match.rb +29 -64
- data/lib/matchi/number.rb +37 -0
- data/lib/matchi/predicate.rb +40 -139
- data/lib/matchi/raise_exception.rb +83 -105
- data/lib/matchi/satisfy.rb +21 -96
- data/lib/matchi.rb +22 -60
- metadata +18 -20
- data/lib/matchi/change/by.rb +0 -137
- data/lib/matchi/change/by_at_least.rb +0 -158
- data/lib/matchi/change/by_at_most.rb +0 -171
- data/lib/matchi/change/from/to.rb +0 -132
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Matchi
|
|
4
|
-
class Change
|
|
5
|
-
# Minimum delta matcher that verifies numeric changes meet or exceed a threshold.
|
|
6
|
-
#
|
|
7
|
-
# This matcher ensures that a numeric value changes by at least the specified amount
|
|
8
|
-
# after executing a block of code. It's particularly useful when testing operations
|
|
9
|
-
# where you want to ensure a minimum change occurs but larger changes are acceptable,
|
|
10
|
-
# such as performance improvements, resource allocation, or progressive counters.
|
|
11
|
-
#
|
|
12
|
-
# @example Testing collection growth
|
|
13
|
-
# items = []
|
|
14
|
-
# matcher = Matchi::Change::ByAtLeast.new(2) { items.size }
|
|
15
|
-
# matcher.match? { items.push(1, 2) } # => true # Changed by exactly 2
|
|
16
|
-
# matcher.match? { items.push(1, 2, 3) } # => true # Changed by more than 2
|
|
17
|
-
# matcher.match? { items.push(1) } # => false # Changed by less than 2
|
|
18
|
-
#
|
|
19
|
-
# @example Verifying performance improvements
|
|
20
|
-
# class Benchmark
|
|
21
|
-
# def initialize
|
|
22
|
-
# @score = 100
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# def optimize!
|
|
26
|
-
# @score += rand(20..30) # Improvement varies
|
|
27
|
-
# end
|
|
28
|
-
#
|
|
29
|
-
# def score
|
|
30
|
-
# @score
|
|
31
|
-
# end
|
|
32
|
-
# end
|
|
33
|
-
#
|
|
34
|
-
# benchmark = Benchmark.new
|
|
35
|
-
# matcher = Matchi::Change::ByAtLeast.new(20) { benchmark.score }
|
|
36
|
-
# matcher.match? { benchmark.optimize! } # => true # Any improvement >= 20 passes
|
|
37
|
-
#
|
|
38
|
-
# @example Resource allocation
|
|
39
|
-
# class Pool
|
|
40
|
-
# def initialize
|
|
41
|
-
# @capacity = 1000
|
|
42
|
-
# end
|
|
43
|
-
#
|
|
44
|
-
# def allocate(minimum, maximum)
|
|
45
|
-
# actual = rand(minimum..maximum)
|
|
46
|
-
# @capacity -= actual
|
|
47
|
-
# actual
|
|
48
|
-
# end
|
|
49
|
-
#
|
|
50
|
-
# def available
|
|
51
|
-
# @capacity
|
|
52
|
-
# end
|
|
53
|
-
# end
|
|
54
|
-
#
|
|
55
|
-
# pool = Pool.new
|
|
56
|
-
# matcher = Matchi::Change::ByAtLeast.new(50) { -pool.available }
|
|
57
|
-
# matcher.match? { pool.allocate(50, 100) } # => true # Allocates at least 50
|
|
58
|
-
#
|
|
59
|
-
# @example Price threshold monitoring
|
|
60
|
-
# class Stock
|
|
61
|
-
# attr_reader :price
|
|
62
|
-
#
|
|
63
|
-
# def initialize(price)
|
|
64
|
-
# @price = price
|
|
65
|
-
# end
|
|
66
|
-
#
|
|
67
|
-
# def fluctuate!
|
|
68
|
-
# @price += rand(-10.0..20.0)
|
|
69
|
-
# end
|
|
70
|
-
# end
|
|
71
|
-
#
|
|
72
|
-
# stock = Stock.new(100.0)
|
|
73
|
-
# matcher = Matchi::Change::ByAtLeast.new(10.0) { stock.price }
|
|
74
|
-
# matcher.match? { stock.fluctuate! } # => true if price rises by 10.0 or more
|
|
75
|
-
#
|
|
76
|
-
# @note This matcher verifies minimum changes only. For exact changes, use By,
|
|
77
|
-
# and for maximum changes, use ByAtMost.
|
|
78
|
-
#
|
|
79
|
-
# @see Matchi::Change::By For exact change validation
|
|
80
|
-
# @see Matchi::Change::ByAtMost For maximum change validation
|
|
81
|
-
# @see Matchi::Change::To For final value validation
|
|
82
|
-
class ByAtLeast
|
|
83
|
-
# Initialize the matcher with a minimum expected change and a state block.
|
|
84
|
-
#
|
|
85
|
-
# @api public
|
|
86
|
-
#
|
|
87
|
-
# @param expected [Numeric] The minimum amount by which the value should change
|
|
88
|
-
# @param state [Proc] Block that retrieves the current value
|
|
89
|
-
#
|
|
90
|
-
# @raise [ArgumentError] if expected is not a Numeric
|
|
91
|
-
# @raise [ArgumentError] if expected is negative
|
|
92
|
-
# @raise [ArgumentError] if no state block is provided
|
|
93
|
-
#
|
|
94
|
-
# @return [ByAtLeast] a new instance of the matcher
|
|
95
|
-
#
|
|
96
|
-
# @example With integer minimum
|
|
97
|
-
# ByAtLeast.new(5) { counter.value }
|
|
98
|
-
#
|
|
99
|
-
# @example With floating point minimum
|
|
100
|
-
# ByAtLeast.new(0.5) { temperature.celsius }
|
|
101
|
-
def initialize(expected, &state)
|
|
102
|
-
raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
|
|
103
|
-
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
104
|
-
raise ::ArgumentError, "expected must be non-negative" if expected.negative?
|
|
105
|
-
|
|
106
|
-
@expected = expected
|
|
107
|
-
@state = state
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Checks if the value changes by at least the expected amount.
|
|
111
|
-
#
|
|
112
|
-
# This method compares the value before and after executing the provided block,
|
|
113
|
-
# ensuring that the difference is greater than or equal to the expected minimum.
|
|
114
|
-
# This is useful when you want to verify that a change meets a minimum threshold.
|
|
115
|
-
#
|
|
116
|
-
# @api public
|
|
117
|
-
#
|
|
118
|
-
# @yield [] Block that should cause the state change
|
|
119
|
-
# @yieldreturn [Object] The result of the block (not used)
|
|
120
|
-
#
|
|
121
|
-
# @return [Boolean] true if the value changed by at least the expected amount
|
|
122
|
-
#
|
|
123
|
-
# @raise [ArgumentError] if no block is provided
|
|
124
|
-
#
|
|
125
|
-
# @example Basic usage
|
|
126
|
-
# counter = 0
|
|
127
|
-
# matcher = ByAtLeast.new(5) { counter }
|
|
128
|
-
# matcher.match? { counter += 7 } # => true # Changed by more than minimum
|
|
129
|
-
#
|
|
130
|
-
# @example Edge case - exact minimum
|
|
131
|
-
# items = []
|
|
132
|
-
# matcher = ByAtLeast.new(2) { items.size }
|
|
133
|
-
# matcher.match? { items.push(1, 2) } # => true # Changed by exactly minimum
|
|
134
|
-
def match?
|
|
135
|
-
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
136
|
-
|
|
137
|
-
value_before = @state.call
|
|
138
|
-
yield
|
|
139
|
-
value_after = @state.call
|
|
140
|
-
|
|
141
|
-
@expected <= (value_after - value_before)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Returns a human-readable description of the matcher.
|
|
145
|
-
#
|
|
146
|
-
# @api public
|
|
147
|
-
#
|
|
148
|
-
# @return [String] A string describing what this matcher verifies
|
|
149
|
-
#
|
|
150
|
-
# @example
|
|
151
|
-
# ByAtLeast.new(5).to_s # => "change by at least 5"
|
|
152
|
-
# ByAtLeast.new(2.5).to_s # => "change by at least 2.5"
|
|
153
|
-
def to_s
|
|
154
|
-
"change by at least #{@expected.inspect}"
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Matchi
|
|
4
|
-
class Change
|
|
5
|
-
# Maximum delta matcher that verifies numeric changes don't exceed a limit.
|
|
6
|
-
#
|
|
7
|
-
# This matcher ensures that a numeric value changes by no more than the specified amount
|
|
8
|
-
# after executing a block of code. It's particularly useful when testing operations
|
|
9
|
-
# where you want to enforce an upper bound on changes, such as rate limiting,
|
|
10
|
-
# resource consumption, or controlled increments.
|
|
11
|
-
#
|
|
12
|
-
# @example Testing controlled collection growth
|
|
13
|
-
# items = []
|
|
14
|
-
# matcher = Matchi::Change::ByAtMost.new(2) { items.size }
|
|
15
|
-
# matcher.match? { items.push(1) } # => true # Changed by less than limit
|
|
16
|
-
# matcher.match? { items.push(1, 2) } # => true # Changed by exactly limit
|
|
17
|
-
# matcher.match? { items.push(1, 2, 3) } # => false # Changed by more than limit
|
|
18
|
-
#
|
|
19
|
-
# @example Rate limiting
|
|
20
|
-
# class RateLimiter
|
|
21
|
-
# def initialize
|
|
22
|
-
# @requests = 0
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# def process_batch(items)
|
|
26
|
-
# items.each do |item|
|
|
27
|
-
# break if @requests >= 3 # Rate limit
|
|
28
|
-
# process_item(item)
|
|
29
|
-
# end
|
|
30
|
-
# end
|
|
31
|
-
#
|
|
32
|
-
# private
|
|
33
|
-
#
|
|
34
|
-
# def process_item(item)
|
|
35
|
-
# @requests += 1
|
|
36
|
-
# # Processing logic...
|
|
37
|
-
# end
|
|
38
|
-
#
|
|
39
|
-
# def requests
|
|
40
|
-
# @requests
|
|
41
|
-
# end
|
|
42
|
-
# end
|
|
43
|
-
#
|
|
44
|
-
# limiter = RateLimiter.new
|
|
45
|
-
# matcher = Matchi::Change::ByAtMost.new(3) { limiter.requests }
|
|
46
|
-
# matcher.match? { limiter.process_batch([1, 2, 3, 4, 5]) } # => true
|
|
47
|
-
#
|
|
48
|
-
# @example Resource consumption
|
|
49
|
-
# class ResourcePool
|
|
50
|
-
# attr_reader :used
|
|
51
|
-
#
|
|
52
|
-
# def initialize
|
|
53
|
-
# @used = 0
|
|
54
|
-
# end
|
|
55
|
-
#
|
|
56
|
-
# def allocate(requested)
|
|
57
|
-
# available = 5 - @used # Maximum pool size is 5
|
|
58
|
-
# granted = [requested, available].min
|
|
59
|
-
# @used += granted
|
|
60
|
-
# granted
|
|
61
|
-
# end
|
|
62
|
-
# end
|
|
63
|
-
#
|
|
64
|
-
# pool = ResourcePool.new
|
|
65
|
-
# matcher = Matchi::Change::ByAtMost.new(2) { pool.used }
|
|
66
|
-
# matcher.match? { pool.allocate(2) } # => true
|
|
67
|
-
# matcher.match? { pool.allocate(3) } # => false
|
|
68
|
-
#
|
|
69
|
-
# @example Score adjustments
|
|
70
|
-
# class GameScore
|
|
71
|
-
# attr_reader :value
|
|
72
|
-
#
|
|
73
|
-
# def initialize
|
|
74
|
-
# @value = 100
|
|
75
|
-
# end
|
|
76
|
-
#
|
|
77
|
-
# def apply_penalty(amount)
|
|
78
|
-
# max_penalty = 10
|
|
79
|
-
# actual_penalty = [amount, max_penalty].min
|
|
80
|
-
# @value -= actual_penalty
|
|
81
|
-
# end
|
|
82
|
-
# end
|
|
83
|
-
#
|
|
84
|
-
# score = GameScore.new
|
|
85
|
-
# matcher = Matchi::Change::ByAtMost.new(10) { -score.value }
|
|
86
|
-
# matcher.match? { score.apply_penalty(5) } # => true # Small penalty
|
|
87
|
-
# matcher.match? { score.apply_penalty(15) } # => true # Limited to max
|
|
88
|
-
#
|
|
89
|
-
# @note This matcher verifies maximum changes only. For exact changes, use By,
|
|
90
|
-
# and for minimum changes, use ByAtLeast.
|
|
91
|
-
#
|
|
92
|
-
# @see Matchi::Change::By For exact change validation
|
|
93
|
-
# @see Matchi::Change::ByAtLeast For minimum change validation
|
|
94
|
-
# @see Matchi::Change::To For final value validation
|
|
95
|
-
class ByAtMost
|
|
96
|
-
# Initialize the matcher with a maximum allowed change and a state block.
|
|
97
|
-
#
|
|
98
|
-
# @api public
|
|
99
|
-
#
|
|
100
|
-
# @param expected [Numeric] The maximum amount by which the value should change
|
|
101
|
-
# @param state [Proc] Block that retrieves the current value
|
|
102
|
-
#
|
|
103
|
-
# @raise [ArgumentError] if expected is not a Numeric
|
|
104
|
-
# @raise [ArgumentError] if expected is negative
|
|
105
|
-
# @raise [ArgumentError] if no state block is provided
|
|
106
|
-
#
|
|
107
|
-
# @return [ByAtMost] a new instance of the matcher
|
|
108
|
-
#
|
|
109
|
-
# @example With integer maximum
|
|
110
|
-
# ByAtMost.new(5) { counter.value }
|
|
111
|
-
#
|
|
112
|
-
# @example With floating point maximum
|
|
113
|
-
# ByAtMost.new(0.5) { temperature.celsius }
|
|
114
|
-
def initialize(expected, &state)
|
|
115
|
-
raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
|
|
116
|
-
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
117
|
-
raise ::ArgumentError, "expected must be non-negative" if expected.negative?
|
|
118
|
-
|
|
119
|
-
@expected = expected
|
|
120
|
-
@state = state
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Checks if the value changes by no more than the expected amount.
|
|
124
|
-
#
|
|
125
|
-
# This method compares the value before and after executing the provided block,
|
|
126
|
-
# ensuring that the absolute difference is less than or equal to the expected maximum.
|
|
127
|
-
# This is useful for enforcing upper bounds on state changes.
|
|
128
|
-
#
|
|
129
|
-
# @api public
|
|
130
|
-
#
|
|
131
|
-
# @yield [] Block that should cause the state change
|
|
132
|
-
# @yieldreturn [Object] The result of the block (not used)
|
|
133
|
-
#
|
|
134
|
-
# @return [Boolean] true if the value changed by at most the expected amount
|
|
135
|
-
#
|
|
136
|
-
# @raise [ArgumentError] if no block is provided
|
|
137
|
-
#
|
|
138
|
-
# @example Basic usage with growth
|
|
139
|
-
# users = []
|
|
140
|
-
# matcher = ByAtMost.new(2) { users.size }
|
|
141
|
-
# matcher.match? { users.push('alice') } # => true
|
|
142
|
-
#
|
|
143
|
-
# @example With negative changes
|
|
144
|
-
# stock = 10
|
|
145
|
-
# matcher = ByAtMost.new(3) { stock }
|
|
146
|
-
# matcher.match? { stock -= 2 } # => true
|
|
147
|
-
def match?
|
|
148
|
-
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
149
|
-
|
|
150
|
-
value_before = @state.call
|
|
151
|
-
yield
|
|
152
|
-
value_after = @state.call
|
|
153
|
-
|
|
154
|
-
@expected >= (value_after - value_before)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Returns a human-readable description of the matcher.
|
|
158
|
-
#
|
|
159
|
-
# @api public
|
|
160
|
-
#
|
|
161
|
-
# @return [String] A string describing what this matcher verifies
|
|
162
|
-
#
|
|
163
|
-
# @example
|
|
164
|
-
# ByAtMost.new(5).to_s # => "change by at most 5"
|
|
165
|
-
# ByAtMost.new(2.5).to_s # => "change by at most 2.5"
|
|
166
|
-
def to_s
|
|
167
|
-
"change by at most #{@expected.inspect}"
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Matchi
|
|
4
|
-
class Change
|
|
5
|
-
class From
|
|
6
|
-
# Value transition matcher that verifies both initial and final states of an operation.
|
|
7
|
-
#
|
|
8
|
-
# This matcher ensures that a value not only changes to an expected final state but also
|
|
9
|
-
# starts from a specific initial state. This is particularly useful when testing state
|
|
10
|
-
# transitions where both the starting and ending conditions are important, such as in
|
|
11
|
-
# workflow systems, state machines, or data transformations.
|
|
12
|
-
#
|
|
13
|
-
# @example Basic string transformation
|
|
14
|
-
# text = "hello"
|
|
15
|
-
# matcher = Matchi::Change::From::To.new("hello", "HELLO") { text.to_s }
|
|
16
|
-
# matcher.match? { text.upcase! } # => true
|
|
17
|
-
# matcher.match? { text.reverse! } # => false # Wrong final state
|
|
18
|
-
#
|
|
19
|
-
# text = "other"
|
|
20
|
-
# matcher.match? { text.upcase! } # => false # Wrong initial state
|
|
21
|
-
#
|
|
22
|
-
# @example State machine transitions
|
|
23
|
-
# class Order
|
|
24
|
-
# attr_accessor :status
|
|
25
|
-
# def initialize(status)
|
|
26
|
-
# @status = status
|
|
27
|
-
# end
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# order = Order.new(:pending)
|
|
31
|
-
# matcher = Matchi::Change::From::To.new(:pending, :shipped) { order.status }
|
|
32
|
-
# matcher.match? { order.status = :shipped } # => true
|
|
33
|
-
#
|
|
34
|
-
# @example Complex object transformations
|
|
35
|
-
# class User
|
|
36
|
-
# attr_accessor :permissions
|
|
37
|
-
# def initialize
|
|
38
|
-
# @permissions = [:read]
|
|
39
|
-
# end
|
|
40
|
-
#
|
|
41
|
-
# def promote!
|
|
42
|
-
# @permissions += [:write, :delete]
|
|
43
|
-
# end
|
|
44
|
-
# end
|
|
45
|
-
#
|
|
46
|
-
# user = User.new
|
|
47
|
-
# matcher = Matchi::Change::From::To.new(
|
|
48
|
-
# [:read],
|
|
49
|
-
# [:read, :write, :delete]
|
|
50
|
-
# ) { user.permissions }
|
|
51
|
-
# matcher.match? { user.promote! } # => true
|
|
52
|
-
#
|
|
53
|
-
# @see Matchi::Change::To For checking only the final state
|
|
54
|
-
# @see Matchi::Change::By For checking numeric changes
|
|
55
|
-
class To
|
|
56
|
-
# Initialize the matcher with expected initial and final values.
|
|
57
|
-
#
|
|
58
|
-
# @api public
|
|
59
|
-
#
|
|
60
|
-
# @param expected_init [#==] The expected initial value
|
|
61
|
-
# @param expected_new_value [#==] The expected final value
|
|
62
|
-
# @param state [Proc] Block that retrieves the current value
|
|
63
|
-
#
|
|
64
|
-
# @raise [ArgumentError] if no state block is provided
|
|
65
|
-
#
|
|
66
|
-
# @return [To] a new instance of the matcher
|
|
67
|
-
#
|
|
68
|
-
# @example With simple value
|
|
69
|
-
# To.new("draft", "published") { document.status }
|
|
70
|
-
#
|
|
71
|
-
# @example With complex state
|
|
72
|
-
# To.new([:user], [:user, :admin]) { account.roles }
|
|
73
|
-
def initialize(expected_init, expected_new_value, &state)
|
|
74
|
-
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
75
|
-
|
|
76
|
-
@expected_init = expected_init
|
|
77
|
-
@expected = expected_new_value
|
|
78
|
-
@state = state
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Verifies both initial and final states during a transition.
|
|
82
|
-
#
|
|
83
|
-
# This method first checks if the initial state matches the expected value,
|
|
84
|
-
# then executes the provided block and verifies the final state. The match
|
|
85
|
-
# fails if either the initial or final state doesn't match expectations.
|
|
86
|
-
#
|
|
87
|
-
# @api public
|
|
88
|
-
#
|
|
89
|
-
# @yield [] Block that should cause the state transition
|
|
90
|
-
# @yieldreturn [Object] The result of the block (not used)
|
|
91
|
-
#
|
|
92
|
-
# @return [Boolean] true if both initial and final states match expectations
|
|
93
|
-
#
|
|
94
|
-
# @raise [ArgumentError] if no block is provided
|
|
95
|
-
#
|
|
96
|
-
# @example Basic usage
|
|
97
|
-
# text = "hello"
|
|
98
|
-
# matcher = To.new("hello", "HELLO") { text }
|
|
99
|
-
# matcher.match? { text.upcase! } # => true
|
|
100
|
-
#
|
|
101
|
-
# @example Failed initial state
|
|
102
|
-
# text = "wrong"
|
|
103
|
-
# matcher = To.new("hello", "HELLO") { text }
|
|
104
|
-
# matcher.match? { text.upcase! } # => false
|
|
105
|
-
def match?
|
|
106
|
-
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
107
|
-
|
|
108
|
-
value_before = @state.call
|
|
109
|
-
return false unless @expected_init == value_before
|
|
110
|
-
|
|
111
|
-
yield
|
|
112
|
-
value_after = @state.call
|
|
113
|
-
|
|
114
|
-
@expected == value_after
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Returns a human-readable description of the matcher.
|
|
118
|
-
#
|
|
119
|
-
# @api public
|
|
120
|
-
#
|
|
121
|
-
# @return [String] A string describing what this matcher verifies
|
|
122
|
-
#
|
|
123
|
-
# @example
|
|
124
|
-
# To.new("draft", "published").to_s
|
|
125
|
-
# # => 'change from "draft" to "published"'
|
|
126
|
-
def to_s
|
|
127
|
-
"change from #{@expected_init.inspect} to #{@expected.inspect}"
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|