zenspec 0.2.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7737ee41815ffc6e727f44826dda5052addaa5925860cd1421665ffed41e85e0
4
- data.tar.gz: 37c0da188f214be5efe29ac58496a24b2831f2e076d4fb247a1bbbaaec57fa59
3
+ metadata.gz: 726804494431887d8cde8ded93f4e74a9a9613812adb1acc276765bbdc0ce5c5
4
+ data.tar.gz: 8f830f054711e25fcbe469ece0500168686af283a2ba2d1ebeb5895ef5d9b88c
5
5
  SHA512:
6
- metadata.gz: cb5d249f606858f4f5835c7889cc6aeacc157ed30e79bac209758c42e5b8abb076e84afdb978ec1c0cbc537d4f1e6c6ba672d53fbcf2f87ae5faa8d3428cbe6b
7
- data.tar.gz: 4796a8d28823742fa70c5417918d310f0ebfaed055a26cd7a8938919b35e2d5cc2de5d18b22622af4d46296d41ea861850b66bf28b76fb955243703f1f1f64e3
6
+ metadata.gz: 9c02f637bab3391379cc0d68e3a61db9759a7c4ffd6e4d956256332aa3723090a44509be9f22cb28e8dd103aa71f40502420740da0dd45c695e15cbc72e9276a
7
+ data.tar.gz: 50f5e33010455625a06c9fa9819969de3e25f9e39575eb4b3a4ce4bdc39304f8d4602b7280e92affecb526465e488007531507889f111679aa8a40002a3bb7a1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2025-11-16
4
+
5
+ ### Fixed
6
+ - **Critical**: Fixed falsy value handling in `have_graphql_data` matcher - now correctly handles `false`, `0`, and empty string values
7
+ - **Critical**: Fixed falsy data value bug in interactor `succeed` matcher - now correctly handles `false` and `0` as expected data
8
+ - Improved snake_case to camelCase conversion to handle edge cases (leading/trailing underscores, consecutive underscores)
9
+
10
+ ### Changed
11
+ - Extracted duplicated type conversion methods to shared `FieldNameHelper` module (reduces code duplication by ~120 lines)
12
+ - Pre-compiled ANSI regex in `ProgressBarFormatter` for better performance
13
+ - Made global `ProgressBarFormatter` constant conditional to prevent namespace conflicts
14
+ - Added documentation comments to magic numbers explaining their values
15
+
16
+ ### Added
17
+ - Support for error count validation in `have_graphql_errors` matcher: `expect(result).to have_graphql_errors(2)`
18
+ - CI testing for Ruby 3.2, 3.3, and 3.4 (expanded from single version)
19
+
3
20
  ## [0.2.0] - 2025-11-16
4
21
 
5
22
  ### Changed
data/README.md CHANGED
@@ -2,54 +2,269 @@
2
2
 
3
3
  A comprehensive RSpec matcher library for testing GraphQL APIs, Interactor service objects, and Rails applications.
4
4
 
5
- ## Installation
5
+ **Features:**
6
+ - GraphQL schema type matchers with snake_case support
7
+ - GraphQL response matchers for queries and mutations
8
+ - Interactor/service object matchers
9
+ - Shoulda matchers integration
10
+ - Beautiful progress bar formatter
6
11
 
7
- Add this line to your application's Gemfile:
12
+ ---
13
+
14
+ ## 30-Second Quickstart
8
15
 
9
16
  ```ruby
17
+ # 1. Add to Gemfile
10
18
  gem "zenspec"
11
- ```
12
-
13
- And then execute:
14
19
 
15
- ```bash
20
+ # 2. Bundle install
16
21
  bundle install
22
+
23
+ # 3. Start testing!
24
+ RSpec.describe UserType do
25
+ # Test GraphQL types (supports snake_case!)
26
+ it { is_expected.to have_field(:id).of_type("ID!") }
27
+ it { is_expected.to have_field(:current_user).of_type("User") }
28
+ end
29
+
30
+ RSpec.describe "GraphQL Queries" do
31
+ subject(:result) { graphql_execute(query) }
32
+
33
+ let(:query) do
34
+ <<~GQL
35
+ query { user(id: "123") { id name } }
36
+ GQL
37
+ end
38
+
39
+ # Test responses
40
+ it { is_expected.to succeed_graphql }
41
+ it { is_expected.to have_graphql_data("user", "name").with_value("John") }
42
+ end
43
+
44
+ RSpec.describe CreateUser do
45
+ subject(:result) { described_class.call(name: "John") }
46
+
47
+ # Test interactors
48
+ it { is_expected.to succeed }
49
+ it { is_expected.to set_context(:user) }
50
+ end
17
51
  ```
18
52
 
19
- ## Quick Start
53
+ ---
54
+
55
+ ## Complete Matchers Reference
56
+
57
+ ### GraphQL Type Matchers
58
+
59
+ Test your GraphQL schema types, fields, and structure.
20
60
 
21
- ### 1. Add to your spec helper
61
+ | Matcher | Usage | Description |
62
+ |---------|-------|-------------|
63
+ | `have_field(name)` | `have_field(:user)` | Check if type has a field (snake_case supported) |
64
+ | `.of_type(type)` | `.of_type("User!")` | Verify field return type |
65
+ | `.with_argument(name, type)` | `.with_argument(:id, "ID!")` | Check field has argument (snake_case supported) |
66
+ | `have_argument(name)` | `have_argument(:limit)` | Check argument exists on field |
67
+ | `.of_type(type)` | `.of_type("Int")` | Verify argument type |
68
+ | `have_enum_values(*values)` | `have_enum_values("ACTIVE", "PENDING")` | Verify enum contains values |
69
+ | `have_query(name)` | `have_query(:current_user)` | Check schema has query (snake_case supported) |
70
+ | `have_mutation(name)` | `have_mutation(:create_user)` | Check schema has mutation (snake_case supported) |
71
+
72
+ **Examples:**
22
73
 
