Initial commit
This commit is contained in:
212
tests/test_app.py
Normal file
212
tests/test_app.py
Normal file
@@ -0,0 +1,212 @@
|
||||
"""GUI smoke tests: the editor driven headless (offscreen Qt, synthetic game, synchronous jobs)."""
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("PySide6")
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
os.environ["ACKIT_NO_AUDIO"] = "1"
|
||||
|
||||
from PySide6.QtTest import QTest # noqa: E402
|
||||
from PySide6.QtWidgets import QApplication # noqa: E402
|
||||
|
||||
from ackit import audio, workspace as W # noqa: E402
|
||||
from ackit.inject import inject # noqa: E402
|
||||
import synth # noqa: E402
|
||||
|
||||
pytestmark = pytest.mark.gui
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def qapp():
|
||||
return QApplication.instance() or QApplication([])
|
||||
|
||||
|
||||
def files(root):
|
||||
out = {}
|
||||
for d, _, fs in os.walk(os.path.join(root, "BmGame")):
|
||||
for f in fs:
|
||||
p = os.path.join(d, f)
|
||||
out[p] = hashlib.sha1(open(p, "rb").read()).hexdigest()
|
||||
return out
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def win(qapp, synth_game, tmp_path):
|
||||
from app.mainwindow import MainWindow
|
||||
from app.workers import Runner
|
||||
root, originals = synth_game
|
||||
w = MainWindow(settings={"full_backup": True, "language": "INT"}, runner=Runner(sync=True), game_dir=root)
|
||||
w.confirm = lambda *a: True
|
||||
w.inform = lambda *a: None
|
||||
w.editor.confirm = lambda *a: True
|
||||
w.editor.inform = lambda title, text: (_ for _ in ()).throw(AssertionError("%s: %s" % (title, text)))
|
||||
QTest.qWait(50)
|
||||
assert w.ws is not None, w.banner_text.text()
|
||||
yield w, root, originals
|
||||
w.close()
|
||||
|
||||
|
||||
def type_search(w, text):
|
||||
w.search.setText(text)
|
||||
w._filter_text()
|
||||
|
||||
|
||||
def test_browse_search_filter(win):
|
||||
w, root, _ = win
|
||||
assert w.model.rowCount() == len(w.ws.catalog.sounds)
|
||||
type_search(w, "radio scanner 2")
|
||||
assert w.proxy.rowCount() == 1
|
||||
type_search(w, "")
|
||||
w.cmb_group.setCurrentText("Music")
|
||||
assert w.proxy.rowCount() == 3 and all(w.proxy.sound_at(w.proxy.index(r, 0)).group == "Music"
|
||||
for r in range(w.proxy.rowCount()))
|
||||
w.clear_filters()
|
||||
assert w.proxy.rowCount() == w.model.rowCount()
|
||||
|
||||
|
||||
def test_replace_apply_revert_byte_identical(win, tmp_path):
|
||||
w, root, originals = win
|
||||
before = files(root)
|
||||
type_search(w, "CHR_RadioScanner_2")
|
||||
assert w.select_name("CHR_RadioScanner_2")
|
||||
ed = w.editor
|
||||
assert ed.sound.name == "CHR_RadioScanner_2" and ed.orig is not None
|
||||
wav = str(tmp_path / "new.wav")
|
||||
x, sr = synth.tone(1.0, 44100, 990)
|
||||
audio.save_wav(wav, x, sr)
|
||||
assert ed.load_file(wav)
|
||||
ed.cb_match.setChecked(True)
|
||||
ed._update_new()
|
||||
assert ed.processed() is not None
|
||||
ed.apply()
|
||||
s = ed.sound
|
||||
assert w.ws.state(s) == W.MODIFIED and s.key in w.model.modified
|
||||
assert files(root) != before
|
||||
w.cb_mod.setChecked(True)
|
||||
assert w.proxy.rowCount() == 1
|
||||
ed.revert()
|
||||
assert files(root) == before
|
||||
assert w.ws.state(s) == W.ORIGINAL and not w.model.modified
|
||||
# undo of the original is impossible to lose: the store is empty again
|
||||
assert not w.ws.store.entries
|
||||
|
||||
|
||||
def test_record_simulated_input(win):
|
||||
w, root, _ = win
|
||||
w.select_name("CHR_RadioScanner_1")
|
||||
ed = w.editor
|
||||
x, sr = synth.tone(0.5, 48000, 500)
|
||||
ed.recorder.start_simulated(x, sr, realtime=True)
|
||||
assert ed.recorder.is_recording()
|
||||
QTest.qWait(800)
|
||||
assert ed.meter.value() > 0
|
||||
ed.toggle_record() # stop -> becomes the replacement source
|
||||
assert ed.src is not None and abs(len(ed.src[0]) - len(x)) <= sr // 50
|
||||
ed.trim_silence()
|
||||
ed.apply()
|
||||
assert w.ws.state(ed.sound) == W.MODIFIED
|
||||
w.revert_all()
|
||||
assert not w.ws.modified_keys()
|
||||
|
||||
|
||||
def test_notes_tags_favorites(win):
|
||||
w, root, _ = win
|
||||
w.select_name("CHR_RadioScanner_3")
|
||||
s = w.selected_sounds()[0]
|
||||
w.edit_notes([s], note="needs a new line", tags=["todo", "radio"])
|
||||
assert w.userdata.tags(s.key) == ["radio", "todo"]
|
||||
w.cmb_tag.setCurrentText("todo")
|
||||
assert w.proxy.rowCount() == 1
|
||||
w.cmb_tag.setCurrentIndex(0)
|
||||
w._toggle_fav([s])
|
||||
w.cb_fav.setChecked(True)
|
||||
assert w.proxy.rowCount() == 1
|
||||
type_search(w, "needs a new")
|
||||
assert w.proxy.rowCount() == 1
|
||||
|
||||
|
||||
def test_mod_export_import(win, tmp_path):
|
||||
w, root, _ = win
|
||||
w.select_name("CHR_RadioScanner_2")
|
||||
x, sr = synth.tone(1.0, 24000, 1200)
|
||||
w.editor.set_source(x, sr, "tone")
|
||||
w.editor.apply()
|
||||
key = w.editor.sound.key
|
||||
pkg = str(tmp_path / "pkg.acmod")
|
||||
w.export_mod_dialog(path=pkg, keys=[key], title="test")
|
||||
assert os.path.exists(pkg)
|
||||
w.revert_all()
|
||||
assert not w.ws.modified_keys()
|
||||
w.import_mod_dialog(path=pkg, confirm=False)
|
||||
assert w.ws.modified_keys() == [key]
|
||||
w.revert_all()
|
||||
|
||||
|
||||
def test_changed_underneath_banner(win):
|
||||
w, root, originals = win
|
||||
w.select_name("CHR_RadioScanner_1")
|
||||
x, sr = synth.tone(0.5, 24000, 1500)
|
||||
w.editor.set_source(x, sr, "tone")
|
||||
w.editor.apply()
|
||||
s = w.editor.sound
|
||||
# Steam "verify files" puts the original back behind our back
|
||||
inject(w.ws.wad_path(s), {s.target: originals[s.target]}, {s.target: w.ws.limit(s)})
|
||||
w.check_problems()
|
||||
assert not w.banner.isHidden() and "undone outside" in w.banner_text.text()
|
||||
w.reapply_lost()
|
||||
assert w.ws.state(s) == W.MODIFIED
|
||||
w.revert_all()
|
||||
|
||||
|
||||
class HeldRunner:
|
||||
"""Keeps jobs pending until release(), to see the loading state."""
|
||||
|
||||
def __init__(self, real):
|
||||
self.real, self.held = real, []
|
||||
|
||||
def start(self, *args):
|
||||
self.held.append(args)
|
||||
|
||||
def release(self):
|
||||
while self.held:
|
||||
self.real.start(*self.held.pop(0))
|
||||
|
||||
|
||||
def test_spinners_while_loading(win, tmp_path):
|
||||
w, root, _ = win
|
||||
held = w.runner = HeldRunner(w.runner)
|
||||
w.select_name("CHR_RadioScanner_1")
|
||||
assert w.editor.wf_orig.busy and w.editor.wf_orig._timer.isActive()
|
||||
held.release()
|
||||
assert not w.editor.wf_orig.busy and not w.editor.wf_orig._timer.isActive()
|
||||
wav = str(tmp_path / "a.wav")
|
||||
audio.save_wav(wav, *synth.tone(0.3, 22050, 700))
|
||||
w.editor.load_file(wav)
|
||||
assert w.editor.wf_new.busy
|
||||
held.release()
|
||||
assert not w.editor.wf_new.busy and w.editor.src is not None
|
||||
w._run_change("Working…", lambda job: None, "done")
|
||||
assert not w.spinner.isHidden()
|
||||
held.release()
|
||||
assert w.spinner.isHidden()
|
||||
w.runner = held.real
|
||||
|
||||
|
||||
def test_background_toggle_and_filters_look_alike(win):
|
||||
w, root, _ = win
|
||||
w.sidebar.configure({"background_enabled": False})
|
||||
assert w.sidebar.image is None
|
||||
# every filter selector is the same kind of plain drop-down
|
||||
assert not any(c.isEditable() for c in (w.cmb_group, w.cmb_cat, w.cmb_char, w.cmb_arch, w.cmb_tag))
|
||||
|
||||
|
||||
def test_editor_panel_fits_its_contents(win):
|
||||
w, root, _ = win
|
||||
w.resize(1400, 900)
|
||||
w.show()
|
||||
QTest.qWait(100)
|
||||
need = w.editor.sizeHint().height()
|
||||
assert w.scroll.viewport().height() >= need - 2, (w.scroll.viewport().height(), need)
|
||||
assert w.vsplit.sizes()[0] >= 160
|
||||
Reference in New Issue
Block a user