mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-17 18:55:53 +08:00
chore(tests): adding tests for Qt widgets (#300)
* chore(tests): adding tests for Qt widgets * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: reset WINDOW name * chore(tests): addign tests * chore: adding more tests * fix how bin existence is checked * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: which takes str * more tests! * todo: fix * change verbosity in tests --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
MANIM_SLIDES_VERBOSITY: debug
|
||||
MANIM_SLIDES_VERBOSITY: error
|
||||
PYTHONFAULTHANDLER: 1
|
||||
DISPLAY: :99
|
||||
GITHUB_WORKFLOWS: 1
|
||||
|
@ -46,6 +46,8 @@ In an effort to better document changes, this CHANGELOG document is now created.
|
||||
- Added `Slide.next_section` for compatibility with `manim`'s
|
||||
`Scene.next_section` method.
|
||||
[#295](https://github.com/jeertmans/manim-slides/pull/295)
|
||||
- Added `--playback-rate` option to `manim-slides present` for testing purposes.
|
||||
[#300](https://github.com/jeertmans/manim-slides/pull/300)
|
||||
|
||||
(v5-changed)=
|
||||
### Changed
|
||||
|
@ -7,11 +7,11 @@ import click
|
||||
from click import Context, Parameter
|
||||
from pydantic import ValidationError
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from ..commons import config_path_option, folder_path_option, verbosity_option
|
||||
from ..config import Config, PresentationConfig
|
||||
from ..logger import logger
|
||||
from ..qt_utils import qapp
|
||||
from .player import Player
|
||||
|
||||
ASPECT_RATIO_MODES = {
|
||||
@ -210,7 +210,14 @@ def start_at_callback(
|
||||
metavar="NUMBER",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Presents content on the given screen (a.k.a. display).",
|
||||
help="Present content on the given screen (a.k.a. display).",
|
||||
)
|
||||
@click.option(
|
||||
"--playback-rate",
|
||||
metavar="RATE",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Playback rate of the video slides, see PySide6 docs for details.",
|
||||
)
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
@ -228,6 +235,7 @@ def present(
|
||||
start_at_scene_number: int,
|
||||
start_at_slide_number: int,
|
||||
screen_number: Optional[int] = None,
|
||||
playback_rate: float = 1.0,
|
||||
) -> None:
|
||||
"""
|
||||
Present SCENE(s), one at a time, in order.
|
||||
@ -258,13 +266,9 @@ def present(
|
||||
start_at_scene_number = start_at[0]
|
||||
|
||||
if start_at[1]:
|
||||
start_at_scene_number = start_at[1]
|
||||
|
||||
if maybe_app := QApplication.instance():
|
||||
app = maybe_app
|
||||
else:
|
||||
app = QApplication(sys.argv)
|
||||
start_at_slide_number = start_at[1]
|
||||
|
||||
app = qapp()
|
||||
app.setApplicationName("Manim Slides")
|
||||
|
||||
if screen_number is not None:
|
||||
@ -291,9 +295,10 @@ def present(
|
||||
presentation_index=start_at_scene_number,
|
||||
slide_index=start_at_slide_number,
|
||||
screen=screen,
|
||||
playback_rate=playback_rate,
|
||||
)
|
||||
|
||||
player.show()
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
sys.exit(app.exec_())
|
||||
sys.exit(app.exec())
|
||||
|
@ -53,6 +53,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
presentation_index: int = 0,
|
||||
slide_index: int = 0,
|
||||
screen: Optional[QScreen] = None,
|
||||
playback_rate: float = 1.0,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
@ -65,11 +66,12 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
self.presentation_configs = presentation_configs
|
||||
self.__current_presentation_index = 0
|
||||
self.__current_slide_index = 0
|
||||
self.__current_file: Path = self.current_slide_config.file
|
||||
|
||||
self.current_presentation_index = presentation_index
|
||||
self.current_slide_index = slide_index
|
||||
|
||||
self.__current_file: Path = self.current_slide_config.file
|
||||
|
||||
self.__playing_reversed_slide = False
|
||||
|
||||
# Widgets
|
||||
@ -100,6 +102,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
|
||||
self.media_player = QMediaPlayer(self)
|
||||
self.media_player.setVideoOutput(self.video_widget)
|
||||
self.media_player.setPlaybackRate(playback_rate)
|
||||
|
||||
self.presentation_changed.connect(self.presentation_changed_callback)
|
||||
self.slide_changed.connect(self.slide_changed_callback)
|
||||
@ -108,7 +111,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
|
||||
# Connecting key callbacks
|
||||
|
||||
self.config.keys.QUIT.connect(self.quit)
|
||||
self.config.keys.QUIT.connect(self.close)
|
||||
self.config.keys.PLAY_PAUSE.connect(self.play_pause)
|
||||
self.config.keys.NEXT.connect(self.next)
|
||||
self.config.keys.PREVIOUS.connect(self.previous)
|
||||
@ -161,7 +164,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
elif -self.presentations_count <= index < 0:
|
||||
self.__current_presentation_index = index + self.presentations_count
|
||||
else:
|
||||
logger.warn(f"Could not set presentation index to {index}")
|
||||
logger.warn(f"Could not set presentation index to {index}.")
|
||||
return
|
||||
|
||||
self.presentation_changed.emit()
|
||||
@ -185,7 +188,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
elif -self.current_slides_count <= index < 0:
|
||||
self.__current_slide_index = index + self.current_slides_count
|
||||
else:
|
||||
logger.warn(f"Could not set slide index to {index}")
|
||||
logger.warn(f"Could not set slide index to {index}.")
|
||||
return
|
||||
|
||||
self.slide_changed.emit()
|
||||
@ -257,7 +260,8 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
self.current_presentation_index += 1
|
||||
self.current_slide_index = 0
|
||||
elif self.exit_after_last_slide:
|
||||
self.quit()
|
||||
self.close()
|
||||
return
|
||||
else:
|
||||
logger.info("No more slide to play.")
|
||||
return
|
||||
@ -290,10 +294,9 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
self.info.show()
|
||||
|
||||
@Slot()
|
||||
def quit(self) -> None:
|
||||
def close(self) -> None:
|
||||
logger.info("Closing gracefully...")
|
||||
self.info.deleteLater()
|
||||
self.deleteLater()
|
||||
super().close()
|
||||
|
||||
@Slot()
|
||||
def next(self) -> None:
|
||||
@ -338,7 +341,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
self.setCursor(Qt.BlankCursor)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
||||
self.quit()
|
||||
self.close()
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
||||
key = event.key()
|
||||
|
14
manim_slides/qt_utils.py
Normal file
14
manim_slides/qt_utils.py
Normal file
@ -0,0 +1,14 @@
|
||||
"""Qt utils."""
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
|
||||
def qapp() -> QApplication:
|
||||
"""
|
||||
Return a QApplication instance, creating one
|
||||
if needed.
|
||||
"""
|
||||
if app := QApplication.instance():
|
||||
return app
|
||||
|
||||
return QApplication([])
|
@ -45,49 +45,49 @@ class BaseSlide:
|
||||
@abstractmethod
|
||||
def _frame_height(self) -> float:
|
||||
"""Return the scene's frame height."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _frame_width(self) -> float:
|
||||
"""Return the scene's frame width."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _background_color(self) -> str:
|
||||
"""Return the scene's background color."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _resolution(self) -> Tuple[int, int]:
|
||||
"""Return the scene's resolution used during rendering."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _partial_movie_files(self) -> List[Path]:
|
||||
"""Return a list of partial movie files, a.k.a animations."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _show_progress_bar(self) -> bool:
|
||||
"""Return True if progress bar should be displayed."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _leave_progress_bar(self) -> bool:
|
||||
"""Return True if progress bar should be left after completed."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _start_at_animation_number(self) -> Optional[int]:
|
||||
"""If set, return the animation number at which rendering start."""
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def canvas(self) -> MutableMapping[str, Mobject]:
|
||||
@ -341,8 +341,9 @@ class BaseSlide:
|
||||
)
|
||||
)
|
||||
|
||||
self._current_slide += 1
|
||||
|
||||
self._pre_slide_config_kwargs = dict(loop=loop)
|
||||
self._current_slide += 1
|
||||
self._start_animation = self._current_animation
|
||||
|
||||
def _add_last_slide(self) -> None:
|
||||
|
@ -7,7 +7,6 @@ import click
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QIcon, QKeyEvent
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QGridLayout,
|
||||
@ -22,6 +21,7 @@ from .commons import config_options, verbosity_option
|
||||
from .config import Config, Key
|
||||
from .defaults import CONFIG_PATH
|
||||
from .logger import logger
|
||||
from .qt_utils import qapp
|
||||
from .resources import * # noqa: F403
|
||||
|
||||
WINDOW_NAME: str = "Configuration Wizard"
|
||||
@ -57,12 +57,13 @@ class Wizard(QWidget): # type: ignore
|
||||
self.config = config
|
||||
self.icon = QIcon(":/icon.png")
|
||||
self.setWindowIcon(self.icon)
|
||||
self.closed_without_saving = False
|
||||
|
||||
button = QDialogButtonBox.Save | QDialogButtonBox.Cancel
|
||||
|
||||
self.buttonBox = QDialogButtonBox(button)
|
||||
self.buttonBox.accepted.connect(self.save_config)
|
||||
self.buttonBox.rejected.connect(self.close_without_saving)
|
||||
self.button_box = QDialogButtonBox(button)
|
||||
self.button_box.accepted.connect(self.save_config)
|
||||
self.button_box.rejected.connect(self.close_without_saving)
|
||||
|
||||
self.buttons = []
|
||||
|
||||
@ -87,17 +88,17 @@ class Wizard(QWidget): # type: ignore
|
||||
)
|
||||
self.layout.addWidget(button, i, 1)
|
||||
|
||||
self.layout.addWidget(self.buttonBox, len(self.buttons), 1)
|
||||
self.layout.addWidget(self.button_box, len(self.buttons), 1)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def close_without_saving(self) -> None:
|
||||
logger.debug("Closing configuration wizard without saving")
|
||||
self.closed_without_saving = True
|
||||
self.deleteLater()
|
||||
sys.exit(0)
|
||||
|
||||
def closeEvent(self, event: Any) -> None: # noqa: N802
|
||||
self.closeWithoutSaving()
|
||||
self.close_without_saving()
|
||||
event.accept()
|
||||
|
||||
def save_config(self) -> None:
|
||||
@ -111,7 +112,7 @@ class Wizard(QWidget): # type: ignore
|
||||
"Two or more actions share a common key: make sure actions have distinct key codes."
|
||||
)
|
||||
msg.setWindowTitle("Error: duplicated keys")
|
||||
msg.exec_()
|
||||
msg.exec()
|
||||
return
|
||||
|
||||
self.deleteLater()
|
||||
@ -119,7 +120,7 @@ class Wizard(QWidget): # type: ignore
|
||||
def open_dialog(self, button_number: int, key: Key) -> None:
|
||||
button = self.buttons[button_number]
|
||||
dialog = KeyInput()
|
||||
dialog.exec_()
|
||||
dialog.exec()
|
||||
if dialog.key is not None:
|
||||
key_name = keymap[dialog.key]
|
||||
key.set_ids(dialog.key)
|
||||
@ -180,12 +181,15 @@ def _init(
|
||||
if config_path.exists():
|
||||
config = Config.from_file(config_path)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app = qapp()
|
||||
app.setApplicationName("Manim Slides Wizard")
|
||||
window = Wizard(config)
|
||||
window.show()
|
||||
app.exec()
|
||||
|
||||
if window.closed_without_saving:
|
||||
sys.exit(0)
|
||||
|
||||
config = window.config
|
||||
|
||||
if merge:
|
||||
|
53
poetry.lock
generated
53
poetry.lock
generated
@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "alabaster"
|
||||
@ -1740,16 +1740,6 @@ files = [
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
|
||||
@ -2309,12 +2299,13 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
numpy = [
|
||||
{version = ">=1.21.0", markers = "python_version <= \"3.9\" and platform_system == \"Darwin\" and platform_machine == \"arm64\" and python_version >= \"3.8\""},
|
||||
{version = ">=1.21.0", markers = "python_version <= \"3.9\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""},
|
||||
{version = ">=1.21.2", markers = "python_version >= \"3.10\""},
|
||||
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""},
|
||||
{version = ">=1.23.5", markers = "python_version >= \"3.11\""},
|
||||
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
|
||||
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
|
||||
{version = ">=1.19.3", markers = "platform_system == \"Linux\" and platform_machine == \"aarch64\" and python_version >= \"3.8\" and python_version < \"3.10\" or python_version > \"3.9\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_system != \"Darwin\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
|
||||
{version = ">=1.17.3", markers = "(platform_system != \"Darwin\" and platform_system != \"Linux\") and python_version >= \"3.8\" and python_version < \"3.9\" or platform_system != \"Darwin\" and python_version >= \"3.8\" and python_version < \"3.9\" and platform_machine != \"aarch64\" or platform_machine != \"arm64\" and python_version >= \"3.8\" and python_version < \"3.9\" and platform_system != \"Linux\" or (platform_machine != \"arm64\" and platform_machine != \"aarch64\") and python_version >= \"3.8\" and python_version < \"3.9\""},
|
||||
{version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""},
|
||||
{version = ">=1.17.0", markers = "python_version >= \"3.7\""},
|
||||
{version = ">=1.17.3", markers = "python_version >= \"3.8\""},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3053,6 +3044,24 @@ pytest = ">=7.3.1"
|
||||
[package.extras]
|
||||
test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-qt"
|
||||
version = "4.2.0"
|
||||
description = "pytest support for PyQt and PySide applications"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-qt-4.2.0.tar.gz", hash = "sha256:00a17b586dd530b6d7a9399923a40489ca4a9a309719011175f55dc6b5dc8f41"},
|
||||
{file = "pytest_qt-4.2.0-py2.py3-none-any.whl", hash = "sha256:a7659960a1ab2af8fc944655a157ff45d714b80ed7a6af96a4b5bb99ecf40a22"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=3.0.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.3.1"
|
||||
@ -3149,7 +3158,6 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
|
||||
@ -3157,15 +3165,8 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
|
||||
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
|
||||
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
|
||||
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
|
||||
@ -3182,7 +3183,6 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
|
||||
@ -3190,7 +3190,6 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
|
||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||
@ -4237,4 +4236,4 @@ sphinx-directive = ["docutils", "manim"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.8.1,<3.12"
|
||||
content-hash = "d691d1abda28f4b940f44bb689b9c34220734168f45f66b905e8b3323ad51e36"
|
||||
content-hash = "a973a459c920323757e0a99c9ec100e9a1f12088deacea718fdd74f3fc8cf357"
|
||||
|
@ -104,6 +104,7 @@ manimgl = "^1.6.1"
|
||||
pytest = "^7.4.0"
|
||||
pytest-cov = "^4.1.0"
|
||||
pytest-env = "^0.8.2"
|
||||
pytest-qt = "^4.2.0"
|
||||
pytest-xdist = "^3.3.1"
|
||||
|
||||
[tool.poetry.plugins]
|
||||
|
@ -8,32 +8,32 @@ import pytest
|
||||
from manim_slides.config import PresentationConfig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def tests_folder() -> Iterator[Path]:
|
||||
yield Path(__file__).parent.resolve(strict=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def project_folder(tests_folder: Path) -> Iterator[Path]:
|
||||
yield tests_folder.parent.resolve(strict=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def data_folder(tests_folder: Path) -> Iterator[Path]:
|
||||
yield (tests_folder / "data").resolve(strict=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def slides_folder(data_folder: Path) -> Iterator[Path]:
|
||||
yield (data_folder / "slides").resolve(strict=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def slides_file(data_folder: Path) -> Iterator[Path]:
|
||||
yield (data_folder / "slides.py").resolve(strict=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def manimgl_config(project_folder: Path) -> Iterator[Path]:
|
||||
yield (project_folder / "custom_config.yml").resolve(strict=True)
|
||||
|
||||
@ -61,7 +61,7 @@ def paths() -> Generator[List[Path], None, None]:
|
||||
yield [random_path() for _ in range(20)]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="session")
|
||||
def presentation_config(
|
||||
slides_folder: Path,
|
||||
) -> Generator[PresentationConfig, None, None]:
|
||||
|
109
tests/test_base_slide.py
Normal file
109
tests/test_base_slide.py
Normal file
@ -0,0 +1,109 @@
|
||||
from typing import MutableMapping
|
||||
|
||||
import pytest
|
||||
|
||||
from manim_slides.defaults import FFMPEG_BIN
|
||||
from manim_slides.slide.base import BaseSlide
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def base_slide() -> BaseSlide:
|
||||
return BaseSlide() # type: ignore[abstract]
|
||||
|
||||
|
||||
class TestBaseSlide:
|
||||
def test_ffmpeg_bin(self, base_slide: BaseSlide) -> None:
|
||||
assert base_slide._ffmpeg_bin == FFMPEG_BIN
|
||||
|
||||
def test_frame_height(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._frame_height
|
||||
|
||||
def test_frame_width(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._frame_width
|
||||
|
||||
def test_background_color(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._background_color
|
||||
|
||||
def test_resolution(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._resolution
|
||||
|
||||
def test_partial_movie_files(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._partial_movie_files
|
||||
|
||||
def test_show_progress_bar(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._show_progress_bar
|
||||
|
||||
def test_leave_progress_bar(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._leave_progress_bar
|
||||
|
||||
def test_start_at_animation_number(self, base_slide: BaseSlide) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = base_slide._start_at_animation_number
|
||||
|
||||
def test_canvas(self, base_slide: BaseSlide) -> None:
|
||||
assert len(base_slide.canvas) == 0
|
||||
assert isinstance(base_slide.canvas, MutableMapping)
|
||||
|
||||
def test_add_to__and_remove_from_canvas(self, base_slide: BaseSlide) -> None:
|
||||
assert len(base_slide.canvas) == 0
|
||||
|
||||
base_slide.add_to_canvas(a=1, b=2)
|
||||
|
||||
assert len(base_slide.canvas) == 2
|
||||
assert base_slide.canvas["a"] == 1
|
||||
assert base_slide.canvas["b"] == 2
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
_ = base_slide.canvas["c"]
|
||||
|
||||
base_slide.add_to_canvas(b=3, c=4)
|
||||
|
||||
assert len(base_slide.canvas) == 3
|
||||
|
||||
assert sorted(base_slide.canvas_mobjects) == [1, 3, 4]
|
||||
|
||||
base_slide.remove_from_canvas("a", "b", "c")
|
||||
|
||||
assert len(base_slide.canvas) == 0
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
base_slide.remove_from_canvas("a")
|
||||
|
||||
def test_mobjects_without_canvas(self) -> None:
|
||||
pass # This property should be tested in test_slide.py
|
||||
|
||||
def test_wait_time_between_slides(self, base_slide: BaseSlide) -> None:
|
||||
assert base_slide.wait_time_between_slides == 0.0
|
||||
|
||||
base_slide.wait_time_between_slides = 1.0
|
||||
|
||||
assert base_slide.wait_time_between_slides == 1.0
|
||||
|
||||
base_slide.wait_time_between_slides = -1.0
|
||||
|
||||
assert base_slide.wait_time_between_slides == 0.0
|
||||
|
||||
def test_play(self) -> None:
|
||||
pass # This method should be tested in test_slide.py
|
||||
|
||||
def test_next_slide(self) -> None:
|
||||
pass # This method should be tested in test_slide.py
|
||||
|
||||
def test_add_last_slide(self) -> None:
|
||||
pass # This method should be tested in test_slide.py
|
||||
|
||||
def test_save_slides(self) -> None:
|
||||
pass # This method should be tested in test_slide.py
|
||||
|
||||
def test_zoom(self) -> None:
|
||||
pass # This method should be tested in test_slide.py
|
||||
|
||||
def test_wipe(self) -> None:
|
||||
pass # This method should be tested in test_slide.py
|
@ -1,3 +1,4 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from manim_slides.defaults import CONFIG_PATH, FFMPEG_BIN, FOLDER_PATH
|
||||
@ -13,3 +14,9 @@ def test_config_path() -> None:
|
||||
|
||||
def test_ffmpeg_bin() -> None:
|
||||
assert FFMPEG_BIN == Path("ffmpeg")
|
||||
|
||||
|
||||
def test_ffmpeg_bin_exists() -> None:
|
||||
assert (
|
||||
shutil.which(str(FFMPEG_BIN)) is not None
|
||||
), "If this fails, many other tests will fail"
|
||||
|
@ -8,35 +8,39 @@ from manim_slides.__main__ import cli
|
||||
|
||||
def test_help() -> None:
|
||||
runner = CliRunner()
|
||||
results = runner.invoke(cli, ["-S", "--help"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(cli, ["-S", "--help"])
|
||||
|
||||
results = runner.invoke(cli, ["-S", "-h"])
|
||||
assert results.exit_code == 0
|
||||
|
||||
assert results.exit_code == 0
|
||||
results = runner.invoke(cli, ["-S", "-h"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert "Usage: cli [OPTIONS] COMMAND [ARGS]..." in results.stdout
|
||||
|
||||
|
||||
def test_defaults_to_present(slides_folder: Path) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(
|
||||
cli, ["BasicSlide", "--folder", str(slides_folder), "-s"]
|
||||
)
|
||||
results = runner.invoke(cli, ["-S", "BasicSlide", "--help"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert "Usage: cli present" in results.stdout
|
||||
|
||||
|
||||
def test_present(slides_folder: Path) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
["subcommand"], [["present"], ["convert"], ["init"], ["list-scenes"], ["wizard"]]
|
||||
)
|
||||
def test_help_subcommand(subcommand: str) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(
|
||||
cli, ["present", "BasicSlide", "--folder", str(slides_folder), "-s"]
|
||||
)
|
||||
results = runner.invoke(cli, ["-S", subcommand, "--help"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert f"Usage: cli {subcommand}" in results.stdout
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("extension",), [("html",), ("pdf",), ("pptx",)])
|
||||
@ -90,8 +94,3 @@ def test_list_scenes(slides_folder: Path) -> None:
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert "BasicSlide" in results.output
|
||||
|
||||
|
||||
def test_wizard() -> None:
|
||||
# TODO
|
||||
pass
|
||||
|
1
tests/test_player.py
Normal file
1
tests/test_player.py
Normal file
@ -0,0 +1 @@
|
||||
# TODO
|
141
tests/test_present.py
Normal file
141
tests/test_present.py
Normal file
@ -0,0 +1,141 @@
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Tuple
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from manim_slides.present import present
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def auto_shutdown_qapp() -> Iterator[None]:
|
||||
if app := QApplication.instance():
|
||||
app.shutdown()
|
||||
|
||||
yield
|
||||
|
||||
if app := QApplication.instance():
|
||||
app.shutdown()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def args(slides_folder: Path) -> Iterator[Tuple[str, ...]]:
|
||||
yield ("--folder", str(slides_folder), "--skip-all", "--playback-rate", "25")
|
||||
|
||||
|
||||
def test_present(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_unexisting_slide(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["UnexistingSlide", *args])
|
||||
|
||||
assert results.exit_code != 0
|
||||
assert "UnexistingSlide.json does not exist" in results.stdout
|
||||
|
||||
|
||||
def test_present_full_screen(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", "--fullscreen", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_hide_mouse(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", "--hide-mouse", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_ignore_aspect_ratio(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(
|
||||
present, ["BasicSlide", "--aspect-ratio", "ignore", *args]
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_start_at(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", "--start-at", "-1,-1", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_start_at_invalid(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", "--start-at", "0,1234", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert "Could not set presentation index to 1234"
|
||||
|
||||
|
||||
def test_present_start_at_scene_number(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(
|
||||
present, ["BasicSlide", "BasicSlide", "--start-at-scene-number", "1", *args]
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_start_at_slide_number(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(
|
||||
present, ["BasicSlide", "--start-at-slide-number", "1", *args]
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
def test_present_set_screen(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", "--screen", "0", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert results.stdout == ""
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Fails when running the whole test suite.")
|
||||
def test_present_set_invalid_screen(args: Tuple[str, ...]) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
results = runner.invoke(present, ["BasicSlide", "--screen", "999", *args])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert "Invalid screen number 999" in results.stdout
|
14
tests/test_qt_utils.py
Normal file
14
tests/test_qt_utils.py
Normal file
@ -0,0 +1,14 @@
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from manim_slides.qt_utils import qapp
|
||||
|
||||
|
||||
def test_qapp() -> None:
|
||||
assert isinstance(qapp(), QApplication)
|
||||
|
||||
|
||||
def test_duplicated_qapp() -> None:
|
||||
app1 = qapp()
|
||||
app2 = qapp()
|
||||
|
||||
assert app1 == app2
|
@ -197,6 +197,42 @@ class TestSlide:
|
||||
assert text not in self.mobjects
|
||||
assert bye in self.mobjects
|
||||
|
||||
@assert_constructs
|
||||
class TestPlay(Slide):
|
||||
def construct(self) -> None:
|
||||
assert self._current_animation == 0
|
||||
circle = Circle(color=BLUE)
|
||||
dot = Dot()
|
||||
|
||||
self.play(GrowFromCenter(circle))
|
||||
assert self._current_animation == 1
|
||||
self.play(FadeIn(dot))
|
||||
assert self._current_animation == 2
|
||||
|
||||
@assert_constructs
|
||||
class TestWaitTimeBetweenSlides(Slide):
|
||||
def construct(self) -> None:
|
||||
self._wait_time_between_slides = 1.0
|
||||
assert self._current_animation == 0
|
||||
circle = Circle(color=BLUE)
|
||||
self.play(GrowFromCenter(circle))
|
||||
assert self._current_animation == 1
|
||||
self.next_slide()
|
||||
assert self._current_animation == 2 # self.wait = +1
|
||||
|
||||
@assert_constructs
|
||||
class TestNextSlide(Slide):
|
||||
def construct(self) -> None:
|
||||
assert self._current_slide == 1
|
||||
self.next_slide()
|
||||
assert self._current_slide == 1
|
||||
circle = Circle(color=BLUE)
|
||||
self.play(GrowFromCenter(circle))
|
||||
self.next_slide()
|
||||
assert self._current_slide == 2
|
||||
self.next_slide()
|
||||
assert self._current_slide == 2
|
||||
|
||||
@assert_constructs
|
||||
class TestCanvas(Slide):
|
||||
def construct(self) -> None:
|
||||
|
181
tests/test_wizard.py
Normal file
181
tests/test_wizard.py
Normal file
@ -0,0 +1,181 @@
|
||||
from pathlib import Path
|
||||
|
||||
from click.testing import CliRunner
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QMessageBox,
|
||||
)
|
||||
from pytest import MonkeyPatch
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from manim_slides.config import Config, Key
|
||||
from manim_slides.defaults import CONFIG_PATH
|
||||
from manim_slides.wizard import KeyInput, Wizard, init, wizard
|
||||
|
||||
|
||||
class TestKeyInput:
|
||||
def test_default_is_none(self, qtbot: QtBot) -> None:
|
||||
widget = KeyInput()
|
||||
widget.show()
|
||||
qtbot.addWidget(widget)
|
||||
assert widget.key is None
|
||||
|
||||
def test_send_key(self, qtbot: QtBot) -> None:
|
||||
widget = KeyInput()
|
||||
widget.show()
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.keyPress(widget, Qt.Key_Q)
|
||||
assert widget.key is Qt.Key_Q.value
|
||||
|
||||
|
||||
class TestWizard:
|
||||
def test_close_without_saving(self, qtbot: QtBot) -> None:
|
||||
wizard = Wizard(Config())
|
||||
wizard.show()
|
||||
qtbot.addWidget(wizard)
|
||||
wizard.button_box.rejected.emit()
|
||||
assert wizard.closed_without_saving
|
||||
|
||||
def test_save_valid_config(self, qtbot: QtBot) -> None:
|
||||
widget = Wizard(Config())
|
||||
widget.show()
|
||||
qtbot.addWidget(widget)
|
||||
widget.button_box.accepted.emit()
|
||||
assert not widget.closed_without_saving
|
||||
|
||||
def test_save_invalid_config(self, qtbot: QtBot, monkeypatch: MonkeyPatch) -> None:
|
||||
wizard = Wizard(Config())
|
||||
wizard.show()
|
||||
qtbot.addWidget(wizard)
|
||||
|
||||
def open_dialog(button_number: int, key: Key) -> None:
|
||||
button = wizard.buttons[button_number]
|
||||
dialog = KeyInput()
|
||||
qtbot.addWidget(dialog)
|
||||
qtbot.keyPress(dialog, Qt.Key_Q)
|
||||
assert dialog.key is not None
|
||||
key.set_ids(dialog.key)
|
||||
button.setText("Q")
|
||||
assert button.text() == "Q"
|
||||
dialog.close()
|
||||
|
||||
message_boxes = []
|
||||
|
||||
def exec_patched(self: QMessageBox) -> None:
|
||||
self.show()
|
||||
message_boxes.append(self)
|
||||
|
||||
monkeypatch.setattr(QMessageBox, "exec", exec_patched)
|
||||
|
||||
for i, (key, _) in enumerate(wizard.config.keys.dict().items()):
|
||||
open_dialog(i, getattr(wizard.config.keys, key))
|
||||
|
||||
wizard.button_box.accepted.emit()
|
||||
message_box = message_boxes.pop()
|
||||
qtbot.addWidget(message_box)
|
||||
assert message_box.isVisible()
|
||||
|
||||
|
||||
def test_init() -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
assert not CONFIG_PATH.exists()
|
||||
results = runner.invoke(
|
||||
init,
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert CONFIG_PATH.exists()
|
||||
assert Config().dict() == Config.from_file(CONFIG_PATH).dict()
|
||||
|
||||
|
||||
def test_init_custom_path() -> None:
|
||||
runner = CliRunner()
|
||||
custom_path = Path("config.toml")
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
assert not custom_path.exists()
|
||||
results = runner.invoke(
|
||||
init,
|
||||
["--config", str(custom_path)],
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert not CONFIG_PATH.exists()
|
||||
assert custom_path.exists()
|
||||
assert Config().dict() == Config.from_file(custom_path).dict()
|
||||
|
||||
|
||||
def test_init_path_exists() -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
assert not CONFIG_PATH.exists()
|
||||
results = runner.invoke(
|
||||
init,
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert CONFIG_PATH.exists()
|
||||
assert Config().dict() == Config.from_file(CONFIG_PATH).dict()
|
||||
|
||||
results = runner.invoke(init, input="o")
|
||||
|
||||
assert results.exit_code == 0
|
||||
|
||||
results = runner.invoke(init, input="m")
|
||||
|
||||
assert results.exit_code == 0
|
||||
|
||||
results = runner.invoke(init, input="q")
|
||||
|
||||
assert results.exit_code == 0
|
||||
|
||||
|
||||
def test_wizard(monkeypatch: MonkeyPatch) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
assert not CONFIG_PATH.exists()
|
||||
|
||||
def show(self: Wizard) -> None:
|
||||
self.button_box.accepted.emit()
|
||||
|
||||
def exec_patched(self: QApplication) -> None:
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(Wizard, "show", show)
|
||||
monkeypatch.setattr(QApplication, "exec", exec_patched)
|
||||
|
||||
results = runner.invoke(
|
||||
wizard,
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert CONFIG_PATH.exists()
|
||||
assert Config().dict() == Config.from_file(CONFIG_PATH).dict()
|
||||
|
||||
|
||||
def test_wizard_closed_without_saving(monkeypatch: MonkeyPatch) -> None:
|
||||
runner = CliRunner()
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
assert not CONFIG_PATH.exists()
|
||||
|
||||
def show(self: Wizard) -> None:
|
||||
self.button_box.rejected.emit()
|
||||
|
||||
def exec_patched(self: QApplication) -> None:
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(Wizard, "show", show)
|
||||
monkeypatch.setattr(QApplication, "exec", exec_patched)
|
||||
|
||||
results = runner.invoke(
|
||||
wizard,
|
||||
)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert not CONFIG_PATH.exists()
|
Reference in New Issue
Block a user