graphql 1.8.0.pre6 → 1.8.0.pre7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +5 -5
  2. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +14 -0
  3. data/lib/graphql/internal_representation/node.rb +3 -20
  4. data/lib/graphql/language/parser.rb +590 -598
  5. data/lib/graphql/language/parser.y +1 -2
  6. data/lib/graphql/query/context.rb +26 -0
  7. data/lib/graphql/schema.rb +77 -22
  8. data/lib/graphql/schema/argument.rb +11 -3
  9. data/lib/graphql/schema/field.rb +50 -3
  10. data/lib/graphql/schema/input_object.rb +6 -0
  11. data/lib/graphql/schema/interface.rb +0 -1
  12. data/lib/graphql/schema/member.rb +2 -16
  13. data/lib/graphql/schema/member/has_fields.rb +4 -0
  14. data/lib/graphql/schema/object.rb +5 -4
  15. data/lib/graphql/schema/union.rb +0 -12
  16. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -1
  17. data/lib/graphql/upgrader/member.rb +97 -10
  18. data/lib/graphql/version.rb +1 -1
  19. data/spec/fixtures/upgrader/starrable.original.rb +46 -0
  20. data/spec/fixtures/upgrader/starrable.transformed.rb +43 -0
  21. data/spec/fixtures/upgrader/subscribable.transformed.rb +32 -30
  22. data/spec/fixtures/upgrader/type_x.original.rb +14 -2
  23. data/spec/fixtures/upgrader/type_x.transformed.rb +12 -0
  24. data/spec/graphql/query/executor_spec.rb +2 -1
  25. data/spec/graphql/rake_task_spec.rb +3 -1
  26. data/spec/graphql/schema/field_spec.rb +94 -1
  27. data/spec/graphql/schema/input_object_spec.rb +6 -0
  28. data/spec/graphql/schema/interface_spec.rb +15 -0
  29. data/spec/graphql/schema/object_spec.rb +5 -3
  30. data/spec/graphql/schema/printer_spec.rb +19 -0
  31. data/spec/graphql/schema_spec.rb +10 -3
  32. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -2
  33. data/spec/graphql/upgrader/member_spec.rb +2 -2
  34. data/spec/support/jazz.rb +21 -14
  35. data/spec/support/star_wars/schema.rb +1 -1
  36. metadata +7 -3
@@ -24,7 +24,11 @@ module GraphQL
24
24
  msg = if resolved_type.nil?
25
25
  nil
26
26
  elsif resolved_type.kind.scalar? && ast_node.selections.any?
27
- "Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])"
27
+ if ast_node.selections.first.is_a?(GraphQL::Language::Nodes::InlineFragment)
28
+ "Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has inline fragments [#{ast_node.selections.map(&:type).map(&:name).join(", ")}])"
29
+ else
30
+ "Selections can't be made on scalars (%{node_name} returns #{resolved_type.name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])"
31
+ end
28
32
  elsif resolved_type.kind.object? && ast_node.selections.none?
29
33
  "Objects must have selections (%{node_name} returns #{resolved_type.name} but has no selections)"
30
34
  else
@@ -11,7 +11,6 @@ module GraphQL
11
11
 
12
12
  class Transform
13
13
  # @param input_text [String] Untransformed GraphQL-Ruby code
14
- # @param rewrite_options [Hash] Used during rewrite
15
14
  # @return [String] The input text, with a transformation applied if necessary
16
15
  def apply(input_text)
17
16
  raise NotImplementedError, "Return transformed text here"
@@ -19,11 +18,11 @@ module GraphQL
19
18
 
20
19
  # Recursively transform a `.define`-DSL-based type expression into a class-ready expression, for example:
21
20
  #
22
- # - `types[X]` -> `[X]`
21
+ # - `types[X]` -> `[X, null: true]`
23
22
  # - `Int` -> `Integer`
24
23
  # - `X!` -> `X`
25
24
  #
26
- # Notice that `!` is removed entirely, because it doesn't exist in the class API.
25
+ # Notice that `!` is removed sometimes, because it doesn't exist in the class API.
27
26
  #
28
27
  # @param type_expr [String] A `.define`-ready expression of a return type or input type
