File size: 3,872 Bytes
18d028b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b576b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Tests for the Gradio space helpers (without launching the UI)."""

from __future__ import annotations

import numpy as np

from signbridge.space import (
    _capture_sign,
    _clear,
    _format_history,
    _new_session,
    _speak,
)


class TestSession:
    def test_new_session_is_empty(self) -> None:
        s = _new_session()
        assert s.sign_history == []
        assert s.last_sentence == ""
        assert s.last_audio_path is None


class TestFormatHistory:
    def test_empty_returns_hint(self) -> None:
        out = _format_history([])
        assert "no signs" in out.lower()

    def test_renders_signs_as_code(self) -> None:
        out = _format_history(["A", "B", "hello"])
        assert "`A`" in out
        assert "`B`" in out
        assert "`hello`" in out


class TestCaptureSign:
    def test_no_frame(self) -> None:
        s = _new_session()
        msg, history, new_state = _capture_sign(None, s)
        assert "no webcam" in msg.lower()
        assert new_state.sign_history == []

    def test_no_provider_says_unrecognized(self) -> None:
        # Without API keys, recognizer returns ("", 0.0) → UI shows hint.
        s = _new_session()
        frame = np.zeros((32, 32, 3), dtype=np.uint8)
        msg, history, new_state = _capture_sign(frame, s)
        assert "couldn't recognise" in msg.lower() or "couldn't recognize" in msg.lower()
        assert new_state.sign_history == []


class TestSpeak:
    def test_empty_history(self) -> None:
        s = _new_session()
        sentence, audio, new_state = _speak(s)
        assert "no signs" in sentence.lower()
        assert audio is None

    def test_with_history_invokes_composer_and_tts(self) -> None:
        s = _new_session()
        s.sign_history = ["L", "U", "C", "A", "S"]
        sentence, audio, new_state = _speak(s)
        # Naive joiner path
        assert "Lucas" in sentence
        # TTS produces a silent-stub WAV path
        assert audio is not None and audio != ""


class TestClear:
    def test_resets_state(self) -> None:
        s = _new_session()
        s.sign_history = ["A", "B"]
        s.last_sentence = "AB."
        s.last_audio_path = "/tmp/foo.wav"

        latest, history, sentence, audio, new_state = _clear(s)
        assert latest == ""
        assert "no signs" in history.lower()
        assert sentence == ""
        assert audio is None
        assert new_state.sign_history == []
        assert new_state.last_sentence == ""
        assert new_state.last_audio_path is None


class TestSampleFramesFromVideo:
    def test_returns_n_frames(self, tmp_path):
        import cv2
        import numpy as np

        from signbridge.space import _sample_frames_from_video

        video_path = str(tmp_path / "synthetic.mp4")
        h, w, fps = 64, 64, 30
        fourcc = cv2.VideoWriter_fourcc(*"mp4v")
        writer = cv2.VideoWriter(video_path, fourcc, fps, (w, h))
        try:
            for i in range(10):
                writer.write(np.full((h, w, 3), 25 * i, dtype=np.uint8))
        finally:
            writer.release()

        frames = _sample_frames_from_video(video_path, n_frames=4)
        assert len(frames) == 4
        for fr in frames:
            assert fr.shape == (h, w, 3)
            assert fr.dtype == np.uint8

    def test_zero_frames_for_missing_file(self, tmp_path):
        from signbridge.space import _sample_frames_from_video

        non_existent = str(tmp_path / "nope.mp4")
        # Must NOT raise; must return [] so the UI can fall back gracefully.
        frames = _sample_frames_from_video(non_existent, n_frames=4)
        assert frames == []

    def test_none_path_returns_empty(self):
        from signbridge.space import _sample_frames_from_video

        assert _sample_frames_from_video(None, n_frames=4) == []
        assert _sample_frames_from_video("", n_frames=4) == []