Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/graphql_relay/connection/array_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,12 @@ def connection_from_array_slice(
pageInfo=page_info_type(
startCursor=first_edge_cursor,
endCursor=last_edge_cursor,
hasPreviousPage=isinstance(last, int) and start_offset > lower_bound,
hasNextPage=isinstance(first, int) and end_offset < upper_bound,
hasPreviousPage=start_offset > lower_bound
if isinstance(last, int)
else (0 <= after_offset < array_length if after is not None else False),
hasNextPage=end_offset < upper_bound
if isinstance(first, int)
else (0 <= before_offset < array_length if before is not None else False),
),
)

Expand Down
92 changes: 73 additions & 19 deletions tests/connection/test_array_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ def respects_first_and_after():
pageInfo=PageInfo(
startCursor=cursor_c,
endCursor=cursor_d,
hasPreviousPage=False,
# `after` was given, so a previous page necessarily exists.
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -123,7 +124,8 @@ def respects_first_and_after_with_long_first():
pageInfo=PageInfo(
startCursor=cursor_c,
endCursor=cursor_e,
hasPreviousPage=False,
# `after` was given, so a previous page necessarily exists.
hasPreviousPage=True,
hasNextPage=False,
),
)
Expand All @@ -136,7 +138,8 @@ def respects_last_and_before():
startCursor=cursor_b,
endCursor=cursor_c,
hasPreviousPage=True,
hasNextPage=False,
# `before` was given, so a next page necessarily exists.
hasNextPage=True,
),
)

Expand All @@ -148,7 +151,8 @@ def respects_last_and_before_with_long_last():
startCursor=cursor_a,
endCursor=cursor_c,
hasPreviousPage=False,
hasNextPage=False,
# `before` was given, so a next page necessarily exists.
hasNextPage=True,
),
)

Expand All @@ -162,7 +166,8 @@ def respects_first_and_after_and_before_too_few():
pageInfo=PageInfo(
startCursor=cursor_b,
endCursor=cursor_c,
hasPreviousPage=False,
# `after` was given, so a previous page necessarily exists.
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -177,7 +182,8 @@ def respects_first_and_after_and_before_too_many():
pageInfo=PageInfo(
startCursor=cursor_b,
endCursor=cursor_d,
hasPreviousPage=False,
# `after` was given, so a previous page necessarily exists.
hasPreviousPage=True,
hasNextPage=False,
),
)
Expand All @@ -192,7 +198,8 @@ def respects_first_and_after_and_before_exactly_right():
pageInfo=PageInfo(
startCursor=cursor_b,
endCursor=cursor_d,
hasPreviousPage=False,
# `after` was given, so a previous page necessarily exists.
hasPreviousPage=True,
hasNextPage=False,
),
)
Expand All @@ -208,7 +215,8 @@ def respects_last_and_after_and_before_too_few():
startCursor=cursor_c,
endCursor=cursor_d,
hasPreviousPage=True,
hasNextPage=False,
# `before` was given, so a next page necessarily exists.
hasNextPage=True,
),
)

Expand All @@ -223,7 +231,8 @@ def respects_last_and_after_and_before_too_many():
startCursor=cursor_b,
endCursor=cursor_d,
hasPreviousPage=False,
hasNextPage=False,
# `before` was given, so a next page necessarily exists.
hasNextPage=True,
),
)

Expand All @@ -238,10 +247,49 @@ def respects_last_and_after_and_before_exactly_right():
startCursor=cursor_b,
endCursor=cursor_d,
hasPreviousPage=False,
hasNextPage=False,
# `before` was given, so a next page necessarily exists.
hasNextPage=True,
),
)

def describe_has_previous_next_page_regression():
"""Regression tests for pageInfo.hasPreviousPage/hasNextPage being
gated solely on `last`/`first` and ignoring `after`/`before`."""

array_0_to_19 = list(range(20))

def has_previous_page_true_on_second_page_of_forward_pagination():
first_page = connection_from_array(array_0_to_19, dict(first=5))
assert first_page.pageInfo.hasPreviousPage is False

second_page = connection_from_array(
array_0_to_19,
dict(first=5, after=first_page.pageInfo.endCursor),
)
# An `after` cursor was explicitly given, guaranteeing a
# previous page exists, even though `last` was never passed.
assert second_page.pageInfo.hasPreviousPage is True

def has_previous_page_false_on_first_page_of_forward_pagination():
first_page = connection_from_array(array_0_to_19, dict(first=5))
assert first_page.pageInfo.hasPreviousPage is False

def has_next_page_true_on_penultimate_page_of_backward_pagination():
last_page = connection_from_array(array_0_to_19, dict(last=5))
assert last_page.pageInfo.hasNextPage is False

previous_page = connection_from_array(
array_0_to_19,
dict(last=5, before=last_page.pageInfo.startCursor),
)
# A `before` cursor was explicitly given, guaranteeing a next
# page exists, even though `first` was never passed.
assert previous_page.pageInfo.hasNextPage is True

def has_next_page_false_on_last_page_of_backward_pagination():
last_page = connection_from_array(array_0_to_19, dict(last=5))
assert last_page.pageInfo.hasNextPage is False

def describe_cursor_edge_cases():
def throws_an_error_if_first_smaller_than_zero():
with raises(ValueError) as exc_info:
Expand Down Expand Up @@ -317,8 +365,12 @@ def returns_no_elements_if_cursors_cross():
pageInfo=PageInfo(
startCursor=None,
endCursor=None,
hasPreviousPage=False,
hasNextPage=False,
# Both `after` and `before` point to valid, existing
# elements, so elements exist both before `after` and
# after `before`, even though the crossed cursors leave
# no edges in the returned slice itself.
hasPreviousPage=True,
hasNextPage=True,
),
)

Expand Down Expand Up @@ -483,7 +535,9 @@ def works_with_a_just_right_array_slice():
pageInfo=PageInfo(
startCursor=cursor_b,
endCursor=cursor_c,
hasPreviousPage=False,
# `after` points at an existing element (A), so a previous
# page necessarily exists.
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -500,7 +554,7 @@ def works_with_an_oversized_array_slice_left_side():
pageInfo=PageInfo(
startCursor=cursor_b,
endCursor=cursor_c,
hasPreviousPage=False,
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -517,7 +571,7 @@ def works_with_an_oversized_array_slice_right_side():
pageInfo=PageInfo(
startCursor=cursor_c,
endCursor=cursor_c,
hasPreviousPage=False,
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -534,7 +588,7 @@ def works_with_an_oversized_array_slice_both_sides():
pageInfo=PageInfo(
startCursor=cursor_c,
endCursor=cursor_c,
hasPreviousPage=False,
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -551,7 +605,7 @@ def works_with_an_undersized_array_slice_left_side():
pageInfo=PageInfo(
startCursor=cursor_d,
endCursor=cursor_e,
hasPreviousPage=False,
hasPreviousPage=True,
hasNextPage=False,
),
)
Expand All @@ -568,7 +622,7 @@ def works_with_an_undersized_array_slice_right_side():
pageInfo=PageInfo(
startCursor=cursor_c,
endCursor=cursor_d,
hasPreviousPage=False,
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand All @@ -585,7 +639,7 @@ def works_with_an_undersized_array_slice_both_sides():
pageInfo=PageInfo(
startCursor=cursor_d,
endCursor=cursor_d,
hasPreviousPage=False,
hasPreviousPage=True,
hasNextPage=True,
),
)
Expand Down