Files
rio/tests/test_utils.py
2025-01-14 16:17:12 +01:00

287 lines
7.2 KiB
Python

import math
import re
import typing as t
import pytest
import rio
import rio.utils
def _assert_stops_match(
stops_should: t.Iterable[tuple[rio.Color, float]],
stops_are: t.Iterable[tuple[rio.Color, float]],
) -> None:
"""
Given two lists of stops (all with positions), verify that they are
identical.
"""
stops_should = list(stops_should)
stops_are = list(stops_are)
assert len(stops_are) == len(stops_should)
for stop_should, stop_are in zip(stops_should, stops_are):
assert isinstance(stop_should, tuple)
assert isinstance(stop_are, tuple)
assert len(stop_should) == 2
assert len(stop_are) == 2
color_should, position_should = stop_should
color_are, position_are = stop_are
assert color_should.hexa == color_are.hexa
assert math.isclose(position_should, position_are)
@pytest.mark.parametrize(
"input_type,output_type",
[
# Simple cases
("pdf", "pdf"),
(".Pdf", "pdf"),
(".PDF", "pdf"),
("*pdf", "pdf"),
("*.Pdf", "pdf"),
("*.PDF", "pdf"),
("application/pdf", "pdf"),
# Make sure the results are standardized
(".jpg", "jpg"),
(".jpeg", "jpg"),
("image/jpeg", "jpg"),
# Invalid MIME types
("not/a/real/type", "type"),
("////Type", "type"),
],
)
def test_standardize_file_types(
input_type: str,
output_type: str,
) -> None:
cleaned_type = rio.utils.normalize_file_extension(input_type)
assert cleaned_type == output_type
@pytest.mark.parametrize(
"unsorted_sequence,keys,sorted_sequence_should",
[
(
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
),
(
[4, 3, 2, 1, 0],
[4, 3, 2, 1, 0],
[0, 1, 2, 3, 4],
),
(
[4, 3, 2, 1, 0],
[4, None, None, None, 0],
[0, 3, 2, 1, 4],
),
(
[0, 1, 2, 3],
[0, 1, 1, 3],
[0, 1, 2, 3],
),
(
[3, 2, 1, 0],
[3, 1, 1, 0],
[0, 2, 1, 3],
),
(
[0, 1, 2, 3],
[0, None, None, None],
[0, 1, 2, 3],
),
(
[0, 1, 2, 3],
[9, None, None, None],
[1, 2, 3, 0],
),
],
)
def test_soft_sort(
unsorted_sequence: list[int],
keys: list[int],
sorted_sequence_should: list[int],
) -> None:
# Sort the sequence
sorted_sequence_is = unsorted_sequence.copy()
rio.utils.soft_sort(
sorted_sequence_is,
key=lambda x: keys[unsorted_sequence.index(x)],
)
# Verify the result
assert sorted_sequence_is == sorted_sequence_should
@pytest.mark.parametrize(
"stops_in, stops_should",
[
# No explicit positions
(
(rio.Color.BLACK,),
(
(rio.Color.BLACK, 0.0),
(rio.Color.BLACK, 1.0),
),
),
(
(
rio.Color.BLACK,
rio.Color.WHITE,
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.WHITE, 1.0),
),
),
(
(
rio.Color.BLACK,
rio.Color.BLUE,
rio.Color.WHITE,
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.BLUE, 0.5),
(rio.Color.WHITE, 1.0),
),
),
# Containing explicit positions
(
(
(rio.Color.BLACK, 0.0),
(rio.Color.WHITE, 1.0),
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.WHITE, 1.0),
),
),
(
(
(rio.Color.BLACK, 0.0),
rio.Color.WHITE,
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.WHITE, 1.0),
),
),
(
(
(rio.Color.BLACK, 0.0),
rio.Color.BLUE,
rio.Color.WHITE,
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.BLUE, 0.5),
(rio.Color.WHITE, 1.0),
),
),
(
(
(rio.Color.BLACK, 0.0),
rio.Color.RED,
rio.Color.GREEN,
rio.Color.BLUE,
(rio.Color.CYAN, 0.8),
rio.Color.MAGENTA,
(rio.Color.WHITE, 1.0),
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.RED, 0.2),
(rio.Color.GREEN, 0.4),
(rio.Color.BLUE, 0.6),
(rio.Color.CYAN, 0.8),
(rio.Color.MAGENTA, 0.9),
(rio.Color.WHITE, 1.0),
),
),
# Duplicate positions
(
(
rio.Color.BLACK,
(rio.Color.RED, 0.5),
(rio.Color.GREEN, 0.5),
rio.Color.WHITE,
),
(
(rio.Color.BLACK, 0.0),
(rio.Color.RED, 0.5),
(rio.Color.GREEN, 0.5),
(rio.Color.WHITE, 1.0),
),
),
],
)
def test_interpolation_is_correct(
stops_in: tuple[rio.Color, ...],
stops_should: tuple[tuple[rio.Color, float], ...],
) -> None:
"""
Test that the interpolation function correctly assigns positions to stops.
"""
stops_are = rio.utils.verify_and_interpolate_gradient_stops(stops_in)
_assert_stops_match(stops_should, stops_are)
@pytest.mark.parametrize(
"stops_in, error_msg",
[
# Empty stops
(
tuple(),
"Gradients must have at least one stop",
),
# Position out of range
(
(
(rio.Color.BLACK, -0.1),
(rio.Color.WHITE, 1.0),
),
"Gradient stop positions must be in range [0, 1], but stop 0 is at position -0.1",
),
(
(
(rio.Color.BLACK, 0.1),
(rio.Color.WHITE, 1.1),
),
"Gradient stop positions must be in range [0, 1], but stop 1 is at position 1.1",
),
# Positions not ascending
(
(
(rio.Color.BLACK, 0.8),
(rio.Color.WHITE, 0.2),
),
"Gradient stops must be in ascending order, but stop 0 is at position 0.8 while stop 1 is at position 0.2",
),
(
(
rio.Color.RED,
(rio.Color.BLACK, 0.8),
rio.Color.BLUE,
(rio.Color.WHITE, 0.2),
),
"Gradient stops must be in ascending order, but stop 1 is at position 0.8 while stop 3 is at position 0.2",
),
],
)
def test_invalid_stops_raise_error(
stops_in: tuple[rio.Color | tuple[rio.Color, float], ...],
error_msg: str,
) -> None:
"""
Test that invalid gradient stop configurations raise appropriate errors.
"""
with pytest.raises(ValueError, match=re.escape(error_msg)):
rio.utils.verify_and_interpolate_gradient_stops(stops_in)