Skip to content

Commit

Permalink
Merge pull request #19 from Muddyblack/15-add-unittests
Browse files Browse the repository at this point in the history
restructured and tried tests for MetaDataEditor
  • Loading branch information
Muddyblack authored Dec 30, 2023
2 parents 2ba75ca + ab59417 commit 06f7862
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 98 deletions.
102 changes: 4 additions & 98 deletions MetaDataEditor/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
QPushButton,
QVBoxLayout,
QHBoxLayout,
QTextEdit,
QScrollArea,
QSizePolicy,
QFileDialog,
)
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QMovie, QPixmap, QIcon, QKeyEvent
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QMovie, QPixmap, QIcon

from image_metadata_handler import read_metadata, write_metadata
from custom_widgets import TagWidget

from image_metadata_handler import read_metadata, write_metadata

# Constants
SCRIPT_DIR_PATH = os.path.dirname(os.path.abspath(__file__))
Expand All @@ -29,8 +29,6 @@
DARKMODE_SYTLE_SHEET = f"{SCRIPT_DIR_PATH}/darkmode_style.qss"
LIGHTMODE_SYTLE_SHEET = f"{SCRIPT_DIR_PATH}/lightmode_style.qss"

TEXT_FIELD_HEIGHT = 50

IMAGE_FORMATS = "Images (*.png *.jpg *.bmp *.jpeg *.gif)"
VIDEO_FORMATS = "Videos (*.mp4 *.avi *.mkv *.flv *.mov)"

Expand All @@ -42,98 +40,6 @@ def load_styles(self, file):
self.setStyleSheet(style)


class TagTextEdit(QTextEdit):
"""
Custom QTextEdit that allows tabbing between widgets
"""

def keyPressEvent(self, event):
"""Overwrite the keyPressEvent to allow tabbing between widgets"""
if event.key() == Qt.Key_Tab and not event.modifiers():
self.focusNextPrevChild(True)
else:
super().keyPressEvent(event)

def event(self, event):
"""Overwrite the event to allow tabbing between widgets"""
if (
event.type() == QEvent.KeyPress
and event.key() == Qt.Key_Tab
and not event.modifiers()
):
QApplication.instance().sendEvent(
self.parent(), QKeyEvent(QEvent.KeyPress, Qt.Key_Tab, Qt.NoModifier)
)
return True
return super().event(event)

def focusInEvent(self, event):
"""Overwrite the focusInEvent to select all text"""
super().focusInEvent(event)
self.selectAll()


class TagWidget(QWidget):
"""
Custom widget for editing a single tag
"""

def __init__(self, tag_name="", tag_value="", parent=None):
super(TagWidget, self).__init__(parent)

self.tag_name_edit = TagTextEdit(self)
if tag_name.strip() != "":
self.tag_name_edit.setPlainText(tag_name)
else:
self.tag_name_edit.setPlaceholderText("Tag Name")
self.tag_name_edit.setFixedHeight(TEXT_FIELD_HEIGHT)

self.tag_value_edit = TagTextEdit(self)
if tag_value.strip() != "":
self.tag_value_edit.setPlainText(tag_value)
else:
self.tag_value_edit.setPlaceholderText("Tag Value")
self.tag_value_edit.setFixedHeight(TEXT_FIELD_HEIGHT)

self.remove_button = QPushButton("Remove", self)
self.remove_button.setObjectName("removeButton")

tag_layout = QHBoxLayout()
tag_layout.addWidget(self.tag_name_edit)
tag_layout.addWidget(self.tag_value_edit)
tag_layout.addWidget(self.remove_button)
tag_layout.setSpacing(10)

self.setLayout(tag_layout)

self.remove_button.clicked.connect(self.remove_self)
self.setTabOrder(self.tag_name_edit, self.tag_value_edit)

def set_values(self, tag_name, tag_value):
"""Sets the values of the tag_name_edit and tag_value_edit"""
self.tag_name_edit.setPlainText(tag_name)
self.tag_value_edit.setPlainText(tag_value)

def clear_fields(self):
"""Clears the tag_name_edit and tag_value_edit"""
try:
self.tag_name_edit.clear()
self.tag_value_edit.clear()
except:
pass

def remove_self(self):
"""Removes itself from the parent layout"""
try:
self.deleteLater()
except:
pass

