Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add testing support #88

Closed
wants to merge 10 commits into from
41 changes: 41 additions & 0 deletions tagstudio/src/core/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
VERSION: str = '9.2.0' # Major.Minor.Patch
VERSION_BRANCH: str = 'Alpha' # 'Alpha', 'Beta', or '' for Full Release

# The folder & file names where TagStudio keeps its data relative to a library.
TS_FOLDER_NAME: str = '.TagStudio'
BACKUP_FOLDER_NAME: str = 'backups'
COLLAGE_FOLDER_NAME: str = 'collages'
LIBRARY_FILENAME: str = 'ts_library.json'

# TODO: Turn this whitelist into a user-configurable blacklist.
IMAGE_TYPES: list[str] = ['png', 'jpg', 'jpeg', 'jpg_large', 'jpeg_large',
'jfif', 'gif', 'tif', 'tiff', 'heic', 'heif', 'webp',
'bmp', 'svg', 'avif', 'apng', 'jp2', 'j2k', 'jpg2']
VIDEO_TYPES: list[str] = ['mp4', 'webm', 'mov', 'hevc', 'mkv', 'avi', 'wmv',
'flv', 'gifv', 'm4p', 'm4v', '3gp']
AUDIO_TYPES: list[str] = ['mp3', 'mp4', 'mpeg4', 'm4a', 'aac', 'wav', 'flac',
'alac', 'wma', 'ogg', 'aiff']
DOC_TYPES: list[str] = ['txt', 'rtf', 'md',
'doc', 'docx', 'pdf', 'tex', 'odt', 'pages']
PLAINTEXT_TYPES: list[str] = ['txt', 'md', 'css', 'html', 'xml', 'json', 'js',
'ts', 'ini', 'htm', 'csv', 'php', 'sh', 'bat']
SPREADSHEET_TYPES: list[str] = ['csv', 'xls', 'xlsx', 'numbers', 'ods']
PRESENTATION_TYPES: list[str] = ['ppt', 'pptx', 'key', 'odp']
ARCHIVE_TYPES: list[str] = ['zip', 'rar', 'tar', 'tar.gz', 'tgz', '7z']
PROGRAM_TYPES: list[str] = ['exe', 'app']
SHORTCUT_TYPES: list[str] = ['lnk', 'desktop', 'url']

ALL_FILE_TYPES: list[str] = IMAGE_TYPES + VIDEO_TYPES + AUDIO_TYPES + \
DOC_TYPES + SPREADSHEET_TYPES + PRESENTATION_TYPES + \
ARCHIVE_TYPES + PROGRAM_TYPES + SHORTCUT_TYPES

BOX_FIELDS = ['tag_box', 'text_box']
TEXT_FIELDS = ['text_line', 'text_box']
DATE_FIELDS = ['datetime']

TAG_COLORS = ['', 'black', 'dark gray', 'gray', 'light gray', 'white', 'light pink',
'pink', 'red', 'red orange', 'orange', 'yellow orange', 'yellow',
'lime', 'light green', 'mint', 'green','teal', 'cyan', 'light blue',
'blue', 'blue violet', 'violet', 'purple', 'lavender', 'berry',
'magenta', 'salmon', 'auburn', 'dark brown', 'brown', 'light brown',
'blonde', 'peach', 'warm gray', 'cool gray', 'olive']
44 changes: 21 additions & 23 deletions tagstudio/src/core/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from enum import Enum
import ujson

from src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag
from src.core import ts_core
from src.core.utils.str import strip_punctuation
from src.core.utils.web import strip_web_protocol
from tagstudio.src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag
from tagstudio.src.core.constants import TS_FOLDER_NAME, BACKUP_FOLDER_NAME, COLLAGE_FOLDER_NAME, VERSION, TEXT_FIELDS
from tagstudio.src.core.utils.str import strip_punctuation
from tagstudio.src.core.utils.web import strip_web_protocol
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagstudio.src... import format breaks at runtime for vscode and when running from the terminal

