Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/graphql')
-rw-r--r--spec/support/graphql/arguments.rb70
-rw-r--r--spec/support/graphql/field_inspection.rb35
-rw-r--r--spec/support/graphql/field_selection.rb73
-rw-r--r--spec/support/graphql/var.rb59
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