feat(lib): switch __init__ to ONNX-backed generate/generate_scroll

This commit is contained in:
2026-05-12 01:16:03 +08:00
parent 4e69ecc963
commit d39db46170
5 changed files with 223 additions and 4 deletions

View File

@@ -43,3 +43,45 @@ def test_mouse_model_invalid_path_raises_model_load_error() -> None:
from ai_mouse.errors import ModelLoadError
with pytest.raises(ModelLoadError):
MouseModel(model_path="/nonexistent/path/here")
def test_mouse_model_timestamps_monotonic() -> None:
from ai_mouse.mouse import MouseModel
m = MouseModel()
pts = m.generate((100, 200), (900, 400))
times = [p[2] for p in pts]
for i in range(1, len(times)):
assert times[i] >= times[i - 1]
def test_mouse_model_starts_and_ends_near_endpoints() -> None:
from ai_mouse.mouse import MouseModel
start = (100, 200)
end = (900, 400)
m = MouseModel()
pts = m.generate(start, end)
assert abs(pts[0][0] - start[0]) < 30
assert abs(pts[0][1] - start[1]) < 30
last_move = pts[-3] # last 2 are click events
assert abs(last_move[0] - end[0]) < 30
assert abs(last_move[1] - end[1]) < 30
def test_mouse_model_n_points_parameter() -> None:
from ai_mouse.mouse import MouseModel
m = MouseModel()
pts = m.generate((100, 200), (900, 400), n_points=32)
# 32 moves + 2 clicks
assert len(pts) == 34
def test_mouse_model_click_events_have_matching_coords() -> None:
from ai_mouse.mouse import MouseModel
m = MouseModel()
pts = m.generate((100, 200), (900, 400))
down, up = pts[-2], pts[-1]
assert down[0] == up[0]
assert down[1] == up[1]
assert up[2] > down[2]
# Within click_dist bounds 20..500
assert 20 <= up[2] - down[2] <= 500

View File

@@ -0,0 +1,43 @@
"""Tests for the public package-level API."""
from __future__ import annotations
def test_public_symbols_importable() -> None:
from ai_mouse import (
MouseModel,
ScrollModel,
generate,
generate_scroll,
errors,
)
assert MouseModel is not None
assert ScrollModel is not None
assert callable(generate)
assert callable(generate_scroll)
assert hasattr(errors, "ModelLoadError")
def test_generate_function_returns_list_of_tuples() -> None:
from ai_mouse import generate
pts = generate((100, 100), (300, 200))
assert isinstance(pts, list)
assert len(pts) > 0
assert isinstance(pts[0], tuple)
assert len(pts[0]) == 3
def test_generate_singleton_reused() -> None:
from ai_mouse import generate
from ai_mouse import _model_cache
_model_cache._get_mouse_model.cache_clear()
generate((0, 0), (100, 100))
info_after_first = _model_cache._get_mouse_model.cache_info()
generate((0, 0), (200, 200))
info_after_second = _model_cache._get_mouse_model.cache_info()
assert info_after_second.hits > info_after_first.hits
def test_version_present() -> None:
import ai_mouse
assert hasattr(ai_mouse, "__version__")
assert isinstance(ai_mouse.__version__, str)

View File

@@ -35,3 +35,19 @@ def test_scroll_invalid_path() -> None:
from ai_mouse.scroll import ScrollModel
with pytest.raises(ModelLoadError):
ScrollModel(model_path="/no/such/path")
def test_scroll_model_timestamps_monotonic() -> None:
from ai_mouse.scroll import ScrollModel
m = ScrollModel()
events = m.generate(0, 2000, mode="target")
times = [e["t"] for e in events]
for i in range(1, len(times)):
assert times[i] >= times[i - 1]
def test_scroll_model_deltaY_are_integers() -> None:
from ai_mouse.scroll import ScrollModel
m = ScrollModel()
events = m.generate(0, 2000, mode="target")
assert all(isinstance(e["deltaY"], int) for e in events)