2020-02-27 13:09:21 -05:00
# frozen_string_literal: true
2021-04-22 08:09:49 -04:00
require 'spec_helper'
2020-02-27 13:09:21 -05:00
2020-06-24 14:09:03 -04:00
RSpec . describe Gitlab :: Graphql :: Docs :: Renderer do
2020-02-27 13:09:21 -05:00
describe '#contents' do
2021-04-22 08:09:49 -04:00
shared_examples 'renders correctly as GraphQL documentation' do
it 'contains the expected section' do
# duplicative - but much better error messages!
section . lines . each { | line | expect ( contents ) . to include ( line ) }
expect ( contents ) . to include ( section )
end
end
2021-03-29 11:09:30 -04:00
let ( :template ) { Rails . root . join ( 'lib/gitlab/graphql/docs/templates/default.md.haml' ) }
2021-04-22 08:09:49 -04:00
let ( :field_description ) { 'List of objects.' }
let ( :type ) { :: GraphQL :: INT_TYPE }
2020-02-27 13:09:21 -05:00
2021-03-29 11:09:30 -04:00
let ( :query_type ) do
Class . new ( Types :: BaseObject ) { graphql_name 'Query' } . tap do | t |
# this keeps type and field_description in scope.
t . field :foo , type , null : true , description : field_description do
2021-02-17 10:09:21 -05:00
argument :id , GraphQL :: ID_TYPE , required : false , description : 'ID of the object.'
end
2020-02-27 13:09:21 -05:00
end
2021-03-29 11:09:30 -04:00
end
2020-02-27 13:09:21 -05:00
2021-04-22 08:09:49 -04:00
let ( :mutation_root ) do
Class . new ( :: Types :: BaseObject ) do
include :: Gitlab :: Graphql :: MountMutation
graphql_name 'Mutation'
end
end
2021-03-29 11:09:30 -04:00
let ( :mock_schema ) do
Class . new ( GraphQL :: Schema ) do
def resolve_type ( obj , ctx )
raise 'Not a real schema'
end
end
2020-02-27 13:09:21 -05:00
end
subject ( :contents ) do
2021-03-29 11:09:30 -04:00
mock_schema . query ( query_type )
2021-04-22 08:09:49 -04:00
mock_schema . mutation ( mutation_root ) if mutation_root . fields . any?
2021-03-29 11:09:30 -04:00
2020-02-27 13:09:21 -05:00
described_class . new (
2021-03-29 11:09:30 -04:00
mock_schema ,
2020-02-27 13:09:21 -05:00
output_dir : nil ,
template : template
) . contents
end
2021-03-12 04:09:06 -05:00
describe 'headings' do
it 'contains the expected sections' do
expect ( contents . lines . map ( & :chomp ) ) . to include (
'## `Query` type' ,
2021-04-22 08:09:49 -04:00
'## `Mutation` type' ,
'## Connections' ,
2021-03-12 04:09:06 -05:00
'## Object types' ,
'## Enumeration types' ,
'## Scalar types' ,
'## Abstract types' ,
'### Unions' ,
2021-04-22 08:09:49 -04:00
'### Interfaces' ,
'## Input types'
2021-03-12 04:09:06 -05:00
)
end
end
context 'when a field has a list type' do
2020-02-27 13:09:21 -05:00
let ( :type ) do
2020-03-20 05:09:22 -04:00
Class . new ( Types :: BaseObject ) do
2020-02-27 13:09:21 -05:00
graphql_name 'ArrayTest'
2020-12-11 10:10:04 -05:00
field :foo , [ GraphQL :: STRING_TYPE ] , null : false , description : 'A description.'
2020-02-27 13:09:21 -05:00
end
end
specify do
2021-03-12 04:09:06 -05:00
type_name = '[String!]!'
inner_type = 'string'
2020-02-27 13:09:21 -05:00
expectation = << ~ DOC
2021-03-05 01:09:26 -05:00
### `ArrayTest`
2020-02-27 13:09:21 -05:00
2021-04-22 08:09:49 -04:00
#### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " arraytestfoo " > < / a>`foo` | [` #{ type_name } `]( # #{ inner_type } ) | A description. |
2020-02-27 13:09:21 -05:00
DOC
is_expected . to include ( expectation )
end
2021-02-17 10:09:21 -05:00
2021-03-12 04:09:06 -05:00
describe 'a top level query field' do
2021-02-17 10:09:21 -05:00
let ( :expectation ) do
<< ~ DOC
2021-04-22 08:09:49 -04:00
### `Query.foo`
2021-02-17 10:09:21 -05:00
List of objects .
2021-03-12 04:09:06 -05:00
Returns [ ` ArrayTest ` ] ( #arraytest).
2021-03-02 13:11:20 -05:00
#### Arguments
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
2021-04-22 08:09:49 -04:00
| < a id = " queryfooid " > < / a>`id` | [`ID`]( # id) | ID of the object. |
2021-02-17 10:09:21 -05:00
DOC
end
it 'generates the query with arguments' do
expect ( subject ) . to include ( expectation )
end
context 'when description does not end with `.`' do
let ( :field_description ) { 'List of objects' }
it 'adds the `.` to the end' do
expect ( subject ) . to include ( expectation )
end
end
end
2020-02-27 13:09:21 -05:00
end
2021-03-12 04:09:06 -05:00
describe 'when fields are not defined in alphabetical order' do
2020-02-27 13:09:21 -05:00
let ( :type ) do
2020-03-20 05:09:22 -04:00
Class . new ( Types :: BaseObject ) do
2020-02-27 13:09:21 -05:00
graphql_name 'OrderingTest'
2020-12-11 10:10:04 -05:00
field :foo , GraphQL :: STRING_TYPE , null : false , description : 'A description of foo field.'
field :bar , GraphQL :: STRING_TYPE , null : false , description : 'A description of bar field.'
2020-02-27 13:09:21 -05:00
end
end
2021-03-12 04:09:06 -05:00
it 'lists the fields in alphabetical order' do
2020-02-27 13:09:21 -05:00
expectation = << ~ DOC
2021-03-05 01:09:26 -05:00
### `OrderingTest`
2020-02-27 13:09:21 -05:00
2021-04-22 08:09:49 -04:00
#### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " orderingtestbar " > < / a>`bar` | [`String!`]( # string) | A description of bar field. |
| < a id = " orderingtestfoo " > < / a>`foo` | [`String!`]( # string) | A description of foo field. |
2020-02-27 13:09:21 -05:00
DOC
is_expected . to include ( expectation )
end
end
2021-04-22 08:09:49 -04:00
context 'when an argument is deprecated' do
let ( :type ) do
Class . new ( Types :: BaseObject ) do
graphql_name 'DeprecatedTest'
description 'A thing we used to use, but no longer support'
field :foo ,
type : GraphQL :: STRING_TYPE ,
null : false ,
description : 'A description.' do
argument :foo_arg , GraphQL :: STRING_TYPE ,
required : false ,
description : 'The argument.' ,
deprecated : { reason : 'Bad argument' , milestone : '101.2' }
end
end
end
let ( :section ) do
<< ~ DOC
##### `DeprecatedTest.foo`
A description .
Returns [ ` String! ` ] ( #string).
###### Arguments
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " deprecatedtestfoofooarg " > < / a>`fooArg` **{warning-solid}** | [`String`]( # string) | **Deprecated** in 101.2. Bad argument. |
DOC
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
2021-03-12 04:09:06 -05:00
context 'when a field is deprecated' do
2020-02-27 13:09:21 -05:00
let ( :type ) do
2020-03-20 05:09:22 -04:00
Class . new ( Types :: BaseObject ) do
2020-02-27 13:09:21 -05:00
graphql_name 'DeprecatedTest'
2021-04-22 08:09:49 -04:00
description 'A thing we used to use, but no longer support'
2020-02-27 13:09:21 -05:00
2021-03-12 04:09:06 -05:00
field :foo ,
type : GraphQL :: STRING_TYPE ,
null : false ,
deprecated : { reason : 'This is deprecated' , milestone : '1.10' } ,
description : 'A description.'
2021-03-29 11:09:30 -04:00
field :foo_with_args ,
type : GraphQL :: STRING_TYPE ,
null : false ,
2021-04-22 08:09:49 -04:00
deprecated : { reason : 'Do not use' , milestone : '1.10' , replacement : 'X.y' } ,
2021-03-29 11:09:30 -04:00
description : 'A description.' do
2021-04-22 08:09:49 -04:00
argument :arg , GraphQL :: INT_TYPE , required : false , description : 'Argity'
2021-03-29 11:09:30 -04:00
end
field :bar ,
type : GraphQL :: STRING_TYPE ,
null : false ,
description : 'A description.' ,
deprecated : {
reason : :renamed ,
milestone : '1.10' ,
replacement : 'Query.boom'
}
2020-02-27 13:09:21 -05:00
end
end
2021-04-22 08:09:49 -04:00
let ( :section ) do
<< ~ DOC
2021-03-05 01:09:26 -05:00
### `DeprecatedTest`
2020-02-27 13:09:21 -05:00
2021-04-22 08:09:49 -04:00
A thing we used to use , but no longer support .
2021-03-29 11:09:30 -04:00
2021-04-22 08:09:49 -04:00
#### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " deprecatedtestbar " > < / a>`bar` **{warning-solid}** | [`String!`]( # string) | **Deprecated** in 1.10. This was renamed. Use: [`Query.boom`]( # queryboom). |
| < a id = " deprecatedtestfoo " > < / a>`foo` **{warning-solid}** | [`String!`]( # string) | **Deprecated** in 1.10. This is deprecated. |
#### Fields with arguments
##### `DeprecatedTest.fooWithArgs`
A description .
WARNING :
** Deprecated ** in 1 . 10 .
Do not use .
Use : [ ` X.y ` ] ( #xy).
Returns [ ` String! ` ] ( #string).
###### Arguments
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " deprecatedtestfoowithargsarg " > < / a>`arg` | [`Int`]( # int) | Argity. |
DOC
2021-03-29 11:09:30 -04:00
end
2021-04-22 08:09:49 -04:00
it_behaves_like 'renders correctly as GraphQL documentation'
2021-03-29 11:09:30 -04:00
end
context 'when a Query.field is deprecated' do
before do
query_type . field (
name : :bar ,
type : type ,
null : true ,
description : 'A bar' ,
deprecated : { reason : :renamed , milestone : '10.11' , replacement : 'Query.foo' }
)
end
2021-04-22 08:09:49 -04:00
let ( :type ) { :: GraphQL :: INT_TYPE }
let ( :section ) do
<< ~ DOC
### `Query.bar`
2021-03-29 11:09:30 -04:00
A bar .
WARNING :
** Deprecated ** in 10 . 11 .
This was renamed .
2021-04-22 08:09:49 -04:00
Use : [ ` Query.foo ` ] ( #queryfoo).
2021-03-29 11:09:30 -04:00
Returns [ ` Int ` ] ( #int).
2020-02-27 13:09:21 -05:00
DOC
end
2021-04-22 08:09:49 -04:00
it_behaves_like 'renders correctly as GraphQL documentation'
2020-02-27 13:09:21 -05:00
end
2020-09-11 05:08:44 -04:00
2021-03-12 04:09:06 -05:00
context 'when a field has an Enumeration type' do
2020-09-11 05:08:44 -04:00
let ( :type ) do
enum_type = Class . new ( Types :: BaseEnum ) do
graphql_name 'MyEnum'
2021-04-22 08:09:49 -04:00
description 'A test of an enum.'
2020-09-11 05:08:44 -04:00
2021-03-12 04:09:06 -05:00
value 'BAZ' ,
description : 'A description of BAZ.'
value 'BAR' ,
description : 'A description of BAR.' ,
deprecated : { reason : 'This is deprecated' , milestone : '1.10' }
2020-09-11 05:08:44 -04:00
end
Class . new ( Types :: BaseObject ) do
graphql_name 'EnumTest'
2020-12-11 10:10:04 -05:00
field :foo , enum_type , null : false , description : 'A description of foo field.'
2020-09-11 05:08:44 -04:00
end
end
2021-04-22 08:09:49 -04:00
let ( :section ) do
<< ~ DOC
2021-03-05 01:09:26 -05:00
### `MyEnum`
2020-09-11 05:08:44 -04:00
2021-04-22 08:09:49 -04:00
A test of an enum .
2020-09-11 05:08:44 -04:00
| Value | Description |
| - - - - - | - - - - - - - - - - - |
2021-04-22 08:09:49 -04:00
| < a id = " myenumbar " > < / a>`BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. |
| < a id = " myenumbaz " > < / a>`BAZ` | A description of BAZ. |
2020-09-11 05:08:44 -04:00
DOC
end
2021-04-22 08:09:49 -04:00
it_behaves_like 'renders correctly as GraphQL documentation'
2020-09-11 05:08:44 -04:00
end
2021-03-12 04:09:06 -05:00
context 'when a field has a global ID type' do
let ( :type ) do
Class . new ( Types :: BaseObject ) do
graphql_name 'IDTest'
description 'A test for rendering IDs.'
field :foo , :: Types :: GlobalIDType [ :: User ] , null : true , description : 'A user foo.'
end
end
2021-04-22 08:09:49 -04:00
describe 'section for IDTest' do
let ( :section ) do
<< ~ DOC
### `IDTest`
2021-03-12 04:09:06 -05:00
2021-04-22 08:09:49 -04:00
A test for rendering IDs .
2021-03-12 04:09:06 -05:00
2021-04-22 08:09:49 -04:00
#### Fields
2021-03-12 04:09:06 -05:00
2021-04-22 08:09:49 -04:00
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " idtestfoo " > < / a>`foo` | [`UserID`]( # userid) | A user foo. |
DOC
end
2021-03-12 04:09:06 -05:00
2021-04-22 08:09:49 -04:00
it_behaves_like 'renders correctly as GraphQL documentation'
end
2021-03-12 04:09:06 -05:00
2021-04-22 08:09:49 -04:00
describe 'section for UserID' do
let ( :section ) do
<< ~ DOC
### `UserID`
A ` UserID ` is a global ID . It is encoded as a string .
An example ` UserID ` is : ` "gid://gitlab/User/1" ` .
DOC
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
end
context 'when there is a mutation' do
let ( :mutation ) do
mutation = Class . new ( :: Mutations :: BaseMutation )
mutation . graphql_name 'MakeItPretty'
mutation . description 'Make everything very pretty.'
mutation . argument :prettiness_factor ,
type : GraphQL :: FLOAT_TYPE ,
required : true ,
description : 'How much prettier?'
mutation . argument :pulchritude ,
type : GraphQL :: FLOAT_TYPE ,
required : false ,
description : 'How much prettier?' ,
deprecated : {
reason : :renamed ,
replacement : 'prettinessFactor' ,
milestone : '72.34'
}
mutation . field :everything ,
type : GraphQL :: STRING_TYPE ,
null : true ,
description : 'What we made prettier.'
mutation . field :omnis ,
type : GraphQL :: STRING_TYPE ,
null : true ,
description : 'What we made prettier.' ,
deprecated : {
reason : :renamed ,
replacement : 'everything' ,
milestone : '72.34'
}
mutation
end
before do
mutation_root . mount_mutation mutation
end
it_behaves_like 'renders correctly as GraphQL documentation' do
let ( :section ) do
<< ~ DOC
### `Mutation.makeItPretty`
Make everything very pretty .
Input type : ` MakeItPrettyInput `
#### Arguments
2021-03-12 04:09:06 -05:00
2021-04-22 08:09:49 -04:00
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " mutationmakeitprettyclientmutationid " > < / a>`clientMutationId` | [`String`]( # string) | A unique identifier for the client performing the mutation. |
| < a id = " mutationmakeitprettyprettinessfactor " > < / a>`prettinessFactor` | [`Float!`]( # float) | How much prettier?. |
| < a id = " mutationmakeitprettypulchritude " > < / a>`pulchritude` **{warning-solid}** | [`Float`]( # float) | **Deprecated:** This was renamed. Please use `prettinessFactor`. Deprecated in 72.34. |
#### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " mutationmakeitprettyclientmutationid " > < / a>`clientMutationId` | [`String`]( # string) | A unique identifier for the client performing the mutation. |
| < a id = " mutationmakeitprettyerrors " > < / a>`errors` | [`[String!]!`]( # string) | Errors encountered during execution of the mutation. |
| < a id = " mutationmakeitprettyeverything " > < / a>`everything` | [`String`]( # string) | What we made prettier. |
| < a id = " mutationmakeitprettyomnis " > < / a>`omnis` **{warning-solid}** | [`String`]( # string) | **Deprecated:** This was renamed. Please use `everything`. Deprecated in 72.34. |
DOC
end
end
it 'does not render the automatically generated payload type' do
expect ( contents ) . not_to include ( 'MakeItPrettyPayload' )
end
it 'does not render the automatically generated input type as its own section' do
expect ( contents ) . not_to include ( '# `MakeItPrettyInput`' )
2021-03-12 04:09:06 -05:00
end
end
2021-04-22 08:09:49 -04:00
context 'when there is an input type' do
let ( :type ) do
Class . new ( :: Types :: BaseObject ) do
graphql_name 'Foo'
field :wibble , type : :: GraphQL :: INT_TYPE , null : true do
argument :date_range ,
type : :: Types :: TimeframeInputType ,
required : true ,
description : 'When the foo happened.'
end
end
end
let ( :section ) do
<< ~ DOC
### `Timeframe`
A time - frame defined as a closed inclusive range of two dates .
#### Arguments
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " timeframeend " > < / a>`end` | [`Date!`]( # date) | The end of the range. |
| < a id = " timeframestart " > < / a>`start` | [`Date!`]( # date) | The start of the range. |
DOC
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
2021-03-12 04:09:06 -05:00
context 'when there is an interface and a union' do
let ( :type ) do
user = Class . new ( :: Types :: BaseObject )
user . graphql_name 'User'
user . field :user_field , :: GraphQL :: STRING_TYPE , null : true
group = Class . new ( :: Types :: BaseObject )
group . graphql_name 'Group'
group . field :group_field , :: GraphQL :: STRING_TYPE , null : true
union = Class . new ( :: Types :: BaseUnion )
union . graphql_name 'UserOrGroup'
union . description 'Either a user or a group.'
union . possible_types user , group
interface = Module . new
interface . include ( :: Types :: BaseInterface )
interface . graphql_name 'Flying'
interface . description 'Something that can fly.'
interface . field :flight_speed , GraphQL :: INT_TYPE , null : true , description : 'Speed in mph.'
african_swallow = Class . new ( :: Types :: BaseObject )
african_swallow . graphql_name 'AfricanSwallow'
african_swallow . description 'A swallow from Africa.'
african_swallow . implements interface
interface . orphan_types african_swallow
Class . new ( :: Types :: BaseObject ) do
2021-04-22 08:09:49 -04:00
graphql_name 'AbstractTypeTest'
2021-03-12 04:09:06 -05:00
description 'A test for abstract types.'
field :foo , union , null : true , description : 'The foo.'
field :flying , interface , null : true , description : 'A flying thing.'
end
end
it 'lists the fields correctly, and includes descriptions of all the types' do
type_section = << ~ DOC
2021-04-22 08:09:49 -04:00
### `AbstractTypeTest`
2021-03-12 04:09:06 -05:00
A test for abstract types .
2021-04-22 08:09:49 -04:00
#### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " abstracttypetestflying " > < / a>`flying` | [`Flying`]( # flying) | A flying thing. |
| < a id = " abstracttypetestfoo " > < / a>`foo` | [`UserOrGroup`]( # userorgroup) | The foo. |
2021-03-12 04:09:06 -05:00
DOC
union_section = << ~ DOC
#### `UserOrGroup`
Either a user or a group .
One of :
- [ ` Group ` ] ( #group)
- [ ` User ` ] ( #user)
DOC
interface_section = << ~ DOC
#### `Flying`
Something that can fly .
Implementations :
- [ ` AfricanSwallow ` ] ( #africanswallow)
2021-04-22 08:09:49 -04:00
##### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " flyingflightspeed " > < / a>`flightSpeed` | [`Int`]( # int) | Speed in mph. |
2021-03-12 04:09:06 -05:00
DOC
implementation_section = << ~ DOC
### `AfricanSwallow`
A swallow from Africa .
2021-04-22 08:09:49 -04:00
#### Fields
| Name | Type | Description |
| - - - - | - - - - | - - - - - - - - - - - |
| < a id = " africanswallowflightspeed " > < / a>`flightSpeed` | [`Int`]( # int) | Speed in mph. |
2021-03-12 04:09:06 -05:00
DOC
is_expected . to include (
type_section ,
union_section ,
interface_section ,
implementation_section
)
end
end
2020-02-27 13:09:21 -05:00
end
end