summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
blob: 6d6e7b761f6c893753e3c53a1f81e16cf250329d (plain)
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
# frozen_string_literal: true

# Use this for testing how a GraphQL query handles sorting and pagination.
# This is particularly important when using keyset pagination connection,
# which is the default for ActiveRecord relations, as certain sort keys
# might not be supportable.
#
# sort_param: the value to specify the sort
# data_path: the keys necessary to dig into the return GraphQL data to get the
#   returned results
# first_param: number of items expected (like a page size)
# all_records: array of comparison data of all items sorted correctly
# pagination_query: method that specifies the GraphQL query
# pagination_results_data: method that extracts the sorted data used to compare against
#   the expected results
#
# Example:
#   describe 'sorting and pagination' do
#     let_it_be(:sort_project) { create(:project, :public) }
#     let(:data_path)    { [:project, :issues] }
#
#     def pagination_query(arguments)
#       graphql_query_for(:project, { full_path: sort_project.full_path },
#         query_nodes(:issues, :iid, include_pagination_info: true, args: arguments)
#       )
#     end
#
#     # A method transforming nodes to data to match against
#     # default: the identity function
#     def pagination_results_data(issues)
#       issues.map { |issue| issue['iid].to_i }
#     end
#
#     context 'when sorting by weight' do
#       let_it_be(:issues) { make_some_issues_with_weights }
#
#       context 'when ascending' do
#         let(:ordered_issues) { issues.sort_by(&:weight) }
#
#         it_behaves_like 'sorted paginated query' do
#           let(:sort_param) { :WEIGHT_ASC }
#           let(:first_param) { 2 }
#           let(:all_records) { ordered_issues.map(&:iid) }
#         end
#       end
#
RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
  # Provided as a convenience when constructing queries using string concatenation
  let(:page_info) { 'pageInfo { startCursor endCursor }' }
  # Convenience for using default implementation of pagination_results_data
  let(:node_path) { ['id'] }

  it_behaves_like 'requires variables' do
    let(:required_variables) { [:sort_param, :first_param, :all_records, :data_path, :current_user] }
  end

  describe do
    let(:sort_argument)  { graphql_args(sort: sort_param) }
    let(:params)         { sort_argument }

    # Convenience helper for the large number of queries defined as a projection
    # from some root value indexed by full_path to a collection of objects with IID
    def nested_internal_id_query(root_field, parent, field, args, selection: :iid)
      graphql_query_for(root_field, { full_path: parent.full_path },
        query_nodes(field, selection, args: args, include_pagination_info: true)
      )
    end

    def pagination_query(params)
      raise('pagination_query(params) must be defined in the test, see example in comment') unless defined?(super)

      super
    end

    def pagination_results_data(nodes)
      if defined?(super)
        super(nodes)
      else
        nodes.map { |n| n.dig(*node_path) }
      end
    end

    def results
      nodes = graphql_dig_at(graphql_data(fresh_response_data), *data_path, :nodes)
      pagination_results_data(nodes)
    end

    def end_cursor
      graphql_dig_at(graphql_data(fresh_response_data), *data_path, :page_info, :end_cursor)
    end

    def start_cursor
      graphql_dig_at(graphql_data(fresh_response_data), *data_path, :page_info, :start_cursor)
    end

    let(:query) { pagination_query(params) }

    before do
      post_graphql(query, current_user: current_user)
    end

    context 'when sorting' do
      it 'sorts correctly' do
        expect(results).to match all_records
      end

      context 'when paginating' do
        let(:params) { sort_argument.merge(first: first_param) }
        let(:first_page) { all_records.first(first_param) }
        let(:rest) { all_records.drop(first_param) }

        it 'paginates correctly' do
          expect(results).to match first_page

          fwds = pagination_query(sort_argument.merge(after: end_cursor))
          post_graphql(fwds, current_user: current_user)

          expect(results).to match rest

          bwds = pagination_query(sort_argument.merge(before: start_cursor))
          post_graphql(bwds, current_user: current_user)

          expect(results).to match first_page
        end
      end

      context 'when last and sort params are present', if: conditions[:is_reversible] do
        let(:params) { sort_argument.merge(last: 1) }

        it 'fetches last elements without error' do
          post_graphql(pagination_query(params), current_user: current_user)

          expect(results.first).to match all_records.last
        end
      end
    end
  end
end