# -*- coding: utf-8 -*- """novelWriter Config Class Tester """ import pytest import sys import os import configparser from shutil import copyfile from dummy import causeOSError from tools import cmpFiles from nw.config import Config from nw.constants import nwConst, nwFiles @pytest.mark.base def testBaseConfig_Constructor(monkeypatch): """Test config contructor. """ # Linux monkeypatch.setattr("sys.platform", "linux") tstConf = Config() assert tstConf.osLinux is True assert tstConf.osDarwin is False assert tstConf.osWindows is False assert tstConf.osUnknown is False monkeypatch.undo() # macOS monkeypatch.setattr("sys.platform", "darwin") tstConf = Config() assert tstConf.osLinux is False assert tstConf.osDarwin is True assert tstConf.osWindows is False assert tstConf.osUnknown is False monkeypatch.undo() # Windows monkeypatch.setattr("sys.platform", "win32") tstConf = Config() assert tstConf.osLinux is False assert tstConf.osDarwin is False assert tstConf.osWindows is True assert tstConf.osUnknown is False monkeypatch.undo() # Cygwin monkeypatch.setattr("sys.platform", "cygwin") tstConf = Config() assert tstConf.osLinux is False assert tstConf.osDarwin is False assert tstConf.osWindows is True assert tstConf.osUnknown is False monkeypatch.undo() # Other monkeypatch.setattr("sys.platform", "some_ther_os") tstConf = Config() assert tstConf.osLinux is False assert tstConf.osDarwin is False assert tstConf.osWindows is False assert tstConf.osUnknown is True monkeypatch.undo() # END Test testBaseConfig_Constructor @pytest.mark.base def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir): """Test config intialisation. """ tstConf = Config() confFile = os.path.join(tmpDir, "novelwriter.conf") testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") # Make sure we don't have any old conf file if os.path.isfile(confFile): os.unlink(confFile) # Let the config class figure out the path monkeypatch.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir) tstConf.verQtValue = 50600 tstConf.initConfig() assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert not os.path.isfile(confFile) tstConf.verQtValue = 50000 tstConf.initConfig() assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert not os.path.isfile(confFile) monkeypatch.undo() # Fail to make folders monkeypatch.setattr("os.mkdir", causeOSError) tstConfDir = os.path.join(fncDir, "test_conf") tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir) assert tstConf.confPath is None assert tstConf.dataPath == tmpDir assert not os.path.isfile(confFile) tstDataDir = os.path.join(fncDir, "test_data") tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath is None assert os.path.isfile(confFile) os.unlink(confFile) monkeypatch.undo() # Test load/save with no path tstConf.confPath = None assert not tstConf.loadConfig() assert not tstConf.saveConfig() # Run again and set the paths directly and correctly # This should create a config file as well monkeypatch.setattr("os.path.expanduser", lambda *args: "") tstConf.spellTool = nwConst.SP_INTERNAL tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath == tmpDir assert os.path.isfile(confFile) copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9]) monkeypatch.undo() # Load and save with OSError monkeypatch.setattr("builtins.open", causeOSError) assert not tstConf.loadConfig() assert tstConf.hasError is True assert tstConf.errData != [] assert tstConf.getErrData().startswith("Could not") assert tstConf.hasError is False assert tstConf.errData == [] assert not tstConf.saveConfig() assert tstConf.hasError is True assert tstConf.errData != [] assert tstConf.getErrData().startswith("Could not") assert tstConf.hasError is False assert tstConf.errData == [] monkeypatch.undo() assert tstConf.loadConfig() assert tstConf.saveConfig() copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9]) # END Test testBaseConfig_Init @pytest.mark.base def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir): """Test recent cache file. """ # Check failing tmpConf.dataPath = None assert not tmpConf.loadRecentCache() assert not tmpConf.saveRecentCache() tmpConf.dataPath = tmpDir # Add a couple of values pathOne = os.path.join(fncDir, "projPathOne", nwFiles.PROJ_FILE) pathTwo = os.path.join(fncDir, "projPathTwo", nwFiles.PROJ_FILE) assert tmpConf.updateRecentCache(pathOne, "Proj One", 100, 1600002000) assert tmpConf.updateRecentCache(pathTwo, "Proj Two", 200, 1600005600) assert tmpConf.recentProj == { pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200}, } # Fail to Save monkeypatch.setattr("builtins.open", causeOSError) assert not tmpConf.saveRecentCache() monkeypatch.undo() # Save Proper cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE) assert tmpConf.saveRecentCache() assert tmpConf.saveRecentCache() assert os.path.isfile(cacheFile) # Fail to Load monkeypatch.setattr("builtins.open", causeOSError) tmpConf.recentProj = {} assert not tmpConf.loadRecentCache() assert tmpConf.recentProj == {} monkeypatch.undo() # Load Proper tmpConf.recentProj = {} assert tmpConf.loadRecentCache() assert tmpConf.recentProj == { pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200}, } # Remove Non-Existent Entry assert not tmpConf.removeFromRecentCache("dummy") assert tmpConf.recentProj == { pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200}, } # Remove Second Entry assert tmpConf.removeFromRecentCache(pathTwo) assert tmpConf.recentProj == { pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, } # END Test testBaseConfig_RecentCache @pytest.mark.base def testBaseConfig_SetPath(tmpConf, tmpDir): """Test path setters. """ # Conf Path assert tmpConf.setConfPath(None) assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow")) assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf")) assert tmpConf.confPath == tmpDir assert tmpConf.confFile == "novelwriter.conf" assert not tmpConf.confChanged # Data Path assert tmpConf.setDataPath(None) assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow")) assert tmpConf.setDataPath(tmpDir) assert tmpConf.dataPath == tmpDir assert not tmpConf.confChanged # Last Path assert tmpConf.setLastPath(None) assert tmpConf.lastPath == "" assert tmpConf.setLastPath(os.path.join(tmpDir, "file.tmp")) assert tmpConf.lastPath == tmpDir assert tmpConf.setLastPath("") assert tmpConf.lastPath == "" # END Test testBaseConfig_SetPath @pytest.mark.base def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir): """Set various sizes and positions """ confFile = os.path.join(tmpDir, "novelwriter.conf") testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") # GUI Scaling # =========== tmpConf.guiScale = 1.0 assert tmpConf.pxInt(10) == 10 assert tmpConf.pxInt(13) == 13 assert tmpConf.rpxInt(10) == 10 assert tmpConf.rpxInt(13) == 13 tmpConf.guiScale = 2.0 assert tmpConf.pxInt(10) == 20 assert tmpConf.pxInt(13) == 26 assert tmpConf.rpxInt(10) == 5 assert tmpConf.rpxInt(13) == 6 # Setter + Getter Combos # ====================== # Window Size tmpConf.guiScale = 1.0 assert tmpConf.setWinSize(1205, 655) assert not tmpConf.confChanged tmpConf.guiScale = 2.0 assert tmpConf.setWinSize(70, 70) assert tmpConf.getWinSize() == [70, 70] assert tmpConf.winGeometry == [35, 35] tmpConf.guiScale = 1.0 assert tmpConf.setWinSize(70, 70) assert tmpConf.getWinSize() == [70, 70] assert tmpConf.winGeometry == [70, 70] assert tmpConf.setWinSize(1200, 650) # Project Tree Columns tmpConf.guiScale = 2.0 assert tmpConf.setTreeColWidths([10, 20, 25]) assert tmpConf.getTreeColWidths() == [10, 20, 24] assert tmpConf.treeColWidth == [5, 10, 12] tmpConf.guiScale = 1.0 assert tmpConf.setTreeColWidths([10, 20, 25]) assert tmpConf.getTreeColWidths() == [10, 20, 25] assert tmpConf.treeColWidth == [10, 20, 25] assert tmpConf.setTreeColWidths([200, 50, 30]) # Novel Tree Columns tmpConf.guiScale = 2.0 assert tmpConf.setNovelColWidths([10, 20]) assert tmpConf.getNovelColWidths() == [10, 20] assert tmpConf.novelColWidth == [5, 10] tmpConf.guiScale = 1.0 assert tmpConf.setNovelColWidths([10, 20]) assert tmpConf.getNovelColWidths() == [10, 20] assert tmpConf.novelColWidth == [10, 20] assert tmpConf.setNovelColWidths([200, 50]) # Project Settings Tree Columns tmpConf.guiScale = 2.0 assert tmpConf.setProjColWidths([10, 20, 30]) assert tmpConf.getProjColWidths() == [10, 20, 30] assert tmpConf.projColWidth == [5, 10, 15] tmpConf.guiScale = 1.0 assert tmpConf.setProjColWidths([10, 20, 30]) assert tmpConf.getProjColWidths() == [10, 20, 30] assert tmpConf.projColWidth == [10, 20, 30] assert tmpConf.setProjColWidths([200, 60, 140]) # Main Pane Splitter tmpConf.guiScale = 2.0 assert tmpConf.setMainPanePos([200, 700]) assert tmpConf.getMainPanePos() == [200, 700] assert tmpConf.mainPanePos == [100, 350] tmpConf.guiScale = 1.0 assert tmpConf.setMainPanePos([200, 700]) assert tmpConf.getMainPanePos() == [200, 700] assert tmpConf.mainPanePos == [200, 700] assert tmpConf.setMainPanePos([300, 800]) # Doc Pane Splitter tmpConf.guiScale = 2.0 assert tmpConf.setDocPanePos([300, 300]) assert tmpConf.getDocPanePos() == [300, 300] assert tmpConf.docPanePos == [150, 150] tmpConf.guiScale = 1.0 assert tmpConf.setDocPanePos([300, 300]) assert tmpConf.getDocPanePos() == [300, 300] assert tmpConf.docPanePos == [300, 300] assert tmpConf.setDocPanePos([400, 400]) # View Pane Splitter tmpConf.guiScale = 2.0 assert tmpConf.setViewPanePos([400, 250]) assert tmpConf.getViewPanePos() == [400, 250] assert tmpConf.viewPanePos == [200, 125] tmpConf.guiScale = 1.0 assert tmpConf.setViewPanePos([400, 250]) assert tmpConf.getViewPanePos() == [400, 250] assert tmpConf.viewPanePos == [400, 250] assert tmpConf.setViewPanePos([500, 150]) # Outline Pane Splitter tmpConf.guiScale = 2.0 assert tmpConf.setOutlinePanePos([400, 250]) assert tmpConf.getOutlinePanePos() == [400, 250] assert tmpConf.outlnPanePos == [200, 125] tmpConf.guiScale = 1.0 assert tmpConf.setOutlinePanePos([400, 250]) assert tmpConf.getOutlinePanePos() == [400, 250] assert tmpConf.outlnPanePos == [400, 250] assert tmpConf.setOutlinePanePos([500, 150]) # Getters Only # ============ tmpConf.guiScale = 1.0 assert tmpConf.getTextWidth() == 600 assert tmpConf.getTextMargin() == 40 assert tmpConf.getTabWidth() == 40 assert tmpConf.getFocusWidth() == 800 tmpConf.guiScale = 2.0 assert tmpConf.getTextWidth() == 1200 assert tmpConf.getTextMargin() == 80 assert tmpConf.getTabWidth() == 80 assert tmpConf.getFocusWidth() == 1600 # Flag Setters # ============ assert not tmpConf.setShowRefPanel(False) assert not tmpConf.showRefPanel assert tmpConf.setShowRefPanel(True) assert not tmpConf.setViewComments(False) assert not tmpConf.viewComments assert tmpConf.setViewComments(True) assert not tmpConf.setViewSynopsis(False) assert not tmpConf.viewSynopsis assert tmpConf.setViewSynopsis(True) # Check Final File # ================ assert tmpConf.confChanged assert tmpConf.saveConfig() assert not tmpConf.confChanged copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9]) # END Test testBaseConfig_SettersGetters @pytest.mark.base def testBaseConfig_Internal(monkeypatch, tmpConf): """Check internal functions. """ # Function _packList assert tmpConf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False" # Function _unpackList assert tmpConf._unpackList("1, 2, 3", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 3] assert tmpConf._unpackList("1, 2 ", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 0] assert tmpConf._unpackList("A, B, C", [0, 0, 0], tmpConf.CNF_I_LST) == [0, 0, 0] assert tmpConf._unpackList("1, 2, 3", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["1", "2", "3"] assert tmpConf._unpackList("A, B ", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "Z"] assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "C"] assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_STR) == ["X", "Y", "Z"] # Function _parseLine cnfParse = configparser.ConfigParser() cnfParse.read_string( "[Main]\n" "val_string = dummy\n" "val_int = 123\n" "val_bool = True\n" "val_list_string = A, B, C\n" "val_list_int = 1, 2, 3\n" ) assert tmpConf._parseLine( cnfParse, "Main", "val_string", tmpConf.CNF_STR, "default" ) == "dummy" assert tmpConf._parseLine( cnfParse, "Main", "nope", tmpConf.CNF_STR, "default" ) == "default" assert tmpConf._parseLine( cnfParse, "Main", "val_int", tmpConf.CNF_INT, "0" ) == 123 assert tmpConf._parseLine( cnfParse, "Main", "nope", tmpConf.CNF_INT, 0 ) == 0 assert tmpConf._parseLine( cnfParse, "Main", "val_string", tmpConf.CNF_INT, 0 ) == 0 assert tmpConf._parseLine( cnfParse, "Main", "val_bool", tmpConf.CNF_BOOL, False ) is True assert tmpConf._parseLine( cnfParse, "Main", "nope", tmpConf.CNF_BOOL, False ) is False assert tmpConf._parseLine( cnfParse, "Main", "val_string", tmpConf.CNF_BOOL, False ) is False assert tmpConf._parseLine( cnfParse, "Main", "val_list_string", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"] ) == ["A", "B", "C", "Z"] assert tmpConf._parseLine( cnfParse, "Main", "nope", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"] ) == ["W", "X", "Y", "Z"] assert tmpConf._parseLine( cnfParse, "Main", "val_list_int", tmpConf.CNF_I_LST, [6, 7, 8, 9] ) == [1, 2, 3, 9] assert tmpConf._parseLine( cnfParse, "Main", "nope", tmpConf.CNF_S_LST, [6, 7, 8, 9] ) == [6, 7, 8, 9] # Function _checkNone assert tmpConf._checkNone(None) is None assert tmpConf._checkNone("None") is None assert tmpConf._checkNone("stuff") == "stuff" # Function _checkOptionalPackages # (Assumes enchant package exists ans is importable) tmpConf._checkOptionalPackages() assert tmpConf.hasEnchant is True monkeypatch.setitem(sys.modules, "enchant", None) tmpConf._checkOptionalPackages() assert tmpConf.hasEnchant is False monkeypatch.undo() monkeypatch.setattr("shutil.which", lambda *args: "dummy") tmpConf._checkOptionalPackages() assert tmpConf.hasAssistant is True monkeypatch.undo() monkeypatch.setattr("shutil.which", lambda *args: None) tmpConf._checkOptionalPackages() assert tmpConf.hasAssistant is False monkeypatch.undo() # END Test testBaseConfig_Internal