mirror of
https://github.com/aclist/dztui.git
synced 2026-11-12 13:00:37 +01:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2208787b4 | ||
|
|
77682cacd4 | ||
|
|
e8d7d9eb15 | ||
|
|
047b9dbbf6 | ||
|
|
d5c9f24ad5 | ||
|
|
f486cc506f | ||
|
|
307792ba5b | ||
|
|
f7d95d5407 | ||
|
|
8b09622fe5 | ||
|
|
7f6d61083c | ||
|
|
3e7b49203a | ||
|
|
269ef18a48 | ||
|
|
35623b6456 | ||
|
|
b9cb7ec973 | ||
|
|
e29433b388 | ||
|
|
170990ee1d | ||
|
|
1dbbd5977d | ||
|
|
c267b1bbc7 | ||
|
|
d0b3a723c6 | ||
|
|
535bc505f7 | ||
|
|
f2375feeec | ||
|
|
1b9448735d | ||
|
|
d5693b07fb | ||
|
|
7a6f4a2bb9 | ||
|
|
ba92d15085 | ||
|
|
65e04c2fc4 | ||
|
|
0907f5a474 | ||
|
|
bfa83c3f94 | ||
|
|
aef48e515b | ||
|
|
e6ebeaeb5e | ||
|
|
88c740105a | ||
|
|
b98a6f95a6 | ||
|
|
87d76e83ba |
No files matched your search
+3
-7
@@ -57,6 +57,7 @@
|
||||
- List timestamp of last DayZ client update
|
||||
- Validate mods via Mod page to pull in updates
|
||||
- Support for Nasdara (Badlands) DLC
|
||||
- Load offline mods
|
||||
|
||||
### Changed
|
||||
- Conform to PEP 440 versioning for beta versions
|
||||
@@ -99,16 +100,11 @@
|
||||
- Current keyword filter not being applied when refreshing table
|
||||
- Steam Flatpak cannot run DZGUI as a non-Steam game
|
||||
- Collisions between native Steam and Steam Flatpak clients
|
||||
|
||||
### Unreleased
|
||||
- Load offline mods
|
||||
- Local documentation
|
||||
- Raw debug command in context menu
|
||||
- Sticky toggle signal on radio buttons when reloading Options
|
||||
|
||||
### Developer-facing
|
||||
- Add pyproject.toml file
|
||||
- Show deprecation warnings
|
||||
- Options -> Dev page
|
||||
- Raw debug command in preconnect menu
|
||||
|
||||
## [6.0.5] 2026-05-17
|
||||
### Changed
|
||||
|
||||
+2
-3
@@ -8,7 +8,7 @@ If you are a developer, you can skip to the end.
|
||||
# Requesting help
|
||||
|
||||
If you encounter a problem with DZGUI, you can submit tickets on the GitHub
|
||||
[issue tracker](https://github.com/aclist/dztui/issues) under the
|
||||
[issue tracker](https://github.com/aclist/dzgui/issues) under the
|
||||
`troubleshooting` tag.
|
||||
|
||||
# How can I help the project?
|
||||
@@ -24,7 +24,7 @@ accordingly.
|
||||
|
||||
## Submitting a ticket
|
||||
|
||||
Navigate to the GitHub [issue tracker](https://github.com/aclist/dztui/issues).
|
||||
Navigate to the GitHub [issue tracker](https://github.com/aclist/dzgui/issues).
|
||||
From there, follow the onscreen prompts. You will be asked questions such as:
|
||||
|
||||
- What version are you using?
|
||||
@@ -55,7 +55,6 @@ The following naming conventions apply for PRs:
|
||||
- fix/<your-fix> - patch/hotfix branches
|
||||
- feat/<your-feature> - feature branches
|
||||
- doc/<your-doc-branch> - documentation branches
|
||||
- infra/<your-infra-branch> - infrastructure branches
|
||||
|
||||
Implement your changes and test them locally. If they work, you may
|
||||
open a merge request, and then we review your changes. If everything is OK, it
|
||||
|
||||
+6
-5
@@ -5,7 +5,7 @@
|
||||
### Fetch submodules
|
||||
git submodule update --recursive --init
|
||||
|
||||
### Install with dev dependencies
|
||||
### Install into editable venv with dev dependencies
|
||||
uv pip install -e '.[dev]'
|
||||
|
||||
This includes all dependencies for building, linting, testing, and checking in code.
|
||||
@@ -16,7 +16,7 @@ docs: documentation tools
|
||||
lint: code linting, fixing, and type checking
|
||||
test: unit and integration tests
|
||||
|
||||
## Miscellaneous
|
||||
## Build process
|
||||
|
||||
### Setting up precommit hooks
|
||||
pre-commit install
|
||||
@@ -25,6 +25,7 @@ pre-commit install
|
||||
sphinx-build -M html source build -a
|
||||
|
||||
### Building standalone release binaries
|
||||
1. Set up the build target: `rustup target add x86_64-unknown-linux-musl`
|
||||
2. Run `python3 scripts/release.py`
|
||||
3. Generate a release and tag against the resulting tarfile generated in `dist`
|
||||
1. Install the applicable system package containing the 'musl-gcc' wrapper
|
||||
2. Set up the build target: `rustup target add x86_64-unknown-linux-musl`
|
||||
3. Run `python3 scripts/release.py`
|
||||
4. Generate a release and tag against the resulting tarfile generated in `dist`
|
||||
@@ -3,7 +3,6 @@ import logging
|
||||
import re
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor, wait
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import subprocess
|
||||
import threading
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional, TYPE_CHECKING, Union
|
||||
from typing import Any, TYPE_CHECKING, Union
|
||||
|
||||
from dzgui.const.constants import APP_NAME, REQUEST_TIMEOUT
|
||||
from dzgui.const.endpoints import STEAM_SERVERS
|
||||
@@ -491,7 +491,7 @@ def query_by_record(record: Record, update_gameport: bool = False) -> A2SInfo:
|
||||
return A2SInfo(record, None)
|
||||
|
||||
|
||||
def short_ip_to_record(addr: str) -> Optional[Record]:
|
||||
def short_ip_to_record(addr: str) -> Record | None:
|
||||
r = addr.split(":")
|
||||
# TODO: raise exceptions instead of Nonetype
|
||||
if len(r) != 2:
|
||||
@@ -499,7 +499,7 @@ def short_ip_to_record(addr: str) -> Optional[Record]:
|
||||
return Record(r[0], 0, int(r[1]))
|
||||
|
||||
|
||||
def fqip_to_record(addr: str) -> Optional[Record]:
|
||||
def fqip_to_record(addr: str) -> Record | None:
|
||||
r = addr.split(":")
|
||||
if len(r) != 3:
|
||||
return None
|
||||
|
||||
+1
-2
@@ -6,7 +6,6 @@ from importlib import resources
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
from dzgui.const.constants import (
|
||||
APP_NAME,
|
||||
APP_NAME_LOWER,
|
||||
@@ -107,7 +106,7 @@ def load_steam_icon() -> None:
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
try:
|
||||
traversable = resources.files(APP_NAME_LOWER).joinpath(ICONS_PATH)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ from dzgui.const.constants import (
|
||||
from dzgui.init.proc import is_sandboxed
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class Xdg:
|
||||
config: Path
|
||||
columns: Path
|
||||
|
||||
@@ -8,9 +8,9 @@ STEAM_UNSUB = "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1
|
||||
STEAM_APP_DETAILS = "https://store.steampowered.com/api/appdetails?"
|
||||
|
||||
GITHUB = "https://github.com/aclist"
|
||||
GITHUB_RELEASES = "https://api.github.com/repos/aclist/dztui/releases/latest"
|
||||
GITHUB_RELEASES = "https://api.github.com/repos/aclist/dzgui/releases/latest"
|
||||
CODEBERG_RELEASES = "https://codeberg.org/api/v1/repos/aclist/dzgui/releases/latest"
|
||||
GITHUB_USER_RELEASES = "https://github.com/aclist/dztui/releases"
|
||||
GITHUB_USER_RELEASES = "https://github.com/aclist/dzgui/releases"
|
||||
DB_IP = "https://db-ip.com/db/download/ip-to-city-lite"
|
||||
IP_ECHO = "https://ipecho.net/plain"
|
||||
COORDS_API = "http://ip-api.com/json"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import gi
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from typing import Any, TYPE_CHECKING, Union
|
||||
|
||||
import dzgui.util._json as JSON # noqa
|
||||
|
||||
|
||||
from dzgui.config.userprefs import UserPrefs
|
||||
from dzgui.const.constants import APP_NAME
|
||||
from dzgui.const.enum import (
|
||||
ButtonType,
|
||||
ContextMenuItem,
|
||||
@@ -14,9 +14,6 @@ from dzgui.const.enum import (
|
||||
Preferences,
|
||||
ServerTab,
|
||||
)
|
||||
|
||||
from dzgui.config.userprefs import UserPrefs
|
||||
from dzgui.const.constants import APP_NAME
|
||||
from dzgui.controllers.emitter import Emitter
|
||||
from dzgui.managers.config import ConfigManager
|
||||
from dzgui.managers.connection import ConnectionManager
|
||||
@@ -29,8 +26,6 @@ from dzgui.util.open_links import open_user_workshop, open_workshop_page
|
||||
from dzgui.views.dialogs.filepicker import FilePicker
|
||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk, GLib, GObject
|
||||
|
||||
@@ -371,7 +366,6 @@ class Controller(GObject.GObject):
|
||||
filter_man = self.get_filter_man()
|
||||
return filter_man.get_filters()
|
||||
|
||||
# FIXME: wipe maps store when changing tabs if model is none
|
||||
def has_server_model(self) -> bool:
|
||||
treeview = self.get_active_treeview()
|
||||
proxy_man = treeview.get_proxy_man()
|
||||
|
||||
+10
-7
@@ -53,6 +53,11 @@
|
||||
- Expand/collapse changelog versions
|
||||
- Support local light/dark mode changes on system theme
|
||||
- Server time calculator
|
||||
- Add "Unmodded" filter to servers
|
||||
- List timestamp of last DayZ client update
|
||||
- Validate mods via Mod page to pull in updates
|
||||
- Support for Nasdara (Badlands) DLC
|
||||
- Load offline mods
|
||||
|
||||
### Changed
|
||||
- Conform to PEP 440 versioning for beta versions
|
||||
@@ -74,6 +79,7 @@
|
||||
- Enable LAN page Empty/Full filters on first run of app
|
||||
- Propagate subscribed mods to Steam client
|
||||
- Make j/k keys in context menus follow same wraparound logic as down arrow/up arrow
|
||||
- Use SQLite DB for geolocation records
|
||||
|
||||
### Dropped
|
||||
- Battlemetrics API support
|
||||
@@ -92,16 +98,13 @@
|
||||
- Moved dialogs out of threads
|
||||
- History table not being in lock-step with state file
|
||||
- Current keyword filter not being applied when refreshing table
|
||||
|
||||
### Unreleased
|
||||
- Load offline mods
|
||||
- Local documentation
|
||||
- Raw debug command in context menu
|
||||
- Steam Flatpak cannot run DZGUI as a non-Steam game
|
||||
- Collisions between native Steam and Steam Flatpak clients
|
||||
- Sticky toggle signal on radio buttons when reloading Options
|
||||
|
||||
### Developer-facing
|
||||
- Add pyproject.toml file
|
||||
- Show deprecation warnings
|
||||
- Options -> Dev page
|
||||
- Raw debug command in preconnect menu
|
||||
|
||||
## [6.0.5] 2026-05-17
|
||||
### Changed
|
||||
|
||||
+7
-4
@@ -30,16 +30,19 @@ def is_steam_running(cmd: str) -> bool:
|
||||
def is_sandboxed() -> bool:
|
||||
return bool(os.environ.get("FLATPAK_ID"))
|
||||
|
||||
def get_flatpak_output() -> list[str]:
|
||||
proc = subprocess.check_output(
|
||||
[FLATPAK_CMD, "ps", "--columns=application"], text=True
|
||||
)
|
||||
lines = proc.splitlines()
|
||||
return lines
|
||||
|
||||
def is_flatpak_steam_running() -> bool:
|
||||
if is_sandboxed():
|
||||
return True
|
||||
if has_cmd(FLATPAK_CMD) is False:
|
||||
return False
|
||||
proc = subprocess.check_output(
|
||||
[FLATPAK_CMD, "ps", "--columns=application"], text=True
|
||||
)
|
||||
lines = proc.splitlines()
|
||||
lines = get_flatpak_output()
|
||||
if FLATPAK_APPID in lines:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -330,7 +330,6 @@ class ConnectionManager:
|
||||
|
||||
self.thread_man.update_dialog(waiting_for_launch)
|
||||
while True:
|
||||
# FIXME: cancel should not be visible here per setting above
|
||||
if self.controller.is_cancel_pending():
|
||||
# TODO: some facility to also close spawned steam process
|
||||
return
|
||||
|
||||
@@ -58,6 +58,11 @@ class ServerRow:
|
||||
getter = cls._to_tuple
|
||||
return [getter(row) for row in rows]
|
||||
|
||||
@classmethod
|
||||
def get_index(cls, field: str) -> int:
|
||||
_fields = [f.name for f in fields(cls)]
|
||||
return _fields.index(field)
|
||||
|
||||
|
||||
class FiltrationManager:
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -34,7 +34,7 @@ LAN_TIMEOUT = 0.5
|
||||
API_TIMEOUT = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class NewPlayerCount:
|
||||
treeiter: Gtk.TreeIter
|
||||
players: int
|
||||
@@ -341,8 +341,8 @@ class ServerModelManager:
|
||||
return
|
||||
config_man.add_saved_server(fqip)
|
||||
|
||||
# FIXME: this is valid if saved servers tab is already open,
|
||||
# but not if app was just booted; causes single row to appear prematurely
|
||||
# TODO: this is valid if saved servers tab is already open,
|
||||
# but not if tab is unloaded; causes single row to appear prematurely
|
||||
if proxy_man.has_control_model() is False:
|
||||
proxy_man.push(records)
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
def immutable_dataclass(cls: type | None = None, **kwargs: bool) -> type | Callable[[type], type]:
|
||||
kwargs["slots"] = True
|
||||
kwargs["frozen"] = True
|
||||
if cls is None:
|
||||
return lambda c: dataclass(c, **kwargs)
|
||||
return dataclass(cls, **kwargs)
|
||||
+2
-3
@@ -1,10 +1,10 @@
|
||||
import a2s
|
||||
from ipaddress import ip_address
|
||||
import requests
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from dataclasses import dataclass
|
||||
from ipaddress import ip_address
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.api.servers import Record
|
||||
@@ -12,12 +12,11 @@ from dzgui.config.ipdb import read_from_table
|
||||
from dzgui.const.constants import REQUEST_TIMEOUT
|
||||
from dzgui.const.endpoints import IP_ECHO
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class Coords:
|
||||
lat: float
|
||||
lon: float
|
||||
|
||||
+5
-3
@@ -1,3 +1,4 @@
|
||||
import gi
|
||||
import logging
|
||||
import signal
|
||||
|
||||
@@ -34,8 +35,6 @@ from dzgui.views.pages.preconnect import PreConnectionAssistant
|
||||
from dzgui.views.pages.servers import ServerNotebook
|
||||
from dzgui.views.pages.thanks import Thanks
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("GLibUnix", "2.0")
|
||||
from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore
|
||||
@@ -49,7 +48,10 @@ logger = logging.getLogger(APP_NAME)
|
||||
class OuterWindow(Gtk.ApplicationWindow):
|
||||
def __init__(self, app: "App") -> None:
|
||||
super().__init__(
|
||||
application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER
|
||||
application=app,
|
||||
title=APP_NAME,
|
||||
border_width=10,
|
||||
icon_name=APP_NAME_LOWER,
|
||||
)
|
||||
|
||||
self.hb = AppHeaderBar()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import Sequence
|
||||
|
||||
import gi
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class GenericBox(Gtk.Box):
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import Self
|
||||
from dzgui.util import css
|
||||
|
||||
import gi
|
||||
|
||||
from typing import Self
|
||||
|
||||
from dzgui.util import css
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
# TODO: subclass Gtk.Frame directly
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import gi
|
||||
from typing import Self
|
||||
from warnings import deprecated
|
||||
|
||||
@@ -8,10 +9,8 @@ from dzgui.strings import options
|
||||
from dzgui.views.components.box import HBox
|
||||
from dzgui.views.components.buttons import Icon
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
@deprecated("Client choice is determined at setup time")
|
||||
|
||||
@@ -106,14 +106,15 @@ class RightPanel(Gtk.Box):
|
||||
if page.loaded is True:
|
||||
return
|
||||
self.filters_vbox.set_sensitive(False)
|
||||
# TODO: unless it is lan page
|
||||
# self.refresh_button.set_sensitive(False)
|
||||
self.refresh_button.set_sensitive(False)
|
||||
|
||||
def _on_lan_page_init(self, emitter: "Emitter") -> None:
|
||||
self.filters_vbox.set_sensitive(False)
|
||||
|
||||
def _on_servers_loaded(self, emitter: "Emitter", context: "ServerTab") -> None:
|
||||
# TODO: similar logic on notebook page change
|
||||
# TODO: wipe maps store when changing tabs if model is None
|
||||
|
||||
state = self.controller.has_server_model()
|
||||
self.filters_vbox.set_sensitive(state)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class NoOverlayScrolledWindow(Gtk.ScrolledWindow):
|
||||
|
||||
@@ -101,7 +101,7 @@ class Statusbar(Gtk.Grid):
|
||||
if type(context) is NotebookPage:
|
||||
self.prior_enum = context
|
||||
self.prior_status = ""
|
||||
# FIXME: CalcDist is being called when table is not loaded
|
||||
|
||||
if dist is None:
|
||||
self.set_by_context(context, "")
|
||||
return
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import gi
|
||||
import sys
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Literal, Self, TYPE_CHECKING
|
||||
|
||||
# import time
|
||||
from enum import Enum
|
||||
|
||||
|
||||
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||
|
||||
# TODO: import dialog titles
|
||||
from dzgui.api.mods import remove_stale_signatures as remove_stale
|
||||
from dzgui.config.ipdb import get_ipdb
|
||||
from dzgui.const.constants import HEX_GREEN, HEX_RED
|
||||
@@ -20,12 +16,10 @@ from dzgui.util.format import format_exception
|
||||
from dzgui.util.strings import dialog_header
|
||||
from dzgui.util.symlink import rebuild_symlinks
|
||||
from dzgui.views.components.buttons import ClipboardButton
|
||||
|
||||
|
||||
import gi
|
||||
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk # noqa
|
||||
from gi.repository import Gtk, GLib, Gdk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.config.xdg import Xdg
|
||||
|
||||
@@ -54,6 +54,7 @@ class FolderPicker(Gtk.FileChooserDialog):
|
||||
return None
|
||||
if res == Gtk.ResponseType.OK:
|
||||
folder = self.get_current_folder()
|
||||
# TODO: this should never be possible
|
||||
if folder is None:
|
||||
try:
|
||||
uri = self.get_uri()
|
||||
@@ -61,7 +62,7 @@ class FolderPicker(Gtk.FileChooserDialog):
|
||||
return None
|
||||
return Path.from_uri(uri)
|
||||
except Exception:
|
||||
# FIXME: edge cases
|
||||
# TODO: edge cases
|
||||
return Path("/")
|
||||
self.destroy()
|
||||
return Path(folder)
|
||||
|
||||
@@ -15,7 +15,7 @@ from dzgui.views.dialogs.generic import GenericDialog
|
||||
from dzgui.views.trees.tree_base import TreeView
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk, Pango # noqa
|
||||
from gi.repository import Gtk, Gdk, Pango
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.api.servers import Details
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import gi
|
||||
|
||||
from typing import Any
|
||||
|
||||
from dzgui.const.constants import (
|
||||
@@ -7,10 +9,8 @@ from dzgui.const.constants import (
|
||||
GIO_SETTINGS_VAL,
|
||||
)
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio # noqa
|
||||
from gi.repository import Gtk, Gio
|
||||
|
||||
|
||||
class ColorAwareApp:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk # noqa
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
|
||||
class HelpMenuMixin:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import gi
|
||||
|
||||
from dzgui.const.enum import VAdjustment
|
||||
from dzgui.const.constants import SCROLL_INCREMENT
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk # noqa
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
|
||||
class ScrollableMixin:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import gi
|
||||
from pathlib import Path
|
||||
|
||||
from typing import Self, TYPE_CHECKING
|
||||
@@ -8,10 +9,8 @@ from dzgui.views.components.buttons import SteamWorkshopButton
|
||||
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
||||
from dzgui.views.trees.tree_mods import ModTreeView
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import gi
|
||||
|
||||
from enum import Enum
|
||||
from typing import Self, TYPE_CHECKING, Union
|
||||
|
||||
from dzgui.util import css
|
||||
from dzgui.const.icons import ICON_EDIT_DELETE, ICON_FOLDER, ICON_WARNING
|
||||
from dzgui.const.constants import (
|
||||
APPID_DAYZ,
|
||||
APPID_DAYZ_EXP,
|
||||
@@ -11,8 +12,10 @@ from dzgui.const.constants import (
|
||||
APPNAME_DAYZ_EXP_HUMAN,
|
||||
)
|
||||
from dzgui.const.enum import ContextMenuGroup, NotebookPage
|
||||
from dzgui.const.icons import ICON_EDIT_DELETE, ICON_FOLDER, ICON_WARNING
|
||||
from dzgui.managers.offline import OfflineManager
|
||||
from dzgui.strings import generic, offline
|
||||
from dzgui.util import css
|
||||
from dzgui.views.components.box import HBox, VBox
|
||||
from dzgui.views.components.buttons import Icon, IconTextButton
|
||||
from dzgui.views.components.eventbox import InfoEventBox
|
||||
@@ -21,11 +24,8 @@ from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
||||
from dzgui.views.components.misc import ErrorPopover
|
||||
from dzgui.views.trees.tree_mods import OfflineModTreeView
|
||||
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk, GObject # noqa
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
@@ -136,7 +136,6 @@ class FolderHBox(HBox):
|
||||
self.folder = ""
|
||||
self.label.set_label("")
|
||||
self.unset_button.hide()
|
||||
# FIXME: rename signal
|
||||
self.emitter.emit("custom_mods_unloaded", self)
|
||||
|
||||
def get_button(self) -> Gtk.Button:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import gi
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, TYPE_CHECKING
|
||||
|
||||
from dzgui.api import pefile as PeFile
|
||||
from dzgui.api.steam import get_last_updated
|
||||
|
||||
from dzgui.config import query
|
||||
from dzgui.const.constants import (
|
||||
APPID_DAYZ,
|
||||
@@ -14,12 +15,11 @@ from dzgui.const.constants import (
|
||||
FLATPAK_SANDBOX,
|
||||
FLATPAK_STEAM_PATH,
|
||||
)
|
||||
from dzgui.const.icons import ICON_VIEW_CONCEAL, ICON_VIEW_REVEAL
|
||||
from dzgui.const.endpoints import STEAM_API_SETUP
|
||||
from dzgui.const.enum import Preferences, ServerTab
|
||||
from dzgui.const.icons import ICON_VIEW_CONCEAL, ICON_VIEW_REVEAL
|
||||
from dzgui.strings import options
|
||||
from dzgui.util import strings, css, open_links
|
||||
|
||||
from dzgui.views.components.box import ShortHBox, VBox
|
||||
from dzgui.views.components.eventbox import InfoEventBox
|
||||
from dzgui.views.components.labels import LeftLabel
|
||||
@@ -29,11 +29,8 @@ from dzgui.views.components.misc import ErrorPopover
|
||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk # noqa
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
@@ -233,10 +230,19 @@ class ToggleField(Gtk.Box):
|
||||
self.pack_start(self.radio1, expand=False, fill=False, padding=0)
|
||||
self.pack_start(self.radio2, expand=False, fill=False, padding=0)
|
||||
|
||||
self.signal_id: int | None = None
|
||||
|
||||
def disconnect_signal(self) -> None:
|
||||
if self.signal_id is not None:
|
||||
self.radio1.disconnect(self.signal_id)
|
||||
|
||||
def set_suboption_active(self, state: bool) -> None:
|
||||
# NOTE: defer connection of signal until after state is set
|
||||
self.radio2.set_active(state)
|
||||
self.radio1.connect("toggled", self._on_radio_toggled, self.context)
|
||||
self.disconnect_signal()
|
||||
self.signal_id = self.radio1.connect(
|
||||
"toggled", self._on_radio_toggled, self.context
|
||||
)
|
||||
|
||||
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
||||
self.controller.toggle_config(context)
|
||||
@@ -486,14 +492,5 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
start_tab = self.controller.query_config(Preferences.START_TAB)
|
||||
self.start_tab_combo.set_active(start_tab)
|
||||
|
||||
def _suppress_toggles(self, state: bool) -> None:
|
||||
for toggle in [
|
||||
self.fullscreen_toggle,
|
||||
self.distance_toggle,
|
||||
]:
|
||||
self.controller.suppress_signal(
|
||||
self, toggle.get_children()[0], "_on_radio_toggled", state
|
||||
)
|
||||
|
||||
def grab_content_area(self) -> None:
|
||||
self.grab_focus()
|
||||
@@ -1,11 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
import gi
|
||||
|
||||
from typing import Self, Sequence, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.enum import NotebookPage
|
||||
from dzgui.const.icons import (
|
||||
ICON_ERROR,
|
||||
ICON_WARNING,
|
||||
)
|
||||
from dzgui.const.enum import NotebookPage
|
||||
from dzgui.util.css import add_class
|
||||
from dzgui.util.keys import is_ctrl_mask
|
||||
from dzgui.util.localize import number
|
||||
@@ -15,33 +16,15 @@ from dzgui.views.dialogs.generic import DebugDialog
|
||||
from dzgui.views.components.frame import HeadingFrame
|
||||
from dzgui.views.trees.tree_server_mods import ServerModTreeView
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gdk, Gtk
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.managers.connection import Prerequisites
|
||||
from dzgui.controllers.mc import Controller
|
||||
from dzgui.controllers.emitter import Emitter
|
||||
|
||||
|
||||
@dataclass
|
||||
class Warnings:
|
||||
passworded: bool
|
||||
dayz_running: bool
|
||||
no_steam: bool
|
||||
wrong_version: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class Errors:
|
||||
no_space: bool
|
||||
no_dayz_exp: bool
|
||||
no_dayz: bool
|
||||
|
||||
|
||||
class Placeholder(Gtk.Label):
|
||||
def __init__(self, text: str) -> None:
|
||||
super().__init__(
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import gi
|
||||
import logging
|
||||
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.constants import APP_NAME, SEPARATOR
|
||||
from dzgui.util.keys import is_ctrl_mask, is_navkey
|
||||
from dzgui.views.mixins.cursor_mixin import CursorMixin
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||
from gi.repository import Gtk, Gdk, GObject
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
@@ -20,7 +19,7 @@ if TYPE_CHECKING:
|
||||
|
||||
class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
||||
def __init__(
|
||||
self, controller: "Controller", menu: Optional["ContextMenuGroup"]
|
||||
self, controller: "Controller", menu: "ContextMenuGroup | None"
|
||||
) -> None:
|
||||
super().__init__(
|
||||
enable_search=False,
|
||||
@@ -59,7 +58,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_current_iter(self) -> Optional[Gtk.TreeIter]:
|
||||
def get_current_iter(self) -> Gtk.TreeIter | None:
|
||||
it = self.get_selection().get_selected()[1]
|
||||
return it
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from dzgui.views.trees.tree_base import TreeView
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||
from gi.repository import Gtk
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import gi
|
||||
import logging
|
||||
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.constants import APP_NAME, HEX_RED
|
||||
@@ -9,10 +11,8 @@ from dzgui.views.mixins.context_mixin import ContextMixin
|
||||
from dzgui.views.mixins.mods_mixin import ModsMixin
|
||||
from dzgui.views.trees.tree_base import TreeView
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import gi
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.const.enum import ContextMenuGroup
|
||||
from dzgui.model.model_factory import ModelFactory
|
||||
from dzgui.strings import server_mods
|
||||
from dzgui.views.mixins.context_mixin import ContextMixin
|
||||
from dzgui.views.trees.tree_base import TreeView
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, Pango # noqa
|
||||
from gi.repository import Gtk, Pango
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
|
||||
@@ -1,30 +1,25 @@
|
||||
import gi
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from queue import Queue
|
||||
from typing import Any, Self, Union
|
||||
from typing import Any, Literal, Self, TYPE_CHECKING, Union
|
||||
from warnings import deprecated
|
||||
|
||||
from dzgui.api.servers import ping, Record
|
||||
from dzgui.const.constants import APP_NAME
|
||||
from dzgui.const.enum import ContextMenuItem, ContextMenuGroup, ServerTab
|
||||
from dzgui.managers.filter import FilterManager
|
||||
from dzgui.model.proxy_model import ProxyModelManager
|
||||
from dzgui.model.proxy_model import ProxyModelManager, ServerRow
|
||||
from dzgui.util import strings
|
||||
from dzgui.util.dist import CalcDist
|
||||
from dzgui.util.keys import is_ctrl_mask
|
||||
from dzgui.views.mixins.context_mixin import ContextMixin
|
||||
from typing import Literal, TYPE_CHECKING
|
||||
|
||||
|
||||
from dzgui.views.trees.tree_base import TreeView
|
||||
import dzgui.util._json as JSON # noqa
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
@@ -34,8 +29,15 @@ if TYPE_CHECKING:
|
||||
from dzgui.model.model_factory import FastInsertListStore
|
||||
from dzgui.util.dist import Haversine
|
||||
|
||||
# TODO: consts
|
||||
QUEUE_CHECK_DELAY = 200
|
||||
|
||||
IND_NAME = ServerRow.get_index("name")
|
||||
IND_ADDR = ServerRow.get_index("ip")
|
||||
IND_QPORT = ServerRow.get_index("qport")
|
||||
IND_PING = ServerRow.get_index("ping")
|
||||
IND_MODDED = ServerRow.get_index("modded")
|
||||
|
||||
|
||||
class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
def __init__(
|
||||
@@ -106,7 +108,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
if column_title in width_map:
|
||||
w = width_map[column_title]
|
||||
column.set_fixed_width(w)
|
||||
if column_title == "Ping":
|
||||
if i == IND_PING:
|
||||
column.set_cell_data_func(renderer, self._get_ping)
|
||||
|
||||
column.connect("notify::fixed-width", self._on_col_width_changed)
|
||||
@@ -145,8 +147,8 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
if model is None:
|
||||
return False
|
||||
tree_iter = model.get_iter(path[0])
|
||||
ip = model.get_value(tree_iter, 7)
|
||||
qport = model.get_value(tree_iter, 8)
|
||||
ip = model.get_value(tree_iter, IND_ADDR)
|
||||
qport = model.get_value(tree_iter, IND_QPORT)
|
||||
addr = ip + ":" + str(qport)
|
||||
note = self.controller.get_note_by_record(addr)
|
||||
|
||||
@@ -169,14 +171,12 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
return self.enum
|
||||
|
||||
def _on_map(self, widget: Self) -> None:
|
||||
# TODO: disable filter panel if current model is None
|
||||
state = self.get_enum() is ServerTab.LAN
|
||||
self.emitter.emit("lan_tab_toggled", state)
|
||||
|
||||
store = self.filter_man.get_map_store()
|
||||
|
||||
# FIXME: if model is none, wipe maps
|
||||
# distinguish this signal from changing map combo in-situ
|
||||
# TODO: distinguish this signal from changing map combo in-situ
|
||||
self.emitter.emit("load_maps", store)
|
||||
self.handler_id = self.emitter.connect("statusbar_loaded", self.start_distcalc)
|
||||
self.start_queue_checker()
|
||||
@@ -221,11 +221,11 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
|
||||
def _check_result_queue(self) -> Literal[True]:
|
||||
latest_result = None
|
||||
# FIXME: empty results cause statusbar to be emptied
|
||||
# TODO: Nonetype results cause statusbar to be blank
|
||||
while not self.queue.empty():
|
||||
latest_result = self.queue.get()
|
||||
|
||||
# FIXME: cache is being checked in two passes
|
||||
# TODO: cache is being checked in two passes
|
||||
cache = self.controller.get_dist_cache()
|
||||
|
||||
if latest_result:
|
||||
@@ -267,15 +267,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
(model, pathlist) = sels
|
||||
path = pathlist[0]
|
||||
tree_iter = model.get_iter(path)
|
||||
has_mods = model.get_value(tree_iter, 11)
|
||||
has_mods = model.get_value(tree_iter, IND_MODDED)
|
||||
return bool(has_mods)
|
||||
|
||||
# def get_selected_row(self) -> Gtk.TreeModelRow:
|
||||
# sel = self.get_selection()
|
||||
# sels = sel.get_selected_rows()
|
||||
# print(type(sels[0]))
|
||||
# return sels[0]
|
||||
|
||||
def is_in_favs(self) -> bool:
|
||||
record = self.get_record_string()
|
||||
if self.controller.get_config_man().is_in_favs(record):
|
||||
@@ -304,17 +298,17 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
self.start_distcalc()
|
||||
|
||||
def get_name(self) -> str:
|
||||
return str(self.get_value_at_index(0))
|
||||
return str(self.get_value_at_index(IND_NAME))
|
||||
|
||||
def get_simplified_ip(self) -> str:
|
||||
addr = self.get_value_at_index(7)
|
||||
qport = self.get_value_at_index(8)
|
||||
addr = self.get_value_at_index(IND_ADDR)
|
||||
qport = self.get_value_at_index(IND_QPORT)
|
||||
ip = addr.split(":")[0]
|
||||
return f"{ip}:{qport}"
|
||||
|
||||
def get_record_string(self) -> str:
|
||||
addr = self.get_value_at_index(7)
|
||||
qport = self.get_value_at_index(8)
|
||||
addr = self.get_value_at_index(IND_ADDR)
|
||||
qport = self.get_value_at_index(IND_QPORT)
|
||||
return f"{addr}:{qport}"
|
||||
|
||||
def get_record(self) -> Record | None:
|
||||
@@ -353,8 +347,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
def _redraw(
|
||||
self, model: "FastInsertListStore", _iter: Gtk.TreeIter, _ping: int
|
||||
) -> None:
|
||||
ping_column = 9
|
||||
model.set(_iter, ping_column, _ping)
|
||||
model.set(_iter, IND_PING, _ping)
|
||||
self.queue_draw()
|
||||
|
||||
def wipe_cache(self) -> None:
|
||||
@@ -369,13 +362,10 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
data: Any,
|
||||
) -> None:
|
||||
|
||||
addr_column = 7
|
||||
qport_column = 8
|
||||
|
||||
addr = model.get_value(_iter, addr_column).split(":")
|
||||
addr = model.get_value(_iter, IND_ADDR).split(":")
|
||||
ip = addr[0]
|
||||
gameport = int(addr[1])
|
||||
qport = model.get_value(_iter, qport_column)
|
||||
qport = model.get_value(_iter, IND_QPORT)
|
||||
record = Record(ip, gameport, qport)
|
||||
|
||||
if record in self.seen_cache:
|
||||
@@ -415,6 +405,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||
real_model = self.proxy_man.get_control()
|
||||
from dataclasses import fields
|
||||
from dzgui.model.proxy_model import ServerRow
|
||||
|
||||
key = fields(ServerRow)[col_index].name
|
||||
|
||||
value = getattr(real_model[row_index], key)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import gi
|
||||
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.model.model_factory import FastInsertListStore
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
|
||||
|
||||
class SimpleTree(Gtk.ScrolledWindow):
|
||||
"""Simple scrollable 1-column tree"""
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import tomllib
|
||||
from shared import get_git_root
|
||||
from typing import Any
|
||||
|
||||
|
||||
def get_entrypoint() -> None:
|
||||
def get_entrypoint() -> Any:
|
||||
root = get_git_root()
|
||||
p = root.joinpath("pyproject.toml")
|
||||
with open(p, "rb") as f:
|
||||
|
||||
+3
-3
@@ -52,7 +52,7 @@ def untar(file: str, glob: str, renamed: str) -> None:
|
||||
os.rename(_dir, build_dir.joinpath(renamed))
|
||||
|
||||
|
||||
def install_deps() -> None:
|
||||
def install_deps() -> int:
|
||||
pip = build_dir.joinpath(f"{CPYTHON_BUILD}/bin/pip")
|
||||
proc = subprocess.run([pip, "install", root])
|
||||
return proc.returncode
|
||||
@@ -65,12 +65,12 @@ def repackage() -> None:
|
||||
tar.add(build_dir.joinpath(CPYTHON_BUILD), arcname="python")
|
||||
|
||||
|
||||
def rebuild_cpython() -> None:
|
||||
def rebuild_cpython() -> str:
|
||||
if cpython_dir.is_dir() is False:
|
||||
get_cpython()
|
||||
code = install_deps()
|
||||
if code != 0:
|
||||
return
|
||||
return ""
|
||||
repackage()
|
||||
return get_packaged_version()
|
||||
|
||||
|
||||
+2
-2
@@ -92,8 +92,8 @@ with tarfile.open(tarpath, "w:gz") as tar:
|
||||
tar.add(subfolder, arcname=appname, filter=update_uname)
|
||||
print(f"Wrote tarfile to '{tarpath}'")
|
||||
|
||||
proc = subprocess.run([release_exe, "-v"], capture_output=True, text=True)
|
||||
assert proc.stdout.rstrip() == version
|
||||
built_proc = subprocess.run([release_exe, "-v"], capture_output=True, text=True)
|
||||
assert str(built_proc.stdout).rstrip() == version
|
||||
|
||||
# TODO: clean up staging directory
|
||||
release_exe.unlink()
|
||||
|
||||
+10
-4
@@ -8,7 +8,13 @@ def get_image_path() -> Path:
|
||||
return get_git_root().joinpath("dzgui/data/images/example.png")
|
||||
|
||||
|
||||
p = get_image_path()
|
||||
app = build_gui("", False)
|
||||
app.set_automation_param("screenshot_path", p)
|
||||
app.run(None)
|
||||
def take_screenshot() -> None:
|
||||
p = get_image_path()
|
||||
app = build_gui("", False)
|
||||
if app is None:
|
||||
return
|
||||
app.set_automation_param("screenshot_path", p)
|
||||
app.run(None)
|
||||
|
||||
|
||||
take_screenshot()
|
||||
+7
-10
@@ -3,14 +3,11 @@ import subprocess
|
||||
import sys
|
||||
|
||||
cmd = ["git", "ls-files", "--others", "--exclude-standard"]
|
||||
result = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True
|
||||
)
|
||||
with open(".gitignore", "r") as f:
|
||||
ignored = [line.rstrip() for line in f]
|
||||
|
||||
untracked = [os.path.basename(line.rstrip()) for line in result.stdout]
|
||||
for file in untracked:
|
||||
if not any(file in item for item in ignored):
|
||||
print(f"File '{file}' is untracked. Add to .gitignore file if this is intended.")
|
||||
sys.exit(1)
|
||||
untracked = [os.path.basename(line.rstrip()) for line in result.stdout.splitlines()]
|
||||
if bool(untracked):
|
||||
print(f"The following files are untracked. Add to .gitignore if this is intended.")
|
||||
print("\n".join(untracked))
|
||||
sys.exit(1)
|
||||
Vendored
+4
-2
@@ -1,5 +1,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
def fixture_path(fixture: str) -> str:
|
||||
return str(CWD / fixture)
|
||||
|
||||
|
||||
def fixture_path(fixture: str) -> Path:
|
||||
return Path(CWD).joinpath(fixture)
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from dzgui.api.steam import get_app_allows_downloads, get_last_updated
|
||||
@@ -36,30 +37,32 @@ def steam_path() -> Path:
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def patch_api() -> None:
|
||||
def patch_api() -> Iterator[None]:
|
||||
mp = MonkeyPatch()
|
||||
mp.setattr("dzgui.api.steam.get_app_path", mock_path)
|
||||
yield
|
||||
mp.undo()
|
||||
|
||||
|
||||
def test_no_last_update(steam_path) -> None:
|
||||
def test_no_last_update(steam_path: Path) -> None:
|
||||
assert get_last_updated(steam_path, 555) is None
|
||||
|
||||
|
||||
def test_last_update(steam_path) -> None:
|
||||
def test_last_update(steam_path: Path) -> None:
|
||||
assert get_last_updated(steam_path, 444) == "2026-10-22 00:09:33"
|
||||
|
||||
|
||||
def test_app_allows_downloads(steam_path) -> None:
|
||||
def test_app_allows_downloads(steam_path: Path) -> None:
|
||||
assert get_app_allows_downloads(steam_path, 111) is True
|
||||
|
||||
|
||||
def test_app_disallows_downloads(steam_path):
|
||||
def test_app_disallows_downloads(steam_path: Path) -> None:
|
||||
assert get_app_allows_downloads(steam_path, 222) is False
|
||||
|
||||
|
||||
def test_app_delegates_downloads(monkeypatch, steam_path, client_allows):
|
||||
def test_app_delegates_downloads(
|
||||
monkeypatch: MonkeyPatch, steam_path: Path, client_allows: Path
|
||||
) -> None:
|
||||
monkeypatch.setattr("dzgui.api.steam.get_config", mock_config_disallows)
|
||||
assert get_app_allows_downloads(steam_path, 333) is False
|
||||
monkeypatch.setattr("dzgui.api.steam.get_config", mock_config_allows)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import pytest
|
||||
import tempfile
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.app_init import copy_bare_configs
|
||||
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
||||
|
||||
@@ -8,9 +10,13 @@ from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
||||
CONF_STRING = "DZGUI_CONF\n"
|
||||
STATE_STRING = "DZGUI_STATE\n"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from dzgui.config.xdg import Xdg
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def state_files():
|
||||
def state_files() -> list[str]:
|
||||
state = [
|
||||
"dzg.history",
|
||||
"dzg.versions",
|
||||
@@ -25,7 +31,7 @@ def state_files():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xdg_paths(monkeypatch):
|
||||
def xdg_paths(monkeypatch: "MonkeyPatch") -> "Xdg":
|
||||
routes = {
|
||||
"XDG_CONFIG_HOME": "",
|
||||
"XDG_STATE_HOME": "",
|
||||
@@ -42,7 +48,7 @@ def xdg_paths(monkeypatch):
|
||||
|
||||
|
||||
@pytest.mark.mods
|
||||
def test_config_file_import(xdg_paths, state_files):
|
||||
def test_config_file_import(xdg_paths: "Xdg", state_files: list[str]) -> None:
|
||||
|
||||
# NOTE: write bare files in root
|
||||
tmp_conf = xdg_paths.config.parent.parent
|
||||
@@ -67,7 +73,7 @@ def test_config_file_import(xdg_paths, state_files):
|
||||
|
||||
|
||||
@pytest.mark.mods
|
||||
def test_config_file_no_import(xdg_paths, state_files):
|
||||
def test_config_file_no_import(xdg_paths: "Xdg", state_files: list[str]) -> None:
|
||||
for subdir in xdg_paths.config.parent, xdg_paths.resolution.parent:
|
||||
subdir.mkdir()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def changelog(request) -> None:
|
||||
def changelog(request: pytest.FixtureRequest) -> str:
|
||||
root = request.config.rootpath
|
||||
changelog = Path(root).joinpath("CHANGELOG.md").read_text()
|
||||
return changelog
|
||||
@@ -19,7 +19,7 @@ def count_hash(line: str) -> int:
|
||||
return cnt
|
||||
|
||||
|
||||
def test_changelog_prefix(changelog) -> None:
|
||||
def test_changelog_prefix(changelog: str) -> None:
|
||||
r = r".*(\[.*\]).*"
|
||||
lines = changelog.splitlines()
|
||||
sort = sorted(lines)
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import pytest
|
||||
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.constants import FLATPAK_RUN_CMD, STEAM_CMD
|
||||
from dzgui.controllers.mc import Controller
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value, expected", [(FLATPAK_RUN_CMD, "Flatpak"), (STEAM_CMD, "Steam")]
|
||||
)
|
||||
def test_human_name(monkeypatch, value, expected) -> None:
|
||||
def mock_query_config(*args) -> str:
|
||||
def test_human_name(monkeypatch: "MonkeyPatch", value: str, expected: str) -> None:
|
||||
def mock_query_config(*args: Any) -> str:
|
||||
return value
|
||||
|
||||
monkeypatch.setattr(
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import gi
|
||||
import pytest
|
||||
import subprocess
|
||||
|
||||
from dzgui.const.constants import GIO_SETTINGS_KEY
|
||||
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, GLib # noqa
|
||||
from gi.repository import Gtk, GLib
|
||||
|
||||
|
||||
class DummyWidget(ColorAwareApp, Gtk.Button):
|
||||
class DummyWidget(ColorAwareApp, Gtk.Button): # type: ignore
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
@@ -27,7 +26,7 @@ def widget() -> DummyWidget:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key, expect", [("default", False), ("prefer-dark", True)])
|
||||
def test_light_mode(widget, key: str, expect: bool) -> None:
|
||||
def test_light_mode(widget: DummyWidget, key: str, expect: bool) -> None:
|
||||
local_pref = subprocess.run(
|
||||
["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
|
||||
capture_output=True,
|
||||
|
||||
+21
-6
@@ -1,33 +1,48 @@
|
||||
import pytest
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.init.migrate import convert_cols_file
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def columns_with_perspective():
|
||||
def columns_with_perspective() -> "Path":
|
||||
return fixture_path("columns_1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def columns_without_perspective():
|
||||
def columns_without_perspective() -> "Path":
|
||||
return fixture_path("columns_2")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def columns_with_view():
|
||||
def columns_with_view() -> "Path":
|
||||
return fixture_path("columns_3")
|
||||
|
||||
|
||||
@pytest.mark.config
|
||||
def test_columns_with_perspective(columns_with_perspective):
|
||||
def test_columns_with_perspective(columns_with_perspective: "Path") -> None:
|
||||
j = convert_cols_file(columns_with_perspective)
|
||||
if j is None:
|
||||
return
|
||||
assert "View" in j["cols"]
|
||||
assert "Max" in j["cols"]
|
||||
|
||||
|
||||
@pytest.mark.config
|
||||
def test_columns_without_perspective(columns_without_perspective):
|
||||
def test_columns_without_perspective(columns_without_perspective: "Path") -> None:
|
||||
j = convert_cols_file(columns_without_perspective)
|
||||
if j is None:
|
||||
return
|
||||
assert "View" not in j["cols"]
|
||||
assert "Max" not in j["cols"]
|
||||
|
||||
|
||||
@pytest.mark.config
|
||||
def test_columns_with_view(columns_with_view):
|
||||
def test_columns_with_view(columns_with_view: "Path") -> None:
|
||||
j = convert_cols_file(columns_with_view)
|
||||
assert j is None
|
||||
+17
-12
@@ -1,25 +1,30 @@
|
||||
import json
|
||||
import pytest
|
||||
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.boilerplate import config_boilerplate
|
||||
from dzgui.config.query import get_config
|
||||
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
||||
from dzgui.config import convert
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def legacy_config():
|
||||
def legacy_config() -> "Path":
|
||||
return fixture_path("dztuirc_1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unset_values():
|
||||
def unset_values() -> "Path":
|
||||
return fixture_path("dztuirc_3")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def keys():
|
||||
def keys() -> list[str]:
|
||||
return [
|
||||
"fav_server",
|
||||
"fav_label",
|
||||
@@ -36,7 +41,7 @@ def keys():
|
||||
|
||||
# TODO: use a static fixture instead of system config
|
||||
@pytest.fixture
|
||||
def config():
|
||||
def config() -> Any:
|
||||
paths = get_xdg_paths()
|
||||
xdg = parse_filepaths(paths)
|
||||
return get_config(xdg.config)
|
||||
@@ -44,21 +49,21 @@ def config():
|
||||
|
||||
@pytest.mark.post_install
|
||||
@pytest.mark.realconfig
|
||||
def test_invalid_config_value(config):
|
||||
def test_invalid_config_value(config: Any) -> None:
|
||||
with pytest.raises(Exception):
|
||||
assert config["foo"] is None
|
||||
|
||||
|
||||
@pytest.mark.post_install
|
||||
@pytest.mark.realconfig
|
||||
def test_default_config_values(keys, config):
|
||||
def test_default_config_values(keys: list[str], config: Any) -> None:
|
||||
for key in keys:
|
||||
assert config[key] is not None
|
||||
|
||||
|
||||
@pytest.mark.post_install
|
||||
@pytest.mark.realconfig
|
||||
def test_contains_invalid_values(keys, config):
|
||||
def test_contains_invalid_values(keys: list[str], config: Any) -> None:
|
||||
for key in config:
|
||||
assert key in keys
|
||||
|
||||
@@ -70,15 +75,15 @@ def test_contains_invalid_values(keys, config):
|
||||
("dztuirc_2", (False, False, False)),
|
||||
],
|
||||
)
|
||||
def test_bool_conversion(fixture, expect):
|
||||
fixture = fixture_path(fixture)
|
||||
j = convert.rc2json(fixture)
|
||||
def test_bool_conversion(fixture: str, expect: tuple[bool, bool, bool]) -> None:
|
||||
fixture_p = fixture_path(fixture)
|
||||
j = convert.rc2json(fixture_p)
|
||||
j = json.loads(j)
|
||||
assert j["fullscreen"] == expect[0]
|
||||
assert not j["use_miles"]
|
||||
|
||||
|
||||
def test_key_conversion(legacy_config):
|
||||
def test_key_conversion(legacy_config: "Path") -> None:
|
||||
j = convert.rc2json(legacy_config)
|
||||
j = json.loads(j)
|
||||
keys = [
|
||||
@@ -92,7 +97,7 @@ def test_key_conversion(legacy_config):
|
||||
assert key not in j
|
||||
|
||||
|
||||
def test_missing_values(unset_values):
|
||||
def test_missing_values(unset_values: "Path") -> None:
|
||||
j = convert.rc2json(unset_values)
|
||||
j = json.loads(j)
|
||||
assert j.keys() == config_boilerplate.keys()
|
||||
@@ -1,6 +1,8 @@
|
||||
import configparser
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from dzgui.const.constants import FLATPAK_APPID, GTK_APP_ID, TMP_PATH
|
||||
from dzgui.config.xdg import get_xdg_paths
|
||||
from dzgui.util.flatpak import apply_overrides
|
||||
@@ -13,17 +15,17 @@ pytestmark = pytest.mark.flatpak
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xdg_paths() -> dict[str, str]:
|
||||
def xdg_paths() -> dict[str, Path]:
|
||||
return get_xdg_paths()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def override(xdg_paths) -> None:
|
||||
def override(xdg_paths: dict[str, Path]) -> None:
|
||||
apply_overrides(xdg_paths)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config(xdg_paths) -> configparser.ConfigParser:
|
||||
def config(xdg_paths: dict[str, Path]) -> configparser.ConfigParser:
|
||||
conf_path = xdg_paths["XDG_DATA_HOME"].parent.joinpath(
|
||||
f"flatpak/overrides/{FLATPAK_APPID}"
|
||||
)
|
||||
@@ -32,14 +34,16 @@ def config(xdg_paths) -> configparser.ConfigParser:
|
||||
return config
|
||||
|
||||
|
||||
def test_session_bus(override, config) -> None:
|
||||
def test_session_bus(override: None, config: configparser.ConfigParser) -> None:
|
||||
bus = config[BUS_KEY][GTK_APP_ID]
|
||||
assert bus == "own"
|
||||
|
||||
|
||||
def test_paths(override, xdg_paths, config) -> None:
|
||||
expect: list[str] = []
|
||||
expect.append(TMP_PATH)
|
||||
def test_paths(
|
||||
override: None, xdg_paths: dict[str, Path], config: configparser.ConfigParser
|
||||
) -> None:
|
||||
expect: list[Path] = []
|
||||
expect.append(Path(TMP_PATH))
|
||||
expect.extend([*xdg_paths.values()])
|
||||
paths = config[PATHS_KEY][PATHS_SUBKEY].split(";")
|
||||
assert all(str(path) in paths for path in expect)
|
||||
@@ -2,10 +2,11 @@ import pytest
|
||||
|
||||
from dzgui.const.constants import FLATPAK_RUN_CMD, FLATPAK_SANDBOX
|
||||
from dzgui.const.enum import Preferences
|
||||
from dzgui.controllers.mc import Controller
|
||||
from dzgui.views.pages.options import FlatpakToggleField
|
||||
|
||||
|
||||
class MockController:
|
||||
class MockController(Controller):
|
||||
def __init__(self) -> None:
|
||||
self.command: str
|
||||
|
||||
|
||||
@@ -20,20 +20,23 @@ def get_db_len(path: Path) -> int:
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT COUNT(*) from ips")
|
||||
db_len = c.fetchone()[0]
|
||||
return db_len
|
||||
return int(db_len)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def csv() -> Path:
|
||||
csv = Path(tempfile.NamedTemporaryFile().name)
|
||||
url = find_download_url(DB_IP)
|
||||
if url is None:
|
||||
raise TypeError
|
||||
tmp = serialize(url)
|
||||
unzip(tmp, csv)
|
||||
strip_ipv6(csv)
|
||||
return csv
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_stripping(csv):
|
||||
def test_stripping(csv: Path) -> None:
|
||||
with open(csv, "rb") as f:
|
||||
first = f.readline().decode().rstrip()
|
||||
try:
|
||||
@@ -52,7 +55,7 @@ def test_stripping(csv):
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_row_parity(csv) -> None:
|
||||
def test_row_parity(csv: Path) -> None:
|
||||
rows = csv.read_text().splitlines()
|
||||
|
||||
db = Path(tempfile.NamedTemporaryFile().name)
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
import gi
|
||||
import pytest
|
||||
|
||||
from dzgui.api.servers import Details
|
||||
from dzgui.controllers.mc import Controller
|
||||
from dzgui.controllers.emitter import Emitter
|
||||
from dzgui.views.dialogs.servers import ServerDetailsDialog
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class MockController:
|
||||
class MockController(Controller):
|
||||
def __init__(self) -> None:
|
||||
self.window = Gtk.Window()
|
||||
pass
|
||||
|
||||
def get_window(self) -> Gtk.Window:
|
||||
return self.window
|
||||
def get_window(self) -> None: # type: ignore[override]
|
||||
return None
|
||||
|
||||
def get_server_name(self) -> str:
|
||||
return "My server"
|
||||
|
||||
def get_emitter(self) -> None:
|
||||
return None
|
||||
def get_emitter(self) -> Emitter:
|
||||
return Emitter()
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ from dzgui.views.components.entry import validate_ip_truthy
|
||||
("192.168.1.101:27016", True),
|
||||
],
|
||||
)
|
||||
def test_ip_entry(ip) -> None:
|
||||
def test_ip_entry(ip: tuple[str, bool]) -> None:
|
||||
string, state = ip
|
||||
assert validate_ip_truthy(string) is state
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import pytest
|
||||
|
||||
from typing import Any
|
||||
|
||||
from dzgui.model.model_factory import FastInsertListStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def heterogeneous_list() -> None:
|
||||
def heterogeneous_list() -> list[list[Any]]:
|
||||
return [["a", 0, True], ["b", 1, False]]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mismatched_length_list() -> None:
|
||||
def mismatched_length_list() -> list[list[Any]]:
|
||||
return [["a", 0, True], ["b", 1, False, 0]]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def extended_list(heterogeneous_list) -> None:
|
||||
def extended_list(heterogeneous_list: list[list[Any]]) -> FastInsertListStore:
|
||||
fs = FastInsertListStore(str, int, bool)
|
||||
fs.extend(heterogeneous_list)
|
||||
return fs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def appended_list(heterogeneous_list) -> None:
|
||||
def appended_list(heterogeneous_list: list[list[Any]]) -> FastInsertListStore:
|
||||
fs = FastInsertListStore(str, int, bool)
|
||||
for row in heterogeneous_list:
|
||||
fs.append(row)
|
||||
@@ -29,19 +31,21 @@ def appended_list(heterogeneous_list) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", ["extended_list", "appended_list"])
|
||||
def test_list_parity(heterogeneous_list, request, target) -> None:
|
||||
def test_list_parity(
|
||||
heterogeneous_list: list[list[Any]], request: pytest.FixtureRequest, target: str
|
||||
) -> None:
|
||||
target = request.getfixturevalue(target)
|
||||
extracted = [[el for el in row] for row in target]
|
||||
assert heterogeneous_list == extracted
|
||||
|
||||
|
||||
def test_type_disparity(heterogeneous_list) -> None:
|
||||
def test_type_disparity(heterogeneous_list: list[list[Any]]) -> None:
|
||||
with pytest.raises(TypeError):
|
||||
fs = FastInsertListStore(str, bool, bool)
|
||||
fs.extend(heterogeneous_list)
|
||||
|
||||
|
||||
def test_length_disparity(mismatched_length_list) -> None:
|
||||
def test_length_disparity(mismatched_length_list: list[list[Any]]) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
fs = FastInsertListStore(str, int, bool)
|
||||
fs.extend(mismatched_length_list)
|
||||
@@ -1,27 +1,32 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.util import localize
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
def test_de(monkeypatch):
|
||||
|
||||
def test_de(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setenv("LC_ALL", "de_DE.UTF-8")
|
||||
localize.set_locale()
|
||||
assert localize.number(9900) == "9.900"
|
||||
assert localize.number(99.88) == "99,88"
|
||||
|
||||
|
||||
def test_en(monkeypatch):
|
||||
def test_en(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setenv("LC_ALL", "en_US.UTF-8")
|
||||
localize.set_locale()
|
||||
assert localize.number(9900) == "9,900"
|
||||
assert localize.number(99.88) == "99.88"
|
||||
|
||||
|
||||
def test_none(monkeypatch):
|
||||
def test_none(monkeypatch: "MonkeyPatch") -> None:
|
||||
localize.set_locale()
|
||||
assert localize.number(9900) == "9,900"
|
||||
assert localize.number(99.88) == "99.88"
|
||||
|
||||
|
||||
def test_empty(monkeypatch):
|
||||
def test_empty(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setenv("LC_ALL", "")
|
||||
localize.set_locale()
|
||||
assert localize.number(9900) == "9,900"
|
||||
|
||||
@@ -7,13 +7,13 @@ from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
||||
class RecordsListHandler(logging.Handler):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.records_list = []
|
||||
self.records_list: list[logging.LogRecord] = []
|
||||
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
self.records_list.append(record)
|
||||
|
||||
def pop(self) -> None:
|
||||
return self.records_list[-1].msg
|
||||
def pop(self) -> str:
|
||||
return str(self.records_list[-1].msg)
|
||||
|
||||
|
||||
@pytest.mark.redact
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.api.steam import find_user_id
|
||||
from dzgui.const.constants import DEFAULT_STEAM_PATH
|
||||
@@ -8,6 +9,9 @@ from tests.fixtures import fixture_path
|
||||
|
||||
pytestmark = pytest.mark.apitest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture, expect",
|
||||
@@ -19,12 +23,14 @@ pytestmark = pytest.mark.apitest
|
||||
("api/loginusers_beta_client_multiple_2.vdf", 0),
|
||||
],
|
||||
)
|
||||
def test_loginusers(monkeypatch, fixture: str, expect: int) -> None:
|
||||
def test_loginusers(monkeypatch: "MonkeyPatch", fixture: str, expect: int) -> None:
|
||||
def mock_loginusers(path: Path) -> Path:
|
||||
return fixture_path(fixture)
|
||||
|
||||
monkeypatch.setattr("dzgui.api.steam.find_loginusers", mock_loginusers)
|
||||
uid = find_user_id(Path(""))
|
||||
if uid is None:
|
||||
raise TypeError
|
||||
assert int(uid) == expect
|
||||
|
||||
|
||||
|
||||
@@ -11,42 +11,52 @@ import dzgui.config.query
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.const.enums import Preferences
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from dzgui.const.enum import Preferences
|
||||
|
||||
pytestmark = pytest.mark.mods
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def versions() -> str:
|
||||
def versions() -> "Path":
|
||||
return fixture_path("dzg.versions")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp() -> str:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
tmp = f.name
|
||||
return tmp
|
||||
|
||||
|
||||
def mock_local_ids(path: Path) -> list[int]:
|
||||
return [3410710885, 3739934289]
|
||||
|
||||
|
||||
def mock_lookup(path: Path, prefs: "Preferences") -> str:
|
||||
return ""
|
||||
|
||||
def test_signatures(monkeypatch, tmp: str, versions: str) -> None:
|
||||
|
||||
def test_signatures(monkeypatch: "MonkeyPatch", tmp: str, versions: str) -> None:
|
||||
ids = [1559212036, 1654462998]
|
||||
shutil.copyfile(versions, tmp)
|
||||
path = Path(tmp)
|
||||
monkeypatch.setattr("dzgui.api.mods.lookup", mock_lookup)
|
||||
dzgui.api.mods.remove_stale_signatures(path, path, ids)
|
||||
with open(tmp, "r") as f:
|
||||
lines = f.readlines()
|
||||
assert ids not in lines
|
||||
lines = [line.rstrip() for line in f.readlines()]
|
||||
concat = ",".join(map(str, ids))
|
||||
assert concat not in lines
|
||||
|
||||
def test_signatures_with_no_ids(monkeypatch, tmp: str, versions: str) -> None:
|
||||
|
||||
def test_signatures_with_no_ids(
|
||||
monkeypatch: "MonkeyPatch", tmp: str, versions: str
|
||||
) -> None:
|
||||
shutil.copyfile(versions, tmp)
|
||||
path = Path(tmp)
|
||||
monkeypatch.setattr("dzgui.api.mods.get_local_mod_ids", mock_local_ids)
|
||||
monkeypatch.setattr("dzgui.api.mods.lookup", mock_lookup)
|
||||
dzgui.api.mods.remove_stale_signatures(path, path)
|
||||
with open(tmp, "r") as f:
|
||||
lines = f.readlines()
|
||||
assert lines == ["3410710885,1752864732\n", "3739934289,1780855136\n"]
|
||||
lines = [line.rstrip() for line in f.readlines()]
|
||||
assert lines == ["3410710885,1752864732", "3739934289,1780855136"]
|
||||
@@ -4,10 +4,13 @@ from pathlib import Path
|
||||
from dzgui.api.mods import tokenize
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
|
||||
@pytest.mark.mods
|
||||
@pytest.mark.parametrize("i", range(1, 7))
|
||||
def test_modmeta(i: int) -> None:
|
||||
fixture = fixture_path(f"cpp/mod{i}")
|
||||
path = Path(fixture)
|
||||
meta = tokenize(path)
|
||||
if meta is None:
|
||||
raise TypeError
|
||||
assert meta["name"] == "ModName"
|
||||
@@ -1,8 +1,13 @@
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.config.xdg import get_xdg_paths
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
def test_path_fallback(monkeypatch) -> None:
|
||||
|
||||
def test_path_fallback(monkeypatch: "MonkeyPatch") -> None:
|
||||
envs = ["XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CONFIG_HOME", "XDG_CACHE_HOME"]
|
||||
for env in envs:
|
||||
monkeypatch.setenv(env, "/etc/dzgui")
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.api.steam import is_dayz_downloading
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_list, expect",
|
||||
@@ -16,12 +20,14 @@ from dzgui.api.steam import is_dayz_downloading
|
||||
(["state_221100_1111"], False),
|
||||
],
|
||||
)
|
||||
def test_dayz_patch_pending(monkeypatch, file_list: list[str], expect: bool) -> None:
|
||||
def mock_get_pending_downloads(steam_path: Path, appid: int) -> None:
|
||||
def test_dayz_patch_pending(
|
||||
monkeypatch: "MonkeyPatch", file_list: list[str], expect: bool
|
||||
) -> None:
|
||||
def mock_get_pending_downloads(steam_path: Path, appid: int) -> list[Path]:
|
||||
files = [Path(file) for file in file_list]
|
||||
return files
|
||||
|
||||
monkeypatch.setattr(
|
||||
"dzgui.api.steam.get_pending_downloads", mock_get_pending_downloads
|
||||
)
|
||||
assert is_dayz_downloading(Path("/tmp"), Path("/tmp")) == expect
|
||||
assert is_dayz_downloading(Path("/tmp"), 221100) == expect
|
||||
+19
-11
@@ -1,13 +1,14 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import dzgui.api.pefile as PeFile
|
||||
from dzgui.api.steam import (
|
||||
VDFLoadError,
|
||||
AppNotInstalledError,
|
||||
AppMovedError,
|
||||
get_app_path
|
||||
get_app_path,
|
||||
)
|
||||
|
||||
from dzgui.config.query import get_config
|
||||
@@ -16,20 +17,22 @@ from dzgui.const.constants import APPID_DAYZ
|
||||
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
pytestmark = pytest.mark.apitest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_steam_path():
|
||||
def default_steam_path() -> str:
|
||||
paths = get_xdg_paths()
|
||||
xdg = parse_filepaths(paths)
|
||||
conf = get_config(xdg.config)
|
||||
return conf["default_steam_path"]
|
||||
return str(conf["default_steam_path"])
|
||||
|
||||
# TODO: additional parsing tests
|
||||
|
||||
# TODO: split into separate test file
|
||||
# FIXME: use fixtures
|
||||
def test_pefile_length(default_steam_path):
|
||||
def test_pefile_length(default_steam_path: str) -> None:
|
||||
try:
|
||||
pe_file_path = PeFile.get_pefile_path(Path(default_steam_path), APPID_DAYZ)
|
||||
vers = PeFile.get_dayz_version(pe_file_path)
|
||||
@@ -39,7 +42,7 @@ def test_pefile_length(default_steam_path):
|
||||
assert len(st) == 3
|
||||
|
||||
|
||||
def test_invalid_pefile_path():
|
||||
def test_invalid_pefile_path() -> None:
|
||||
with pytest.raises(VDFLoadError):
|
||||
try:
|
||||
PeFile.get_pefile_path(Path("/not/a/path"), APPID_DAYZ)
|
||||
@@ -47,6 +50,7 @@ def test_invalid_pefile_path():
|
||||
raise e
|
||||
|
||||
|
||||
# TODO: split into separate test file
|
||||
@pytest.mark.parametrize(
|
||||
"fixture, exception",
|
||||
[
|
||||
@@ -55,8 +59,11 @@ def test_invalid_pefile_path():
|
||||
("malformed.vdf", VDFLoadError),
|
||||
],
|
||||
)
|
||||
def test_not_in_library(monkeypatch, fixture, exception):
|
||||
def test_not_in_library(
|
||||
monkeypatch: "MonkeyPatch", fixture: str, exception: type[Exception]
|
||||
) -> None:
|
||||
import dzgui.api.steam as steam
|
||||
|
||||
monkeypatch.setattr(steam, "LIBRARYFOLDERS_PATH", fixture)
|
||||
|
||||
folder_path = Path(fixture_path("api"))
|
||||
@@ -68,12 +75,13 @@ def test_not_in_library(monkeypatch, fixture, exception):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def second_drive():
|
||||
def second_drive() -> str:
|
||||
return "in_library_on_second_drive.vdf"
|
||||
|
||||
|
||||
def test_on_second_drive(monkeypatch, second_drive):
|
||||
def test_on_second_drive(monkeypatch: "MonkeyPatch", second_drive: str) -> None:
|
||||
import dzgui.api.steam as steam
|
||||
|
||||
monkeypatch.setattr(steam, "LIBRARYFOLDERS_PATH", second_drive)
|
||||
folder_path = Path(fixture_path("api"))
|
||||
path = get_app_path(folder_path, APPID_DAYZ)
|
||||
|
||||
@@ -1,78 +1,93 @@
|
||||
import pytest
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from typing import Literal, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.constants import (
|
||||
FLATPAK_APPID,
|
||||
FLATPAK_ROOT_PATH,
|
||||
FLATPAK_RUN_CMD,
|
||||
FLATPAK_SANDBOX,
|
||||
)
|
||||
from dzgui.init import proc
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
pytestmark = pytest.mark.flatpak
|
||||
|
||||
|
||||
class TestSteamDetection(unittest.TestCase):
|
||||
def test_flatpak_detection_with_multiple_running_apps(self):
|
||||
def flatpak_output(args, *, text):
|
||||
# Default `flatpak ps` includes instance, PID, app and runtime columns.
|
||||
if args == ["flatpak", "ps", "--columns=application"]:
|
||||
return f"com.spotify.Client\n{FLATPAK_APPID}\n{FLATPAK_APPID}\n"
|
||||
return f"3904921611\t1257689\t{FLATPAK_APPID}\torg.freedesktop.Platform\n"
|
||||
def mock_has_cmd(cmd: str) -> Literal[True]:
|
||||
return True
|
||||
|
||||
with (
|
||||
patch.object(proc, "has_cmd", return_value=True),
|
||||
patch.object(proc.subprocess, "check_output", side_effect=flatpak_output),
|
||||
):
|
||||
for client in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX):
|
||||
with self.subTest(client=client):
|
||||
self.assertTrue(proc.is_steam_running(client))
|
||||
|
||||
def test_flatpak_not_running(self):
|
||||
with patch.object(proc, "has_cmd", return_value=True):
|
||||
for output in ("", "com.spotify.Client\n", f"{FLATPAK_APPID}Helper\n"):
|
||||
with (
|
||||
self.subTest(output=output),
|
||||
patch.object(proc.subprocess, "check_output", return_value=output),
|
||||
):
|
||||
self.assertFalse(proc.is_flatpak_steam_running())
|
||||
def mock_has_not_cmd(cmd: str) -> Literal[False]:
|
||||
return False
|
||||
|
||||
def test_flatpak_not_installed(self):
|
||||
with (
|
||||
patch.object(proc, "has_cmd", return_value=False),
|
||||
patch.object(proc.subprocess, "check_output") as check_output,
|
||||
):
|
||||
self.assertFalse(proc.is_flatpak_steam_running())
|
||||
check_output.assert_not_called()
|
||||
|
||||
def test_native_steam(self):
|
||||
with patch.object(proc, "has_cmd", return_value=True):
|
||||
for output, expected in (
|
||||
(
|
||||
[
|
||||
[FLATPAK_ROOT_PATH],
|
||||
"/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper",
|
||||
],
|
||||
True,
|
||||
),
|
||||
([[FLATPAK_ROOT_PATH]], False),
|
||||
([[FLATPAK_ROOT_PATH], ["steam-runtime-launcher-service"]], False),
|
||||
([["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"]], True),
|
||||
(
|
||||
[
|
||||
["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"],
|
||||
"steam-runtime-launcher-service",
|
||||
],
|
||||
True,
|
||||
),
|
||||
):
|
||||
with (
|
||||
self.subTest(running=output),
|
||||
patch.object(proc, "get_steam_procs", return_value=output),
|
||||
):
|
||||
self.assertEqual(proc.is_native_steam_running(), expected)
|
||||
def mock_get_flatpak_output() -> list[str]:
|
||||
lines = ["Application", "com.freedesktop.Platform", f"{FLATPAK_APPID}"]
|
||||
return lines
|
||||
|
||||
def test_invalid_client(self):
|
||||
with self.assertRaises(TypeError):
|
||||
proc.is_steam_running("invalid-client")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"output, expected",
|
||||
[
|
||||
(
|
||||
[
|
||||
[f"/home/user/{FLATPAK_ROOT_PATH}/.local/share/Steam"],
|
||||
["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"],
|
||||
],
|
||||
True,
|
||||
),
|
||||
(
|
||||
[
|
||||
[f"/home/user/{FLATPAK_ROOT_PATH}/.local/share/Steam"],
|
||||
["steam-runtime-launcher-service"],
|
||||
],
|
||||
False,
|
||||
),
|
||||
([[f"/home/user/{FLATPAK_ROOT_PATH}/.local/share/Steam"]], False),
|
||||
([["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"]], True),
|
||||
(
|
||||
[
|
||||
["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"],
|
||||
["steam-runtime-launcher-service"],
|
||||
],
|
||||
True,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_native_steam_running(
|
||||
monkeypatch: "MonkeyPatch", output: list[list[str]], expected: bool
|
||||
) -> None:
|
||||
def mock_get_steam_procs() -> list[list[str]]:
|
||||
return output
|
||||
|
||||
monkeypatch.setattr("dzgui.init.proc.has_cmd", mock_has_cmd)
|
||||
monkeypatch.setattr("dzgui.init.proc.get_steam_procs", mock_get_steam_procs)
|
||||
assert proc.is_native_steam_running() == expected
|
||||
|
||||
|
||||
def test_flatpak_running(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setattr("dzgui.init.proc.has_cmd", mock_has_cmd)
|
||||
monkeypatch.setattr("dzgui.init.proc.get_flatpak_output", mock_get_flatpak_output)
|
||||
assert proc.is_flatpak_steam_running()
|
||||
|
||||
|
||||
def test_flatpak_sandboxed(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setattr("dzgui.init.proc.has_cmd", mock_has_not_cmd)
|
||||
monkeypatch.setenv("FLATPAK_ID", FLATPAK_APPID)
|
||||
assert proc.is_flatpak_steam_running()
|
||||
|
||||
|
||||
def test_flatpak_not_installed(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setattr("dzgui.init.proc.has_cmd", mock_has_not_cmd)
|
||||
assert not proc.is_flatpak_steam_running()
|
||||
|
||||
|
||||
def test_steam_not_installed(monkeypatch: "MonkeyPatch") -> None:
|
||||
monkeypatch.setattr("dzgui.init.proc.has_cmd", mock_has_not_cmd)
|
||||
assert not proc.is_native_steam_running()
|
||||
|
||||
|
||||
def test_invalid_client() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
proc.is_steam_running("invalid-client")
|
||||
@@ -1,23 +1,25 @@
|
||||
import pytest
|
||||
|
||||
from typing import Any
|
||||
|
||||
from dzgui.api import probe
|
||||
from dzgui.config.query import get_config
|
||||
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
||||
from dzgui.api import probe
|
||||
|
||||
pytestmark = pytest.mark.webtest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config():
|
||||
def config() -> Any:
|
||||
paths = get_xdg_paths()
|
||||
xdg = parse_filepaths(paths)
|
||||
return get_config(xdg.config)
|
||||
|
||||
|
||||
def test_ipdb():
|
||||
def test_ipdb() -> None:
|
||||
assert probe.test_ipdb()
|
||||
|
||||
|
||||
def test_steam(config):
|
||||
def test_steam(config: Any) -> None:
|
||||
key = config["steam_api"]
|
||||
assert probe.test_steam_api(key)
|
||||
@@ -18,9 +18,10 @@ def test_row_conversion() -> None:
|
||||
"Unofficial",
|
||||
False,
|
||||
]
|
||||
rows = [ServerRow(*data)]
|
||||
rows = [ServerRow(*data)] # type: ignore
|
||||
marshalled = ServerRow.marshal_rows(rows)
|
||||
store = ModelFactory().make_server_store()
|
||||
store.extend(marshalled)
|
||||
converted = [[el for el in row] for row in store]
|
||||
cols = store.get_n_columns()
|
||||
converted = [[row[i] for i in range(cols)] for row in store]
|
||||
assert data == converted[0]
|
||||
@@ -3,9 +3,11 @@ import re
|
||||
|
||||
from dzgui.api.servers import Details, Record, get_details
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def details() -> Details:
|
||||
def details() -> Details | None:
|
||||
"""
|
||||
Static official server, metadata is expected to be invariate
|
||||
"""
|
||||
@@ -19,17 +21,21 @@ def test_invalid_server() -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key, expect", [("day_accel", 2.3), ("night_accel", 6.8)])
|
||||
def test_time_accel(details, key, expect) -> None:
|
||||
def test_time_accel(details: Details | None, key: str, expect: float) -> None:
|
||||
assert getattr(details, key) == expect
|
||||
|
||||
|
||||
def test_gametime(details) -> None:
|
||||
def test_gametime(details: Details | None) -> None:
|
||||
time_reg = r"^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]$)"
|
||||
if details is None:
|
||||
raise TypeError
|
||||
match = re.match(time_reg, details.gametime)
|
||||
assert match is not None
|
||||
|
||||
|
||||
def test_metadata(details) -> None:
|
||||
def test_metadata(details: Details | None) -> None:
|
||||
if details is None:
|
||||
raise TypeError
|
||||
for row in details.data:
|
||||
label, value = row
|
||||
match label:
|
||||
|
||||
+18
-17
@@ -1,9 +1,9 @@
|
||||
import pytest
|
||||
|
||||
from hypothesis import given, strategies as st
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from hypothesis import given, strategies as st
|
||||
from dzgui.views.dialogs.calc import (
|
||||
Emitter,
|
||||
LocalClock,
|
||||
ServerClock,
|
||||
Time,
|
||||
@@ -13,31 +13,28 @@ from dzgui.views.dialogs.calc import (
|
||||
)
|
||||
|
||||
|
||||
class DummyEmitter:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def connect(self, _, __) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])
|
||||
def accel_server_clock(request) -> None:
|
||||
def accel_server_clock(request: pytest.FixtureRequest) -> tuple[str, ServerClock]:
|
||||
clock_time, expect = request.param
|
||||
cur_time = Time(clock_time, 5.0, 2.0)
|
||||
emitter = DummyEmitter()
|
||||
emitter = Emitter()
|
||||
local_clock = LocalClock(emitter)
|
||||
server_clock = ServerClock(cur_time, emitter, local_clock)
|
||||
return expect, server_clock
|
||||
|
||||
|
||||
def test_accel_factor(accel_server_clock) -> None:
|
||||
def test_accel_factor(accel_server_clock: tuple[float, ServerClock]) -> None:
|
||||
"""Static test checking times at known boundaries"""
|
||||
expect, clock = accel_server_clock
|
||||
assert clock.get_accel_factor(clock.cur_time) == expect
|
||||
|
||||
|
||||
def reference_calc(start, end, day_factor, night_factor):
|
||||
def reference_calc(
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
day_factor: float,
|
||||
night_factor: float,
|
||||
) -> timedelta:
|
||||
"""
|
||||
Reference implementation to test validity of UI-side calculator
|
||||
Aggregates total seconds and applies accel factors after
|
||||
@@ -80,7 +77,7 @@ night_accel = st.floats(
|
||||
|
||||
|
||||
@st.composite
|
||||
def time_ranges(draw):
|
||||
def time_ranges(draw: st.DrawFn) -> tuple[datetime, datetime]:
|
||||
start_time = draw(st.times())
|
||||
deltas = st.timedeltas(min_value=timedelta(0), max_value=timedelta(days=1))
|
||||
duration = draw(deltas)
|
||||
@@ -91,12 +88,16 @@ def time_ranges(draw):
|
||||
|
||||
|
||||
@given(span=time_ranges(), day_factor=day_accel, night_factor=night_accel)
|
||||
def test_time_aggregation(span, day_factor, night_factor) -> None:
|
||||
def test_time_aggregation(
|
||||
span: tuple[datetime, datetime],
|
||||
day_factor: float,
|
||||
night_factor: float,
|
||||
) -> None:
|
||||
start, end = span
|
||||
|
||||
strtime = start.strftime("%H:%M")
|
||||
t = Time(strtime, day_factor, night_factor)
|
||||
emitter = DummyEmitter()
|
||||
emitter = Emitter()
|
||||
local_clock = LocalClock(emitter)
|
||||
server_clock = ServerClock(t, emitter, local_clock)
|
||||
h = end.time().hour
|
||||
|
||||
+41
-37
@@ -1,91 +1,95 @@
|
||||
import pytest
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from typing import Any, cast, TYPE_CHECKING
|
||||
|
||||
from dzgui.api.shortcuts import Shortcuts
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
pytestmark = pytest.mark.apitest
|
||||
|
||||
|
||||
def mock_find_shortcuts(self, steam_path: Path) -> Path:
|
||||
def mock_find_shortcuts(self: Shortcuts, steam_path: Path) -> Path:
|
||||
return Path(fixture_path("api/no_shortcuts.vdf"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def patch_api() -> None:
|
||||
def patch_api() -> Iterator[None]:
|
||||
mp = MonkeyPatch()
|
||||
mp.setattr("dzgui.api.shortcuts.Shortcuts.find_shortcuts_path", mock_find_shortcuts)
|
||||
yield
|
||||
mp.undo()
|
||||
|
||||
|
||||
def test_no_shortcuts(monkeypatch) -> None:
|
||||
def test_no_shortcuts(monkeypatch: "MonkeyPatch") -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
assert len(s.shortcuts["shortcuts"]) == 0
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_app() -> None:
|
||||
def dummy_app() -> dict[str, Any]:
|
||||
d = {
|
||||
"AppName": "TEST APP",
|
||||
"StartDir": "TEST_DIR",
|
||||
"Exe": "TEST_DIR/TEST_EXE.EXE",
|
||||
"icon": "IMAGES_DIR/TEST_IMAGE.PNG",
|
||||
"StartDir": Path("TEST_DIR"),
|
||||
"Exe": Path("TEST_DIR/TEST_EXE.EXE"),
|
||||
"icon": Path("IMAGES_DIR/TEST_IMAGE.PNG"),
|
||||
}
|
||||
return d
|
||||
|
||||
|
||||
def test_wrap_exe(dummy_app: dict[str, str]) -> None:
|
||||
@pytest.fixture
|
||||
def shortcuts(dummy_app: dict[str, Any]) -> Shortcuts:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
|
||||
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
|
||||
args = cast(tuple[str, Path, Path, Path], dummy_app.values())
|
||||
s.add_shortcut(*args)
|
||||
return s
|
||||
|
||||
|
||||
def test_add_shortcut(dummy_app: dict[str, str]) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
new = s.shortcuts["shortcuts"]
|
||||
def test_wrap_exe(shortcuts: Shortcuts) -> None:
|
||||
assert shortcuts.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
|
||||
assert shortcuts.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
|
||||
|
||||
|
||||
def test_add_shortcut(shortcuts: Shortcuts, dummy_app: dict[str, Any]) -> None:
|
||||
new = shortcuts.shortcuts["shortcuts"]
|
||||
ind = str(len(new) - 1)
|
||||
for k, v in dummy_app.items():
|
||||
if k == "Exe":
|
||||
v = f'"{v}"'
|
||||
assert new[ind][k] == v
|
||||
assert new[ind][k] == str(v)
|
||||
|
||||
|
||||
def test_save_shortcut(dummy_app: dict[str, str]) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
def test_save_shortcut(shortcuts: Shortcuts) -> None:
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
tmp = Path(f.name)
|
||||
s.shortcuts_path = tmp
|
||||
s.save_shortcuts()
|
||||
s._load_shortcuts(tmp)
|
||||
assert len(s.shortcuts["shortcuts"]) == 1
|
||||
shortcuts.shortcuts_path = tmp
|
||||
shortcuts.save_shortcuts()
|
||||
shortcuts._load_shortcuts(tmp)
|
||||
assert len(shortcuts.shortcuts["shortcuts"]) == 1
|
||||
|
||||
|
||||
def test_shortcut_crc(dummy_app: dict[str, str]) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
for key in s.shortcuts["shortcuts"].keys():
|
||||
entry = s.shortcuts["shortcuts"][key]
|
||||
def test_shortcut_crc(shortcuts: Shortcuts) -> None:
|
||||
for key in shortcuts.shortcuts["shortcuts"].keys():
|
||||
entry = shortcuts.shortcuts["shortcuts"][key]
|
||||
name = entry["AppName"]
|
||||
exe = entry["Exe"]
|
||||
uid = name + exe
|
||||
bpid = s.gen_bpid(uid)
|
||||
bpid = shortcuts.gen_bpid(uid)
|
||||
assert entry["appid"] & 0xFFFFFFFF == bpid
|
||||
|
||||
|
||||
def test_reverse_crc(dummy_app: dict[str, str]) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
for key in s.shortcuts["shortcuts"].keys():
|
||||
entry = s.shortcuts["shortcuts"][key]
|
||||
def test_reverse_crc(shortcuts: Shortcuts) -> None:
|
||||
for key in shortcuts.shortcuts["shortcuts"].keys():
|
||||
entry = shortcuts.shortcuts["shortcuts"][key]
|
||||
name = entry["AppName"]
|
||||
exe = entry["Exe"]
|
||||
uid = name + exe
|
||||
bpid = s.gen_bpid(uid)
|
||||
assert s.find_appname_by_unsigned_id(bpid) == name
|
||||
bpid = shortcuts.gen_bpid(uid)
|
||||
assert shortcuts.find_appname_by_unsigned_id(bpid) == name
|
||||
+2
-1
@@ -2,7 +2,8 @@ import re
|
||||
|
||||
from dzgui.model.proxy_model import DAY_REG, NIGHT_REG
|
||||
|
||||
def iterate(h: str, r: str) -> None:
|
||||
|
||||
def iterate(h: int, r: str) -> None:
|
||||
for m in range(60):
|
||||
time = f"{h:02}:{m:02}"
|
||||
assert re.match(r, time) is not None
|
||||
|
||||
Reference in new issue
Block a user