Runtime assertions for Ruby
literal.fun
ruby
1# frozen_string_literal: true
2
3include Literal::Types
4
5test "result block returns a checked result" do
6 result = Literal::Result(Integer, Symbol) do |type|
7 type.success(42)
8 end
9
10 assert Literal::Success === result
11 assert_equal 42, result.value!
12end
13
14test "result block requires throwing a result" do
15 error = assert_raises(Literal::ArgumentError) do
16 Literal::Result(Integer, Symbol) do
17 42
18 end
19 end
20
21 assert_equal "Expected block to throw a success or failure result", error.message
22end
23
24test "result block must throw, not return, a result" do
25 error = assert_raises(Literal::ArgumentError) do
26 Literal::Result(Integer, Symbol) do
27 Literal::Result(String, Symbol) { |type| type.success("42") }
28 end
29 end
30
31 assert_equal "Expected block to throw a success or failure result", error.message
32end
33
34test "and_then adopts returned success type" do
35 result = Literal::Result(Integer, Symbol) { |type| type.success(42) }
36 .and_then { |value| Literal::Result(String, RuntimeError) { |type| type.success(value.to_s) } }
37
38 assert result.success?
39 assert_equal "42", result.value!
40 assert Literal::Result(String, _Union(Symbol, RuntimeError)) === result
41end
42
43test "and_then unions failure types" do
44 result = Literal::Result(Integer, Symbol) { |type| type.success(42) }
45 .and_then { Literal::Result(String, RuntimeError) { |type| type.failure(RuntimeError.new("boom")) } }
46
47 assert result.failure?
48 assert RuntimeError === result.error!
49 assert Literal::Result(String, _Union(Symbol, RuntimeError)) === result
50end
51
52test "failure and_then does not yield" do
53 original = Literal::Result(Integer, Symbol) { |type| type.failure(:nope) }
54 yielded = false
55
56 result = original.and_then do
57 yielded = true
58 Literal::Result(String, RuntimeError) { |type| type.success("ok") }
59 end
60
61 refute yielded
62 assert result.equal?(original)
63end
64
65test "also yields success value and returns self" do
66 original = Literal::Result(Integer, Symbol) { |type| type.success(42) }
67 yielded = nil
68
69 result = original.also do |value|
70 yielded = value
71 end
72
73 assert_equal 42, yielded
74 assert result.equal?(original)
75end
76
77test "failure also does not yield" do
78 original = Literal::Result(Integer, Symbol) { |type| type.failure(:nope) }
79 yielded = false
80
81 result = original.also do
82 yielded = true
83 end
84
85 refute yielded
86 assert result.equal?(original)
87end
88
89test "and_then block must return result" do
90 result = Literal::Result(Integer, Symbol) { |type| type.success(42) }
91
92 error = assert_raises(Literal::ArgumentError) do
93 result.and_then(&:to_s)
94 end
95
96 assert_equal "Expected block to return a Literal::Result, got String", error.message
97end
98
99test "failure map updates success type metadata" do
100 result = Literal::Result(Integer, Symbol) { |type| type.failure(:nope) }
101 mapped = result.map(String, &:to_s)
102
103 assert mapped.failure?
104 assert_equal :nope, mapped.error!
105 assert_equal String, mapped.success_type
106 assert_equal Symbol, mapped.failure_type
107end
108
109test "success deconstruct_keys delegates to wrapped value" do
110 person_class = Class.new do
111 def initialize(name)
112 @name = name
113 end
114
115 def deconstruct_keys(keys)
116 h = { name: @name }
117 keys ? h.slice(*keys) : h
118 end
119 end
120
121 result = Literal::Result(person_class, Symbol) { |type| type.success(person_class.new("Joel")) }
122
123 assert_equal({ name: "Joel" }, result.deconstruct_keys([:name]))
124 assert_equal({}, result.deconstruct_keys([:value]))
125end
126
127test "failure deconstruct_keys delegates to wrapped error" do
128 error_class = Class.new do
129 def initialize(message)
130 @message = message
131 end
132
133 def deconstruct_keys(keys)
134 h = { message: @message }
135 keys ? h.slice(*keys) : h
136 end
137 end
138
139 result = Literal::Result(String, error_class) { |type| type.failure(error_class.new("oops")) }
140
141 assert_equal({ message: "oops" }, result.deconstruct_keys([:message]))
142 assert_equal({}, result.deconstruct_keys([:error]))
143end
144
145test "deconstruct_keys returns empty hash when wrapped object does not support it" do
146 success = Literal::Result(Integer, Symbol) { |type| type.success(1) }
147 failure = Literal::Result(String, Symbol) { |type| type.failure(:oops) }
148
149 assert_equal({}, success.deconstruct_keys([:anything]))
150 assert_equal({}, failure.deconstruct_keys([:anything]))
151end
152
153test "pattern matches success with positional pattern" do
154 result = Literal::Result(Integer, Symbol) { |type| type.success(1) }
155
156 matched = case result
157 in Literal::Success[Integer]
158 true
159 else
160 false
161 end
162
163 assert matched
164end
165
166test "pattern matches success with delegated keyword pattern" do
167 message_class = Class.new do
168 def initialize(message)
169 @message = message
170 end
171
172 def deconstruct_keys(keys)
173 h = { message: @message }
174 keys ? h.slice(*keys) : h
175 end
176 end
177
178 result = Literal::Result(message_class, Symbol) { |type| type.success(message_class.new("Hello")) }
179
180 matched = case result
181 in Literal::Success[message: "Hello"]
182 true
183 else
184 false
185 end
186
187 assert matched
188end