graphql 0.17.2 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/analysis/query_depth.rb +1 -1
  4. data/lib/graphql/base_type.rb +25 -1
  5. data/lib/graphql/define.rb +2 -0
  6. data/lib/graphql/define/assign_connection.rb +11 -0
  7. data/lib/graphql/define/assign_global_id_field.rb +11 -0
  8. data/lib/graphql/define/assign_object_field.rb +21 -20
  9. data/lib/graphql/define/defined_object_proxy.rb +2 -2
  10. data/lib/graphql/define/instance_definable.rb +13 -3
  11. data/lib/graphql/field.rb +1 -1
  12. data/lib/graphql/language/generation.rb +57 -6
  13. data/lib/graphql/language/lexer.rb +434 -212
  14. data/lib/graphql/language/lexer.rl +18 -0
  15. data/lib/graphql/language/nodes.rb +75 -0
  16. data/lib/graphql/language/parser.rb +853 -341
  17. data/lib/graphql/language/parser.y +114 -17
  18. data/lib/graphql/query.rb +15 -1
  19. data/lib/graphql/relay.rb +13 -0
  20. data/lib/graphql/relay/array_connection.rb +80 -0
  21. data/lib/graphql/relay/base_connection.rb +138 -0
  22. data/lib/graphql/relay/connection_field.rb +54 -0
  23. data/lib/graphql/relay/connection_type.rb +25 -0
  24. data/lib/graphql/relay/edge.rb +22 -0
  25. data/lib/graphql/relay/edge_type.rb +14 -0
  26. data/lib/graphql/relay/global_id_resolve.rb +15 -0
  27. data/lib/graphql/relay/global_node_identification.rb +124 -0
  28. data/lib/graphql/relay/mutation.rb +146 -0
  29. data/lib/graphql/relay/page_info.rb +13 -0
  30. data/lib/graphql/relay/relation_connection.rb +98 -0
  31. data/lib/graphql/schema.rb +3 -0
  32. data/lib/graphql/schema/printer.rb +12 -2
  33. data/lib/graphql/static_validation/message.rb +9 -5
  34. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  37. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
  38. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  39. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  40. data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
  41. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
  42. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  43. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
  44. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  45. data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
  46. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  47. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
  48. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
  49. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  50. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
  51. data/lib/graphql/static_validation/type_stack.rb +33 -2
  52. data/lib/graphql/static_validation/validation_context.rb +5 -0
  53. data/lib/graphql/version.rb +1 -1
  54. data/readme.md +16 -4
  55. data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
  56. data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
  57. data/spec/graphql/argument_spec.rb +1 -1
  58. data/spec/graphql/define/instance_definable_spec.rb +9 -0
  59. data/spec/graphql/field_spec.rb +1 -1
  60. data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
  61. data/spec/graphql/language/generation_spec.rb +25 -4
  62. data/spec/graphql/language/parser_spec.rb +116 -1
  63. data/spec/graphql/query_spec.rb +10 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +164 -0
  65. data/spec/graphql/relay/connection_type_spec.rb +37 -0
  66. data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
  67. data/spec/graphql/relay/mutation_spec.rb +55 -0
  68. data/spec/graphql/relay/page_info_spec.rb +106 -0
  69. data/spec/graphql/relay/relation_connection_spec.rb +348 -0
  70. data/spec/graphql/schema/printer_spec.rb +8 -0
  71. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
  73. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
  74. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  75. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  76. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
  77. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
  78. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  79. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
  80. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  81. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
  82. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
  83. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
  84. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
  85. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  86. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
  87. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  88. data/spec/spec_helper.rb +7 -0
  89. data/spec/support/dairy_app.rb +11 -10
  90. data/spec/support/star_wars_data.rb +65 -58
  91. data/spec/support/star_wars_schema.rb +192 -54
  92. metadata +84 -2
@@ -124,6 +124,16 @@ describe GraphQL::Query do
124
124
  end