23
74
  ```ruby
24
- # In your spec/rails_helper.rb or spec/spec_helper.rb
25
- require "zenspec"
75
+ # Field testing (snake_case supported!)
76
+ expect(UserType).to have_field(:id).of_type("ID!")
77
+ expect(UserType).to have_field(:current_user).of_type("User") # Works with snake_case!
78
+ expect(UserType).to have_field(:posts).of_type("[Post!]!")
79
+
80
+ # Fields with arguments (snake_case supported!)
81
+ expect(UserType).to have_field(:posts)
82
+ .with_argument(:limit, "Int")
83
+ .with_argument(:include_archived, "Boolean") # snake_case works!
26
84
 
27
- # Everything is automatically configured!
85
+ # Enum testing
86
+ expect(StatusEnum).to have_enum_values("ACTIVE", "INACTIVE", "PENDING")
87
+
88
+ # Schema queries (snake_case supported!)
89
+ expect(AppSchema).to have_query(:user).with_argument(:id, "ID!")
90
+ expect(AppSchema).to have_query(:current_user).of_type("User") # snake_case!
91
+
92
+ # Schema mutations (snake_case supported!)
93
+ expect(AppSchema).to have_mutation(:create_user).of_type("UserPayload!")
94
+ expect(AppSchema).to have_mutation(:update_user_status)
95
+ .with_argument(:user_id, "ID!") # snake_case arguments!
96
+ .with_argument(:new_status, "Status!")
28
97
  ```
29
98
 
30
- ### 2. (Optional) Add progress bar formatter to `.rspec`
99
+ ### GraphQL Response Matchers
100
+
101
+ Test GraphQL query and mutation responses.
102
+
103
+ | Matcher | Usage | Description |
104
+ |---------|-------|-------------|
105
+ | `succeed_graphql` | `expect(result).to succeed_graphql` | Verify query/mutation succeeded (no errors) |
106
+ | `have_graphql_data(path...)` | `have_graphql_data("user", "name")` | Check response data at path |
107
+ | `.with_value(expected)` | `.with_value("John")` | Exact value match |
108
+ | `.matching(hash)` | `.matching(id: "123", name: "John")` | Partial hash match |
109
+ | `.that_includes(hash)` | `.that_includes(name: "John")` | Includes these key-value pairs |
110
+ | `.that_is_present` | `.that_is_present` | Value exists and not null |
111
+ | `.that_is_null` | `.that_is_null` | Value is explicitly null |
112
+ | `.with_count(n)` | `.with_count(5)` | Array has exactly n items |
113
+ | `have_graphql_error` | `have_graphql_error` | Has at least one error |
114
+ | `.with_message(msg)` | `.with_message("Not found")` | Error message matches |
115
+ | `.with_extensions(hash)` | `.with_extensions(code: "NOT_FOUND")` | Error extensions match |
116
+ | `.at_path(array)` | `.at_path(["user", "email"])` | Error occurred at path |
117
+ | `have_graphql_errors(n)` | `have_graphql_errors(2)` | Has exactly n errors |
118
+ | `have_graphql_field(name)` | `have_graphql_field("user")` | Response has field |
119
+ | `have_graphql_fields(hash)` | `have_graphql_fields("user" => true)` | Response has multiple fields |
120
+
121
+ **Examples:**
31
122
 
123
+ ```ruby
124
+ # Basic success check
125
+ expect(result).to succeed_graphql
126
+
127
+ # Data checks
128
+ expect(result).to have_graphql_data("user")
129
+ expect(result).to have_graphql_data("user", "name").with_value("John")
130
+ expect(result).to have_graphql_data("user", "email").that_is_present
131
+
132
+ # Hash matching
133
+ expect(result).to have_graphql_data("user").matching(
134
+ id: "123",
135
+ name: "John Doe",
136
+ email: "john@example.com"
137
+ )
138
+
139
+ # Array checks
140
+ expect(result).to have_graphql_data("users").with_count(5)
141
+ expect(result).to have_graphql_data("users").that_is_present
142
+
143
+ # Error handling
144
+ expect(result).to have_graphql_error
145
+ expect(result).to have_graphql_error.with_message("Not found")
146
+ expect(result).to have_graphql_error.with_extensions(code: "NOT_FOUND")
147
+ expect(result).to have_graphql_error.at_path(["user", "email"])
148
+ expect(result).to have_graphql_errors(2)
32
149
  ```
33
- --require zenspec/formatters/progress_bar_formatter
34
- --format ProgressBarFormatter
35
- --color
36
- --require spec_helper
150
+
151
+ ### Interactor Matchers
152
+
153
+ Test your Interactor service objects.
154
+
155
+ | Matcher | Usage | Description |
156
+ |---------|-------|-------------|
157
+ | `succeed` | `expect(result).to succeed` | Interactor succeeded |
158
+ | `.with_context(key, value)` | `.with_context(:user, user)` | Check context value |
159
+ | `.with_data(value)` | `.with_data(user)` | Check context.data value |
160
+ | `fail_interactor` | `expect(result).to fail_interactor` | Interactor failed |
161
+ | `.with_error(code)` | `.with_error("not_found")` | Failed with specific error code |
162
+ | `.with_errors(*codes)` | `.with_errors("invalid", "missing")` | Failed with multiple error codes |
163
+ | `set_context(key)` | `set_context(:user)` | Context key was set |
164
+ | `set_context(key, value)` | `set_context(:user, user)` | Context key set to value |
165
+ | `have_error_code(code)` | `have_error_code("not_found")` | Has specific error code |
166
+ | `have_error_codes(*codes)` | `have_error_codes("a", "b")` | Has multiple error codes |
167
+
168
+ **Examples:**
169
+
170
+ ```ruby
171
+ # Success checks
172
+ expect(result).to succeed
173
+ expect(result).to succeed.with_context(:user, kind_of(User))
174
+ expect(result).to succeed.with_data(user)
175
+ expect(result).to set_context(:user)
176
+
177
+ # Failure checks
178
+ expect(result).to fail_interactor
179
+ expect(result).to fail_interactor.with_error("validation_failed")
180
+ expect(result).to fail_interactor.with_errors("invalid_email", "missing_name")
181
+ expect(result).to have_error_code("not_found")
182
+ expect(result).to have_error_codes("invalid", "missing")
37
183
  ```
38
184
 
