Updated tests for NWSpell* classes

This commit is contained in:
Veronica K. B. Olsen
2020-12-03 18:37:52 +01:00
parent c13bd18271
commit c7c70a35a0
7 changed files with 219 additions and 33 deletions
+18 -14
View File
@@ -28,8 +28,7 @@
import nw import nw
import logging import logging
import os import os
import difflib
from difflib import get_close_matches
from nw.constants import nwConst, isoLanguage from nw.constants import nwConst, isoLanguage
@@ -70,14 +69,16 @@ class NWSpellCheck():
""" """
if self.projectDict is not None and newWord not in self.projDict: if self.projectDict is not None and newWord not in self.projDict:
newWord = newWord.strip() newWord = newWord.strip()
self.projDict.append(newWord)
try: try:
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile: with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
outFile.write("%s\n" % newWord) outFile.write("%s\n" % newWord)
self.projDict.append(newWord)
except Exception as e: except Exception as e:
logger.error("Failed to add word to project word list %s" % str(self.projectDict)) logger.error("Failed to add word to project word list %s" % str(self.projectDict))
logger.error(str(e)) logger.error(str(e))
return return False
return True
return False
def listDictionaries(self): def listDictionaries(self):
"""Dummy function. """Dummy function.
@@ -109,9 +110,12 @@ class NWSpellCheck():
""" """
self.projDict = [] self.projDict = []
if projectDict is not None: if projectDict is not None:
self.projectDict = projectDict
if not os.path.isfile(projectDict): if not os.path.isfile(projectDict):
return self.projectDict = None
return False
else:
self.projectDict = projectDict
try: try:
logger.debug("Loading project word list") logger.debug("Loading project word list")
with open(projectDict, mode="r", encoding="utf-8") as wordsFile: with open(projectDict, mode="r", encoding="utf-8") as wordsFile:
@@ -123,7 +127,9 @@ class NWSpellCheck():
except Exception as e: except Exception as e:
logger.error("Failed to load project word list") logger.error("Failed to load project word list")
logger.error(str(e)) logger.error(str(e))
return return False
return True
# END Class NWSpellCheck # END Class NWSpellCheck
@@ -287,7 +293,7 @@ class NWSpellSimple(NWSpellCheck):
if len(theWord) == 0: if len(theWord) == 0:
return [] return []
theMatches = get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75) theMatches = difflib.get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75)
theOptions = [] theOptions = []
for aWord in theMatches: for aWord in theMatches:
if len(aWord) == 0: if len(aWord) == 0:
@@ -314,14 +320,12 @@ class NWSpellSimple(NWSpellCheck):
retList = [] retList = []
for dictFile in os.listdir(self.mainConf.dictPath): for dictFile in os.listdir(self.mainConf.dictPath):
theBits = os.path.splitext(dictFile) fRoot, fExt = os.path.splitext(dictFile)
if len(theBits) != 2: if fExt != ".dict":
continue
if theBits[1] != ".dict":
continue continue
spName = "%s [%s]" % (self.expandLanguage(theBits[0]), nwConst.SP_INTERNAL) spName = "%s [%s]" % (self.expandLanguage(fRoot), nwConst.SP_INTERNAL)
retList.append((theBits[0], spName)) retList.append((fRoot, spName))
return retList return retList
+9 -9
View File
@@ -59,12 +59,12 @@ Available markers are:
To filter specific groups of tests, use the `-k` switch. To filter specific groups of tests, use the `-k` switch.
The commands for the respective test categories are listed below. The commands for the respective test categories are listed below.
| Type | Test Target | Source File(s) | Marker | Filter | | Type | Test Target | Source File(s) | Marker | Filter |
| :--- | :----------------- | :------------------ | :-------- | :-------------------- | | :--- | :----------------- | :-------------------- | :-------- | :-------------------- |
| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | | Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` |
| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | | Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` |
| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | | Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` |
| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | | Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` |
| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | | Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | | Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` |
+1 -1
View File
@@ -47,5 +47,5 @@ class StatusBar():
# Dummy functions that will raise errors instead. # Dummy functions that will raise errors instead.
# =========================================================================== # # =========================================================================== #
def dummyIO(*args, **kwargs): def causeOSError(*args, **kwargs):
raise OSError raise OSError
+4 -5
View File
@@ -5,6 +5,8 @@
import os import os
import pytest import pytest
from dummy import causeOSError
from nw.core import NWProject, NWDoc from nw.core import NWProject, NWDoc
from nw.core.item import NWItem from nw.core.item import NWItem
from nw.constants import nwItemClass, nwItemLayout from nw.constants import nwItemClass, nwItemLayout
@@ -75,10 +77,7 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
assert inFile.read() == theText assert inFile.read() == theText
# Cause open() to fail while saving # Cause open() to fail while saving
def dummyIO(*args, **kwargs): monkeypatch.setattr("builtins.open", causeOSError)
raise OSError
monkeypatch.setattr("builtins.open", dummyIO)
assert not theDoc.saveDocument(theText) assert not theDoc.saveDocument(theText)
monkeypatch.undo() monkeypatch.undo()
@@ -91,7 +90,7 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
assert os.path.isfile(docPath) assert os.path.isfile(docPath)
# Cause the delete to fail # Cause the delete to fail
monkeypatch.setattr("os.unlink", dummyIO) monkeypatch.setattr("os.unlink", causeOSError)
assert not theDoc.deleteDocument(xHandle) assert not theDoc.deleteDocument(xHandle)
monkeypatch.undo() monkeypatch.undo()
+3 -4
View File
@@ -6,6 +6,8 @@ import os
import json import json
import pytest import pytest
from dummy import causeOSError
from nw.core import NWProject from nw.core import NWProject
from nw.core.options import OptionState from nw.core.options import OptionState
from nw.constants import nwFiles from nw.constants import nwFiles
@@ -44,10 +46,7 @@ def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir):
assert theProject.projMeta == tmpDir assert theProject.projMeta == tmpDir
# Cause open() to fail # Cause open() to fail
def dummyIO(*args, **kwargs): monkeypatch.setattr("builtins.open", causeOSError)
raise OSError
monkeypatch.setattr("builtins.open", dummyIO)
assert not theOpts.loadSettings() assert not theOpts.loadSettings()
assert not theOpts.saveSettings() assert not theOpts.saveSettings()
monkeypatch.undo() monkeypatch.undo()
+172
View File
@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
"""novelWriter Spell Check Class Tester
"""
import os
import sys
import pytest
from difflib import get_close_matches
from dummy import causeOSError
from tools import readFile, writeFile
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
from nw.constants import nwConst
@pytest.mark.core
def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf):
"""Test the spell checker super class
"""
wList = os.path.join(tmpDir, "wordlist.txt")
writeFile(wList, "a_word\nb_word\nc_word\n")
spChk = NWSpellCheck()
spChk.mainConf = tmpConf
# Check that dummy functions return results that reflects that spell
# checking is effectively disabled
assert spChk.setLanguage("", "") is None
assert spChk.checkWord("")
assert spChk.suggestWords("") == []
assert spChk.listDictionaries() == []
assert spChk.describeDict() == ("", "")
# Check language info
assert NWSpellCheck.expandLanguage("en") == "English"
assert NWSpellCheck.expandLanguage("en_GB") == "English (GB)"
# Add a word to the user's dictionary
assert spChk._readProjectDictionary("dummy") is False
monkeypatch.setattr("builtins.open", causeOSError)
assert spChk._readProjectDictionary(wList) is False
monkeypatch.undo()
assert spChk._readProjectDictionary(wList) is True
assert spChk.projectDict == wList
# Cannot write to file
monkeypatch.setattr("builtins.open", causeOSError)
assert spChk.addWord("d_word") is False
monkeypatch.undo()
assert readFile(wList) == "a_word\nb_word\nc_word\n"
# First time, OK
assert spChk.addWord("d_word") is True
assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n"
# But not added twice
assert spChk.addWord("d_word") is False
assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n"
# END Test testCoreSpell_Super
@pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, tmpDir, tmpConf):
"""Test the pyenchant spell checker
"""
wList = os.path.join(tmpDir, "wordlist.txt")
writeFile(wList, "a_word\nb_word\nc_word\n")
# Block the enchant package (and trigger the dummy class)
monkeypatch.setitem(sys.modules, "enchant", None)
spChk = NWSpellEnchant()
spChk.setLanguage("en", wList)
assert spChk.setLanguage("", "") is None
assert spChk.checkWord("")
assert spChk.suggestWords("") == []
assert spChk.listDictionaries() == []
assert spChk.describeDict() == ("", "")
monkeypatch.undo()
# Load the proper enchant package
spChk = NWSpellEnchant()
spChk.mainConf = tmpConf
spChk.setLanguage("en", wList)
assert spChk.checkWord("a_word")
assert spChk.checkWord("b_word")
assert spChk.checkWord("c_word")
assert not spChk.checkWord("d_word")
spChk.addWord("d_word")
assert spChk.checkWord("d_word")
wSuggest = spChk.suggestWords("wrod")
assert len(wSuggest) > 0
assert "word" in wSuggest
dList = spChk.listDictionaries()
assert len(dList) > 0
aTag, aName = spChk.describeDict()
assert aTag == "en"
assert aName != ""
# END Test testCoreSpell_Enchant
@pytest.mark.core
def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf):
"""Test the fallback simple spell checker
"""
wList = os.path.join(tmpDir, "wordlist.txt")
wDict = os.path.join(tmpDir, "en.dict")
writeFile(wList, "a_word\nb_word\nc_word\n")
writeFile(wDict, "# Comment\ne_word\nf_word\ng_word\n")
spChk = NWSpellSimple()
spChk.mainConf = tmpConf
spChk.mainConf.dictPath = tmpDir
# Load dictionary, but fail
monkeypatch.setattr("builtins.open", causeOSError)
spChk.setLanguage("en", wList)
assert spChk.spellLanguage is None
assert spChk.WORDS == spChk.projDict
monkeypatch.undo()
# Load dictionary properly
spChk.setLanguage("en", wList)
assert spChk.projDict == ["a_word", "b_word", "c_word"]
assert spChk.WORDS == ["e_word", "f_word", "g_word", "a_word", "b_word", "c_word"]
# Check words
assert spChk.checkWord("a_word")
assert spChk.checkWord("b_word")
assert spChk.checkWord("c_word")
assert not spChk.checkWord("d_word")
assert spChk.checkWord("e_word")
assert spChk.checkWord("f_word")
assert spChk.checkWord("g_word")
# Add word
spChk.addWord("d_word")
assert spChk.checkWord("d_word")
# Check spelling
assert spChk.suggestWords(" \t") == []
wSuggest = spChk.suggestWords("d_wrod")
assert len(wSuggest) > 0
assert "d_word" in wSuggest
# Break the matching
monkeypatch.setattr("difflib.get_close_matches", lambda *args, **kwargs: [""])
assert spChk.suggestWords("word") == []
monkeypatch.undo()
# Capitalisation
wSuggest = spChk.suggestWords("D_wrod")
assert len(wSuggest) > 0
assert "D_word" in wSuggest
# List dictionaries
assert spChk.listDictionaries() == [("en", "English [%s]" % nwConst.SP_INTERNAL)]
# Description
aTag, aName = spChk.describeDict()
assert aTag == "en"
assert aName == nwConst.SP_INTERNAL
# END Test testCoreSpell_Simple
+12
View File
@@ -70,3 +70,15 @@ def getGuiItem(theName):
if qWidget.objectName() == theName: if qWidget.objectName() == theName:
return qWidget return qWidget
return None return None
def readFile(fileName):
"""Returns the content of a file as a string.
"""
with open(fileName, mode="r", encoding="utf8") as inFile:
return inFile.read()
def writeFile(fileName, fileData):
"""Write the contents of a string to a file.
"""
with open(fileName, mode="w", encoding="utf8") as outFile:
outFile.write(fileData)