29
28
  # @return [String] A class-ready expression of the same type`
@@ -34,7 +33,15 @@ module GraphQL
34
33
  "#{preserve_bang ? "!" : ""}#{normalize_type_expression(type_expr[1..-1], preserve_bang: preserve_bang)}"
35
34
  when /\Atypes\[.*\]\Z/
36
35
  # Unwrap the brackets, normalize, then re-wrap
37
- "[#{normalize_type_expression(type_expr[6..-2], preserve_bang: preserve_bang)}]"
36
+ inner_type = type_expr[6..-2]
37
+ if inner_type.start_with?("!")
38
+ nullable = false
39
+ inner_type = inner_type[1..-1]
40
+ else
41
+ nullable = true
42
+ end
43
+
44
+ "[#{normalize_type_expression(inner_type, preserve_bang: preserve_bang)}#{nullable ? ", null: true" : ""}]"
38
45
  when /\Atypes\./
39
46
  # Remove the prefix
40
47
  normalize_type_expression(type_expr[6..-1], preserve_bang: preserve_bang)
@@ -326,8 +333,12 @@ module GraphQL
326
333
  obj_arg_name, args_arg_name, ctx_arg_name = processor.proc_arg_names
327
334
  # This is not good, it will hit false positives
328
335
  # Should use AST to make this substitution
329
- proc_body.gsub!(/([^\w:]|^)#{obj_arg_name}([^\w]|$)/, '\1@object\2')
330
- proc_body.gsub!(/([^\w:]|^)#{ctx_arg_name}([^\w]|$)/, '\1@context\2')
336
+ if obj_arg_name != "_"
337
+ proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w]|$)/, '\1@object\2')
338
+ end
339
+ if ctx_arg_name != "_"
340
+ proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w]|$)/, '\1@context\2')
341
+ end
331
342
 
332
343
  method_def_indent = " " * (processor.resolve_indent - 2)
333
344
  # Turn the proc body into a method body
@@ -442,7 +453,7 @@ module GraphQL
442
453
  class UpdateMethodSignatureTransform < Transform
443
454
  def apply(input_text)
444
455
  input_text.scan(/(?:input_field|field|connection|argument) .*$/).each do |field|
445
- matches = /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)?(:?, (?<return_type>([A-Za-z\[\]\.\!_0-9\(\)]|::|-> ?\{ ?| ?\})+))?(?<remainder>( |,|$).*)/.match(field)
456
+ matches = /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)?(:?, +(?<return_type>([A-Za-z\[\]\.\!_0-9\(\)]|::|-> ?\{ ?| ?\})+))?(?<remainder>( |,|$).*)/.match(field)
446
457
  if matches
447
458
  name = matches[:name]
448
459
  return_type = matches[:return_type]