39
- That's it! You can now use all GraphQL and Interactor matchers in your tests.
185
+ ### GraphQL Helpers
186
+
187
+ Helper methods for executing GraphQL queries and mutations in tests.
188
+
189
+ | Helper | Usage | Description |
190
+ |--------|-------|-------------|
191
+ | `graphql_execute(query, **options)` | `graphql_execute(query, variables: {id: "123"})` | Execute GraphQL query |
192
+ | `graphql_execute_as(user, query, **options)` | `graphql_execute_as(user, query)` | Execute query with user in context |
193
+ | `graphql_mutate(mutation, **options)` | `graphql_mutate(mutation, input: {name: "John"})` | Execute GraphQL mutation |
194
+ | `graphql_mutate_as(user, mutation, **options)` | `graphql_mutate_as(user, mutation, input: {})` | Execute mutation with user in context |
195
+
196
+ **Examples:**
197
+
198
+ ```ruby
199
+ # Execute query
200
+ result = graphql_execute(query, variables: { id: "123" }, context: { current_user: user })
201
+
202
+ # Execute as user (adds to context automatically)
203
+ result = graphql_execute_as(user, query, variables: { id: "123" })
204
+
205
+ # Execute mutation
206
+ result = graphql_mutate(mutation, input: { name: "John" }, context: { current_user: user })
207
+
208
+ # Execute mutation as user
209
+ result = graphql_mutate_as(user, mutation, input: { name: "John" })
210
+ ```
40
211
 
41
212
  ---
42
213
 
43
- ## GraphQL Testing
214
+ ## Detailed Usage Examples
215
+
216
+ ### Testing GraphQL Types
44
217
 
45
- ### Response Matchers
218
+ ```ruby
219
+ RSpec.describe UserType do
220
+ subject { described_class }
46
221
 
47
- Test GraphQL query and mutation responses with clean, expressive matchers.
222
+ # Basic fields (snake_case supported!)
223
+ it { is_expected.to have_field(:id).of_type("ID!") }
224
+ it { is_expected.to have_field(:name).of_type("String!") }
225
+ it { is_expected.to have_field(:email).of_type("String") }
226
+ it { is_expected.to have_field(:current_user).of_type("User") } # snake_case!
227
+
228
+ # Array types
229
+ it { is_expected.to have_field(:posts).of_type("[Post!]!") }
230
+
231
+ # Fields with arguments (snake_case supported!)
232
+ it do
233
+ is_expected.to have_field(:posts)
234
+ .with_argument(:limit, "Int")
235
+ .with_argument(:offset, "Int")
236
+ .with_argument(:include_archived, "Boolean") # snake_case works!
237
+ end
238
+ end
239
+
240
+ RSpec.describe StatusEnum do
241
+ subject { described_class }
242
+
243
+ it { is_expected.to have_enum_values("ACTIVE", "INACTIVE", "PENDING") }
244
+ end
245
+
246
+ RSpec.describe AppSchema do
247
+ subject { described_class }
248
+
249
+ # Queries (snake_case supported!)
250
+ it { is_expected.to have_query(:user).with_argument(:id, "ID!") }
251
+ it { is_expected.to have_query(:current_user).of_type("User") }
252
+
253
+ # Mutations (snake_case supported!)
254
+ it { is_expected.to have_mutation(:create_user).of_type("UserPayload!") }
255
+ it { is_expected.to have_mutation(:update_user_status)
256
+ .with_argument(:user_id, "ID!")
257
+ .with_argument(:new_status, "Status!") }
258
+ end
259
+ ```
260
+
261
+ ### Testing GraphQL Queries
48
262
 
49
263
  ```ruby
50
264
  RSpec.describe "User Queries" do
51
265
  subject(:result) { graphql_execute_as(user, query, variables: { id: user.id }) }
52
266
 
267
+ let(:user) { create(:user, name: "John Doe", email: "john@example.com") }
53
268
  let(:query) do
54
269
  <<~GQL
55
270
  query GetUser($id: ID!) {
@@ -65,9 +280,11 @@ RSpec.describe "User Queries" do
65
280
  # Basic checks
66
281
  it { is_expected.to succeed_graphql }
67
282
  it { is_expected.to have_graphql_data("user") }
68
- it { is_expected.to have_graphql_data("user", "name").with_value("John") }
283
+ it { is_expected.to have_graphql_data("user", "id").with_value(user.id) }
284
+ it { is_expected.to have_graphql_data("user", "name").with_value("John Doe") }
285
+ it { is_expected.to have_graphql_data("user", "email").that_is_present }
69
286
 
70
- # Matching data
287
+ # Full object matching
71
288
  it "returns correct user data" do
72
289
  expect(result).to have_graphql_data("user").matching(
73
290
  id: user.id,
@@ -76,111 +293,113 @@ RSpec.describe "User Queries" do
76
293
  )
77
294
  end
78
295
 
79
- # Array checks
80
- it { is_expected.to have_graphql_data("users").with_count(5) }
81
- it { is_expected.to have_graphql_data("users").that_is_present }
82
-
83
- # Error handling
84
- expect(result).to have_graphql_error.with_message("Not found")
85
- expect(result).to have_graphql_error.with_extensions(code: "NOT_FOUND")
86
- expect(result).to have_graphql_error.at_path(["user", "email"])
296
+ # Partial matching
297
+ it "includes user name" do
298
+ expect(result).to have_graphql_data("user").that_includes(name: "John Doe")
299
+ end
87
300
  end
88
- ```
89
301
 
90
- **Available matchers:**
91
- - `succeed_graphql` - Verify successful query/mutation
92
- - `have_graphql_data(path...)` - Check response data
93
- - `have_graphql_error` / `have_graphql_errors` - Verify errors
94
- - `have_graphql_field(name)` / `have_graphql_fields(hash)` - Check field presence
302
+ RSpec.describe "User List Query" do
303
+ subject(:result) { graphql_execute(query) }
95
304
 
96
- **Data matchers:**
97
- - `.with_value(expected)` - Exact value match
98
- - `.matching(hash)` - Partial hash match
99
- - `.that_includes(hash)` - Includes these values
100
- - `.that_is_present` / `.that_is_null` - Presence checks
101
- - `.with_count(n)` - Array size
305
+ let!(:users) { create_list(:user, 5) }
306
+ let(:query) do
307
+ <<~GQL
308
+ query { users { id name } }
309
+ GQL
310
+ end
102
311
 
103
- ### Type Matchers
312
+ it { is_expected.to succeed_graphql }
313
+ it { is_expected.to have_graphql_data("users").with_count(5) }
314
+ it { is_expected.to have_graphql_data("users").that_is_present }
315
+ end
316
+ ```
104
317
 
105
- Test your GraphQL schema types, fields, and arguments.
318
+ ### Testing GraphQL Mutations
106
319
 
107
320
  ```ruby
108
- RSpec.describe UserType do
109
- subject { described_class }
110
-
111
- # Field testing
112
- it { is_expected.to have_field(:id).of_type("ID!") }
113
- it { is_expected.to have_field(:name).of_type("String!") }
114
- it { is_expected.to have_field(:email).of_type("String") }
115
- it { is_expected.to have_field(:posts).of_type("[Post!]!") }
321
+ RSpec.describe "Create User Mutation" do
322
+ subject(:result) do
323
+ graphql_mutate_as(admin, mutation, input: { name: "Jane", email: "jane@example.com" })
324
+ end
116
325
 
