From 053272cbf8ffaa8a1a6637dbb9a6e182cf0234f8 Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Wed, 28 Jan 2026 23:41:26 -0500 Subject: [PATCH 1/6] search more than one filename for compName changing moduleIndex of Classic Visualizer from 0 to 1 exposed this bug to me --- src/avp/command.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/avp/command.py b/src/avp/command.py index 7a77848..870391b 100644 --- a/src/avp/command.py +++ b/src/avp/command.py @@ -256,7 +256,6 @@ def parseCompName(self, name): for i, compFileName in enumerate(compFileNames): if name.lower() in compFileName: return self.core.compNames[i] - return return None From dc48c5a8e8ded53595d03d8cb10254c851c91ee7 Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Wed, 28 Jan 2026 23:41:54 -0500 Subject: [PATCH 2/6] remove extra QVBoxLayout --- src/avp/components/spectrum.ui | 1686 ++++++++++++++++---------------- 1 file changed, 838 insertions(+), 848 deletions(-) diff --git a/src/avp/components/spectrum.ui b/src/avp/components/spectrum.ui index c6a8a15..3bde075 100644 --- a/src/avp/components/spectrum.ui +++ b/src/avp/components/spectrum.ui @@ -27,909 +27,899 @@ - - - 4 - + - + + + + 0 + 0 + + + + Type + + - - - - - - 0 - 0 - - - - Type - - - + - - - - Spectrum - - - - - Histogram - - - - - Vector Scope - - - - - Musical Scale - - - - - Phase - - - + + Spectrum + - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - + + Histogram + - - - - 0 - 0 - - - - X - - + + Vector Scope + - - - - 0 - 0 - - - - - 80 - 16777215 - - - - -10000 - - - 10000 - - + + Musical Scale + - - - - 0 - 0 - - - - Y - - + + Phase + - - - - - 0 - 0 - - - - - 80 - 16777215 - - - - - 0 - 0 - - - - -10000 - - - 10000 - - - 0 - - - - + - - - - - Compress - - - - - - - Mono - - - - - - - Mirror - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Hue - - - 4 - - - - - - - ° - - - 359 - - - - - - - - 0 - 0 - - - - Scale - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - QAbstractSpinBox::UpDownArrows - - - % - - - 10 - - - 400 - - - 100 - - - - + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + - + - + 0 0 - - false + + X - - QFrame::NoFrame + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + -10000 + + + 10000 + + + + + + + + 0 + 0 + - - QFrame::Plain + + Y - + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + -10000 + + + 10000 + + 0 - - - - - 0 - 0 - 561 - 66 - - - + + + + + + + + + + Compress + + + + + + + Mono + + + + + + + Mirror + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Hue + + + 4 + + + + + + + ° + + + 359 + + + + + + + + 0 + 0 + + + + Scale + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + QAbstractSpinBox::ButtonSymbols::UpDownArrows + + + % + + + 10 + + + 400 + + + 100 + + + + + + + + + + 0 + 0 + + + + false + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + 0 + + + + + + 0 + 0 + 561 + 76 + + + + + QLayout::SizeConstraint::SetMaximumSize + + + 0 + + + - QLayout::SetMaximumSize - - - 0 + QLayout::SizeConstraint::SetDefaultConstraint - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - - 31 - 0 - - - - Window - - - 4 - - - - - - - - hann - - - - - gauss - - - - - tukey - - - - - dolph - - - - - cauchy - - - - - parzen - - - - - poisson - - - - - rect - - - - - bartlett - - - - - hanning - - - - - hamming - - - - - blackman - - - - - welch - - - - - flattop - - - - - bharris - - - - - bnuttall - - - - - lanczos - - - - - - - - - 0 - 0 - - - - Amplitude - - - 4 - - - - - - - - Square root - - - - - Cubic root - - - - - 4thrt - - - - - 5thrt - - - - - Linear - - - - - Logarithmic - - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 10 - 20 - - - - - + + + + 0 + 0 + + + + + 31 + 0 + + + + Window + + + 4 + + - - - - - - 0 - 0 - - - - Color - - - 4 - - - - - - - - Channel - - - - - Intensity - - - - - Rainbow - - - - - Moreland - - - - - Nebulae - - - - - Fire - - - - - Fiery - - - - - Fruit - - - - - Cool - - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 10 - 20 - - - - - + + + + hann + + + + + gauss + + + + + tukey + + + + + dolph + + + + + cauchy + + + + + parzen + + + + + poisson + + + + + rect + + + + + bartlett + + + + + hanning + + + + + hamming + + + + + blackman + + + + + welch + + + + + flattop + + + + + bharris + + + + + bnuttall + + + + + lanczos + + + + + + + + + 0 + 0 + + + + Amplitude + + + 4 + + + + + + + + Square root + + + + + Cubic root + + + + + 4thrt + + + + + 5thrt + + + + + Linear + + + + + Logarithmic + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::MinimumExpanding + + + + 10 + 20 + + + - - - - - - - -1 - -1 - 561 - 31 - - - + + + - - - - - - 0 - 0 - - - - Display Scale - - - 4 - - - - - - - - Logarithmic - - - - - Square root - - - - - Cubic root - - - - - Linear - - - - - Reverse Log - - - - - - - - - 0 - 0 - - - - Amplitude - - - 4 - - - - - - - - Logarithmic - - - - - Linear - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 40 - 20 - - - - - + + + + 0 + 0 + + + + Color + + + 4 + + + + + + + + Channel + + + + + Intensity + + + + + Rainbow + + + + + Moreland + + + + + Nebulae + + + + + Fire + + + + + Fiery + + + + + Fruit + + + + + Cool + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::MinimumExpanding + + + + 10 + 20 + + + - - - - - - - -1 - -1 - 585 - 64 - - - + + + + + + + + + -1 + -1 + 561 + 36 + + + + + - - - - - Mode - - - - - - - - lissajous - - - - - lissajous_xy - - - - - polar - - - - - - - - - 0 - 0 - - - - Amplitude - - - 4 - - - - - - - - Linear - - - - - Square root - - - - - Cubic root - - - - - Logarithmic - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 0 + 0 + + + + Display Scale + + + 4 + + - - - - - - 0 - 0 - - - - Zoom - - - 4 - - - - - - - 1 - - - 10 - - - - - - - Line - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + Logarithmic + + + + + Square root + + + + + Cubic root + + + + + Linear + + + + + Reverse Log + + + + + + + + + 0 + 0 + + + + Amplitude + + + 4 + + + + + + + + Logarithmic + + + + + Linear + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Minimum + + + + 40 + 20 + + + + + + + + + + + + + + -1 + -1 + 585 + 76 + + + + + + + + + Mode + + + + + + + + lissajous + + + + + lissajous_xy + + + + + polar + + + + + + + + + 0 + 0 + + + + Amplitude + + + 4 + + + + + + + + Linear + + + + + Square root + + + + + Cubic root + + + + + Logarithmic + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + - - - - - - - 0 - 0 - 561 - 31 - - - + + + + + + + + 0 + 0 + + + + Zoom + + + 4 + + + + + + + 1 + + + 10 + + + + + + + Line + + + - - - - - - 0 - 0 - - - - Timeclamp - - - 4 - - - - - - - s - - - 3 - - - 0.002000000000000 - - - 1.000000000000000 - - - 0.010000000000000 - - - 0.017000000000000 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + - - - - - - - 0 - 0 - 551 - 31 - - - + + + + + + + + + 0 + 0 + 561 + 36 + + + + + - + + + + 0 + 0 + + + + Timeclamp + + + 4 + + + + + + + s + + + 3 + + + 0.002000000000000 + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.017000000000000 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + - - + + - - + + + + + + 0 + 0 + 551 + 31 + + + + + + + + + + - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed From d54ea29f1f0fa28c8b952d74855d2f80586e6bb4 Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Wed, 28 Jan 2026 23:42:45 -0500 Subject: [PATCH 3/6] insert default components using name instead of index --- src/avp/gui/mainwindow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/avp/gui/mainwindow.py b/src/avp/gui/mainwindow.py index 5a051fd..72fa4f6 100644 --- a/src/avp/gui/mainwindow.py +++ b/src/avp/gui/mainwindow.py @@ -414,8 +414,10 @@ def changedField(): # Add initial components if none are in the list if not self.core.selectedComponents: - self.core.insertComponent(0, 0, self) - self.core.insertComponent(1, 1, self) + self.core.insertComponent( + 0, self.core.moduleIndexFor("Classic Visualizer"), self + ) + self.core.insertComponent(1, self.core.moduleIndexFor("Color"), self) # set colors to white and black to match classic appearance of program self.core.selectedComponents[0].page.lineEdit_visColor.setText( "255,255,255" From 8be6c4c05fef466ceb273bed12b738504174bc71 Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Thu, 29 Jan 2026 12:48:48 -0500 Subject: [PATCH 4/6] bump version 2.2.1 to 2.2.2 --- pyproject.toml | 2 +- src/avp/__init__.py | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2f0647c..ea27839 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "uv_build" name = "audio-visualizer-python" description = "Create audio visualization videos from a GUI or commandline" readme = "README.md" -version = "2.2.1" +version = "2.2.2" requires-python = ">= 3.12" license = "MIT" classifiers=[ diff --git a/src/avp/__init__.py b/src/avp/__init__.py index a88bf10..9de9f93 100644 --- a/src/avp/__init__.py +++ b/src/avp/__init__.py @@ -3,7 +3,7 @@ import logging -__version__ = "2.2.1" +__version__ = "2.2.2" class Logger(logging.getLoggerClass()): diff --git a/uv.lock b/uv.lock index edc9406..a23039a 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.12" [[package]] name = "audio-visualizer-python" -version = "2.2.1" +version = "2.2.2" source = { editable = "." } dependencies = [ { name = "numpy" }, From 236bb3f38cd983713d3683def7c8ce594ac9bfc5 Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Thu, 29 Jan 2026 12:58:35 -0500 Subject: [PATCH 5/6] tests do not use `.config` for settings Core.storeSettings() is no longer called as a side effect any time avp.core is imported. Thus the tests use a new `initCore` method and the normal user path now relies on entering via `cli.py`. This means certain toolkit functions (e.g., ones using `FFMPEG_BIN`) no longer work if imported from a different python script, unless they call Core.storeSettings() themselves to initialize the settings.ini file --- .gitignore | 3 ++ src/avp/cli.py | 5 ++++ src/avp/core.py | 14 +++++----- tests/__init__.py | 40 ++++++++++++++++++++++++--- tests/data/{ => inputfiles}/test.jpg | Bin tests/data/{ => inputfiles}/test.ogg | Bin tests/data/{ => inputfiles}/test.png | Bin tests/data/projects/testproject.avp | 17 ++++++++++++ tests/test_commandline_export.py | 10 ++----- tests/test_commandline_parser.py | 26 ++++++----------- tests/test_comp_color.py | 5 ++-- tests/test_comp_image.py | 11 ++++---- tests/test_comp_life.py | 5 ++-- tests/test_comp_original.py | 5 ++-- tests/test_comp_spectrum.py | 5 ++-- tests/test_comp_text.py | 5 ++-- tests/test_comp_waveform.py | 5 ++-- tests/test_mainwindow_projects.py | 40 +++++++++++++++++++++++++++ tests/test_mainwindow_undostack.py | 15 ++-------- tests/test_toolkit_common.py | 10 +++++-- tests/test_toolkit_ffmpeg.py | 4 ++- 21 files changed, 150 insertions(+), 75 deletions(-) rename tests/data/{ => inputfiles}/test.jpg (100%) rename tests/data/{ => inputfiles}/test.ogg (100%) rename tests/data/{ => inputfiles}/test.png (100%) create mode 100644 tests/data/projects/testproject.avp create mode 100644 tests/test_mainwindow_projects.py diff --git a/.gitignore b/.gitignore index 9800ef6..278948f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ prof/ .venv/ .env/ .vscode/ +tests/data/log/ +tests/data/settings.ini +tests/data/autosave.avp *.mkv *.mp4 *.wav diff --git a/src/avp/cli.py b/src/avp/cli.py index 02ceee6..0176f76 100644 --- a/src/avp/cli.py +++ b/src/avp/cli.py @@ -5,6 +5,11 @@ import string +from .core import Core + +# Core class must store settings as class variables +# before we can use many things in AVP +Core.storeSettings() log = logging.getLogger("AVP.Main") diff --git a/src/avp/core.py b/src/avp/core.py index 099b0b4..1e9a9c3 100644 --- a/src/avp/core.py +++ b/src/avp/core.py @@ -420,14 +420,18 @@ def reset(self): Core.canceled = False @classmethod - def storeSettings(cls): + def storeSettings(cls, dataDir=None): """Store settings/paths to directories as class variables""" from .__init__ import wd from .toolkit.ffmpeg import findFfmpeg cls.wd = wd - dataDir = QtCore.QStandardPaths.writableLocation( - QtCore.QStandardPaths.StandardLocation.AppConfigLocation + dataDir = ( + QtCore.QStandardPaths.writableLocation( + QtCore.QStandardPaths.StandardLocation.AppConfigLocation + ) + if dataDir is None + else dataDir ) # Windows: C:/Users//AppData/Local/audio-visualizer # macOS: ~/Library/Preferences/audio-visualizer @@ -589,7 +593,3 @@ def makeLogger(deleteOldLogs=False, fileLogLvl=None): libLog.addHandler(libLogFile) # lowest level must be explicitly set on the root Logger libLog.setLevel(0) - - -# always store settings in class variables even if a Core object is not created -Core.storeSettings() diff --git a/tests/__init__.py b/tests/__init__.py index b615681..df08c7c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,8 +1,9 @@ import os import numpy -# core always has to be imported first -import avp.core +from avp.core import Core +from avp.command import Command +from avp.gui.mainwindow import MainWindow from avp.toolkit.ffmpeg import readAudioFile from pytest import fixture @@ -10,16 +11,47 @@ @fixture def audioData(): """Fixture that gives a tuple of (completeAudioArray, duration)""" - soundFile = getTestDataPath("test.ogg") + # Core.storeSettings() needed to store ffmpeg bin location + initCore() + soundFile = getTestDataPath("inputfiles/test.ogg") yield readAudioFile(soundFile, MockVideoWorker()) -def getTestDataPath(filename): +@fixture +def command(qtbot): + initCore() + command = Command() + command.quit = lambda _: None + yield command + + +@fixture +def window(qtbot): + initCore() + window = MainWindow(None, None) + window.clear() + qtbot.addWidget(window) + window.settings.setValue("outputWidth", 1920) + window.settings.setValue("outputHeight", 1080) + yield window + + +def getTestDataPath(filename=""): """Get path to a file in the ./data directory""" tests_dir = os.path.dirname(os.path.abspath(__file__)) return os.path.join(tests_dir, "data", filename) +def initCore(): + testDataDir = getTestDataPath() + unwanted = ["autosave.avp", "settings.ini"] + for file in unwanted: + filename = os.path.join(testDataDir, "autosave.avp") + if os.path.exists(filename): + os.remove(filename) + Core.storeSettings(testDataDir) + + class MockSignal: """Pretends to be a pyqtSignal""" diff --git a/tests/data/test.jpg b/tests/data/inputfiles/test.jpg similarity index 100% rename from tests/data/test.jpg rename to tests/data/inputfiles/test.jpg diff --git a/tests/data/test.ogg b/tests/data/inputfiles/test.ogg similarity index 100% rename from tests/data/test.ogg rename to tests/data/inputfiles/test.ogg diff --git a/tests/data/test.png b/tests/data/inputfiles/test.png similarity index 100% rename from tests/data/test.png rename to tests/data/inputfiles/test.png diff --git a/tests/data/projects/testproject.avp b/tests/data/projects/testproject.avp new file mode 100644 index 0000000..fd6b6eb --- /dev/null +++ b/tests/data/projects/testproject.avp @@ -0,0 +1,17 @@ +[Components] +Classic Visualizer +1 +OrderedDict({'bars': 63, 'layout': 0, 'preset': None, 'scale': 20, 'smooth': 0, 'visColor': (255, 255, 255), 'y': 0.0}) +Color +1 +OrderedDict({'LG_end': 0.0, 'LG_start': 0.0, 'RG_centre': 0.0015625, 'RG_end': 0.0, 'RG_start': 0.0, 'color1': (0, 0, 0), 'color2': (133, 133, 133), 'fillType': 0, 'height': 1.0, 'preset': None, 'spread': 0, 'stretch': False, 'trans': False, 'width': 1.0, 'x': 0.0, 'y': 0.0}) + +[Settings] +componentDir=tests/data/inputfiles +inputDir=tests/data/inputfiles +presetDir=tests/data/presets +projectDir=tests/data/projects + +[WindowFields] +lineEdit_audioFile=tests/data/test.ogg +lineEdit_outputFile= diff --git a/tests/test_commandline_export.py b/tests/test_commandline_export.py index 05ead77..6d7f068 100644 --- a/tests/test_commandline_export.py +++ b/tests/test_commandline_export.py @@ -1,14 +1,13 @@ import sys import os import tempfile -from avp.command import Command -from . import getTestDataPath +from . import command, getTestDataPath, MockSignal from pytestqt import qtbot -def test_commandline_classic_export(qtbot): +def test_commandline_classic_export(qtbot, command): """Run Qt event loop and create a video in the system /tmp or /temp""" - soundFile = getTestDataPath("test.ogg") + soundFile = getTestDataPath("inputfiles/test.ogg") outputDir = tempfile.mkdtemp(prefix="avp-test-") outputFilename = os.path.join(outputDir, "output.mp4") sys.argv = [ @@ -21,9 +20,6 @@ def test_commandline_classic_export(qtbot): "-o", outputFilename, ] - - command = Command() - command.quit = lambda _: None command.parseArgs() # Command object now has a video_thread Worker which is exporting the video diff --git a/tests/test_commandline_parser.py b/tests/test_commandline_parser.py index 77836ce..186c602 100644 --- a/tests/test_commandline_parser.py +++ b/tests/test_commandline_parser.py @@ -1,39 +1,34 @@ import sys import pytest -from avp.command import Command from pytestqt import qtbot +from . import command -def test_commandline_help(qtbot): - command = Command() +def test_commandline_help(qtbot, command): sys.argv = ["", "--help"] with pytest.raises(SystemExit): command.parseArgs() -def test_commandline_help_if_bad_args(qtbot): - command = Command() +def test_commandline_help_if_bad_args(qtbot, command): sys.argv = ["", "--junk"] with pytest.raises(SystemExit): command.parseArgs() -def test_commandline_launches_gui_if_verbose(qtbot): - command = Command() +def test_commandline_launches_gui_if_verbose(qtbot, command): sys.argv = ["", "--verbose"] mode = command.parseArgs() assert mode == "GUI" -def test_commandline_launches_gui_if_verbose_with_project(qtbot): - command = Command() +def test_commandline_launches_gui_if_verbose_with_project(qtbot, command): sys.argv = ["", "test", "--verbose"] mode = command.parseArgs() assert mode == "GUI" -def test_commandline_tries_to_export(qtbot): - command = Command() +def test_commandline_tries_to_export(qtbot, command): didCallFunction = False def captureFunction(*args): @@ -46,16 +41,13 @@ def captureFunction(*args): assert didCallFunction -def test_commandline_parses_classic_by_alias(qtbot): - command = Command() +def test_commandline_parses_classic_by_alias(qtbot, command): assert command.parseCompName("original") == "Classic Visualizer" -def test_commandline_parses_conway_by_short_name(qtbot): - command = Command() +def test_commandline_parses_conway_by_short_name(qtbot, command): assert command.parseCompName("conway") == "Conway's Game of Life" -def test_commandline_parses_image_by_name(qtbot): - command = Command() +def test_commandline_parses_image_by_name(qtbot, command): assert command.parseCompName("image") == "Image" diff --git a/tests/test_comp_color.py b/tests/test_comp_color.py index 6b82e4c..48b07ff 100644 --- a/tests/test_comp_color.py +++ b/tests/test_comp_color.py @@ -1,13 +1,12 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import imageDataSum +from . import imageDataSum, command @fixture -def coreWithColorComp(qtbot): +def coreWithColorComp(qtbot, command): """Fixture providing a Command object with Color component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent(0, command.core.moduleIndexFor("Color"), command) diff --git a/tests/test_comp_image.py b/tests/test_comp_image.py index a4f05e1..c580d5a 100644 --- a/tests/test_comp_image.py +++ b/tests/test_comp_image.py @@ -1,16 +1,15 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import audioData, MockSignal, imageDataSum, getTestDataPath +from . import audioData, command, MockSignal, imageDataSum, getTestDataPath sampleSize = 1470 # 44100 / 30 = 1470 @fixture -def coreWithImageComp(qtbot): +def coreWithImageComp(qtbot, command): """Fixture providing a Command object with Image component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent(0, command.core.moduleIndexFor("Image"), command) @@ -20,7 +19,7 @@ def coreWithImageComp(qtbot): def test_comp_image_set_path(coreWithImageComp): "Set imagePath of Image component" comp = coreWithImageComp.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") image = comp.previewRender() assert imageDataSum(image) == 463711601 @@ -28,7 +27,7 @@ def test_comp_image_set_path(coreWithImageComp): def test_comp_image_scale_50_1080p(coreWithImageComp): """Image component stretches image to 50% at 1080p""" comp = coreWithImageComp.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") image = comp.previewRender() sum = imageDataSum(image) comp.page.spinBox_scale.setValue(50) @@ -38,7 +37,7 @@ def test_comp_image_scale_50_1080p(coreWithImageComp): def test_comp_image_scale_50_720p(coreWithImageComp): """Image component stretches image to 50% at 720p""" comp = coreWithImageComp.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") comp.page.spinBox_scale.setValue(50) image = comp.previewRender() sum = imageDataSum(image) diff --git a/tests/test_comp_life.py b/tests/test_comp_life.py index ad78e52..3c02117 100644 --- a/tests/test_comp_life.py +++ b/tests/test_comp_life.py @@ -1,13 +1,12 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import imageDataSum +from . import imageDataSum, command @fixture -def coreWithLifeComp(qtbot): +def coreWithLifeComp(qtbot, command): """Fixture providing a Command object with Waveform component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent( diff --git a/tests/test_comp_original.py b/tests/test_comp_original.py index 6264644..8cd00a4 100644 --- a/tests/test_comp_original.py +++ b/tests/test_comp_original.py @@ -2,16 +2,15 @@ from avp.toolkit.visualizer import transformData from pytestqt import qtbot from pytest import fixture -from . import audioData, MockSignal, imageDataSum +from . import audioData, command, MockSignal, imageDataSum sampleSize = 1470 # 44100 / 30 = 1470 @fixture -def coreWithClassicComp(qtbot): +def coreWithClassicComp(qtbot, command): """Fixture providing a Command object with Classic Visualizer component added""" - command = Command() command.core.insertComponent( 0, command.core.moduleIndexFor("Classic Visualizer"), command ) diff --git a/tests/test_comp_spectrum.py b/tests/test_comp_spectrum.py index 44fb257..870185c 100644 --- a/tests/test_comp_spectrum.py +++ b/tests/test_comp_spectrum.py @@ -1,13 +1,12 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import imageDataSum +from . import imageDataSum, command @fixture -def coreWithSpectrumComp(qtbot): +def coreWithSpectrumComp(qtbot, command): """Fixture providing a Command object with Spectrum component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent(0, command.core.moduleIndexFor("Spectrum"), command) diff --git a/tests/test_comp_text.py b/tests/test_comp_text.py index e389ff9..20b202d 100644 --- a/tests/test_comp_text.py +++ b/tests/test_comp_text.py @@ -2,13 +2,12 @@ from PyQt6.QtGui import QFont from pytestqt import qtbot from pytest import fixture, mark -from . import audioData, MockSignal, imageDataSum +from . import audioData, command, MockSignal, imageDataSum @fixture -def coreWithTextComp(qtbot): +def coreWithTextComp(qtbot, command): """Fixture providing a Command object with Title Text component added""" - command = Command() command.core.insertComponent(0, command.core.moduleIndexFor("Title Text"), command) yield command.core diff --git a/tests/test_comp_waveform.py b/tests/test_comp_waveform.py index a71040b..eb5800d 100644 --- a/tests/test_comp_waveform.py +++ b/tests/test_comp_waveform.py @@ -1,12 +1,11 @@ -from avp.command import Command from pytestqt import qtbot from pytest import fixture +from . import command @fixture -def coreWithWaveformComp(qtbot): +def coreWithWaveformComp(qtbot, command): """Fixture providing a Command object with Waveform component added""" - command = Command() command.core.insertComponent(0, command.core.moduleIndexFor("Waveform"), command) yield command.core diff --git a/tests/test_mainwindow_projects.py b/tests/test_mainwindow_projects.py new file mode 100644 index 0000000..8ad491a --- /dev/null +++ b/tests/test_mainwindow_projects.py @@ -0,0 +1,40 @@ +from pytest import fixture +from pytestqt import qtbot +from . import getTestDataPath, window + + +def test_mainwindow_clear(qtbot, window): + """MainWindow.clear() gives us a clean slate""" + assert len(window.core.selectedComponents) == 0 + + +def test_mainwindow_openProject(qtbot, window): + """Open testproject.avp using MainWindow.openProject()""" + window.openProject(getTestDataPath("projects/testproject.avp"), prompt=False) + assert len(window.core.selectedComponents) == 2 + + +def test_mainwindow_newProject_without_unsaved_changes(qtbot, window): + """Starting new project without unsaved changes""" + didCallFunction = False + + def captureFunction(*args, **kwargs): + nonlocal didCallFunction + didCallFunction = True + + window.createNewProject(prompt=False) + assert not didCallFunction + assert len(window.core.selectedComponents) == 0 + + +def test_mainwindow_newProject_with_unsaved_changes(qtbot, window): + """Starting new project with unsaved changes""" + didCallFunction = False + + def captureFunction(*args, **kwargs): + nonlocal didCallFunction + didCallFunction = True + + window.openSaveChangesDialog = captureFunction + window.createNewProject(prompt=True) + assert didCallFunction diff --git a/tests/test_mainwindow_undostack.py b/tests/test_mainwindow_undostack.py index 1eec1ef..ceaf87e 100644 --- a/tests/test_mainwindow_undostack.py +++ b/tests/test_mainwindow_undostack.py @@ -1,16 +1,7 @@ from pytest import fixture from pytestqt import qtbot from avp.gui.mainwindow import MainWindow -from . import getTestDataPath - - -@fixture -def window(qtbot): - window = MainWindow(None, None) - qtbot.addWidget(window) - window.settings.setValue("outputWidth", 1920) - window.settings.setValue("outputHeight", 1080) - yield window +from . import getTestDataPath, window def test_undo_classic_visualizer_sensitivity(window, qtbot): @@ -20,7 +11,7 @@ def test_undo_classic_visualizer_sensitivity(window, qtbot): 0, window.core.moduleIndexFor("Classic Visualizer"), window ) comp = window.core.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") for i in range(1, 100): comp.page.spinBox_scale.setValue(i) assert comp.scale == 99 @@ -32,7 +23,7 @@ def test_undo_image_scale(window, qtbot): """Undo Image component scale setting should undo multiple merged actions.""" window.core.insertComponent(0, window.core.moduleIndexFor("Image"), window) comp = window.core.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") comp.page.spinBox_scale.setValue(100) for i in range(10, 401): comp.page.spinBox_scale.setValue(i) diff --git a/tests/test_toolkit_common.py b/tests/test_toolkit_common.py index 8e9dca2..b20ae53 100644 --- a/tests/test_toolkit_common.py +++ b/tests/test_toolkit_common.py @@ -2,20 +2,25 @@ from pytestqt import qtbot from avp.command import Command from avp.toolkit import blockSignals, rgbFromString +from . import command @fixture def gotWarning(): """Check if a function called log.warning""" import avp.toolkit.common as tk + warning = False + def gotWarning(): nonlocal warning return warning + class log: def warning(self, *args): nonlocal warning warning = True + oldLog = tk.log tk.log = log() try: @@ -24,8 +29,7 @@ def warning(self, *args): tk.log = oldLog -def test_blockSignals(qtbot): - command = Command() +def test_blockSignals(qtbot, command): command.core.insertComponent(0, 0, command) comp = command.core.selectedComponents[0] assert comp.page.spinBox_scale.signalsBlocked() == False @@ -41,4 +45,4 @@ def test_rgbFromString(gotWarning): def test_rgbFromString_error(gotWarning): assert rgbFromString("255,255,256") == (255, 255, 255) - assert gotWarning() \ No newline at end of file + assert gotWarning() diff --git a/tests/test_toolkit_ffmpeg.py b/tests/test_toolkit_ffmpeg.py index b015470..363eba1 100644 --- a/tests/test_toolkit_ffmpeg.py +++ b/tests/test_toolkit_ffmpeg.py @@ -1,7 +1,8 @@ import pytest +from avp.core import Core from avp.command import Command from avp.toolkit.ffmpeg import createFfmpegCommand -from . import audioData +from . import audioData, getTestDataPath, initCore def test_readAudioFile_data(audioData): @@ -14,6 +15,7 @@ def test_readAudioFile_duration(audioData): @pytest.mark.parametrize("width, height", ((1920, 1080), (1280, 720))) def test_createFfmpegCommand(width, height): + initCore() command = Command() command.settings.setValue("outputWidth", width) command.settings.setValue("outputHeight", height) From 5826b7ba44aefa774772de719d65961d5aed7c65 Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Thu, 29 Jan 2026 13:00:08 -0500 Subject: [PATCH 6/6] fix UnboundLocalError in createNewProject --- src/avp/gui/mainwindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/avp/gui/mainwindow.py b/src/avp/gui/mainwindow.py index 72fa4f6..3221783 100644 --- a/src/avp/gui/mainwindow.py +++ b/src/avp/gui/mainwindow.py @@ -902,8 +902,8 @@ def clear(self): def createNewProject(self, prompt=True): if prompt: ch = self.openSaveChangesDialog("starting a new project") - if ch is None: - return + if ch is None: + return self.clear() self.currentProject = None