File size: 5,211 Bytes
5e84ffc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d53fa1b
 
 
5e84ffc
 
 
 
d53fa1b
5e84ffc
 
 
 
 
 
 
 
d53fa1b
 
 
5e84ffc
 
 
 
 
d53fa1b
5e84ffc
 
 
 
 
 
 
 
 
 
 
 
 
 
d53fa1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
123
124
125
126
from unittest.mock import Mock, patch

import numpy as np
import pytest

from improvisation_lab.application.interval_practice.web_interval_app import \
    WebIntervalPracticeApp
from improvisation_lab.config import Config
from improvisation_lab.domain.music_theory import Notes
from improvisation_lab.infrastructure.audio import WebAudioProcessor
from improvisation_lab.presentation.interval_practice.web_interval_view import \
    WebIntervalPracticeView
from improvisation_lab.service import IntervalPracticeService


class TestWebIntervalPracticeApp:
    @pytest.fixture
    def init_module(self):
        """Initialize WebIntervalPracticeApp for testing."""
        config = Config()
        service = IntervalPracticeService(config)
        self.app = WebIntervalPracticeApp(service, config)
        self.app.ui = Mock(spec=WebIntervalPracticeView)
        self.app.audio_processor = Mock(spec=WebAudioProcessor)

    @pytest.mark.usefixtures("init_module")
    def test_launch(self):
        """Test launching the application."""
        with patch.object(self.app.ui, "launch", return_value=None) as mock_launch:
            self.app.launch()
            mock_launch.assert_called_once()

    @pytest.mark.usefixtures("init_module")
    def test_process_audio_callback(self):
        """Test processing audio callback."""
        audio_data = np.array([0.0])
        self.app.is_running = True
        self.app.phrases = [
            [Notes.C, Notes.C_SHARP, Notes.C],
            [Notes.D, Notes.D_SHARP, Notes.D],
        ]
        self.app.current_phrase_idx = 0
        self.app.current_note_idx = 1

        mock_result = Mock()
        mock_result.target_note = "C#"
        mock_result.current_base_note = "C#"
        mock_result.remaining_time = 0.0

        with patch.object(
            self.app.service, "process_audio", return_value=mock_result
        ) as mock_process_audio:
            self.app._process_audio_callback(audio_data)
            mock_process_audio.assert_called_once_with(audio_data, "C#")
            assert (
                self.app.text_manager.result_text
                == "Target: C# | Your note: C# | Remaining: 0.0s"
            )

    @pytest.mark.usefixtures("init_module")
    def test_handle_audio(self):
        """Test handling audio input."""
        audio_data = (48000, np.array([0.0]))
        self.app.is_running = True
        with patch.object(
            self.app.audio_processor, "process_audio", return_value=None
        ) as mock_process_audio:
            base_note, phrase_text, result_text, results_table = self.app.handle_audio(
                audio_data
            )
            mock_process_audio.assert_called_once_with(audio_data)
            assert base_note == self.app.base_note
            assert phrase_text == self.app.text_manager.phrase_text
            assert result_text == self.app.text_manager.result_text
            assert results_table == self.app.results_table

    @pytest.mark.usefixtures("init_module")
    def test_start(self):
        """Test starting the application."""
        self.app.audio_processor.is_recording = False
        with patch.object(
            self.app.audio_processor, "start_recording", return_value=None
        ) as mock_start_recording:
            base_note, phrase_text, result_text, results_table = self.app.start(
                "minor 2nd", "Up", 10, True, 1.5
            )
            mock_start_recording.assert_called_once()
            assert self.app.is_running
            assert base_note == self.app.base_note
            assert phrase_text == self.app.text_manager.phrase_text
            assert result_text == self.app.text_manager.result_text
            assert results_table == self.app.results_table

    @pytest.mark.usefixtures("init_module")
    def test_stop(self):
        """Test stopping the application."""
        self.app.audio_processor.is_recording = True
        with patch.object(
            self.app.audio_processor, "stop_recording", return_value=None
        ) as mock_stop_recording:
            base_note, phrase_text, result_text = self.app.stop()
            mock_stop_recording.assert_called_once()
            assert not self.app.is_running
            assert base_note == "-"
            assert phrase_text == self.app.text_manager.phrase_text
            assert result_text == self.app.text_manager.result_text

    @pytest.mark.usefixtures("init_module")
    @pytest.mark.parametrize(
        "detected_note, expected_result",
        [("C#", "⭕️"), ("D", "X")],  # Correct case  # Incorrect case
    )
    def test_update_results_table(self, detected_note, expected_result):
        """Test updating the results table with correct and incorrect results."""
        self.app.phrases = [[Notes.C, Notes.C_SHARP, Notes.C]]
        self.app.current_phrase_idx = 0
        self.app.current_note_idx = 1
        self.app.base_note = "C"
        self.app.text_manager.result_text = f"Target: C# | Your note: {detected_note}"

        self.app.is_auto_advance = True
        self.app.update_results_table()

        expected_entry = [1, "C", "C#", detected_note, expected_result]
        assert self.app.results_table[-1] == expected_entry