Split the tag handling in PlayerApp into two parts: The low level handling of changed/removed tags from the Nfc reader, including the timeout processing is moved into the nested TagStateMachine class. The main PlayerApp has two new (internal) callbacks "onNewTag" and "onTagRemoved" which are called when a new tag is presented or a tag was actually removed. This will hopefully make the implementation of #28 "Configurable Tag Handling" cleaner. Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
164 lines
4.8 KiB
Python
164 lines
4.8 KiB
Python
# SPDX-License-Identifier: MIT
|
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
|
|
|
import app
|
|
import builtins
|
|
import pytest # type: ignore
|
|
import time
|
|
import utils
|
|
|
|
|
|
@pytest.fixture
|
|
def micropythonify():
|
|
def time_ticks_ms():
|
|
return time.time_ns() // 1000000
|
|
time.ticks_ms = time_ticks_ms
|
|
yield
|
|
del time.ticks_ms
|
|
|
|
|
|
class FakeFile:
|
|
def __init__(self, filename, mode):
|
|
self.filename = filename
|
|
self.mode = mode
|
|
self.closed = False
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
|
|
class FakeMp3Player:
|
|
def __init__(self):
|
|
self.volume: int | None = None
|
|
self.track: FakeFile | None = None
|
|
|
|
def set_volume(self, vol: int):
|
|
self.volume = vol
|
|
|
|
def play(self, track: FakeFile, offset: int):
|
|
self.track = track
|
|
|
|
|
|
class FakeTimerManager:
|
|
def __init__(self): pass
|
|
def cancel(self, timer): pass
|
|
|
|
|
|
class FakeNfcReader:
|
|
tag_callback = None
|
|
|
|
def __init__(self, tag_callback=None):
|
|
FakeNfcReader.tag_callback = tag_callback
|
|
|
|
|
|
class FakeButtons:
|
|
def __init__(self): pass
|
|
|
|
|
|
class FakePlaylistDb:
|
|
class FakePlaylist:
|
|
def __init__(self, parent, pos=0):
|
|
self.parent = parent
|
|
self.pos = 0
|
|
|
|
def getCurrentPath(self):
|
|
return self.parent.tracklist[self.pos]
|
|
|
|
def getNextPath(self):
|
|
self.pos += 1
|
|
if self.pos >= len(self.parent.tracklist):
|
|
return None
|
|
return self.parent.tracklist[self.pos]
|
|
|
|
def getPlaybackOffset(self):
|
|
return 0
|
|
|
|
def __init__(self, tracklist=[b'test/path.mp3']):
|
|
self.tracklist = tracklist
|
|
|
|
def getPlaylistForTag(self, tag: bytes):
|
|
return self.FakePlaylist(self)
|
|
|
|
|
|
def fake_open(filename, mode):
|
|
return FakeFile(filename, mode)
|
|
|
|
|
|
@pytest.fixture
|
|
def faketimermanager(monkeypatch):
|
|
monkeypatch.setattr(utils.timer.TimerManager, '_instance', FakeTimerManager())
|
|
|
|
|
|
def test_construct_app(micropythonify, faketimermanager):
|
|
fake_mp3 = FakeMp3Player()
|
|
deps = app.Dependencies(mp3player=lambda _: fake_mp3,
|
|
nfcreader=lambda _: FakeNfcReader(),
|
|
buttons=lambda _: FakeButtons(),
|
|
playlistdb=lambda _: FakePlaylistDb())
|
|
_ = app.PlayerApp(deps)
|
|
assert fake_mp3.volume is not None
|
|
|
|
|
|
def test_load_playlist_on_tag(micropythonify, faketimermanager, monkeypatch):
|
|
fake_db = FakePlaylistDb()
|
|
fake_mp3 = FakeMp3Player()
|
|
deps = app.Dependencies(mp3player=lambda _: fake_mp3,
|
|
nfcreader=lambda x: FakeNfcReader(x),
|
|
buttons=lambda _: FakeButtons(),
|
|
playlistdb=lambda _: fake_db)
|
|
dut = app.PlayerApp(deps)
|
|
with monkeypatch.context() as m:
|
|
m.setattr(builtins, 'open', fake_open)
|
|
FakeNfcReader.tag_callback.onTagChange([23, 42, 1, 2, 3])
|
|
assert fake_mp3.track is not None
|
|
assert fake_mp3.track.filename == b'test/path.mp3'
|
|
assert "r" in fake_mp3.track.mode
|
|
assert "b" in fake_mp3.track.mode
|
|
|
|
|
|
def test_playlist_seq(micropythonify, faketimermanager, monkeypatch):
|
|
fake_db = FakePlaylistDb([b'track1.mp3', b'track2.mp3', b'track3.mp3'])
|
|
fake_mp3 = FakeMp3Player()
|
|
deps = app.Dependencies(mp3player=lambda _: fake_mp3,
|
|
nfcreader=lambda x: FakeNfcReader(x),
|
|
buttons=lambda _: FakeButtons(),
|
|
playlistdb=lambda _: fake_db)
|
|
dut = app.PlayerApp(deps)
|
|
with monkeypatch.context() as m:
|
|
m.setattr(builtins, 'open', fake_open)
|
|
FakeNfcReader.tag_callback.onTagChange([23, 42, 1, 2, 3])
|
|
assert fake_mp3.track is not None
|
|
assert fake_mp3.track.filename == b'track1.mp3'
|
|
|
|
fake_mp3.track = None
|
|
dut.onPlaybackDone()
|
|
assert fake_mp3.track is not None
|
|
assert fake_mp3.track.filename == b'track2.mp3'
|
|
|
|
fake_mp3.track = None
|
|
dut.onPlaybackDone()
|
|
assert fake_mp3.track is not None
|
|
assert fake_mp3.track.filename == b'track3.mp3'
|
|
|
|
fake_mp3.track = None
|
|
dut.onPlaybackDone()
|
|
assert fake_mp3.track is None
|
|
|
|
|
|
def test_playlist_unknown_tag(micropythonify, faketimermanager, monkeypatch):
|
|
class FakeNoPlaylistDb:
|
|
def getPlaylistForTag(self, tag):
|
|
return None
|
|
|
|
fake_db = FakeNoPlaylistDb()
|
|
fake_mp3 = FakeMp3Player()
|
|
deps = app.Dependencies(mp3player=lambda _: fake_mp3,
|
|
nfcreader=lambda x: FakeNfcReader(x),
|
|
buttons=lambda _: FakeButtons(),
|
|
playlistdb=lambda _: fake_db)
|
|
dut = app.PlayerApp(deps)
|
|
with monkeypatch.context() as m:
|
|
m.setattr(builtins, 'open', fake_open)
|
|
FakeNfcReader.tag_callback.onTagChange([23, 42, 1, 2, 3])
|
|
assert fake_mp3.track is None
|