@@ -458,7 +469,6 @@ module GraphQL
458
469
  non_nullable = return_type.sub! /(^|[^\[])!/, '\1'
459
470
  nullable = !non_nullable
460
471
  return_type = normalize_type_expression(return_type)
461
- return_type = return_type.gsub ',', ''
462
472
  else
463
473
  non_nullable = nil
464
474
  nullable = nil
@@ -516,15 +526,91 @@ module GraphQL
516
526
  # Remove redundant newlines, which may have trailing spaces
517
527
  # Remove double newline after `do`
518
528
  # Remove double newline before `end`
529
+ # Remove lines with whitespace only
519
530
  class RemoveExcessWhitespaceTransform < Transform
520
531
  def apply(input_text)
521
532
  input_text
522
533
  .gsub(/\n{3,}/m, "\n\n")
523
534
  .gsub(/do\n{2,}/m, "do\n")
524
535
  .gsub(/\n{2,}(\s*)end/m, "\n\\1end")
536
+ .gsub(/\n +\n/m, "\n\n")
525
537
  end
526
538
  end
527
539
 
540
+ class MoveInterfaceMethodsToImplementationTransform < Transform
541
+ def initialize(interface_file_pattern: /<.*Interface/)
542
+ @interface_file_pattern = interface_file_pattern
543
+ end
544
+
545
+ def apply(input_text)
546
+ if input_text =~ @interface_file_pattern && input_text =~ /\n *def /
547
+ # Extract the method bodies and figure out where the module should be inserted
548
+ method_bodies = []
549
+ processor = apply_processor(input_text, InterfaceMethodProcessor.new)
550
+ processor.methods.each do |(begin_pos, end_pos)|
551
+ # go all the way back to the newline so we get whitespace too
552
+ while input_text[begin_pos] != "\n" && begin_pos >= 0
553
+ begin_pos -= 1
554
+ end
555
+ # Tuck away the method body, and remove it from here
556
+ method_body = input_text[begin_pos..end_pos]
557
+ method_bodies << method_body
558
+ end
559
+ # How far are these method bodies indented? The module will be this indented
560
+ leading_indent = method_bodies.first[/\n +/][1..-1]
561
+ # Increase the indent since it will be in a nested module
562
+ indented_method_bodies = method_bodies.map {|m| m.gsub("\n", "\n ").rstrip }
563
+ # Build the ruby module definition
564
+ module_body = "\n\n#{leading_indent}module Implementation#{indented_method_bodies.join("\n")}\n#{leading_indent}end"
565
+
566
+ # find the `end` of the class definition, and put the module before it
567
+ _class_start, class_end = processor.class_definition
568
+ # This might target the newline _after_ `end`, but we don't want that one
569
+ if input_text[class_end] == "\n"
570
+ class_end -= 1
571
+ end
572
+
573
+ while input_text[class_end] != "\n" && class_end > 0
574
+ class_end -= 1
575
+ end
576
+
577
+ input_text.insert(class_end, module_body)
578
+
579
+ # Do the replacement _after_ identifying the bodies,
580
+ # otherwise the offsets get messed up
581
+ method_bodies.each do |method_body|
582
+ input_text.sub!(method_body, "")
583
+ end
584
+ end
585
+ input_text
586
+ end
587
+
588
+ # Find the beginning and end of each method def,
589
+ # so that we can move it wholesale
590
+ class InterfaceMethodProcessor < Parser::AST::Processor
591
+ attr_reader :methods, :class_definition
592
+ def initialize
593
+ @class_definition = nil
594
+ @methods = []
595
+ super
596
+ end
597
+
598
+ def on_def(node)
599
+ start = node.loc.expression.begin_pos
600
+ finish = node.loc.expression.end_pos
601
+ @methods << [start, finish]
602
+ super(node)
603
+ end
604
+
605
+ def on_class(node)
606
+ @class_definition = [
607
+ node.loc.expression.begin_pos,
608
+ node.loc.expression.end_pos
609
+ ]
610
+ super(node)
611
+ end
612
+ end
613
+ end
528
614
  # Skip this file if you see any `field`
529
615
  # helpers with `null: true` or `null: false` keywords
530
616
  # or `argument` helpers with `required:` keywords,
@@ -562,14 +648,15 @@ module GraphQL
562
648
  ConfigurationToKwargTransform.new(kwarg: "deprecation_reason"),
563
649
  ConfigurationToKwargTransform.new(kwarg: "hash_key"),
564
650
  PropertyToMethodTransform,
565
- RemoveRedundantKwargTransform.new(kwarg: "hash_key"),
566
- RemoveRedundantKwargTransform.new(kwarg: "method"),
567
651
  UnderscoreizeFieldNameTransform,
568
652
  ResolveProcToMethodTransform,
569
653
  UpdateMethodSignatureTransform,
654
+ RemoveRedundantKwargTransform.new(kwarg: "hash_key"),
655
+ RemoveRedundantKwargTransform.new(kwarg: "method"),
570
656
  ]
571
657
 
