from unittest.mock import MagicMock, patch from styles.blink import ( STATE_BLINKING, STATE_EVALUATING, STATE_WAIT_BEFORE_BLINK, STATE_WAIT_BEFORE_RETRY, STATE_WAIT_FOR_INPUT, BlinkStyle, ) def _make_blink(music_file="music.mp3"): """Return (style, set_led calls) for a BlinkStyle ready to update.""" calls = [] style = BlinkStyle(lambda on: calls.append(on), music_file) return style, calls def test_start_plays_music_and_led_off(): style, calls = _make_blink("track.mp3") with patch("styles.blink.pygame", MagicMock()) as pg: style.start() pg.mixer.music.load.assert_called_with("track.mp3") pg.mixer.music.set_volume.assert_called_with(1.0) pg.mixer.music.play.assert_called_with(-1) assert calls == [False] # LED off while music plays until a press starts the puzzle def test_press_starts_puzzle(): style, _ = _make_blink() assert style.update(100.0, True) is True assert style.state == STATE_WAIT_BEFORE_BLINK def test_blink_correct_sequence_stops(monkeypatch): style, _ = _make_blink() style.update(100.0, True) # press -> start puzzle assert style.state == STATE_WAIT_BEFORE_BLINK monkeypatch.setattr("styles.blink.random.randint", lambda a, b: 3) style.update(103.1, False) # 3s passed -> blinking, 3 blinks assert style.state == STATE_BLINKING assert style.target_blinks == 3 style.update(105.0, False) # run through the blink sequence assert style.state == STATE_WAIT_FOR_INPUT # Press exactly 3 times for t in (105.5, 106.0, 106.5): style.update(t, True) style.update(t + 0.05, False) assert style.user_presses == 3 style.update(109.7, False) # 3s idle -> evaluating assert style.state == STATE_EVALUATING assert style.update(109.8, False) is False # correct -> stop def test_blink_incorrect_retries(): style, _ = _make_blink() style.state = STATE_WAIT_FOR_INPUT style.target_blinks = 3 style.update(100.0, True) # one press style.update(100.1, False) style.last_interaction_time = 100.1 style.update(103.2, False) # -> evaluating keep_running = style.update(103.3, False) # 1 != 3 -> retry assert keep_running is True assert style.state == STATE_WAIT_BEFORE_RETRY def test_blinking_non_blocking(): style, calls = _make_blink() style.set_led = calls.append style.state = STATE_BLINKING style.target_blinks = 2 style.update(100.0, False) # start of blinking: LED on assert style.state == STATE_BLINKING assert calls[-1] is True style.update(100.3, False) # mid-blink toggles by elapsed time assert calls[-1] is False # 2 blinks * 2 phases * 0.3s = 1.2s style.update(101.2, False) assert style.state == STATE_WAIT_FOR_INPUT assert calls[-1] is False def test_blinking_advances_time(): style, _ = _make_blink() style.state = STATE_BLINKING style.target_blinks = 4 # 4 blinks * 2 phases * 0.3s = 2.4s style.update(100.0, False) style.update(102.4, False) assert style.state == STATE_WAIT_FOR_INPUT assert style.last_interaction_time == 102.4