125
125
  end
126
126
 
127
+ it "fails to execute a query containing a type definition" do
128
+ query_string = '
129
+ { root }
130
+
131
+ type Query { foo: String }
132
+ '
133
+ exc = assert_raises(GraphQL::ExecutionError) { GraphQL::Query.new(schema, query_string) }
134
+ assert_equal "GraphQL query cannot contain a schema definition", exc.message
135
+ end
136
+
127
137
  it "uses root_value as the object for the root type" do
128
138
  result = GraphQL::Query.new(schema, '{ root }', root_value: "I am root").result
129
139
  assert_equal 'I am root', result.fetch('data').fetch('root')
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Relay::ArrayConnection do
4
+ def get_names(result)
5
+ ships = result["data"]["rebels"]["ships"]["edges"]
6
+ names = ships.map { |e| e["node"]["name"] }
7
+ end
8
+
9
+ def get_last_cursor(result)
10
+ result["data"]["rebels"]["ships"]["edges"].last["cursor"]
11
+ end
12
+
13
+ describe "results" do
14
+ let(:query_string) {%|
15
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
16
+ rebels {
17
+ ships(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
18
+ edges {
19
+ cursor
20
+ node {
21
+ name
22
+ }
23
+ }
24
+ pageInfo {
25
+ hasNextPage
26
+ hasPreviousPage
27
+ startCursor
28
+ endCursor
29
+ }
30
+ }
31
+ }
32
+ }
33
+ |}
34
+
35
+ it 'limits the result' do
36
+ result = query(query_string, "first" => 2)
37
+ number_of_ships = get_names(result).length
38
+ assert_equal(2, number_of_ships)
39
+ assert_equal(true, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
40
+ assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
41
+ assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
42
+ assert_equal("Mg==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
43
+
44
+ result = query(query_string, "first" => 3)
45
+ number_of_ships = get_names(result).length
46
+ assert_equal(3, number_of_ships)
47
+ end
48
+
49
+ it 'provides pageInfo' do
50
+ result = query(query_string, "first" => 2)
51
+ assert_equal(true, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
52
+ assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
53
+ assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
54
+ assert_equal("Mg==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
55
+
56
+ result = query(query_string, "first" => 100)
57
+ assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
58
+ assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
59
+ assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
60
+ assert_equal("NQ==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
61
+ end
62
+
63
+ it 'slices the result' do
64
+ result = query(query_string, "first" => 1)
65
+ assert_equal(["X-Wing"], get_names(result))
66
+
67
+ # After the last result, find the next 2:
68
+ last_cursor = get_last_cursor(result)
69
+
70
+ result = query(query_string, "after" => last_cursor, "first" => 2)
71
+ assert_equal(["Y-Wing", "A-Wing"], get_names(result))
72
+
73
+ # After the last result, find the next 2:
74
+ last_cursor = get_last_cursor(result)
75
+
76
+ result = query(query_string, "after" => last_cursor, "first" => 2)
77
+ assert_equal(["Millenium Falcon", "Home One"], get_names(result))
78
+
79
+ result = query(query_string, "before" => last_cursor, "last" => 2)
80
+ assert_equal(["X-Wing", "Y-Wing"], get_names(result))
81
+ end
82
+
83
+ it 'applies custom arguments' do
84
+ result = query(query_string, "nameIncludes" => "Wing", "first" => 2)
85
+ names = get_names(result)
86
+ assert_equal(2, names.length)
87
+
88
+ after = get_last_cursor(result)
89
+ result = query(query_string, "nameIncludes" => "Wing", "after" => after, "first" => 3)
90
+ names = get_names(result)
91
+ assert_equal(1, names.length)
92
+ end
93
+
94
+ it 'works without first/last/after/before' do
95
+ result = query(query_string)
96
+
97
+ assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
98
+ assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
99
+ assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
100
+ assert_equal("NQ==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
101
+ assert_equal(5, result["data"]["rebels"]["ships"]["edges"].length)
102
+ end
103
+
104
+ describe "applying max_page_size" do
105
+ def get_names(result)
106
+ result["data"]["rebels"]["bases"]["edges"].map { |e| e["node"]["name"] }
107
+ end
108
+
109
+ def get_page_info(result)
110
+ result["data"]["rebels"]["bases"]["pageInfo"]
111
+ end
112
+
113
+ let(:query_string) {%|
114
+ query getShips($first: Int, $after: String, $last: Int, $before: String){
115
+ rebels {
116
+ bases: basesWithMaxLimitArray(first: $first, after: $after, last: $last, before: $before) {
117
+ edges {
118
+ cursor
119
+ node {
120
+ name
121
+ }
122
+ }
123
+ pageInfo {
124
+ hasNextPage
125
+ hasPreviousPage
126
+ }
127
+ }
128
+ }
129
+ }
130
+ |}
131
+
132
+ it "applies to queries by `first`" do
133
+ result = query(query_string, "first" => 100)
134
+ assert_equal(["Yavin", "Echo Base"], get_names(result))
135
+ assert_equal(true, get_page_info(result)["hasNextPage"])
136
+
137
+ # Max page size is applied _without_ `first`, also
138
+ result = query(query_string)
139
+ assert_equal(["Yavin", "Echo Base"], get_names(result))
140
+ assert_equal(false, get_page_info(result)["hasNextPage"], "hasNextPage is false when first is not specified")
141
+ end
142
+
143
+ it "applies to queries by `last`" do
144
+ last_cursor = "Ng=="
145
+ second_to_last_two_names = ["Death Star", "Shield Generator"]
146
+ result = query(query_string, "last" => 100, "before" => last_cursor)
147
+ assert_equal(second_to_last_two_names, get_names(result))
148
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
149
+
150
+ result = query(query_string, "before" => last_cursor)
151
+ assert_equal(second_to_last_two_names, get_names(result))
152
+ assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
153
+
154
+ third_cursor = "Mw=="
155
+ first_and_second_names = ["Yavin", "Echo Base"]
156
+ result = query(query_string, "last" => 100, "before" => third_cursor)
157
+ assert_equal(first_and_second_names, get_names(result))
158
+
159
+ result = query(query_string, "before" => third_cursor)
160
+ assert_equal(first_and_second_names, get_names(result))
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::Relay::ConnectionType do
4
+ describe ".create_type" do
5
+ describe "connections with custom Edge classes / EdgeTypes" do
6
+ let(:query_string) {%|
7
+ {
8
+ rebels {
9
+ basesWithCustomEdge {
10
+ totalCountTimes100
11
+ edges {
12
+ upcasedName
13
+ upcasedParentName
14
+ edgeClassName
15
+ node {
16
+ name
17
+ }
18
+ cursor
19
+ }
20
+ }
21
+ }
22
+ }
23
+ |}
24
+
25
+ it "uses the custom edge and custom connection" do
26
+ result = query(query_string)
27
+ bases = result["data"]["rebels"]["basesWithCustomEdge"]
28
+ assert_equal 300, bases["totalCountTimes100"]
29
+ assert_equal ["YAVIN", "ECHO BASE", "SECRET HIDEOUT"] , bases["edges"].map { |e| e["upcasedName"] }
30
+ assert_equal ["Yavin", "Echo Base", "Secret Hideout"] , bases["edges"].map { |e| e["node"]["name"] }
31
+ assert_equal ["CustomBaseEdge"] , bases["edges"].map { |e| e["edgeClassName"] }.uniq
32
+ upcased_rebels_name = "ALLIANCE TO RESTORE THE REPUBLIC"
33
+ assert_equal [upcased_rebels_name] , bases["edges"].map { |e| e["upcasedParentName"] }.uniq
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Relay::GlobalNodeIdentification do
4
+ let(:node_identification) { StarWarsSchema.node_identification }
5
+ describe 'NodeField' do
6
+ it 'finds objects by id' do
7
+ global_id = node_identification.to_global_id("Faction", "1")
8
+ result = query(%|{
9
+ node(id: "#{global_id}") {
10
+ id,
11
+ ... on Faction {
12
+ name
13
+ ships(first: 1) {
14
+ edges {
15
+ node {
16
+ name
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }|)
23
+ expected = {"data" => {
24
+ "node"=>{
25
+ "id"=>"RmFjdGlvbi0x",
26
+ "name"=>"Alliance to Restore the Republic",
27
+ "ships"=>{
28
+ "edges"=>[
29
+ {"node"=>{
30
+ "name" => "X-Wing"
31
+ }
32
+ }
33
+ ]
34
+ }
35
+ }
36
+ }}
37
+ assert_equal(expected, result)
38
+ end
39
+ end
40
+
41
+ after do
42
+ # Set the id_separator back to it's default after each spec, since some of
43
+ # them change it at runtime
44
+ GraphQL::Relay::GlobalNodeIdentification.id_separator = "-"
45
+ end
46
+
47
+ describe 'id_separator' do
48
+ it "allows you to change it at runtime" do
49
+ GraphQL::Relay::GlobalNodeIdentification.id_separator = "-zomg-"
50
+
51
+ assert_equal("-zomg-", GraphQL::Relay::GlobalNodeIdentification.id_separator)
52
+ end
53
+ end
54
+
55
+ describe 'to_global_id / from_global_id ' do
56
+ it 'Converts typename and ID to and from ID' do
57
+ global_id = node_identification.to_global_id("SomeType", 123)
58
+ type_name, id = node_identification.from_global_id(global_id)
59
+ assert_equal("SomeType", type_name)
60
+ assert_equal("123", id)
61
+ end
62
+
63
+ it "allows you to change the id_separator" do
64
+ GraphQL::Relay::GlobalNodeIdentification.id_separator = "---"
65
+
66
+ global_id = node_identification.to_global_id("Type-With-UUID", "250cda0e-a89d-41cf-99e1-2872d89f1100")
67
+ type_name, id = node_identification.from_global_id(global_id)
68
+ assert_equal("Type-With-UUID", type_name)
69
+ assert_equal("250cda0e-a89d-41cf-99e1-2872d89f1100", id)
70
+ end
71
+
72
+ it "raises an error if you try and use a reserved character in the ID" do
73
+ err = assert_raises(RuntimeError) {
74
+ node_identification.to_global_id("Best-Thing", "234")
75
+ }
76
+ assert_includes err.message, "to_global_id(Best-Thing, 234) contains reserved characters `-`"
77
+ end
78
+
79
+ describe "custom definitions" do
80
+ let(:custom_node_identification) {
81
+ ident = GraphQL::Relay::GlobalNodeIdentification.define do
82
+ to_global_id -> (type_name, id) {
83
+ "#{type_name}/#{id}"
84
+ }
85
+
86
+ from_global_id -> (global_id) {
87
+ global_id.split("/")
88
+ }
89
+
90
+ object_from_id -> (node_id, ctx) do
91
+ type_name, id = ident.from_global_id(node_id)
92
+ STAR_WARS_DATA[type_name][id]
93
+ end
94
+
95
+ description "Hello, World!"
96
+ end
97
+ }
98
+
99
+ before do
100
+ @prev_node_identification = StarWarsSchema.node_identification
101
+ StarWarsSchema.node_identification = custom_node_identification
102
+ end
103
+
104
+ after do
105
+ StarWarsSchema.node_identification = @prev_node_identification
106
+ end
107
+
108
+ describe "generating IDs" do
109
+ it "Applies custom-defined ID generation" do
110
+ result = query(%| { largestBase { id } }|)
111
+ generated_id = result["data"]["largestBase"]["id"]
112
+ assert_equal "Base/3", generated_id
113
+ end
114
+ end
115
+
116
+ describe "fetching by ID" do
117
+ it "Deconstructs the ID by the custom proc" do
118
+ result = query(%| { node(id: "Base/1") { ... on Base { name } } }|)
119
+ base_name = result["data"]["node"]["name"]
120
+ assert_equal "Yavin", base_name
121
+ end
122
+ end
123
+
124
+ describe "setting a description" do
125
+ it "allows you to set a description" do
126
+ assert_equal "Hello, World!", custom_node_identification.field.description
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "type_from_object" do
133
+ describe "when the return value is nil" do
134
+ it "returns nil" do
135
+ result = node_identification.type_from_object(123)
136
+ assert_equal(nil, result)
137
+ end
138
+ end
139
+
140
+ describe "when the return value is not a BaseType" do
141
+ it "raises an error " do
142
+ err = assert_raises(RuntimeError) {
143
+ node_identification.type_from_object(:test_error)
144
+ }
145
+ assert_includes err.message, "not_a_type (Symbol)"
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Relay::Mutation do
4
+ let(:query_string) {%|
5
+ mutation addBagel($clientMutationId: String) {
6
+ introduceShip(input: {shipName: "Bagel", factionId: "1", clientMutationId: $clientMutationId}) {
7
+ clientMutationId
8
+ ship { name, id }
9
+ faction { name }
10
+ }
11
+ }
12
+ |}
13
+ let(:introspect) {%|
14
+ {
15
+ __schema {
16
+ types { name, fields { name } }
17
+ }
18
+ }
19
+ |}
20
+
21
+ after do
22
+ STAR_WARS_DATA["Ship"].delete("9")
23
+ STAR_WARS_DATA["Faction"]["1"]["ships"].delete("9")
24
+ end
25
+
26
+ it "returns the result & clientMutationId" do
27
+ result = query(query_string, "clientMutationId" => "1234")
28
+ expected = {"data" => {
29
+ "introduceShip" => {
30
+ "clientMutationId" => "1234",
31
+ "ship" => {
32
+ "name" => "Bagel",
33
+ "id" => NodeIdentification.to_global_id("Ship", "9"),
34
+ },
35
+ "faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
36
+ }
37
+ }}
38
+ assert_equal(expected, result)
39
+ end
40
+
41
+ it "doesn't require a clientMutationId to perform mutations" do
42
+ result = query(query_string)
43
+ expected = {"data" => {
44
+ "introduceShip" => {
45
+ "clientMutationId" => nil,
46
+ "ship" => {
47
+ "name" => "Bagel",
48
+ "id" => NodeIdentification.to_global_id("Ship", "9"),
49
+ },
50
+ "faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
51
+ }
52
+ }}
53
+ assert_equal(expected, result)
54
+ end
55
+ end
@@ -0,0 +1,106 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::Relay::PageInfo do
4
+ def get_page_info(result)
5
+ result["data"]["empire"]["bases"]["pageInfo"]
6
+ end
7
+
8
+ def get_first_cursor(result)
9
+ result["data"]["empire"]["bases"]["edges"].first["cursor"]
10
+ end
11
+
12
+ def get_last_cursor(result)
13
+ result["data"]["empire"]["bases"]["edges"].last["cursor"]
14
+ end
15
+
16
+ let(:cursor_of_last_base) {
17
+ result = query(query_string, "first" => 100)
18
+ last_cursor = get_last_cursor(result)
19
+ }
20
+
21
+ let(:query_string) {%|
22
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
23
+ empire {
24
+ bases(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
25
+ edges {
26
+ cursor
27
+ }
28
+ pageInfo {
29
+ hasNextPage
30
+ hasPreviousPage
31
+ startCursor
32
+ endCursor
33
+ }
34
+ }
35
+ }
36
+ }
37
+ |}
38
+
39
+ describe 'hasNextPage / hasPreviousPage' do
40
+ it "hasNextPage is true if there are more items" do
41
+ result = query(query_string, "first" => 2)
42
+ assert_equal(true, get_page_info(result)["hasNextPage"])
43
+ assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false if 'last' is missing")
44
+ assert_equal("MQ==", get_page_info(result)["startCursor"])
45
+ assert_equal("Mg==", get_page_info(result)["endCursor"])
46
+
47
+ last_cursor = get_last_cursor(result)
48
+ result = query(query_string, "first" => 100, "after" => last_cursor)
49
+ assert_equal(false, get_page_info(result)["hasNextPage"])
50
+ assert_equal(false, get_page_info(result)["hasPreviousPage"])
51
+ assert_equal("Mw==", get_page_info(result)["startCursor"])
52
+ assert_equal("Mw==", get_page_info(result)["endCursor"])
53
+ end
54
+
55
+ it "hasPreviousPage if there are more items" do
56
+ result = query(query_string, "last" => 100, "before" => cursor_of_last_base)
57
+ assert_equal(false, get_page_info(result)["hasNextPage"])
58
+ assert_equal(false, get_page_info(result)["hasPreviousPage"])
59
+ assert_equal("MQ==", get_page_info(result)["startCursor"])
60
+ assert_equal("Mg==", get_page_info(result)["endCursor"])
61
+
62
+ result = query(query_string, "last" => 1, "before" => cursor_of_last_base)
63
+ assert_equal(false, get_page_info(result)["hasNextPage"])
64
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
65
+ assert_equal("Mg==", get_page_info(result)["startCursor"])
66
+ assert_equal("Mg==", get_page_info(result)["endCursor"])
67
+ end
68
+
69
+ it "has both if first and last are present" do
70
+ result = query(query_string, "last" => 1, "first" => 1, "before" => cursor_of_last_base)
71
+ assert_equal(true, get_page_info(result)["hasNextPage"])
72
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
73
+ assert_equal("Mg==", get_page_info(result)["startCursor"])
74
+ assert_equal("Mg==", get_page_info(result)["endCursor"])
75
+ end
76
+
77
+ it "startCursor and endCursor are the cursors of the first and last edge" do
78
+ result = query(query_string, "first" => 2)
79
+ assert_equal(true, get_page_info(result)["hasNextPage"])
80
+ assert_equal(false, get_page_info(result)["hasPreviousPage"])
81
+ assert_equal("MQ==", get_page_info(result)["startCursor"])
82
+ assert_equal("Mg==", get_page_info(result)["endCursor"])
83
+ assert_equal("MQ==", get_first_cursor(result))
84
+ assert_equal("Mg==", get_last_cursor(result))
85
+
86
+ result = query(query_string, "first" => 1, "after" => get_page_info(result)["endCursor"])
87
+ assert_equal(false, get_page_info(result)["hasNextPage"])
88
+ assert_equal(false, get_page_info(result)["hasPreviousPage"])
89
+ assert_equal("Mw==", get_page_info(result)["startCursor"])
90
+ assert_equal("Mw==", get_page_info(result)["endCursor"])
91
+ assert_equal("Mw==", get_first_cursor(result))
92
+ assert_equal("Mw==", get_last_cursor(result))
93
+
94
+ result = query(query_string, "last" => 1, "before" => get_page_info(result)["endCursor"])
95
+ assert_equal(false, get_page_info(result)["hasNextPage"])
96
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
97
+ assert_equal("Mg==", get_page_info(result)["startCursor"])
98
+ assert_equal("Mg==", get_page_info(result)["endCursor"])
99
+ assert_equal("Mg==", get_first_cursor(result))
100
+ assert_equal("Mg==", get_last_cursor(result))
101
+ end
102
+ end
103
+
104
+
105
+
106
+ end