graphql 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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