"""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