# -*- coding: utf-8 -*- """ novelWriter – Config Class Tester ================================= This file is a part of novelWriter Copyright 2018–2021, 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 . """ import pytest import sys import os import configparser from shutil import copyfile from dummy import causeOSError, DummyApp from tools import cmpFiles, writeFile 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 # 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 # 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 # 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 # Other monkeypatch.setattr("sys.platform", "some_other_os") tstConf = Config() assert tstConf.osLinux is False assert tstConf.osDarwin is False assert tstConf.osWindows is False assert tstConf.osUnknown is True # END Test testBaseConfig_Constructor @pytest.mark.base def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): """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 with monkeypatch.context() as mp: mp.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) # Fail to make folders with monkeypatch.context() as mp: mp.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) # 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 with monkeypatch.context() as mp: mp.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, 10]) # Load and save with OSError with monkeypatch.context() as mp: mp.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 == [] assert tstConf.loadConfig() assert tstConf.saveConfig() # Test Correcting Quote Settings origDbl = tstConf.fmtDoubleQuotes origSng = tstConf.fmtSingleQuotes orDoDbl = tstConf.doReplaceDQuote orDoSng = tstConf.doReplaceSQuote tstConf.fmtDoubleQuotes = ["\"", "\""] tstConf.fmtSingleQuotes = ["'", "'"] tstConf.doReplaceDQuote = True tstConf.doReplaceSQuote = True assert tstConf.saveConfig() assert tstConf.loadConfig() assert not tstConf.doReplaceDQuote assert not tstConf.doReplaceSQuote tstConf.fmtDoubleQuotes = origDbl tstConf.fmtSingleQuotes = origSng tstConf.doReplaceDQuote = orDoDbl tstConf.doReplaceSQuote = orDoSng assert tstConf.saveConfig() # Localisation i18nDir = os.path.join(fncDir, "i18n") os.mkdir(i18nDir) os.mkdir(os.path.join(i18nDir, "stuff")) tstConf.nwLangPath = i18nDir copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(fncDir, "nw_en_GB.qm")) writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "") writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "") tstApp = DummyApp() tstConf.initLocalisation(tstApp) theList = tstConf.listLanguages(tstConf.LANG_NW) assert theList == [("en_GB", "British English")] copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9, 10]) # 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 with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert not tmpConf.saveRecentCache() # Save Proper cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE) assert tmpConf.saveRecentCache() assert tmpConf.saveRecentCache() assert os.path.isfile(cacheFile) # Fail to Load with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) tmpConf.recentProj = {} assert not tmpConf.loadRecentCache() assert tmpConf.recentProj == {} # 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, 10]) # 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 with monkeypatch.context() as mp: mp.setitem(sys.modules, "enchant", None) tmpConf._checkOptionalPackages() assert tmpConf.hasEnchant is False with monkeypatch.context() as mp: mp.setattr("shutil.which", lambda *args: "dummy") tmpConf._checkOptionalPackages() assert tmpConf.hasAssistant is True with monkeypatch.context() as mp: mp.setattr("shutil.which", lambda *args: None) tmpConf._checkOptionalPackages() assert tmpConf.hasAssistant is False # END Test testBaseConfig_Internal