Clean up and add test coverage
This commit is contained in:
@@ -39,6 +39,7 @@ from PyQt5.QtWidgets import (
|
|||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.common import formatInt, getFileSize
|
from novelwriter.common import formatInt, getFileSize
|
||||||
|
from novelwriter.error import formatException
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ class GuiDictionaries(QDialog):
|
|||||||
i.stem for i in hunspell.iterdir() if i.is_file() and i.suffix == ".aff"
|
i.stem for i in hunspell.iterdir() if i.is_file() and i.suffix == ".aff"
|
||||||
)
|
)
|
||||||
self._appendLog(self.tr(
|
self._appendLog(self.tr(
|
||||||
"{0} additional dictionaries currently installed"
|
"Additional dictionaries found: {0}"
|
||||||
).format(len(self._currDicts)))
|
).format(len(self._currDicts)))
|
||||||
|
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
@@ -196,29 +197,21 @@ class GuiDictionaries(QDialog):
|
|||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _doImportHunspell(self):
|
def _doImportHunspell(self):
|
||||||
"""Import a hunspell dictionary from .sox or .oxt file."""
|
"""Import a hunspell dictionary from .sox or .oxt file."""
|
||||||
temp = self.huInput.text()
|
procErr = self.tr("Could not process dictionary file")
|
||||||
output = self._installPath
|
if self._installPath:
|
||||||
if not output:
|
temp = self.huInput.text()
|
||||||
return
|
if temp and (path := Path(temp)).is_file():
|
||||||
if output and temp and (path := Path(temp)).is_file():
|
hunspell = self._installPath / "hunspell"
|
||||||
hunspell = output / "hunspell"
|
hunspell.mkdir(exist_ok=True)
|
||||||
hunspell.mkdir(exist_ok=True)
|
try:
|
||||||
try:
|
nAff, nDic = self._extractDicts(path, hunspell)
|
||||||
with ZipFile(path, mode="r") as zipObj:
|
if nAff == 0 or nDic == 0:
|
||||||
for item in zipObj.namelist():
|
self._appendLog(procErr, err=True)
|
||||||
zPath = Path(item)
|
except Exception as exc:
|
||||||
if zPath.suffix in (".aff", ".dic"):
|
self._appendLog(procErr, err=True)
|
||||||
with zipObj.open(item) as zF:
|
self._appendLog(formatException(exc), err=True)
|
||||||
oPath = hunspell / zPath.name
|
else:
|
||||||
oPath.write_bytes(zF.read())
|
self._appendLog(procErr, err=True)
|
||||||
size = getFileSize(oPath)
|
|
||||||
self._appendLog(self.tr(
|
|
||||||
"Added: {0} [{1}B]"
|
|
||||||
).format(zPath.name, formatInt(size)))
|
|
||||||
except Exception as exc:
|
|
||||||
SHARED.error(self.tr("Could not process dictionary file."), exc=exc)
|
|
||||||
else:
|
|
||||||
SHARED.error(self.tr("File not found."))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -243,13 +236,40 @@ class GuiDictionaries(QDialog):
|
|||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
def _appendLog(self, text: str) -> None:
|
def _extractDicts(self, path: Path, output: Path) -> tuple[int, int]:
|
||||||
|
"""Extract a zip archive and return the number of .aff and .dic
|
||||||
|
files found in it.
|
||||||
|
"""
|
||||||
|
nAff = nDic = 0
|
||||||
|
with ZipFile(path, mode="r") as zipObj:
|
||||||
|
for item in zipObj.namelist():
|
||||||
|
zPath = Path(item)
|
||||||
|
if zPath.suffix not in (".aff", ".dic"):
|
||||||
|
continue
|
||||||
|
nAff += 1 if zPath.suffix == ".aff" else 0
|
||||||
|
nDic += 1 if zPath.suffix == ".dic" else 0
|
||||||
|
with zipObj.open(item) as zF:
|
||||||
|
oPath = output / zPath.name
|
||||||
|
oPath.write_bytes(zF.read())
|
||||||
|
size = getFileSize(oPath)
|
||||||
|
self._appendLog(self.tr(
|
||||||
|
"Added: {0} [{1}B]"
|
||||||
|
).format(zPath.name, formatInt(size)))
|
||||||
|
return nAff, nDic
|
||||||
|
|
||||||
|
def _appendLog(self, text: str, err: bool = False) -> None:
|
||||||
"""Append a line to the log output."""
|
"""Append a line to the log output."""
|
||||||
self.infoBox.moveCursor(QTextCursor.MoveOperation.End)
|
cursor = self.infoBox.textCursor()
|
||||||
if self.infoBox.textCursor().position() > 0:
|
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||||
self.infoBox.insertPlainText("\n")
|
if cursor.position() > 0:
|
||||||
self.infoBox.insertPlainText(text)
|
cursor.insertText("\n")
|
||||||
self.infoBox.moveCursor(QTextCursor.MoveOperation.End)
|
if err:
|
||||||
|
cursor.insertHtml(f"<font color='red'>{text}</font>")
|
||||||
|
else:
|
||||||
|
cursor.insertText(text)
|
||||||
|
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||||
|
cursor.deleteChar()
|
||||||
|
self.infoBox.setTextCursor(cursor)
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiDictionaries
|
# END Class GuiDictionaries
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"""
|
||||||
|
novelWriter – Dictionary Downloader Tester
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
This file is a part of novelWriter
|
||||||
|
Copyright 2018–2023, Veronica Berglyd Olsen
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import enchant
|
||||||
|
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from tools import getGuiItem
|
||||||
|
from mocked import causeException
|
||||||
|
|
||||||
|
from PyQt5.QtGui import QDesktopServices
|
||||||
|
from PyQt5.QtWidgets import QFileDialog
|
||||||
|
|
||||||
|
from novelwriter import SHARED
|
||||||
|
from novelwriter.tools.dictionaries import GuiDictionaries
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testToolDictionaries_Main(qtbot, monkeypatch, nwGUI, fncPath):
|
||||||
|
"""Test the Dictionaries downloader tool."""
|
||||||
|
monkeypatch.setattr(enchant, "get_user_config_dir", lambda *a: str(fncPath))
|
||||||
|
|
||||||
|
# Fail to open
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(enchant, "get_user_config_dir", lambda *a: causeException)
|
||||||
|
nwGUI.showDictionariesDialog()
|
||||||
|
assert SHARED.alert is not None
|
||||||
|
assert SHARED.alert.logMessage == "Could not initialise the dialog."
|
||||||
|
|
||||||
|
# Open the tool
|
||||||
|
nwGUI.showDictionariesDialog()
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiDictionaries") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwDicts = getGuiItem("GuiDictionaries")
|
||||||
|
assert isinstance(nwDicts, GuiDictionaries)
|
||||||
|
assert nwDicts.isVisible()
|
||||||
|
assert nwDicts.inPath.text() == str(fncPath)
|
||||||
|
|
||||||
|
# Allow Open Dir
|
||||||
|
SHARED._alert = None
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QDesktopServices, "openUrl", lambda *a: None)
|
||||||
|
nwDicts._doOpenInstallLocation()
|
||||||
|
assert SHARED.alert is None
|
||||||
|
|
||||||
|
# Fail Open Dir
|
||||||
|
nwDicts.inPath.setText("/foo/bar")
|
||||||
|
nwDicts._doOpenInstallLocation()
|
||||||
|
assert SHARED.alert is not None
|
||||||
|
assert SHARED.alert.logMessage == "Path not found."
|
||||||
|
nwDicts.inPath.setText(str(fncPath))
|
||||||
|
|
||||||
|
# Create Mock Dicts
|
||||||
|
foDict = fncPath / "freeoffice.sox"
|
||||||
|
with ZipFile(foDict, mode="w") as zipObj:
|
||||||
|
zipObj.writestr("en_GB.aff", "foobar")
|
||||||
|
zipObj.writestr("en_GB.dic", "foobar")
|
||||||
|
zipObj.writestr("README.txt", "foobar")
|
||||||
|
|
||||||
|
loDict = fncPath / "libreoffice.oxt"
|
||||||
|
with ZipFile(loDict, mode="w") as zipObj:
|
||||||
|
zipObj.writestr("en_US/en_US.aff", "foobar")
|
||||||
|
zipObj.writestr("en_US/en_US.dic", "foobar")
|
||||||
|
zipObj.writestr("README.txt", "foobar")
|
||||||
|
|
||||||
|
emDict = fncPath / "empty.oxt"
|
||||||
|
with ZipFile(emDict, mode="w") as zipObj:
|
||||||
|
zipObj.writestr("README.txt", "foobar")
|
||||||
|
|
||||||
|
noFile = fncPath / "foobar.oxt"
|
||||||
|
noDict = fncPath / "foobar.sox"
|
||||||
|
noDict.write_bytes(b"foobar")
|
||||||
|
|
||||||
|
assert nwDicts.infoBox.toPlainText().splitlines()[-1] == (
|
||||||
|
"Additional dictionaries found: 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Import Free Office Dictionary
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (str(foDict), ""))
|
||||||
|
nwDicts._doBrowseHunspell()
|
||||||
|
assert nwDicts.huInput.text() == str(foDict)
|
||||||
|
nwDicts._doImportHunspell()
|
||||||
|
assert (fncPath / "hunspell").is_dir()
|
||||||
|
assert (fncPath / "hunspell" / "en_GB.aff").is_file()
|
||||||
|
assert (fncPath / "hunspell" / "en_GB.dic").is_file()
|
||||||
|
assert nwDicts.infoBox.blockCount() == 3
|
||||||
|
|
||||||
|
# Import Libre Office Dictionary
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (str(loDict), ""))
|
||||||
|
nwDicts._doBrowseHunspell()
|
||||||
|
assert nwDicts.huInput.text() == str(loDict)
|
||||||
|
nwDicts._doImportHunspell()
|
||||||
|
assert (fncPath / "hunspell").is_dir()
|
||||||
|
assert (fncPath / "hunspell" / "en_US.aff").is_file()
|
||||||
|
assert (fncPath / "hunspell" / "en_US.dic").is_file()
|
||||||
|
assert nwDicts.infoBox.blockCount() == 5
|
||||||
|
|
||||||
|
# Handle Unreadable File
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (str(noDict), ""))
|
||||||
|
nwDicts._doBrowseHunspell()
|
||||||
|
assert nwDicts.huInput.text() == str(noDict)
|
||||||
|
nwDicts._doImportHunspell()
|
||||||
|
assert nwDicts.infoBox.blockCount() == 7
|
||||||
|
|
||||||
|
# Handle File w/No Dicts
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (str(emDict), ""))
|
||||||
|
nwDicts._doBrowseHunspell()
|
||||||
|
assert nwDicts.huInput.text() == str(emDict)
|
||||||
|
nwDicts._doImportHunspell()
|
||||||
|
assert nwDicts.infoBox.blockCount() == 8
|
||||||
|
assert nwDicts.infoBox.toPlainText().splitlines()[-1] == (
|
||||||
|
"Could not process dictionary file"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle Non-Existing File
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (str(noFile), ""))
|
||||||
|
nwDicts._doBrowseHunspell()
|
||||||
|
nwDicts._doImportHunspell()
|
||||||
|
assert nwDicts.infoBox.blockCount() == 9
|
||||||
|
assert nwDicts.infoBox.toPlainText().splitlines()[-1] == (
|
||||||
|
"Could not process dictionary file"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Re-init
|
||||||
|
nwDicts.initDialog()
|
||||||
|
assert nwDicts.infoBox.blockCount() == 10
|
||||||
|
assert nwDicts.infoBox.toPlainText().splitlines()[-1] == (
|
||||||
|
"Additional dictionaries found: 2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Close
|
||||||
|
nwDicts._doClose()
|
||||||
|
# qtbot.stop()
|
||||||
|
|
||||||
|
# END Test testToolDictionaries_Main
|
||||||
Reference in New Issue
Block a user