from unittest.mock import MagicMock 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, sound_mock) for a BlinkStyle ready to start.""" sound = MagicMock() style = BlinkStyle(lambda on: None, sound, music_file) return style, sound def test_start_plays_music_and_led_off(): calls = [] sound = MagicMock() style = BlinkStyle(lambda on: calls.append(on), sound, "track.mp3") style.start() sound.play_music.assert_called_once_with("track.mp3") assert calls == [False] # LED off while music plays until a press starts the puzzle assert style.state == 0 # STATE_RINGING until a press 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(): calls = [] style, _ = _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