117
- # Fields with arguments
118
- it do
119
- is_expected.to have_field(:posts)
120
- .with_argument(:limit, "Int")
121
- .with_argument(:offset, "Int")
326
+ let(:admin) { create(:user, :admin) }
327
+ let(:mutation) do
328
+ <<~GQL
329
+ mutation CreateUser($input: CreateUserInput!) {
330
+ createUser(input: $input) {
331
+ user { id name email }
332
+ errors
333
+ }
334
+ }
335
+ GQL
122
336
  end
123
- end
124
337
 
125
- # Enum testing
126
- RSpec.describe StatusEnum do
127
- subject { described_class }
338
+ context "with valid input" do
339
+ it { is_expected.to succeed_graphql }
340
+ it { is_expected.to have_graphql_data("createUser", "user", "name").with_value("Jane") }
341
+ it { is_expected.to have_graphql_data("createUser", "user", "email").with_value("jane@example.com") }
342
+ it { is_expected.to have_graphql_data("createUser", "errors").that_is_null }
128
343
 
129
- it { is_expected.to have_enum_values("ACTIVE", "INACTIVE", "PENDING") }
130
- end
344
+ it "creates a new user" do
345
+ expect { result }.to change(User, :count).by(1)
346
+ end
347
+ end
131
348
 
132
- # Schema queries and mutations
133
- RSpec.describe AppSchema do
134
- subject { described_class }
349
+ context "with invalid input" do
350
+ subject(:result) do
351
+ graphql_mutate_as(admin, mutation, input: { name: "", email: "invalid" })
352
+ end
135
353
 
136
- it { is_expected.to have_query(:user).with_argument(:id, "ID!") }
137
- it { is_expected.to have_mutation(:createUser).of_type("UserPayload!") }
354
+ it { is_expected.to have_graphql_data("createUser", "user").that_is_null }
355
+ it { is_expected.to have_graphql_data("createUser", "errors").that_is_present }
356
+ end
138
357
  end
139
358
  ```
140
359
 
141
- **Available matchers:**
142
- - `have_field(name)` - Check field exists
143
- - `of_type(type)` - Verify field type
144
- - `with_argument(name, type)` - Check field arguments
145
- - `have_argument(name)` - Check argument on field
146
- - `have_enum_values(*values)` - Verify enum values
147
- - `have_query(name)` / `have_mutation(name)` - Schema-level checks
148
-
149
- ### GraphQL Helpers
150
-
151
- Execute queries and mutations easily in your tests.
360
+ ### Testing GraphQL Errors
152
361
 
153
362
  ```ruby
154
- # Execute query
155
- result = graphql_execute(query, variables: { id: "123" }, context: { current_user: user })
363
+ RSpec.describe "User Query with errors" do
364
+ subject(:result) { graphql_execute(query, variables: { id: "999" }) }
156
365
 
157
- # Execute as user (automatically adds to context)
158
- result = graphql_execute_as(user, query, variables: { id: "123" })
366
+ let(:query) do
367
+ <<~GQL
368
+ query GetUser($id: ID!) {
369
+ user(id: $id) { id name }
370
+ }
371
+ GQL
372
+ end
159
373
 
160
- # Execute mutation
161
- result = graphql_mutate(mutation, input: { name: "John" }, context: { current_user: user })
374
+ it { is_expected.not_to succeed_graphql }
375
+ it { is_expected.to have_graphql_error }
376
+ it { is_expected.to have_graphql_error.with_message("User not found") }
377
+ it { is_expected.to have_graphql_error.with_extensions(code: "NOT_FOUND") }
378
+ it { is_expected.to have_graphql_error.at_path(["user"]) }
162
379
 
163
- # Execute mutation as user
164
- result = graphql_mutate_as(user, mutation, input: { name: "John" })
380
+ context "with multiple errors" do
381
+ it { is_expected.to have_graphql_errors(2) }
382
+ end
383
+ end
165
384
  ```
166
385
 
167
- ---
168
-
169
- ## Interactor Testing
170
-
171
- Test your Interactor service objects with clean, expressive matchers.
386
+ ### Testing Interactors
172
387
 
173
388
  ```ruby
174
389
  RSpec.describe CreateUser do
175
390
  subject(:result) { described_class.call(params) }
176
391
 
177
392
  context "with valid params" do
178
- let(:params) { { name: "John", email: "john@example.com" } }
393
+ let(:params) { { name: "John Doe", email: "john@example.com" } }
179
394
 
180
- # Basic checks
181
395
  it { is_expected.to succeed }
182
396
  it { is_expected.to set_context(:user) }
183
397
  it { is_expected.to succeed.with_context(:user, kind_of(User)) }
398
+
399
+ it "creates a user" do
400
+ expect(result.user).to be_persisted
401
+ expect(result.user.name).to eq("John Doe")
402
+ end
184
403
  end
185
404
 
186
405
  context "with invalid params" do
@@ -189,25 +408,39 @@ RSpec.describe CreateUser do
189
408
  it { is_expected.to fail_interactor }
190
409
  it { is_expected.to fail_interactor.with_error("validation_failed") }
191
410
  it { is_expected.to have_error_code("validation_failed") }
411
+
412
+ it "does not create a user" do
413
+ expect { result }.not_to change(User, :count)
414
+ end
415
+ end
416
+
417
+ context "with multiple errors" do
418
+ let(:params) { { name: "", email: "" } }
419
+
420
+ it { is_expected.to fail_interactor.with_errors("missing_name", "missing_email") }
421
+ it { is_expected.to have_error_codes("missing_name", "missing_email") }
192
422
  end
193
423
  end
194
- ```
195
424
 
196
- **Available matchers:**
197
- - `succeed` - Verify interactor succeeded
198
- - `.with_context(key, value)` - Check context value
199
- - `.with_data(value)` - Check context.data value
200
- - `fail_interactor` - Verify interactor failed
201
- - `.with_error(code)` - Check single error code
202
- - `.with_errors(*codes)` - Check multiple error codes
203
- - `set_context(key, value)` - Check context was set
204
- - `have_error_code(code)` / `have_error_codes(*codes)` - Check error codes
425
+ RSpec.describe UpdateUser do
426
+ subject(:result) { described_class.call(user: user, name: new_name) }
205
427
 
206
- ---
428
+ let(:user) { create(:user, name: "Old Name") }
429
+ let(:new_name) { "New Name" }
430
+
431
+ it { is_expected.to succeed }
432
+ it { is_expected.to succeed.with_context(:user, user) }
433
+ it { is_expected.to succeed.with_data(user) }
434
+
435
+ it "updates the user's name" do
436
+ expect { result }.to change { user.reload.name }.from("Old Name").to("New Name")
437
+ end
438
+ end
439
+ ```
207
440
 