572
658
  DEFAULT_CLEAN_UP_TRANSFORMS = [
659
+ MoveInterfaceMethodsToImplementationTransform,
573
660
  RemoveExcessWhitespaceTransform,
574
661
  RemoveEmptyBlocksTransform,
575
662
  ]
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0.pre6"
3
+ VERSION = "1.8.0.pre7"
4
4
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Platform
4
+ module Interfaces
5
+ Starrable = GraphQL::InterfaceType.define do
6
+ name "Starrable"
7
+ description "Things that can be starred."
8
+
9
+ global_id_field :id
10
+
11
+ field :viewerHasStarred, !types.Boolean do
12
+ description "Returns a boolean indicating whether the viewing user has starred this starrable."
13
+
14
+ resolve ->(object, arguments, context) do
15
+ if context[:viewer]
16
+ context[:viewer].starred?(object)
17
+ else
18
+ false
19
+ end
20
+ end
21
+ end
22
+
23
+ connection :stargazers, -> { !Connections::Stargazer } do
24
+ description "A list of users who have starred this starrable."
25
+
26
+ argument :orderBy, Inputs::StarOrder, "Order for connection"
27
+
28
+ resolve ->(object, arguments, context) do
29
+ scope = case object
30
+ when Repository
31
+ object.stars
32
+ when Gist
33
+ GistStar.where(gist_id: object.id)
34
+ end
35
+
36
+ table = scope.table_name
37
+ if order_by = arguments["orderBy"]
38
+ scope = scope.order("#{table}.#{order_by["field"]} #{order_by["direction"]}")
39
+ end
40
+
41
+ scope
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Platform
4
+ module Interfaces
5
+ class Starrable < Platform::Interfaces::Base
6
+ description "Things that can be starred."
7
+
8
+ global_id_field :id
9
+
10
+ field :viewer_has_starred, Boolean, description: "Returns a boolean indicating whether the viewing user has starred this starrable.", null: false
11
+
12
+ field :stargazers, Connections::Stargazer, description: "A list of users who have starred this starrable.", null: false, connection: true do
13
+ argument :order_by, Inputs::StarOrder, "Order for connection", required: false
14
+ end
15
+
16
+ module Implementation
17
+ def viewer_has_starred
18
+ if @context[:viewer]
19
+ @context[:viewer].starred?(@object)
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def stargazers(**arguments)
26
+ scope = case @object
27
+ when Repository
28
+ @object.stars
29
+ when Gist
30
+ GistStar.where(gist_id: @object.id)
31
+ end
32
+
33
+ table = scope.table_name
34
+ if order_by = arguments[:order_by]
35
+ scope = scope.order("#{table}.#{order_by["field"]} #{order_by["direction"]}")
36
+ end
37
+
38
+ scope
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -9,42 +9,44 @@ module Platform
9
9
 
10
10
  field :viewer_subscription, Enums::SubscriptionState, description: "Identifies if the viewer is watching, not watching, or ignoring the subscribable entity.", null: false
11
11
 
12
- def viewer_subscription
13
- if @context[:viewer].nil?
14
- return "unsubscribed"
15
- end
12
+ field :viewer_can_subscribe, Boolean, description: "Check if the viewer is able to change their subscription status for the repository.", null: false
16
13
 
17
- subscription_status_response = @object.async_subscription_status(@context[:viewer]).sync
14
+ field :issues, function: Platform::Functions::Issues.new, description: "A list of issues associated with the milestone.", connection: true
15
+ field :files, Connections.define(PackageFile), description: "List of files associated with this registry package version", null: false, connection: true
16
+ field :enabled, Boolean, "Whether enabled for this project", method: :enabled?, null: false
18
17
 
19
- if subscription_status_response.failed?
20
- error = Platform::Errors::ServiceUnavailable.new("Subscriptions are currently unavailable. Please try again later.")
21
- error.ast_node = @context.irep_node.ast_node
22
- error.path = @context.path
23
- @context.errors << error
24
- return "unavailable"
18
+ module Implementation
19
+ def viewer_subscription
20
+ if @context[:viewer].nil?
21
+ return "unsubscribed"
22
+ end
23
+
24
+ subscription_status_response = @object.async_subscription_status(@context[:viewer]).sync
25
+
26
+ if subscription_status_response.failed?
27
+ error = Platform::Errors::ServiceUnavailable.new("Subscriptions are currently unavailable. Please try again later.")
28
+ error.ast_node = @context.irep_node.ast_node
29
+ error.path = @context.path
30
+ @context.errors << error
31
+ return "unavailable"
32
+ end
33
+
34
+ subscription = subscription_status_response.value
35
+ if subscription.included?
36
+ "unsubscribed"
37
+ elsif subscription.subscribed?
38
+ "subscribed"
39
+ elsif subscription.ignored?
40
+ "ignored"
41
+ end
25
42
  end