Suggested change
from tagstudio.src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag
from tagstudio.src.core.constants import TS_FOLDER_NAME, BACKUP_FOLDER_NAME, COLLAGE_FOLDER_NAME, VERSION, TEXT_FIELDS
from tagstudio.src.core.utils.str import strip_punctuation
from tagstudio.src.core.utils.web import strip_web_protocol
from src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag
from src.core.constants import TS_FOLDER_NAME, BACKUP_FOLDER_NAME, COLLAGE_FOLDER_NAME, VERSION, TEXT_FIELDS
from src.core.utils.str import strip_punctuation
from src.core.utils.web import strip_web_protocol

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.


TYPE = ['file', 'meta', 'alt', 'mask']
# RESULT_TYPE = Enum('Result', ['ENTRY', 'COLLATION', 'TAG_GROUP'])
Expand Down Expand Up @@ -154,8 +154,6 @@ def add_tag(self, library:'Library', tag_id:int, field_id:int, field_index:int=N
self.fields[field_index][field_id] = sorted(tags, key=lambda t: library.get_tag(t).display_name(library))

# logging.info(f'Tags: {self.fields[field_index][field_id]}')



class Tag:
"""A Library Tag Object. Referenced by ID."""
Expand Down Expand Up @@ -546,8 +544,8 @@ def create_library(self, path) -> int:
path = os.path.normpath(path).rstrip('\\')

# If '.TagStudio' is included in the path, trim the path up to it.
if ts_core.TS_FOLDER_NAME in path:
path = path.split(ts_core.TS_FOLDER_NAME)[0]
if TS_FOLDER_NAME in path:
path = path.split(TS_FOLDER_NAME)[0]

try:
self.clear_internal_vars()
Expand All @@ -565,11 +563,11 @@ def verify_ts_folders(self) -> None:
"""Verifies/creates folders required by TagStudio."""

full_ts_path = os.path.normpath(
f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}')
f'{self.library_dir}/{TS_FOLDER_NAME}')
full_backup_path = os.path.normpath(
f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{ts_core.BACKUP_FOLDER_NAME}')
f'{self.library_dir}/{TS_FOLDER_NAME}/{BACKUP_FOLDER_NAME}')
full_collage_path = os.path.normpath(
f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{ts_core.COLLAGE_FOLDER_NAME}')
f'{self.library_dir}/{TS_FOLDER_NAME}/{COLLAGE_FOLDER_NAME}')

if not os.path.isdir(full_ts_path):
os.mkdir(full_ts_path)
Expand Down Expand Up @@ -606,13 +604,13 @@ def open_library(self, path: str) -> int:
path = os.path.normpath(path).rstrip('\\')

# If '.TagStudio' is included in the path, trim the path up to it.
if ts_core.TS_FOLDER_NAME in path:
path = path.split(ts_core.TS_FOLDER_NAME)[0]
if TS_FOLDER_NAME in path:
path = path.split(TS_FOLDER_NAME)[0]

if os.path.exists(os.path.normpath(f'{path}/{ts_core.TS_FOLDER_NAME}/ts_library.json')):
if os.path.exists(os.path.normpath(f'{path}/{TS_FOLDER_NAME}/ts_library.json')):

try:
with open(os.path.normpath(f'{path}/{ts_core.TS_FOLDER_NAME}/ts_library.json'), 'r', encoding='utf-8') as f:
with open(os.path.normpath(f'{path}/{TS_FOLDER_NAME}/ts_library.json'), 'r', encoding='utf-8') as f:
json_dump: JsonLibary = ujson.load(f)
self.library_dir = str(path)
self.verify_ts_folders()
Expand Down Expand Up @@ -788,9 +786,9 @@ def open_library(self, path: str) -> int:
if return_code == 1:

if not os.path.exists(os.path.normpath(
f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}')):
f'{self.library_dir}/{TS_FOLDER_NAME}')):
os.makedirs(os.path.normpath(
f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}'))
f'{self.library_dir}/{TS_FOLDER_NAME}'))

self._map_filenames_to_entry_ids()

