"""Pure-algorithm tests for human_mouse.transform — no IO, no playwright.""" from __future__ import annotations import math import pytest from human_mouse import Segment, affine_warp, upsample def _seg(points: list[tuple[float, float, float]]) -> Segment: return Segment(user="t", session="s", points=points) # ---------------------------------------------------------------- affine_warp def test_affine_warp_pins_endpoints(): seg = _seg([(0, 0, 0), (50, 50, 10), (100, 100, 20)]) out = affine_warp(seg, start=(500, 300), end=(700, 400)) assert math.isclose(out[0][0], 500) and math.isclose(out[0][1], 300) assert math.isclose(out[-1][0], 700) and math.isclose(out[-1][1], 400) def test_affine_warp_uniform_along_axis(): """A straight segment along +x maps to a straight segment between targets.""" seg = _seg([(0, 0, 0), (50, 0, 10), (100, 0, 20)]) out = affine_warp(seg, start=(0, 0), end=(200, 0)) assert math.isclose(out[1][0], 100, abs_tol=1e-6) assert math.isclose(out[1][1], 0, abs_tol=1e-6) def test_affine_warp_preserves_time(): seg = _seg([(0, 0, 100), (10, 5, 130), (20, 0, 160)]) out = affine_warp(seg, start=(0, 0), end=(40, 0)) assert out[0][2] == 0 # rebased assert out[1][2] == 30 assert out[2][2] == 60 def test_affine_warp_rotates_perpendicular_offset(): """A 90-degree rotation: source goes east, target goes north.""" seg = _seg([(0, 0, 0), (50, 10, 10), (100, 0, 20)]) out = affine_warp(seg, start=(0, 0), end=(0, 100)) # The middle point sat 10 px to the left of the source axis (east-pointing # axis -> left is +y); after rotating the axis to north, "left of axis" # means -x. So midpoint x should be approximately -10. assert math.isclose(out[1][0], -10, abs_tol=1e-6) assert math.isclose(out[1][1], 50, abs_tol=1e-6) # ------------------------------------------------------------------- upsample def test_upsample_factor_1_is_identity(): pts = [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0), (2.0, 2.0, 2.0)] assert upsample(pts, 1) == pts def test_upsample_factor_4_count_formula(): # For N input points and factor k, expect k*(N-1) + 1 output points. pts = [(0.0, 0.0, 0.0), (4.0, 0.0, 4.0), (8.0, 0.0, 8.0)] out = upsample(pts, 4) assert len(out) == 4 * (3 - 1) + 1 def test_upsample_endpoints_unchanged(): pts = [(0.0, 0.0, 0.0), (10.0, 10.0, 5.0)] out = upsample(pts, 5) assert out[0] == pts[0] assert out[-1] == pts[-1] def test_upsample_midpoint_correct_for_factor_2(): pts = [(0.0, 0.0, 0.0), (10.0, 20.0, 100.0)] out = upsample(pts, 2) # Should be: P0, P0+0.5*delta, P1 assert out[1] == (5.0, 10.0, 50.0) def test_upsample_time_monotonic_with_monotonic_input(): pts = [(0.0, 0.0, 0.0), (1.0, 0.0, 5.0), (2.0, 0.0, 12.0), (3.0, 0.0, 20.0)] out = upsample(pts, 4) times = [p[2] for p in out] assert all(t1 >= t0 for t0, t1 in zip(times, times[1:])) @pytest.mark.parametrize("factor", [0, -1]) def test_upsample_non_positive_factor_returns_copy(factor: int): pts = [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)] out = upsample(pts, factor) assert out == pts assert out is not pts # returns a copy, not the same list