26
43
 
27
- subscription = subscription_status_response.value
28
- if subscription.included?
29
- "unsubscribed"
30
- elsif subscription.subscribed?
31
- "subscribed"
32
- elsif subscription.ignored?
33
- "ignored"
34
- end
35
- end
44
+ def viewer_can_subscribe
45
+ return false if @context[:viewer].nil?
36
46
 
37
- field :viewer_can_subscribe, Boolean, description: "Check if the viewer is able to change their subscription status for the repository.", null: false
38
-
39
- def viewer_can_subscribe
40
- return false if @context[:viewer].nil?
41
-
42
- @object.async_subscription_status(@context[:viewer]).then(&:success?)
47
+ @object.async_subscription_status(@context[:viewer]).then(&:success?)
48
+ end
43
49
  end
44
-
45
- field :issues, function: Platform::Functions::Issues.new, description: "A list of issues associated with the milestone.", connection: true
46
- field :files, Connections.define(PackageFile), description: "List of files associated with this registry package version", null: false, connection: true
47
- field :enabled, Boolean, "Whether enabled for this project", method: :enabled?, null: false
48
50
  end
49
51
  end
50
52
  end
@@ -11,8 +11,8 @@ module Platform
11
11
  global_id_field :id
12
12
  interfaces [GraphQL::Relay::Node.interface]
13
13
 
14
- field :f1, !Objects::O1, "The x being y."
15
- field :f2, !Enums::E1, "x for the y.",
14
+ field :f1, !Objects::O1, "The x being y."
15
+ field :f2, !Enums::E1, "x for the y.",
16
16
  property: :field_2
17
17
  field :f3, Enums::E2, "x for y."
18
18
  field :details, types.String, "Details."
@@ -48,6 +48,18 @@ module Platform
48
48
 
49
49
  field :f7, field: SomeField
50
50
  field :f8, function: SomeFunction
51
+ field :f9, types[Objects::O2]
52
+ field :fieldField, types.String, hash_key: "fieldField"
53
+ field :fieldField2, types.String, property: :field_field2
54
+
55
+ field :f10, types.String do
56
+ resolve ->(obj, _, _) do
57
+ obj.something do |_|
58
+ xyz_obj.obj
59
+ obj.f10
60
+ end
61
+ end
62
+ end
51
63
  end
52
64
  end
53
65
  end
@@ -39,6 +39,18 @@ module Platform
39
39
 
40
40
  field :f7, field: SomeField
41
41
  field :f8, function: SomeFunction
42
+ field :f9, [Objects::O2, null: true], null: true
43
+ field :field_field, String, hash_key: "fieldField", null: true
44
+ field :field_field2, String, null: true
45
+
46
+ field :f10, String, null: true
47
+
48
+ def f10
49
+ @object.something do |_|
50
+ xyz_obj.obj
51
+ @object.f10
52
+ end
53
+ end
42
54
  end
43
55
  end
44
56
  end
@@ -188,7 +188,8 @@ describe GraphQL::Query::Executor do
188
188
 
189
189
  describe "if the schema has a rescue handler" do
190
190
  before do
191
- schema.rescue_from(RuntimeError) { "Error was handled!" }
191
+ # HACK: reach to the underlying instance to perform a side-effect
192
+ schema.graphql_definition.rescue_from(RuntimeError) { "Error was handled!" }
192
193
  end
193
194
 
194
195
  after do
@@ -34,7 +34,9 @@ describe GraphQL::RakeTask do
34
34
  end
35
35
  dumped_json = File.read("./schema.json")
36
36
  expected_json = JSON.pretty_generate(RakeTaskSchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY))
37
- assert_equal(expected_json, dumped_json)
37
+
38
+ # Test that that JSON is logically equivalent, not serialized the same
39
+ assert_equal(JSON.parse(expected_json), JSON.parse(dumped_json))
38
40
 
39
41
  dumped_idl = File.read("./schema.graphql")
40
42
  expected_idl = rake_task_schema_defn.chomp