def get_values(self):
"""Returns the values of the tag_name_edit and tag_value_edit"""
return self.tag_name_edit.toPlainText(), self.tag_value_edit.toPlainText()


class ImageEditorGUI(QWidget):
"""
Main GUI Window for editing images MetaData
Expand Down
104 changes: 104 additions & 0 deletions MetaDataEditor/custom_widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
""" This module contains custom widgets for the MetaDataEditor """
from PyQt5.QtWidgets import ( # pylint: disable=no-name-in-module
QApplication,
QWidget,
QTextEdit,
QPushButton,
QHBoxLayout,
)
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QKeyEvent


class TagTextEdit(QTextEdit):
"""
Custom QTextEdit that allows tabbing between widgets
"""

def keyPressEvent(self, event):
"""Overwrite the keyPressEvent to allow tabbing between widgets"""
if event.key() == Qt.Key_Tab and not event.modifiers():
self.focusNextPrevChild(True)
else:
super().keyPressEvent(event)

def event(self, event):
"""Overwrite the event to allow tabbing between widgets"""
if (
event.type() == QEvent.KeyPress
and event.key() == Qt.Key_Tab
and not event.modifiers()
):
QApplication.instance().sendEvent(
self.parent(), QKeyEvent(QEvent.KeyPress, Qt.Key_Tab, Qt.NoModifier)
)
return True
return super().event(event)

def focusInEvent(self, event):
"""Overwrite the focusInEvent to select all text"""
super().focusInEvent(event)
self.selectAll()


class TagWidget(QWidget):
"""
Custom widget for editing a single tag
"""

def __init__(self, tag_name="", tag_value="", text_field_height=50, parent=None):
super(TagWidget, self).__init__(parent)

self.TEXT_FIELD_HEIGHT = text_field_height

self.tag_name_edit = TagTextEdit(self)
if tag_name.strip() != "":
self.tag_name_edit.setPlainText(tag_name)
else:
self.tag_name_edit.setPlaceholderText("Tag Name")
self.tag_name_edit.setFixedHeight(self.TEXT_FIELD_HEIGHT)

self.tag_value_edit = TagTextEdit(self)
if tag_value.strip() != "":
self.tag_value_edit.setPlainText(tag_value)
else:
self.tag_value_edit.setPlaceholderText("Tag Value")
self.tag_value_edit.setFixedHeight(self.TEXT_FIELD_HEIGHT)

self.remove_button = QPushButton("Remove", self)
self.remove_button.setObjectName("removeButton")

tag_layout = QHBoxLayout()
tag_layout.addWidget(self.tag_name_edit)
tag_layout.addWidget(self.tag_value_edit)
tag_layout.addWidget(self.remove_button)
tag_layout.setSpacing(10)

self.setLayout(tag_layout)

self.remove_button.clicked.connect(self.remove_self)
self.setTabOrder(self.tag_name_edit, self.tag_value_edit)

def set_values(self, tag_name, tag_value):
"""Sets the values of the tag_name_edit and tag_value_edit"""
self.tag_name_edit.setPlainText(tag_name)
self.tag_value_edit.setPlainText(tag_value)

def clear_fields(self):
"""Clears the tag_name_edit and tag_value_edit"""
try:
self.tag_name_edit.clear()
self.tag_value_edit.clear()
except:
pass

def remove_self(self):
"""Removes itself from the parent layout"""
try:
self.deleteLater()
except:
pass

def get_values(self):
"""Returns the values of the tag_name_edit and tag_value_edit"""
return self.tag_name_edit.toPlainText(), self.tag_value_edit.toPlainText()
5 changes: 5 additions & 0 deletions MetaDataEditor/unittests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import os
import sys

PARENT_DIRECTORY_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(PARENT_DIRECTORY_PATH)
70 changes: 70 additions & 0 deletions MetaDataEditor/unittests/test_custom_widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# pylint: disable=C
# pylint: disable=unused-import
# pylint: disable=pointless-string-statement
import __init__
import unittest
from PyQt5.QtWidgets import QApplication
from custom_widgets import TagWidget, TagTextEdit
from PyQt5.QtGui import QKeyEvent, QFocusEvent
from PyQt5.QtCore import Qt, QEvent


class TestTagWidget(unittest.TestCase):
def setUp(self):
self.app = QApplication([])

def tearDown(self):
self.app.quit()

def test_set_values(self):
widget = TagWidget()
widget.set_values("TagName", "TagValue")
self.assertEqual(widget.tag_name_edit.toPlainText(), "TagName")
self.assertEqual(widget.tag_value_edit.toPlainText(), "TagValue")

def test_clear_fields(self):
widget = TagWidget()
widget.set_values("TagName", "TagValue")
widget.clear_fields()
self.assertEqual(widget.tag_name_edit.toPlainText(), "")
self.assertEqual(widget.tag_value_edit.toPlainText(), "")

def test_remove_self(self):
widget = TagWidget()
parent_layout = widget.parent().layout() if widget.parent() else None
widget.remove_self()
self.assertIsNone(parent_layout)

def test_get_values(self):
widget = TagWidget()
widget.set_values("TagName", "TagValue")
result = widget.get_values()
self.assertEqual(result, ("TagName", "TagValue"))


#!!! This test is not working
"""
class TestTagTextEdit(unittest.TestCase):
def setUp(self):
self.app = QApplication([])
def tearDown(self):
self.app.quit()
def test_tab_key_press_event(self):
text_edit = TagTextEdit()
event = QKeyEvent(QEvent.KeyPress, Qt.Key_Tab, Qt.NoModifier)
text_edit.keyPressEvent(event)
# Add assertions based on the expected behavior of Tab key press event
def test_focus_in_event(self):
text_edit = TagTextEdit()
# Simulate a focus in event
text_edit.focusInEvent(QFocusEvent(QEvent.FocusIn))
# Add assertions based on the expected behavior of focus in event
# Add more tests as needed
"""

if __name__ == "__main__":
unittest.main()
76 changes: 76 additions & 0 deletions MetaDataEditor/unittests/test_image_metadata_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# pylint: disable=C
# pylint: disable=unused-import
import __init__
import unittest
from unittest.mock import patch, MagicMock
from PIL import Image
from image_metadata_handler import read_metadata, write_metadata


class TestImageMetadataHandler(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.file_format = "JPEG"

def setUp(self):
self.mock_open = patch("image_metadata_handler.Image.open").start()
self.mock_image = self.create_mock_image()
self.mock_open.return_value = self.mock_image

def tearDown(self):
patch.stopall()

def create_mock_image(self, format=None, exif=None, text=None, info=None):
mock_image = MagicMock(spec=Image.Image)
mock_image.format = format or self.file_format
mock_image.getexif.return_value = exif or {}
mock_image.text = text or {}
mock_image.info = info or {}
return mock_image

def test_read_metadata(self):
# Mock the Image object returned by Image.open
exif_data = {274: 1}
text_data = {"key": "value"}
info_data = {"dpi": (72, 72)}
self.mock_image = self.create_mock_image(
exif=exif_data, text=text_data, info=info_data
)
self.mock_open.return_value = self.mock_image

# Call the function and check the returned metadata
metadata = read_metadata("test.jpg")
expected_metadata = {"Orientation": 1, "key": "value", "dpi": (72, 72)}
self.assertEqual(metadata, expected_metadata)

def test_read_metadata_no_exif(self):
# Mock the Image object returned by Image.open
text_data = {"key": "value"}
info_data = {"dpi": (72, 72)}
self.mock_image = self.create_mock_image(text=text_data, info=info_data)
self.mock_open.return_value = self.mock_image

# Call the function and check the returned metadata
metadata = read_metadata("test.jpg")
expected_metadata = {"key": "value", "dpi": (72, 72)}
self.assertEqual(metadata, expected_metadata)

def test_write_metadata(self):
# Call the function with some test metadata
write_metadata("test.jpg", "./test", {"Orientation": 1})

# Check that the image was saved with the correct metadata
self.mock_image.save.assert_called_once_with(
"./test.jpeg", exif={"Orientation": 1}
)

def test_write_metadata_no_exif(self):
# Call the function with no metadata
write_metadata("test.jpg", "./test", {})

# Check that the image was saved with no metadata
self.mock_image.save.assert_called_once_with("./test.jpeg", exif={})


if __name__ == "__main__":
unittest.main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 06f7862

Please sign in to comment.