diff options
Diffstat (limited to 'spec/support/graphql')
-rw-r--r-- | spec/support/graphql/arguments.rb | 70 | ||||
-rw-r--r-- | spec/support/graphql/field_inspection.rb | 35 | ||||
-rw-r--r-- | spec/support/graphql/field_selection.rb | 73 | ||||
-rw-r--r-- | spec/support/graphql/var.rb | 59 |
4 files changed, 237 insertions, 0 deletions
diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb new file mode 100644 index 00000000000..d8c334c2ca4 --- /dev/null +++ b/spec/support/graphql/arguments.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Graphql + class Arguments + delegate :blank?, :empty?, to: :to_h + + def initialize(values) + @values = values.compact + end + + def to_h + @values + end + + def ==(other) + to_h == other&.to_h + end + + alias_method :eql, :== + + def to_s + return '' if empty? + + @values.map do |name, value| + value_str = as_graphql_literal(value) + + "#{GraphqlHelpers.fieldnamerize(name.to_s)}: #{value_str}" + end.join(", ") + end + + def as_graphql_literal(value) + self.class.as_graphql_literal(value) + end + + # Transform values to GraphQL literal arguments. + # Use symbol for Enum values + def self.as_graphql_literal(value) + case value + when ::Graphql::Arguments then "{#{value}}" + when Array then "[#{value.map { |v| as_graphql_literal(v) }.join(',')}]" + when Hash then "{#{new(value)}}" + when Integer, Float, Symbol then value.to_s + when String then "\"#{value.gsub(/"/, '\\"')}\"" + when nil then 'null' + when true then 'true' + when false then 'false' + else + value.to_graphql_value + end + rescue NoMethodError + raise ArgumentError, "Cannot represent #{value} as GraphQL literal" + end + + def merge(other) + self.class.new(@values.merge(other.to_h)) + end + + def +(other) + if blank? + other + elsif other.blank? + self + elsif other.is_a?(String) + [to_s, other].compact.join(', ') + else + merge(other) + end + end + end +end diff --git a/spec/support/graphql/field_inspection.rb b/spec/support/graphql/field_inspection.rb new file mode 100644 index 00000000000..f39ba751141 --- /dev/null +++ b/spec/support/graphql/field_inspection.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Graphql + class FieldInspection + def initialize(field) + @field = field + end + + def nested_fields? + !scalar? && !enum? + end + + def scalar? + type.kind.scalar? + end + + def enum? + type.kind.enum? + end + + def type + @type ||= begin + field_type = @field.type.respond_to?(:to_graphql) ? @field.type.to_graphql : @field.type + + # The type could be nested. For example `[GraphQL::STRING_TYPE]`: + # - List + # - String! + # - String + field_type = field_type.of_type while field_type.respond_to?(:of_type) + + field_type + end + end + end +end diff --git a/spec/support/graphql/field_selection.rb b/spec/support/graphql/field_selection.rb new file mode 100644 index 00000000000..e2a3334acac --- /dev/null +++ b/spec/support/graphql/field_selection.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module Graphql + class FieldSelection + delegate :empty?, :blank?, :to_h, to: :selection + delegate :size, to: :paths + + attr_reader :selection + + def initialize(selection = {}) + @selection = selection.to_h + end + + def to_s + serialize_field_selection(selection) + end + + def paths + selection.flat_map do |field, subselection| + paths_in([field], subselection) + end + end + + private + + def paths_in(path, leaves) + return [path] if leaves.nil? + + leaves.to_a.flat_map do |k, v| + paths_in([k], v).map { |tail| path + tail } + end + end + + def serialize_field_selection(hash, level = 0) + indent = ' ' * level + + hash.map do |field, subselection| + if subselection.nil? + "#{indent}#{field}" + else + subfields = serialize_field_selection(subselection, level + 1) + "#{indent}#{field} {\n#{subfields}\n#{indent}}" + end + end.join("\n") + end + + NO_SKIP = ->(_name, _field) { false } + + def self.select_fields(type, skip = NO_SKIP, parent_types = Set.new, max_depth = 3) + return new if max_depth <= 0 + + new(type.fields.flat_map do |name, field| + next [] if skip[name, field] + + inspected = ::Graphql::FieldInspection.new(field) + singular_field_type = inspected.type + + # If field type is the same as parent type, then we're hitting into + # mutual dependency. Break it from infinite recursion + next [] if parent_types.include?(singular_field_type) + + if inspected.nested_fields? + subselection = select_fields(singular_field_type, skip, parent_types | [type], max_depth - 1) + next [] if subselection.empty? + + [[name, subselection.to_h]] + else + [[name, nil]] + end + end) + end + end +end diff --git a/spec/support/graphql/var.rb b/spec/support/graphql/var.rb new file mode 100644 index 00000000000..4f2c774e898 --- /dev/null +++ b/spec/support/graphql/var.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Graphql + # Helper to pass variables around generated queries. + # + # e.g.: + # first = var('Int') + # after = var('String') + # + # query = with_signature( + # [first, after], + # query_graphql_path([ + # [:project, { full_path: project.full_path }], + # [:issues, { after: after, first: first }] + # :nodes + # ], all_graphql_fields_for('Issue')) + # ) + # + # post_graphql(query, variables: [first.with(2), after.with(some_cursor)]) + # + class Var + attr_reader :name, :type + attr_accessor :value + + def initialize(name, type) + @name = name + @type = type + end + + def sig + "#{to_graphql_value}: #{type}" + end + + def to_graphql_value + "$#{name}" + end + + # We return a new object so that running the same query twice with + # different values does not risk re-using the value + # + # e.g. + # + # x = var('Int') + # expect { post_graphql(query, variables: x) } + # .to issue_same_number_of_queries_as { post_graphql(query, variables: x.with(1)) } + # + # Here we post the `x` variable once with the value set to 1, and once with + # the value set to `nil`. + def with(value) + copy = Var.new(name, type) + copy.value = value + copy + end + + def to_h + { name => value } + end + end +end |