Expand Down Expand Up @@ -832,7 +830,7 @@ def to_json(self):
Used in saving the library to disk.
"""

file_to_save: JsonLibary = {"ts-version": ts_core.VERSION,
file_to_save: JsonLibary = {"ts-version": VERSION,
"ignored_extensions": [],
"tags": [],
"collations": [],
Expand Down Expand Up @@ -869,7 +867,7 @@ def save_library_to_disk(self):

self.verify_ts_folders()

with open(os.path.normpath(f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{filename}'), 'w', encoding='utf-8') as outfile:
with open(os.path.normpath(f'{self.library_dir}/{TS_FOLDER_NAME}/{filename}'), 'w', encoding='utf-8') as outfile:
outfile.flush()
ujson.dump(self.to_json(), outfile, ensure_ascii=False, escape_forward_slashes=False)
# , indent=4 <-- How to prettyprint dump
Expand All @@ -886,7 +884,7 @@ def save_library_backup_to_disk(self) -> str:
filename = f'ts_library_backup_{datetime.datetime.utcnow().strftime("%F_%T").replace(":", "")}.json'

self.verify_ts_folders()
with open(os.path.normpath(f'{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{ts_core.BACKUP_FOLDER_NAME}/{filename}'), 'w', encoding='utf-8') as outfile:
with open(os.path.normpath(f'{self.library_dir}/{TS_FOLDER_NAME}/{BACKUP_FOLDER_NAME}/{filename}'), 'w', encoding='utf-8') as outfile:
outfile.flush()
ujson.dump(self.to_json(), outfile, ensure_ascii=False, escape_forward_slashes=False)
end_time = time.time()
Expand Down Expand Up @@ -932,11 +930,11 @@ def refresh_dir(self):
# Scans the directory for files, keeping track of:
# - Total file count
# - Files without library entries
# for type in ts_core.TYPES:
# for type in TYPES:
start_time = time.time()
for f in glob.glob(self.library_dir + "/**/*", recursive=True):
# p = Path(os.path.normpath(f))
if ('$RECYCLE.BIN' not in f and ts_core.TS_FOLDER_NAME not in f
if ('$RECYCLE.BIN' not in f and TS_FOLDER_NAME not in f
and 'tagstudio_thumbs' not in f and not os.path.isdir(f)):
if os.path.splitext(f)[1][1:].lower() not in self.ignored_extensions:
self.dir_file_count += 1
Expand Down Expand Up @@ -2034,7 +2032,7 @@ def add_field_to_entry(self, entry_id: int, field_id: int) -> None:
# entry = self.entries[entry_index]
entry = self.get_entry(entry_id)
field_type = self.get_field_obj(field_id)['type']
if field_type in ts_core.TEXT_FIELDS:
if field_type in TEXT_FIELDS:
entry.fields.append({int(field_id): ''})
elif field_type == 'tag_box':
entry.fields.append({int(field_id): []})
Expand Down
46 changes: 2 additions & 44 deletions tagstudio/src/core/ts_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,8 @@
import json
import os

from src.core.library import Entry, Library

VERSION: str = '9.2.0' # Major.Minor.Patch
VERSION_BRANCH: str = 'Alpha' # 'Alpha', 'Beta', or '' for Full Release

# The folder & file names where TagStudio keeps its data relative to a library.
TS_FOLDER_NAME: str = '.TagStudio'
BACKUP_FOLDER_NAME: str = 'backups'
COLLAGE_FOLDER_NAME: str = 'collages'
LIBRARY_FILENAME: str = 'ts_library.json'

# TODO: Turn this whitelist into a user-configurable blacklist.
IMAGE_TYPES: list[str] = ['png', 'jpg', 'jpeg', 'jpg_large', 'jpeg_large',
'jfif', 'gif', 'tif', 'tiff', 'heic', 'heif', 'webp',
'bmp', 'svg', 'avif', 'apng', 'jp2', 'j2k', 'jpg2']
VIDEO_TYPES: list[str] = ['mp4', 'webm', 'mov', 'hevc', 'mkv', 'avi', 'wmv',
'flv', 'gifv', 'm4p', 'm4v', '3gp']
AUDIO_TYPES: list[str] = ['mp3', 'mp4', 'mpeg4', 'm4a', 'aac', 'wav', 'flac',
'alac', 'wma', 'ogg', 'aiff']
DOC_TYPES: list[str] = ['txt', 'rtf', 'md',
'doc', 'docx', 'pdf', 'tex', 'odt', 'pages']
PLAINTEXT_TYPES: list[str] = ['txt', 'md', 'css', 'html', 'xml', 'json', 'js',
'ts', 'ini', 'htm', 'csv', 'php', 'sh', 'bat']
SPREADSHEET_TYPES: list[str] = ['csv', 'xls', 'xlsx', 'numbers', 'ods']
PRESENTATION_TYPES: list[str] = ['ppt', 'pptx', 'key', 'odp']
ARCHIVE_TYPES: list[str] = ['zip', 'rar', 'tar', 'tar.gz', 'tgz', '7z']
PROGRAM_TYPES: list[str] = ['exe', 'app']
SHORTCUT_TYPES: list[str] = ['lnk', 'desktop', 'url']

ALL_FILE_TYPES: list[str] = IMAGE_TYPES + VIDEO_TYPES + AUDIO_TYPES + \
DOC_TYPES + SPREADSHEET_TYPES + PRESENTATION_TYPES + \
ARCHIVE_TYPES + PROGRAM_TYPES + SHORTCUT_TYPES

BOX_FIELDS = ['tag_box', 'text_box']
TEXT_FIELDS = ['text_line', 'text_box']
DATE_FIELDS = ['datetime']

TAG_COLORS = ['', 'black', 'dark gray', 'gray', 'light gray', 'white', 'light pink',
'pink', 'red', 'red orange', 'orange', 'yellow orange', 'yellow',
'lime', 'light green', 'mint', 'green','teal', 'cyan', 'light blue',
'blue', 'blue violet', 'violet', 'purple', 'lavender', 'berry',
'magenta', 'salmon', 'auburn', 'dark brown', 'brown', 'light brown',
'blonde', 'peach', 'warm gray', 'cool gray', 'olive']

from tagstudio.src.core.constants import TEXT_FIELDS
from tagstudio.src.core.library import Entry, Library
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagstudio.src... import format breaks at runtime for vscode and when running from the terminal

Suggested change
from tagstudio.src.core.constants import TEXT_FIELDS
from tagstudio.src.core.library import Entry, Library
from src.core.constants import TEXT_FIELDS
from src.core.library import Entry, Library

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.


class TagStudioCore:
"""
Expand Down
15 changes: 9 additions & 6 deletions tagstudio/src/core/utils/str.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio

def strip_punctuation(string: str) -> str:
def strip_punctuation(text: str) -> str:
"""Returns a given string stripped of all punctuation characters."""
return string.replace('(', '').replace(')', '').replace('[', '') \
.replace(']', '').replace('{', '').replace('}', '').replace("'", '') \
.replace('`', '').replace('’', '').replace('‘', '').replace('"', '') \
.replace('“', '').replace('”', '').replace('_', '').replace('-', '') \
.replace(' ', '').replace(' ', '')
punctuation = '{}[]()\'"`‘’“”-_  '
result = text

for p in punctuation:
result = result.replace(p, '')

return result

13 changes: 7 additions & 6 deletions tagstudio/src/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@
QMenu, QTableWidget, QTableWidgetItem)
from humanfriendly import format_timespan, format_size

from src.core.library import Collation, Entry, ItemType, Library, Tag
from src.core.palette import ColorType, get_tag_color
from src.core.ts_core import (PLAINTEXT_TYPES, TagStudioCore, TAG_COLORS, DATE_FIELDS, TEXT_FIELDS, BOX_FIELDS, ALL_FILE_TYPES,
from tagstudio.src.core.library import Collation, Entry, ItemType, Library, Tag
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line was previously reduced to only import ItemType as it is the only item used in ts_qt.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.

from tagstudio.src.core.palette import ColorType, get_tag_color
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line was removed in a previous commit and is no longer used in ts_qt.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.

from tagstudio.src.core.ts_core import TagStudioCore
from tagstudio.src.core.constants import (PLAINTEXT_TYPES, TAG_COLORS, DATE_FIELDS, TEXT_FIELDS, BOX_FIELDS, ALL_FILE_TYPES,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagstudio.src... import format breaks at runtime for vscode and when running from the terminal

Suggested change
from tagstudio.src.core.library import Collation, Entry, ItemType, Library, Tag
from tagstudio.src.core.palette import ColorType, get_tag_color
from tagstudio.src.core.ts_core import TagStudioCore
from tagstudio.src.core.constants import (PLAINTEXT_TYPES, TAG_COLORS, DATE_FIELDS, TEXT_FIELDS, BOX_FIELDS, ALL_FILE_TYPES,
from src.core.library import Collation, Entry, ItemType, Library, Tag
from src.core.palette import ColorType, get_tag_color
from src.core.ts_core import TagStudioCore
from src.core.constants import (PLAINTEXT_TYPES, TAG_COLORS, DATE_FIELDS, TEXT_FIELDS, BOX_FIELDS, ALL_FILE_TYPES,

SHORTCUT_TYPES, PROGRAM_TYPES, ARCHIVE_TYPES, PRESENTATION_TYPES,
SPREADSHEET_TYPES, DOC_TYPES, AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES,
LIBRARY_FILENAME, COLLAGE_FOLDER_NAME, BACKUP_FOLDER_NAME, TS_FOLDER_NAME,
VERSION_BRANCH, VERSION)
from src.core.utils.web import strip_web_protocol
from src.qt.flowlayout import FlowLayout, FlowWidget
from src.qt.main_window import Ui_MainWindow
from tagstudio.src.core.utils.web import strip_web_protocol
from tagstudio.src.qt.flowlayout import FlowLayout, FlowWidget
from tagstudio.src.qt.main_window import Ui_MainWindow
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagstudio.src... import format breaks at runtime for vscode and when running from the terminal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.

import src.qt.resources_rc

# SIGQUIT is not defined on Windows
Expand Down
9 changes: 9 additions & 0 deletions tagstudio/tests/main.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having luck getting python.exe -m unittest discover tagstudio.tests -t "tagstudio" to run from the <local_repo> directory but this file is throwing a fit working between all the different use cases.
Something like unittest.TestSuite() or unittest load test protocol might be more appropriate for the longer term and avoid needing to change the import format in the src library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added pytest instead of unittest and I found that running python -m pytest tests/ from the tagstudio folder works. Does not seem to work any other way though.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import unittest

if __name__ == '__main__':
"""Runs every test."""
test_loader = unittest.TestLoader()
test_suite = test_loader.discover(start_dir='src', pattern='test_*.py')

runner = unittest.TextTestRunner()
result = runner.run(test_suite)
File renamed without changes.
Empty file.
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from src.core.library import Tag
import unittest

from tagstudio.src.core.library import Tag
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagstudio.src... import format breaks at runtime for vscode and when running from the terminal

Suggested change
from tagstudio.src.core.library import Tag
from src.core.library import Tag

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.


class TestTags:

class TestTags(unittest.TestCase):
def test_construction(self):
tag = Tag(id=1, name='Tag Name', shorthand='TN', aliases=[
'First A', 'Second A'], subtags_ids=[2, 3, 4], color='')
assert (tag)
assert tag

def test_empty_construction(self):
tag = Tag(id=1, name='', shorthand='', aliases=[], subtags_ids=[], color='')
assert (tag)
assert tag
Empty file.
16 changes: 16 additions & 0 deletions tagstudio/tests/src/utils/test_str.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import unittest

from parameterized import parameterized

from tagstudio.src.core.utils.str import strip_punctuation
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagstudio.src... import format breaks at runtime for vscode and when running from the terminal

Suggested change
from tagstudio.src.core.utils.str import strip_punctuation
from src.core.utils.str import strip_punctuation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback! I have made the changes.



class StrTest(unittest.TestCase):
@parameterized.expand([
('{[(parenthesis)]}', 'parenthesis'),
('‘“`"\'quotes\'"`”’', 'quotes'),
('_-  spacers', 'spacers'),
('{}[]()\'"`‘’“”-  ', '')
])
def test_strip_punctuation(self, text, expected_output):
self.assertEqual(strip_punctuation(text), expected_output)