208
- ## Shoulda Matchers
441
+ ### Testing with Shoulda Matchers
209
442
 
210
- Automatically includes and configures [Shoulda Matchers](https://github.com/thoughtbot/shoulda-matchers) for Rails projects.
443
+ Shoulda matchers are automatically included for Rails projects.
211
444
 
212
445
  ```ruby
213
446
  RSpec.describe User do
@@ -216,26 +449,42 @@ RSpec.describe User do
216
449
  # Validations
217
450
  it { is_expected.to validate_presence_of(:email) }
218
451
  it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
452
+ it { is_expected.to validate_length_of(:name).is_at_least(2).is_at_most(100) }
219
453
 
220
454
  # Associations
221
455
  it { is_expected.to have_many(:posts).dependent(:destroy) }
222
456
  it { is_expected.to belong_to(:organization) }
457
+ it { is_expected.to have_one(:profile) }
223
458
  end
224
459
  ```
225
460
 
226
461
  ---
227
462
 
228
- ## Progress Bar Formatter
463
+ ## Installation
229
464
 
230
- Clean, colorful test output with status icons.
465
+ Add this line to your application's Gemfile:
231
466
 
232
- ### Setup
467
+ ```ruby
468
+ gem "zenspec"
469
+ ```
233
470
 
234
- Add to your `.rspec` file:
471
+ And then execute:
472
+
473
+ ```bash
474
+ bundle install
475
+ ```
476
+
477
+ That's it! All matchers and helpers are automatically available in your RSpec tests.
478
+
479
+ ### Optional: Progress Bar Formatter
480
+
481
+ For a cleaner test output with a progress bar, add to your `.rspec` file:
235
482
 
236
483
  ```
237
484
  --require zenspec/formatters/progress_bar_formatter
238
485
  --format ProgressBarFormatter
486
+ --color
487
+ --require spec_helper
239
488
  ```
240
489
 
241
490
  Or use via command line:
@@ -244,25 +493,28 @@ Or use via command line:
244
493
  rspec --require zenspec/formatters/progress_bar_formatter --format ProgressBarFormatter
245
494
  ```
246
495
 
247
- ### Output
496
+ **Example output:**
248
497
 
249
498
  ```
250
- ✔ user_spec.rb [10% 1/10]
251
- ✔ post_spec.rb [20% 2/10]
252
- ⠿ auth_spec.rb --> authenticates with OAuth [30% 3/10]
499
+ ✔ user_spec.rb [10% 15/152]
500
+ ✔ post_spec.rb [20% 30/152]
501
+ ⠿ auth_spec.rb --> authenticates with OAuth [30% 45/152]
253
502
  ```
254
503
 
255
504
  **Icons:**
256
505
  - ✔ Green - Passed
257
506
  - ✗ Red - Failed
258
- - ⠿ Yellow - Running (shows test description)
507
+ - ⠿ Yellow - Running (shows current test description)
259
508
  - ⊘ Cyan - Pending
260
509
 
261
510
  ---
262
511
 
263
512
  ## Configuration
264
513
 
265
- Zenspec works out of the box with sensible defaults. For Rails applications, it automatically configures itself.
514
+ Zenspec works out of the box with sensible defaults. For Rails applications, it automatically:
515
+ - Includes all matchers in RSpec
516
+ - Configures Shoulda Matchers
517
+ - Sets up GraphQL helpers with your `AppSchema`
266
518
 
267
519
  ### Non-Rails Projects
268
520
 
@@ -270,21 +522,54 @@ Zenspec works out of the box with sensible defaults. For Rails applications, it
270
522
  # spec/spec_helper.rb
271
523
  require "zenspec"
272
524
 
273
- # That's it! All matchers and helpers are available
525
+ # All matchers and helpers are now available!
526
+ ```
527
+
528
+ ### Custom GraphQL Schema
529
+
530
+ If your schema is not named `AppSchema`, you can configure it:
531
+
532
+ ```ruby
533
+ # spec/spec_helper.rb
534
+ RSpec.configure do |config|
535
+ config.include Zenspec::Helpers::GraphQLHelpers
536
+
537
+ # Override the schema
538
+ config.before do
539
+ stub_const("AppSchema", YourCustomSchema)
540
+ end
541
+ end
274
542
  ```
275
543
 
276
544
  ---
277
545
 
278
546
  ## Development
279
547
 
280
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
548
+ After checking out the repo, run `bin/setup` to install dependencies. Then run the tests:
549
+
550
+ ```bash
551
+ bundle exec rspec
552
+ ```
553
+
554
+ To install this gem onto your local machine:
555
+
556
+ ```bash
557
+ bundle exec rake install
558
+ ```
281
559
 
282
- To install this gem onto your local machine, run `bundle exec rake install`.
560
+ ---
283
561
 
284
562
  ## Contributing
285
563
 
286
564
  Bug reports and pull requests are welcome on GitHub at https://github.com/zyxzen/zenspec.
287
565
 
566
+ Please ensure:
567
+ 1. All tests pass (`bundle exec rspec`)
568
+ 2. Code follows the existing style
569
+ 3. New features include tests and documentation
570
+
571
+ ---
572
+
288
573
  ## License
289
574
 
290
575
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -56,15 +56,18 @@ module Zenspec
56
56
  # Spinner frames for animation
57
57
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"].freeze
58
58
 
59
- # Spinner update interval in seconds
59
+ # Spinner update interval in seconds (80ms provides smooth 12.5 FPS animation)
60
60
  SPINNER_INTERVAL = 0.08
61
61
 
62
- # Default terminal width if unable to detect
62
+ # Default terminal width if unable to detect (standard wide terminal)
63
63
  DEFAULT_TERMINAL_WIDTH = 120
64
64
 
65
- # Minimum padding between left and right parts
65
+ # Minimum padding between left and right parts for readability
66
66
  MIN_PADDING = 2
67
67
 
68
+ # Pre-compiled regex for stripping ANSI color codes (performance optimization)
69
+ ANSI_REGEX = /\e\[[0-9;]*m/.freeze
70
+
68
71
  def initialize(output)
69
72
  super
70
73
  @current_count = 0
@@ -338,8 +341,9 @@ module Zenspec
338
341
  end
339
342
 
340
343
  # Strip ANSI color codes from text
344
+ # Uses pre-compiled regex for performance in hot path
341
345
  def strip_ansi(text)
342
- text.gsub(/\e\[[0-9;]*m/, "")
346
+ text.gsub(ANSI_REGEX, "")
343
347
  end
344
348
 
345
349
  # Extract short filename from example
@@ -430,4 +434,10 @@ module Zenspec
430
434
  end
431
435
 
432
436
  # Register the formatter as ProgressBarFormatter for easier use
433
- ProgressBarFormatter = Zenspec::Formatters::ProgressBarFormatter
437
+ # Users can use the short form by requiring this file directly:
438
+ # --require zenspec/formatters/progress_bar_formatter
439
+ # --format ProgressBarFormatter
440
+ # Or use the full constant name: Zenspec::Formatters::ProgressBarFormatter
441
+ unless Object.const_defined?(:ProgressBarFormatter)
442
+ ProgressBarFormatter = Zenspec::Formatters::ProgressBarFormatter
443
+ end
@@ -141,7 +141,7 @@ module Zenspec
141
141
  end
142
142
 
143
143
  # Check exact value
144
- if @expected_value
144
+ unless @expected_value.nil?
145
145
  data == @expected_value
146
146
  else
147
147
  !data.nil?
@@ -210,13 +210,15 @@ module Zenspec
210
210
  #
211
211
  # @example
212
212
  # expect(result).to have_graphql_errors
213
+ # expect(result).to have_graphql_errors(2) # Exactly 2 errors
213
214
  # expect(result).to have_graphql_error.with_message("Not found")
214
215
  # expect(result).to have_graphql_error.with_extensions(code: "NOT_FOUND")
215
216
  # expect(result).to have_graphql_error.at_path(["user", "email"])
216
217
  # expect { execute_query }.to have_graphql_errors
217
218
  #
218
- RSpec::Matchers.define :have_graphql_errors do
219
+ RSpec::Matchers.define :have_graphql_errors do |expected_count = nil|
219
220
  match do |result_or_block|
221
+ @expected_count = expected_count
220
222
  if result_or_block.is_a?(Proc)
221
223
  begin
222
224
  result_or_block.call
@@ -232,6 +234,12 @@ module Zenspec
232
234
  errors = result_or_block["errors"]
233
235
  return false if errors.nil? || (errors.is_a?(Array) && errors.empty?)
234
236
 
237
+ # Check for exact error count if specified
238
+ if @expected_count
239
+ @actual_count = errors.is_a?(Array) ? errors.length : 0
240
+ return false unless @actual_count == @expected_count
241
+ end
242
+
235
243
  # Check for specific message
236
244
  if @expected_message
237
245
  return errors.any? { |error| error["message"]&.include?(@expected_message) }
@@ -269,6 +277,8 @@ module Zenspec
269
277
  failure_message do
270
278
  if @raised_error
271
279
  "expected no errors, but got: #{@error.message}"
280
+ elsif @expected_count
281
+ "expected exactly #{@expected_count} error(s), but got #{@actual_count}"
272
282
  elsif @expected_message
273
283
  errors = @result["errors"] || []
274
284
  messages = errors.map { |e| e["message"] }
@@ -5,6 +5,118 @@ require "rspec/expectations"
5
5
  module Zenspec
6
6
  module Matchers
7
7
  module GraphQLTypeMatchers
8
+ # Helper module for field name conversion
9
+ module FieldNameHelper
10
+ # Convert snake_case to camelCase for GraphQL field lookup
11
+ # GraphQL-Ruby automatically converts snake_case field definitions to camelCase
12
+ #
13
+ # Handles edge cases:
14
+ # - Empty strings: "" -> ""
15
+ # - No underscores: "foo" -> "foo"
16
+ # - Leading underscores: "_foo_bar" -> "fooBar"
17
+ # - Trailing underscores: "foo_bar_" -> "fooBar"
18
+ # - Consecutive underscores: "foo__bar" -> "fooBar"
19
+ def camelize(name)
20
+ str = name.to_s
21
+ return str if str.empty? || !str.include?("_")
22
+
23
+ # Split on underscores and reject empty parts (handles consecutive underscores)
24
+ parts = str.split("_").reject(&:empty?)
25
+ return str if parts.empty?
26
+
27
+ parts.map.with_index { |word, i| i.zero? ? word : word.capitalize }.join
28
+ end
29
+
30
+ # Try to get a field by name, attempting both the original name and camelCase version
31
+ def get_field_with_conversion(type, field_name)
32
+ field_name_str = field_name.to_s
33
+
34
+ # Try the exact name first (for backward compatibility)
35
+ field = get_field_exact(type, field_name_str)
36
+ return field if field
37
+
38
+ # Try the camelCase version if the original name contains underscores
39
+ if field_name_str.include?("_")
40
+ camel_name = camelize(field_name_str)
41
+ field = get_field_exact(type, camel_name)
42
+ return field if field
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ def get_field_exact(type, field_name)
49
+ if type.respond_to?(:fields)
50
+ type.fields[field_name]
51
+ elsif type.respond_to?(:get_field)
52
+ type.get_field(field_name)
53
+ end
54
+ end
55
+
56
+ # Try to get an argument by name, attempting both the original name and camelCase version
57
+ def get_argument_with_conversion(field, arg_name)
58
+ return nil unless field.respond_to?(:arguments)
59
+
60
+ arg_name_str = arg_name.to_s
61
+
62
+ # Try the exact name first (for backward compatibility)
63
+ arg = field.arguments[arg_name_str]
64
+ return arg if arg
65
+
66
+ # Try the camelCase version if the original name contains underscores
67
+ if arg_name_str.include?("_")
68
+ camel_name = camelize(arg_name_str)
69
+ arg = field.arguments[camel_name]
70
+ return arg if arg
71
+ end
72
+
73
+ nil
74
+ end
75
+
76
+ # Shared type conversion methods to avoid duplication across matchers
77
+
78
+ # Convert a GraphQL type object to its string representation
79
+ # Handles NonNull (!), List ([]), and nested types
80
+ def type_to_string(type)
81
+ case type
82
+ when GraphQL::Schema::NonNull
83
+ "#{type_to_string(type.of_type)}!"
84
+ when GraphQL::Schema::List
85
+ "[#{type_to_string(type.of_type)}]"
86
+ when GraphQL::Schema::Member
87
+ type.graphql_name
88
+ else
89
+ # Handle wrapped types from graphql-ruby
90
+ if type.respond_to?(:unwrap)
91
+ unwrapped = type.unwrap
92
+ base_name = unwrapped.respond_to?(:graphql_name) ? unwrapped.graphql_name : unwrapped.to_s
93
+
94
+ # Build the type string with wrappers
95
+ result = base_name
96
+ result = "[#{result}]" if list?(type)
97
+ result = "#{result}!" if non_null?(type)
98
+ result
99
+ elsif type.respond_to?(:graphql_name)
100
+ type.graphql_name
101
+ else
102
+ type.to_s
103
+ end
104
+ end
105
+ end
106
+
107
+ # Check if a type is a List type
108
+ def list?(type)
109
+ type.is_a?(GraphQL::Schema::List) ||
110
+ (type.respond_to?(:list?) && type.list?)
111
+ end
112
+
113
+ # Check if a type is a NonNull type
114
+ def non_null?(type)
115
+ type.is_a?(GraphQL::Schema::NonNull) ||
116
+ (type.respond_to?(:non_null?) && type.non_null?)
117
+ end
118
+ end
119
+
8
120
  # Matcher for checking if a GraphQL type has a specific field with type and arguments
9
121
  #
10
122
  # @example
@@ -14,12 +126,14 @@ module Zenspec
14
126
  # expect(UserType).to have_field(:posts).with_argument(:limit, "Int")
15
127
  #
16
128
  RSpec::Matchers.define :have_field do |field_name|
129
+ include FieldNameHelper
130
+
17
131
  match do |type|
18
132
  @field_name = field_name.to_s
19
133
  @type = type
20
134
 
21
135
  # Get the field from the type
22
- @field = get_field(type, @field_name)
136
+ @field = get_field_with_conversion(type, @field_name)
23
137
  return false unless @field
24
138
 
25
139
  # Check field type if specified
@@ -62,14 +176,6 @@ module Zenspec
62
176
 
63
177
  private
64
178
 
65
- def get_field(type, field_name)
66
- if type.respond_to?(:fields)
67
- type.fields[field_name]
68
- elsif type.respond_to?(:get_field)
69
- type.get_field(field_name)
70
- end
71
- end
72
-
73
179
  def field_type_string(field)
74
180
  return nil unless field
75
181
 
@@ -77,47 +183,10 @@ module Zenspec
77
183
  type_to_string(type)
78
184
  end
79
185
 
80
- def type_to_string(type)
81
- case type
82
- when GraphQL::Schema::NonNull
83
- "#{type_to_string(type.of_type)}!"
84
- when GraphQL::Schema::List
85
- "[#{type_to_string(type.of_type)}]"
86
- when GraphQL::Schema::Member
87
- type.graphql_name
88
- else
89
- # Handle wrapped types from graphql-ruby
90
- if type.respond_to?(:unwrap)
91
- unwrapped = type.unwrap
92
- base_name = unwrapped.respond_to?(:graphql_name) ? unwrapped.graphql_name : unwrapped.to_s
93
-
94
- # Build the type string with wrappers
95
- result = base_name
96
- result = "[#{result}]" if list?(type)
97
- result = "#{result}!" if non_null?(type)
98
- result
99
- elsif type.respond_to?(:graphql_name)
100
- type.graphql_name
101
- else
102
- type.to_s
103
- end
104
- end
105
- end
106
-
107
- def list?(type)
108
- type.is_a?(GraphQL::Schema::List) ||
109
- (type.respond_to?(:list?) && type.list?)
110
- end
111
-
112
- def non_null?(type)
113
- type.is_a?(GraphQL::Schema::NonNull) ||
114
- (type.respond_to?(:non_null?) && type.non_null?)
115
- end
116
-
117
186
  def check_argument(arg_name, expected_type)
118
187
  return false unless @field.respond_to?(:arguments)
119
188
 
120
- arg = @field.arguments[arg_name]
189
+ arg = get_argument_with_conversion(@field, arg_name)
121
190
  return false unless arg
122
191
 
123
192
  if expected_type
@@ -206,12 +275,14 @@ module Zenspec
206
275
  # expect(QueryType.fields["users"]).to have_argument(:filter).of_type("UserFilterInput")
207
276
  #
208
277
  RSpec::Matchers.define :have_argument do |arg_name|
278
+ include FieldNameHelper
279
+
209
280
  match do |field|
210
281
  @field = field
211
282
  @arg_name = arg_name.to_s
212
283
 
213
284
  # Get the argument
214
- @argument = get_argument(field, @arg_name)
285
+ @argument = get_argument_with_conversion(field, @arg_name)
215
286
  return false unless @argument
216
287
 
217
288
  # Check argument type if specified
@@ -239,55 +310,12 @@ module Zenspec
239
310
 
240
311
  private
241
312
 
242
- def get_argument(field, arg_name)
243
- return nil unless field.respond_to?(:arguments)
244
-
245
- field.arguments[arg_name]
246
- end
247
-
248
313
  def argument_type_string(argument)
249
314
  return nil unless argument
250
315
 
251
316
  type = argument.type
252
317
  type_to_string(type)
253
318
  end
254
-
255
- def type_to_string(type)
256
- case type
257
- when GraphQL::Schema::NonNull
258
- "#{type_to_string(type.of_type)}!"
259
- when GraphQL::Schema::List
260
- "[#{type_to_string(type.of_type)}]"
261
- when GraphQL::Schema::Member
262
- type.graphql_name
263
- else
264
- # Handle wrapped types from graphql-ruby
265
- if type.respond_to?(:unwrap)
266
- unwrapped = type.unwrap
267
- base_name = unwrapped.respond_to?(:graphql_name) ? unwrapped.graphql_name : unwrapped.to_s
268
-
269
- # Build the type string with wrappers
270
- result = base_name
271
- result = "[#{result}]" if list?(type)
272
- result = "#{result}!" if non_null?(type)
273
- result
274
- elsif type.respond_to?(:graphql_name)
275
- type.graphql_name
276
- else
277
- type.to_s
278
- end
279
- end
280
- end
281
-
282
- def list?(type)
283
- type.is_a?(GraphQL::Schema::List) ||
284
- (type.respond_to?(:list?) && type.list?)
285
- end
286
-
287
- def non_null?(type)
288
- type.is_a?(GraphQL::Schema::NonNull) ||
289
- (type.respond_to?(:non_null?) && type.non_null?)
290
- end
291
319
  end
292
320
 
293
321
  # Matcher for checking if a schema has a specific query field
@@ -297,6 +325,8 @@ module Zenspec
297
325
  # expect(schema).to have_query(:users).of_type("[User!]!")
298
326
  #
299
327
  RSpec::Matchers.define :have_query do |query_name|
328
+ include FieldNameHelper
329
+
300
330
  match do |schema|
301
331
  @schema = schema
302
332
  @query_name = query_name.to_s
@@ -306,7 +336,7 @@ module Zenspec
306
336
  return false unless query_type
307
337
 
308
338
  # Get the field
309
- @field = get_field(query_type, @query_name)
339
+ @field = get_field_with_conversion(query_type, @query_name)
310
340
  return false unless @field
311
341
 
312
342
  # Check field type if specified
@@ -353,14 +383,6 @@ module Zenspec
353
383
  schema.respond_to?(:query) ? schema.query : nil
354
384
  end
355
385
 
356
- def get_field(type, field_name)
357
- if type.respond_to?(:fields)
358
- type.fields[field_name]
359
- elsif type.respond_to?(:get_field)
360
- type.get_field(field_name)
361
- end
362
- end
363
-
364
386
  def field_type_string(field)
365
387
  return nil unless field
366
388
 
@@ -368,45 +390,10 @@ module Zenspec
368
390
  type_to_string(type)
369
391
  end
370
392
 
371
- def type_to_string(type)
372
- case type
373
- when GraphQL::Schema::NonNull
374
- "#{type_to_string(type.of_type)}!"
375
- when GraphQL::Schema::List
376
- "[#{type_to_string(type.of_type)}]"
377
- when GraphQL::Schema::Member
378
- type.graphql_name
379
- else
380
- if type.respond_to?(:unwrap)
381
- unwrapped = type.unwrap
382
- base_name = unwrapped.respond_to?(:graphql_name) ? unwrapped.graphql_name : unwrapped.to_s
383
-
384
- result = base_name
385
- result = "[#{result}]" if list?(type)
386
- result = "#{result}!" if non_null?(type)
387
- result
388
- elsif type.respond_to?(:graphql_name)
389
- type.graphql_name
390
- else
391
- type.to_s
392
- end
393
- end
394
- end
395
-
396
- def list?(type)
397
- type.is_a?(GraphQL::Schema::List) ||
398
- (type.respond_to?(:list?) && type.list?)
399
- end
400
-
401
- def non_null?(type)
402
- type.is_a?(GraphQL::Schema::NonNull) ||
403
- (type.respond_to?(:non_null?) && type.non_null?)
404
- end
405
-
406
393
  def check_argument(arg_name, expected_type)
407
394
  return false unless @field.respond_to?(:arguments)
408
395
 
409
- arg = @field.arguments[arg_name]
396
+ arg = get_argument_with_conversion(@field, arg_name)
410
397
  return false unless arg
411
398
 
412
399
  if expected_type
@@ -425,6 +412,8 @@ module Zenspec
425
412
  # expect(schema).to have_mutation(:deleteUser).of_type("DeleteUserPayload!")
426
413
  #
427
414
  RSpec::Matchers.define :have_mutation do |mutation_name|
415
+ include FieldNameHelper
416
+
428
417
  match do |schema|
429
418
  @schema = schema
430
419
  @mutation_name = mutation_name.to_s
@@ -434,7 +423,7 @@ module Zenspec
434
423
  return false unless mutation_type
435
424
 
436
425
  # Get the field
437
- @field = get_field(mutation_type, @mutation_name)
426
+ @field = get_field_with_conversion(mutation_type, @mutation_name)
438
427
  return false unless @field
439
428
 
440
429
  # Check field type if specified
@@ -481,14 +470,6 @@ module Zenspec
481
470
  schema.respond_to?(:mutation) ? schema.mutation : nil
482
471
  end
483
472
 
484
- def get_field(type, field_name)
485
- if type.respond_to?(:fields)
486
- type.fields[field_name]
487
- elsif type.respond_to?(:get_field)
488
- type.get_field(field_name)
489
- end
490
- end
491
-
492
473
  def field_type_string(field)
493
474
  return nil unless field
494
475
 
@@ -496,45 +477,10 @@ module Zenspec
496
477
  type_to_string(type)
497
478
  end
498
479
 
499
- def type_to_string(type)
500
- case type
501
- when GraphQL::Schema::NonNull
502
- "#{type_to_string(type.of_type)}!"
503
- when GraphQL::Schema::List
504
- "[#{type_to_string(type.of_type)}]"
505
- when GraphQL::Schema::Member
506
- type.graphql_name
507
- else
508
- if type.respond_to?(:unwrap)
509
- unwrapped = type.unwrap
510
- base_name = unwrapped.respond_to?(:graphql_name) ? unwrapped.graphql_name : unwrapped.to_s
511
-
512
- result = base_name
513
- result = "[#{result}]" if list?(type)
514
- result = "#{result}!" if non_null?(type)
515
- result
516
- elsif type.respond_to?(:graphql_name)
517
- type.graphql_name
518
- else
519
- type.to_s
520
- end
521
- end
522
- end
523
-
524
- def list?(type)
525
- type.is_a?(GraphQL::Schema::List) ||
526
- (type.respond_to?(:list?) && type.list?)
527
- end
528
-
529
- def non_null?(type)
530
- type.is_a?(GraphQL::Schema::NonNull) ||
531
- (type.respond_to?(:non_null?) && type.non_null?)
532
- end
533
-
534
480
  def check_argument(arg_name, expected_type)
535
481
  return false unless @field.respond_to?(:arguments)
536
482
 
537
- arg = @field.arguments[arg_name]
483
+ arg = get_argument_with_conversion(@field, arg_name)
538
484
  return false unless arg
539
485
 
540
486
  if expected_type
@@ -18,7 +18,7 @@ module Zenspec
18
18
  @result = get_result(actual)
19
19
  return false if @result.failure?
20
20
 
21
- return false if @expected_data && @result.data != @expected_data
21
+ return false if !@expected_data.nil? && @result.data != @expected_data
22
22
 
23
23
  if @context_checks
24
24
  @context_checks.all? do |key, value|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zenspec
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zenspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wilson Anciro