Skip to content

Commit

Permalink
added unit tests for the ui components
Browse files Browse the repository at this point in the history
  • Loading branch information
Samweli committed Jul 14, 2024
1 parent d00e374 commit e4dbd6d
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 59 deletions.
62 changes: 49 additions & 13 deletions admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

import os
import sys

import configparser
import datetime as dt
Expand Down Expand Up @@ -61,6 +62,20 @@ def main(context: typer.Context, verbose: bool = False, qgis_profile: str = "def
}


def _qgis_profile_path() -> str:
"""Returns the path segment to QGIS profiles folder based on the platform.
:returns: Correct path segment corresponding to the current platform.
:rtype: str
"""
if sys.platform == "win32":
app_data_dir = "AppData/Roaming"
else:
app_data_dir = ".local/share"

return f"{app_data_dir}/QGIS/QGIS3/profiles/"


@app.command()
def install(context: typer.Context, build_src: bool = True):
"""Deploys plugin to QGIS plugins directory
Expand All @@ -80,8 +95,7 @@ def install(context: typer.Context, build_src: bool = True):
)

root_directory = (
Path.home() / f".local/share/QGIS/QGIS3/profiles/"
f"{context.obj['qgis_profile']}"
Path.home() / f"{_qgis_profile_path()}{context.obj['qgis_profile']}"
)

base_target_directory = root_directory / "python/plugins" / SRC_NAME
Expand All @@ -104,8 +118,7 @@ def symlink(context: typer.Context):
build_path = LOCAL_ROOT_DIR / "build" / SRC_NAME

root_directory = (
Path.home() / f".local/share/QGIS/QGIS3/profiles/"
f"{context.obj['qgis_profile']}"
Path.home() / f"{_qgis_profile_path()}{context.obj['qgis_profile']}"
)

destination_path = root_directory / "python/plugins" / SRC_NAME
Expand All @@ -124,8 +137,7 @@ def uninstall(context: typer.Context):
:type context: typer.Context
"""
root_directory = (
Path.home() / f".local/share/QGIS/QGIS3/profiles/"
f"{context.obj['qgis_profile']}"
Path.home() / f"{_qgis_profile_path()}{context.obj['qgis_profile']}"
)
base_target_directory = root_directory / "python/plugins" / SRC_NAME
shutil.rmtree(str(base_target_directory), ignore_errors=True)
Expand All @@ -136,6 +148,7 @@ def uninstall(context: typer.Context):
def generate_zip(
context: typer.Context,
version: str = None,
file_name: str = None,
output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "dist",
):
"""Generates plugin zip folder, that can be used to installed the
Expand All @@ -147,14 +160,20 @@ def generate_zip(
:param version: Plugin version
:type version: str
:param file_name: Plugin zip file name
:type file_name: str
:param output_directory: Directory where the zip folder will be saved.
:type context: Path
"""
build_dir = build(context)
metadata = _get_metadata()
plugin_version = metadata["version"] if version is None else version
output_directory.mkdir(parents=True, exist_ok=True)
zip_path = output_directory / f"{SRC_NAME}.{plugin_version}.zip"
zip_file_name = (
f"{SRC_NAME}.{plugin_version}.zip" if file_name is None else file_name
)
zip_path = output_directory / f"{zip_file_name}"
with zipfile.ZipFile(zip_path, "w") as fh:
_add_to_zip(build_dir, fh, arc_path_base=build_dir.parent)
typer.echo(
Expand Down Expand Up @@ -190,7 +209,7 @@ def build(
:returns: Build directory path.
:rtype: Path
"""
if clean:
if clean and output_directory.exists():
shutil.rmtree(str(output_directory), ignore_errors=True)
output_directory.mkdir(parents=True, exist_ok=True)
copy_source_files(output_directory, tests=tests)
Expand All @@ -201,7 +220,6 @@ def build(
generate_metadata(context, output_directory)
return output_directory


@app.command()
def copy_icon(
output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "build/temp",
Expand Down Expand Up @@ -247,14 +265,26 @@ def copy_source_files(
for child in (LOCAL_ROOT_DIR / "src" / SRC_NAME).iterdir():
if child.name != "__pycache__":
target_path = output_directory / child.name
handler = shutil.copytree if child.is_dir() else shutil.copy
handler(str(child.resolve()), str(target_path))
if child.is_dir():
shutil.copytree(
str(child.resolve()),
str(target_path),
ignore=shutil.ignore_patterns("*.pyc", "__pycache*"),
)
else:
shutil.copy(str(child.resolve()), str(target_path))
if tests:
for child in LOCAL_ROOT_DIR.iterdir():
if child.name in TEST_FILES:
target_path = output_directory / child.name
handler = shutil.copytree if child.is_dir() else shutil.copy
handler(str(child.resolve()), str(target_path))
if child.is_dir():
shutil.copytree(
str(child.resolve()),
str(target_path),
ignore=shutil.ignore_patterns("*.pyc", "__pycache*"),
)
else:
shutil.copy(str(child.resolve()), str(target_path))


@app.command()
Expand All @@ -273,6 +303,12 @@ def compile_resources(
resources_path = LOCAL_ROOT_DIR / "resources" / "resources.qrc"
target_path = output_directory / "resources.py"
target_path.parent.mkdir(parents=True, exist_ok=True)

# Windows handling of paths for shlex.split function
if sys.platform == "win32":
target_path = target_path.as_posix()
resources_path = resources_path.as_posix()

_log(f"compile_resources target_path: {target_path}", context=context)
subprocess.run(shlex.split(f"pyrcc5 -o {target_path} {resources_path}"))

Expand Down
4 changes: 3 additions & 1 deletion run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ QGIS_IMAGE=qgis/qgis
QGIS_IMAGE_latest=latest
QGIS_IMAGE_V_3_26=release-3_26

QGIS_VERSION_TAGS=($QGIS_IMAGE_latest $QGIS_IMAGE_V_3_26)
QGIS_VERSION_TAGS=($QGIS_IMAGE_V_3_26)

export IMAGE=$QGIS_IMAGE

python admin.py build --tests

for TAG in "${QGIS_VERSION_TAGS[@]}"
do
echo "Running tests for QGIS $TAG"
Expand Down
8 changes: 4 additions & 4 deletions scripts/docker/qgis-gea-plugin-test-pre-scripts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

qgis_setup.sh

# FIX default installation because the sources must be in "qgis-gea-plugin" parent folder
rm -rf /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-gea-plugin
ln -sf /tests_directory /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-gea-plugin
ln -sf /tests_directory /usr/share/qgis/python/plugins/qgis-gea-plugin
# FIX default installation because the sources must be in "qgis_gea_plugin" parent folder
rm -rf /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis_gea_plugin
ln -sf /tests_directory /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis_gea_plugin
ln -sf /tests_directory /usr/share/qgis/python/plugins/qgis_gea_plugin
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

"""
The plugin main window class file
The plugin main window class file.
"""

import os
Expand All @@ -17,29 +17,37 @@
from qgis.core import QgsProject, QgsInterval, QgsUnitTypes, QgsTemporalNavigationObject
from qgis.gui import QgsLayerTreeView

from qgis.utils import iface

from ..resources import *

from ..models.base import IMAGERY
from ..definitions.defaults import ANIMATION_PLAY_ICON, ANIMATION_PAUSE_ICON, PLUGIN_ICON
from ..conf import settings_manager, Settings
from ..utils import animation_state_change, log, tr


WidgetUi, _ = loadUiType(
os.path.join(os.path.dirname(__file__), "../ui/main_dockwidget.ui")
)


class QgisGeaPlugin(QtWidgets.QDockWidget, WidgetUi):
"""Main plugin UI"""
"""
Main plugin UI class for QGIS GEA Plugin.
This class represents the main dock widget for the plugin, providing
functionality for temporal navigation, layer management and plugin settings.
"""

def __init__(self, iface, parent=None):
"""
Initialize the QGIS Gea Plugin dock widget.
def __init__(
self,
iface,
parent=None,
):
:param iface: Reference to the QGIS interface.
:type iface: QgsInterface
:param parent: Parent widget. Defaults to None.
:type parent: QWidget
"""
super().__init__(parent)
self.setupUi(self)
self.iface = iface
Expand All @@ -54,9 +62,7 @@ def __init__(
icon_pixmap = QtGui.QPixmap(PLUGIN_ICON)
self.icon_la.setPixmap(icon_pixmap)

self.play_btn.setIcon(
QtGui.QIcon(ANIMATION_PLAY_ICON)
)
self.play_btn.setIcon(QtGui.QIcon(ANIMATION_PLAY_ICON))

self.time_values = []

Expand Down Expand Up @@ -86,26 +92,40 @@ def __init__(
self.slider_value_changed
)

iface.projectRead.connect(self.prepare_time_slider)
self.iface.projectRead.connect(self.prepare_time_slider)

def slider_value_changed(self, value):
"""
Slot function for handling time slider value change.
:param value: New value of the slider.
:type value: int
"""
self.navigation_object.setCurrentFrameNumber(value)

def animate_layers(self):
"""
Toggle animation of layers based on the current animation state.
This function is called when user press the play button.
"""
if self.navigation_object.animationState() == \
QgsTemporalNavigationObject.AnimationState.Idle:
self.play_btn.setIcon(
QtGui.QIcon(ANIMATION_PAUSE_ICON)
)
QgsTemporalNavigationObject.AnimationState.Idle:
self.play_btn.setIcon(QtGui.QIcon(ANIMATION_PAUSE_ICON))
self.play_btn.setToolTip(tr("Pause animation"))
self.navigation_object.playForward()
else:
self.navigation_object.pause()
self.play_btn.setIcon(
QtGui.QIcon(ANIMATION_PLAY_ICON)
)
self.play_btn.setToolTip(tr("Click to play animation"))
self.play_btn.setIcon(QtGui.QIcon(ANIMATION_PLAY_ICON))

def temporal_range_changed(self, temporal_range):
iface.mapCanvas().setTemporalRange(temporal_range)
"""
Update temporal range and UI elements when temporal range changes.
:param temporal_range: New temporal range.
:type temporal_range: QgsDateTimeRange
"""
self.iface.mapCanvas().setTemporalRange(temporal_range)
self.temporal_range_la.setText(
tr(
f'Current time range: '
Expand All @@ -118,13 +138,14 @@ def temporal_range_changed(self, temporal_range):

# On the last animation frame
if self.navigation_object.currentFrameNumber() == \
len(self.navigation_object.availableTemporalRanges()) - 1:
len(self.navigation_object.availableTemporalRanges()) - 1:

self.play_btn.setIcon(
QtGui.QIcon(ANIMATION_PLAY_ICON)
)
self.play_btn.setIcon(QtGui.QIcon(ANIMATION_PLAY_ICON))

def prepare_time_slider(self):
"""
Prepare the time slider based on current selected imagery type.
"""
values = []
set_layer = None
active_layer = None
Expand Down Expand Up @@ -161,7 +182,7 @@ def prepare_time_slider(self):
temporal_range = values[0] if len(values) > 0 else None

if temporal_range:
iface.mapCanvas().setTemporalRange(temporal_range)
self.iface.mapCanvas().setTemporalRange(temporal_range)
self.temporal_range_la.setText(
tr(
f'Current time range: '
Expand All @@ -170,7 +191,15 @@ def prepare_time_slider(self):
))

def update_layer_group(self, layer, show=False):
"""
Update visibility of provided layer parent group.
:param layer: Layer to update.
:type layer: QgsMapLayer
:param show: Group visibility state. Defaults to False.
:type show: bool
"""
if layer is not None:
root = QgsProject.instance().layerTreeRoot()
layer_tree = root.findLayer(layer.id())
Expand Down
3 changes: 3 additions & 0 deletions src/qgis_gea_plugin/ui/main_dockwidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@
</item>
<item row="2" column="2">
<widget class="QToolButton" name="play_btn">
<property name="toolTip">
<string>Play animation</string>
</property>
<property name="text">
<string>...</string>
</property>
Expand Down
Loading

0 comments on commit e4dbd6d

Please sign in to comment.