1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Present::FieldExtension do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let(:object) { double(value: 'foo') }
let(:owner) { fresh_object_type }
let(:field_name) { 'value' }
let(:field) do
::Types::BaseField.new(name: field_name, type: GraphQL::STRING_TYPE, null: true, owner: owner)
end
let(:base_presenter) do
Class.new(SimpleDelegator) do
def initialize(object, **options)
super(object)
@object = object
@options = options
end
end
end
def resolve_value
resolve_field(field, object, current_user: user, object_type: owner)
end
context 'when the object does not declare a presenter' do
it 'does not affect normal resolution' do
expect(resolve_value).to eq 'foo'
end
end
context 'when the field is declared on an interface, and implemented by a presenter' do
let(:interface) do
Module.new do
include ::Types::BaseInterface
field :interface_field, GraphQL::STRING_TYPE, null: true
end
end
let(:implementation) do
type = fresh_object_type('Concrete')
type.present_using(concrete_impl)
type.implements(interface)
type
end
def concrete_impl
Class.new(base_presenter) do
def interface_field
'made of concrete'
end
end
end
it 'resolves the interface field using the implementation from the presenter' do
field = ::Types::BaseField.new(name: :interface_field, type: GraphQL::STRING_TYPE, null: true, owner: interface)
value = resolve_field(field, object, object_type: implementation)
expect(value).to eq 'made of concrete'
end
context 'when the implementation is inherited' do
it 'resolves the interface field using the implementation from the presenter' do
subclass = Class.new(implementation) { graphql_name 'Subclass' }
field = ::Types::BaseField.new(name: :interface_field, type: GraphQL::STRING_TYPE, null: true, owner: interface)
value = resolve_field(field, object, object_type: subclass)
expect(value).to eq 'made of concrete'
end
end
end
describe 'interactions with inheritance' do
def parent
type = fresh_object_type('Parent')
type.present_using(provide_foo)
type.field :foo, ::GraphQL::INT_TYPE, null: true
type.field :value, ::GraphQL::STRING_TYPE, null: true
type
end
def child
type = Class.new(parent)
type.graphql_name 'Child'
type.present_using(provide_bar)
type.field :bar, ::GraphQL::INT_TYPE, null: true
type
end
def provide_foo
Class.new(base_presenter) do
def foo
100
end
end
end
def provide_bar
Class.new(base_presenter) do
def bar
101
end
end
end
it 'can resolve value, foo and bar' do
type = child
value = resolve_field(:value, object, object_type: type)
foo = resolve_field(:foo, object, object_type: type)
bar = resolve_field(:bar, object, object_type: type)
expect([value, foo, bar]).to eq ['foo', 100, 101]
end
end
shared_examples 'calling the presenter method' do
it 'calls the presenter method' do
expect(resolve_value).to eq presenter.new(object, current_user: user).send(field_name)
end
end
context 'when the object declares a presenter' do
before do
owner.present_using(presenter)
end
context 'when the presenter overrides the original method' do
def twice
Class.new(base_presenter) do
def value
@object.value * 2
end
end
end
let(:presenter) { twice }
it_behaves_like 'calling the presenter method'
end
# This is exercised here using an explicit `resolve:` proc, but
# @resolver_proc values are used in field instrumentation as well.
context 'when the field uses a resolve proc' do
let(:presenter) { base_presenter }
let(:field) do
::Types::BaseField.new(
name: field_name,
type: GraphQL::STRING_TYPE,
null: true,
owner: owner,
resolve: ->(obj, args, ctx) { 'Hello from a proc' }
)
end
specify { expect(resolve_value).to eq 'Hello from a proc' }
end
context 'when the presenter provides a new method' do
def presenter
Class.new(base_presenter) do
def current_username
"Hello #{@options[:current_user]&.username} from the presenter!"
end
end
end
context 'when we select the original field' do
it 'is unaffected' do
expect(resolve_value).to eq 'foo'
end
end
context 'when we select the new field' do
let(:field_name) { 'current_username' }
it_behaves_like 'calling the presenter method'
end
end
end
end
|