@@ -11,8 +11,17 @@ describe GraphQL::Schema::Field do
11
11
  assert_equal :ok, arg_defn.metadata[:custom]
12
12
  end
13
13
 
14
- it "camelizes the field name" do
14
+ it "camelizes the field name, unless camelize: false" do
15
15
  assert_equal 'inspectInput', field.graphql_definition.name
16
+
17
+ underscored_field = GraphQL::Schema::Field.new(:underscored_field, String, null: false, camelize: false) do
18
+ argument :underscored_arg, String, required: true, camelize: false
19
+ end
20
+
21
+ assert_equal 'underscored_field', underscored_field.to_graphql.name
22
+ arg_name, arg_defn = underscored_field.to_graphql.arguments.first
23
+ assert_equal 'underscored_arg', arg_name
24
+ assert_equal 'underscored_arg', arg_defn.name
16
25
  end
17
26
 
18
27
  it "exposes the method override" do
@@ -36,5 +45,89 @@ describe GraphQL::Schema::Field do
36
45
  assert_equal "test", object.fields["test"].arguments["test"].name
37
46
  assert_equal "A Description.", object.fields["test"].description
38
47
  end
48
+
49
+ describe "extras" do
50
+ it "can get errors, which adds path" do
51
+ query_str = <<-GRAPHQL
52
+ query {
53
+ find(id: "Musician/Herbie Hancock") {
54
+ ... on Musician {
55
+ addError
56
+ }
57
+ }
58
+ }
59
+ GRAPHQL
60
+
61
+ res = Jazz::Schema.execute(query_str)
62
+ err = res["errors"].first
63
+ assert_equal "this has a path", err["message"]
64
+ assert_equal ["find", "addError"], err["path"]
65
+ assert_equal [{"line"=>4, "column"=>15}], err["locations"]
66
+ end
67
+ end
68
+
69
+ describe "complexity" do
70
+ it "accepts a keyword argument" do
71
+ object = Class.new(Jazz::BaseObject) do
72
+ graphql_name "complexityKeyword"
73
+
74
+ field :complexityTest, String, null: true, complexity: 25
75
+ end.to_graphql
76
+
77
+ assert_equal 25, object.fields["complexityTest"].complexity
78
+ end
79
+
80
+ it "accepts a proc in the definition block" do
81
+ object = Class.new(Jazz::BaseObject) do
82
+ graphql_name "complexityKeyword"
83
+
84
+ field :complexityTest, String, null: true do
85
+ complexity ->(_ctx, _args, _child_complexity) { 52 }
86
+ end
87
+ end.to_graphql
88
+
89
+ assert_equal 52, object.fields["complexityTest"].complexity.call(nil, nil, nil)
90
+ end
91
+
92
+ it "accepts an integer in the definition block" do
93
+ object = Class.new(Jazz::BaseObject) do
94
+ graphql_name "complexityKeyword"
95
+
96
+ field :complexityTest, String, null: true do
97
+ complexity 38
98
+ end
99
+ end.to_graphql
100
+
101
+ assert_equal 38, object.fields["complexityTest"].complexity
102
+ end
103
+
104
+ it 'fails if the complexity is not numeric and not a proc' do
105
+ err = assert_raises(RuntimeError) do
106
+ Class.new(Jazz::BaseObject) do
107
+ graphql_name "complexityKeyword"
108
+
109
+ field :complexityTest, String, null: true do
110
+ complexity 'One hundred and eighty'
111
+ end
112
+ end.to_graphql
113
+ end
114
+
115
+ assert_match /^Invalid complexity:/, err.message
116
+ end
117
+
118
+ it 'fails if the proc does not accept 3 parameters' do
119
+ err = assert_raises(RuntimeError) do
120
+ Class.new(Jazz::BaseObject) do
121
+ graphql_name "complexityKeyword"
122
+
123
+ field :complexityTest, String, null: true do
124
+ complexity ->(one, two) { 52 }
125
+ end
126
+ end.to_graphql
127
+ end
128
+
129
+ assert_match /^A complexity proc should always accept 3 parameters/, err.message
130
+ end
131
+ end
39
132
  end
40
133
  end