Files
UmbraEdit/tests/test_config.py
B.D. Murphy 026f486b3e
Some checks failed
Syntax / Linting and Type Checking (push) Has been cancelled
Tests / Linux Python 3.11 (push) Has been cancelled
Tests / Linux Python 3.12 (push) Has been cancelled
Tests / Linux Python 3.13 (push) Has been cancelled
Tests / Linux Python 3.14 (push) Has been cancelled
Tests / Linux Python 3.15 (push) Has been cancelled
Tests / MacOS Python 3.14 (push) Has been cancelled
Tests / Windows Python 3.14 (push) Has been cancelled
Tests / PR Coverage Report (push) Has been cancelled
Tests / Action Coverage Report (push) Has been cancelled
Tests / Ubuntu 24.04 & Sys Packages (push) Has been cancelled
Tests / Fedora 44 & Sys Packages (push) Has been cancelled
chore: fix test fixtures and asset names for UmbraEdit rename
- Rename test fixture files: config_novelwriter.toml -> config_umbraedit.toml
- Rename test fixture files: old_novelwriter.conf -> old_umbraedit.conf
- Rename test fixture files: nw_en_GB.qm -> ue_en_GB.qm
- Rename asset files: novelwriter-text-*.svg -> umbraedit-text-*.svg
- Rename asset files: novelwriter.svg/ico -> umbraedit.svg/ico
- Rename asset files: x-novelwriter-project.* -> x-umbraedit-project.*
- Fix test references to _nwLangPath -> _ueLangPath
- Bump version to 1.0.0 (first UmbraEdit release)
- All 735 tests pass, 2 skipped
2026-11-05 07:36:45 -08:00

741 lines
24 KiB
Python

"""
UmbraEdit - Config Tests
==========================
This file is a part of UmbraEdit.
Copyright (C) 2020 Veronica Berglyd Olsen and UmbraEdit contributors.
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/>.
""" # noqa
from __future__ import annotations
import datetime
import json
import sys
import tomllib
from pathlib import Path
from shutil import copyfile
import pytest
from PyQt6.QtCore import QRect, Qt
from PyQt6.QtGui import QFont, QFontDatabase
from umbraedit import CONFIG
from umbraedit.config import MOVED_CONFIGS, Config, RecentPaths, RecentProjects
from umbraedit.constants import nwConst, nwFiles
from umbraedit.core.project import NWProject
from umbraedit.enum import nwTheme
from umbraedit.formats.configparser import NConfigParser
from umbraedit.formats.tomlparser import NTomlParser, T_TomlConfig
from umbraedit.guimain import GuiMain
from tests.helpers import TstPaths, cmpFiles, writeFile
from tests.mocked import MockApp, causeOSError
@pytest.mark.base
def testConfig_Constructor(monkeypatch: pytest.MonkeyPatch):
"""Test config constructor."""
# Linux
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "linux")
conf = Config()
assert conf.osLinux is True
assert conf.osDarwin is False
assert conf.osWindows is False
assert conf.osUnknown is False
# MacOS
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "darwin")
conf = Config()
assert conf.osLinux is False
assert conf.osDarwin is True
assert conf.osWindows is False
assert conf.osUnknown is False
# Windows
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "win32")
conf = Config()
assert conf.osLinux is False
assert conf.osDarwin is False
assert conf.osWindows is True
assert conf.osUnknown is False
# Cygwin
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "cygwin")
conf = Config()
assert conf.osLinux is False
assert conf.osDarwin is False
assert conf.osWindows is True
assert conf.osUnknown is False
# Other
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "some_other_os")
conf = Config()
assert conf.osLinux is False
assert conf.osDarwin is False
assert conf.osWindows is False
assert conf.osUnknown is True
@pytest.mark.base
def testConfig_BuildMeta(monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, tstPaths: TstPaths):
"""Test parsing of the build meta.toml file, both as the checked-in
placeholder and as stamped by the build scripts for a release.
"""
metaFile = tstPaths.tmpDir / "meta.toml"
monkeypatch.setattr("umbraedit.config.Config.assetPath", lambda *a: metaFile)
# Placeholder, as checked into the repo
writeFile(
metaFile, ('[Build]\ntimestamp = ""\ntype = "testing"\nformat = "source"\ninstall_source = "repository"\n')
)
conf = Config()
assert conf.buildTime == ""
assert conf.buildType == "testing"
assert conf.buildFormat == "source"
assert conf.installSource == "repository"
# Stamped by the build scripts ahead of packaging
writeFile(
metaFile,
(
"[Build]\n"
'timestamp = "2026-09-26T19:33:58+02:00"\n'
'type = "stable"\n'
'format = "debian"\n'
'install_source = "cloudsmith"\n'
),
)
conf._parseBuildMeta()
assert conf.buildTime == "2026-09-26T19:33:58+02:00"
assert conf.buildType == "stable"
assert conf.buildFormat == "debian"
assert conf.installSource == "cloudsmith"
# An error while reading the file must be caught and logged, and
# must leave the previously parsed values untouched
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
caplog.clear()
conf._parseBuildMeta()
assert "OSError" in caplog.text
assert conf.buildTime == "2026-09-26T19:33:58+02:00"
assert conf.buildType == "stable"
assert conf.buildFormat == "debian"
assert conf.installSource == "cloudsmith"
@pytest.mark.base
def testConfig_InitLoadSave(monkeypatch: pytest.MonkeyPatch, fncPath: Path, tstPaths: TstPaths):
"""Test config initialisation."""
conf = Config()
confFile = fncPath / nwFiles.CONF_FILE
testFile = tstPaths.outDir / "config_umbraedit.toml"
compFile = tstPaths.refDir / "config_umbraedit.toml"
# Make sure we don't have any old conf file
if confFile.is_file():
confFile.unlink()
# Running init + load against a new path should write a new config file
conf.initConfig(confPath=fncPath, dataPath=fncPath)
assert conf._confPath == fncPath
assert conf._dataPath == fncPath
conf.loadConfig()
assert confFile.exists()
# Check that we have a default file
copyfile(confFile, testFile)
ignore = (
"timeStamp",
"font",
"localisation",
"lastNotes",
"textFont",
"backupPath",
)
assert cmpFiles(testFile, compFile, ignStart=ignore)
conf.errorText() # This clears the error cache
# Block saving the file
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert conf.saveConfig() is False
assert conf.hasError is True
assert conf.errorText().startswith("Could not save config file")
# Block loading the file
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert conf.loadConfig() is False
assert conf.hasError is True
assert conf.errorText().startswith("Could not load config file")
# Change a few settings, save, reset, and reload
conf.lightTheme = "foo"
conf.darkTheme = "bar"
assert conf.saveConfig() is True
newConf = Config()
newConf.initConfig(confPath=fncPath, dataPath=fncPath)
newConf.loadConfig()
assert newConf.lightTheme == "foo"
assert newConf.darkTheme == "bar"
# If the data path doesn't materialise as a folder, sub-folders are skipped
with monkeypatch.context() as mp:
freshDataPath = fncPath / "freshdata"
origIsDir = Path.is_dir
mp.setattr(Path, "is_dir", lambda self: False if self == freshDataPath else origIsDir(self))
conf.initConfig(confPath=fncPath, dataPath=freshDataPath)
assert not (freshDataPath / "cache").exists()
# Test Correcting Quote Settings
conf.fmtDQuoteOpen = '"'
conf.fmtDQuoteClose = '"'
conf.fmtSQuoteOpen = "'"
conf.fmtSQuoteClose = "'"
conf.doReplaceDQuote = True
conf.doReplaceSQuote = True
assert conf.saveConfig() is True
assert newConf.loadConfig() is True
assert newConf.doReplaceDQuote is False
assert newConf.doReplaceSQuote is False
@pytest.mark.base
def testConfig_Localisation(fncPath: Path, tstPaths: TstPaths):
"""Test localisation."""
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
# Localisation
# ============
i18nDir = fncPath / "i18n"
i18nDir.mkdir()
conf._ueLangPath = i18nDir
copyfile(tstPaths.filesDir / "ue_en_GB.qm", i18nDir / "ue_en_GB.qm")
writeFile(i18nDir / "ue_en_GB.ts", "")
writeFile(i18nDir / "ue_abcd.qm", "")
tstApp = MockApp()
conf.initLocalisation(tstApp) # type: ignore
assert conf.rtlMode is False
# Forcing RTL sets the app layout direction
conf.guiForceRTL = True
conf.initLocalisation(tstApp) # type: ignore
assert tstApp.layoutDirection() == Qt.LayoutDirection.RightToLeft
assert conf.rtlMode is True
# Check Lists
languages = conf.listLanguages(conf.LANG_NW)
assert languages == [("en_GB", "British English")]
languages = conf.listLanguages(conf.LANG_PROJ)
assert languages == [("en_GB", "British English")]
languages = conf.listLanguages(None) # type: ignore
assert languages == []
# Add Language
copyfile(tstPaths.filesDir / "ue_en_GB.qm", i18nDir / "ue_fr.qm")
writeFile(i18nDir / "ue_fr.ts", "")
languages = conf.listLanguages(conf.LANG_NW)
assert languages == [("en_GB", "British English"), ("fr", "Français")]
# Date Formats
# Checks for bug #2325
ts = datetime.datetime.fromtimestamp(1746370775)
CONFIG._dShortDate = "dd/MM/yyyy"
CONFIG._dShortDateTime = "dd/MM/yyyy HH:mm"
assert CONFIG.localDate(ts) == ts.strftime("%d/%m/%Y")
assert CONFIG.localDateTime(ts) == ts.strftime("%d/%m/%Y %H:%M")
@pytest.mark.base
def testConfig_Methods(fncPath: Path):
"""Check class methods."""
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
# Home Path
assert conf.homePath() == Path.home().absolute()
# Data Path
assert conf.dataPath() == fncPath
assert conf.dataPath("stuff") == fncPath / "stuff"
# Assets Path
appPath = conf._appPath
assert conf.assetPath() == appPath / "assets"
assert conf.assetPath("stuff") == appPath / "assets" / "stuff"
# Last Path
assert conf.lastPath("project") == Path.home().absolute()
tmpStuff = fncPath / "stuff"
tmpStuff.mkdir()
conf.setLastPath("project", tmpStuff)
assert conf.lastPath("project") == tmpStuff
fileStuff = tmpStuff / "more_stuff.txt"
fileStuff.write_text("Stuff")
conf.setLastPath("project", fileStuff)
assert conf.lastPath("project") == tmpStuff
fileStuff.unlink()
tmpStuff.rmdir()
assert conf.lastPath("project") == Path.home().absolute()
# Invalid type does nothing
conf.setLastPath("project", None) # type: ignore
assert conf.lastPath("project") == Path.home().absolute()
# A path where neither it nor its parent exist does nothing
conf.setLastPath("project", fncPath / "missing" / "deeper" / "file.txt")
assert conf.lastPath("project") == Path.home().absolute()
# Backup Path
assert conf.backupPath() == conf._backPath
conf.setBackupPath(fncPath)
assert conf.backupPath() == fncPath
# Recent Projects
assert isinstance(conf.recentProjects, RecentProjects)
# Last Author
assert CONFIG.lastAuthor == ""
CONFIG.setLastAuthor(" Jane Doe ")
assert CONFIG.lastAuthor == "Jane Doe"
@pytest.mark.base
def testConfig_Fonts(monkeypatch: pytest.MonkeyPatch, fncPath: Path):
"""Check the OS-specific default font selection."""
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
with monkeypatch.context() as mp:
mp.setattr(conf, "osWindows", True)
mp.setattr(QFontDatabase, "families", lambda *a: ["Segoe UI"])
conf.setGuiFont(None)
assert conf.guiFont.family() == "Segoe UI"
conf.setTextFont(None)
assert conf.textFont.family() == "Segoe UI"
with monkeypatch.context() as mp:
mp.setattr(conf, "osDarwin", True)
mp.setattr(QFontDatabase, "families", lambda *a: ["Helvetica"])
conf.setTextFont(None)
assert conf.textFont.family() == "Helvetica"
# Text font size is capped to the zoom range, without changing the font passed in
font = QFont(conf.textFont)
font.setPointSizeF(2.0)
conf.setTextFont(font)
assert conf.textFont.pointSizeF() == nwConst.TEXT_FONT_MIN_SIZE
assert font.pointSizeF() == 2.0
font.setPointSizeF(100.0)
conf.setTextFont(font.toString())
assert conf.textFont.pointSizeF() == nwConst.TEXT_FONT_MAX_SIZE
font.setPointSizeF(12.0)
conf.setTextFont(font)
assert conf.textFont.pointSizeF() == 12.0
# Pixel sized fonts are left as is
font.setPixelSize(4)
conf.setTextFont(font)
assert conf.textFont.pixelSize() == 4
@pytest.mark.base
def testConfig_SettersGetters(fncPath: Path):
"""Set various sizes and positions."""
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
# Setter + Getter Combos
# ======================
# Window Size
conf.setMainWinSize(QRect(0, 0, 1205, 655))
assert conf.mainWinSize == [1200, 650]
conf.setMainWinSize(QRect(0, 0, 70, 70))
assert conf.mainWinSize == [70, 70]
conf.setMainWinSize(QRect(0, 0, 1200, 650))
# Welcome Window Size
conf.setWelcomeWinSize(QRect(0, 0, 70, 70))
assert conf.welcomeWinSize == [70, 70]
conf.setWelcomeWinSize(QRect(0, 0, 800, 500))
# Preferences Size
conf.setPreferencesWinSize(QRect(0, 0, 70, 70))
assert conf.prefsWinSize == [70, 70]
conf.setPreferencesWinSize(QRect(0, 0, 700, 615))
# Font Dialog Size
conf.setFontWinSize(QRect(0, 0, 70, 70))
assert conf.fontWinSize == [70, 70]
conf.setFontWinSize(QRect(0, 0, 700, 550))
# Getters Only
# ============
assert conf.getTextWidth(False) == 700
assert conf.getTextWidth(True) == 800
@pytest.mark.base
def testConfig_Internal(monkeypatch: pytest.MonkeyPatch, fncPath: Path):
"""Check internal functions."""
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
# Function _packList
assert conf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False"
# Function _checkOptionalPackages
# (Assumes enchant package exists and is importable)
conf._checkOptionalPackages()
assert conf.hasEnchant is True
with monkeypatch.context() as mp:
mp.setitem(sys.modules, "enchant", None)
conf._checkOptionalPackages()
assert conf.hasEnchant is False
@pytest.mark.base
def testConfig_RecentCache(monkeypatch: pytest.MonkeyPatch, tstPaths: TstPaths, fncPath: Path, nwGUI: GuiMain):
"""Test recent cache file."""
cacheFile = tstPaths.cnfDir / nwFiles.RECENT_FILE
recent = RecentProjects(CONFIG)
# Load when there is no file should pass, but load nothing
assert not cacheFile.exists()
assert recent.loadCache() is True
assert recent.listEntries() == []
# Add a couple of values
pathOne = tstPaths.cnfDir / "projPathOne" / nwFiles.PROJ_FILE
pathTwo = tstPaths.cnfDir / "projPathTwo" / nwFiles.PROJ_FILE
prjOne = NWProject()
prjTwo = NWProject()
prjOne.data.setUuid(None)
prjTwo.data.setUuid(None)
prjOne.data.setName("Proj One")
prjTwo.data.setName("Proj Two")
prjOne.data.setCurrCounts(100, 0)
prjTwo.data.setCurrCounts(200, 0)
recent.update(pathOne, prjOne.data, 1600002000)
recent.update(pathTwo, prjTwo.data, 1600005600)
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
assert cacheFile.exists()
cacheFile.unlink()
assert not cacheFile.exists()
# Fail to Save
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert recent.saveCache() is False
assert not cacheFile.exists()
# Save Proper
assert recent.saveCache() is True
assert cacheFile.exists()
# Fail to Load
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert recent.loadCache() is False
assert recent.listEntries() == []
# Load Proper
assert recent.loadCache() is True
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
# Pass Invalid
recent.update(None, None, 0) # type: ignore
recent.update(None, None, 0) # type: ignore
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
# Remove Non-Existent Entry
recent.remove("stuff")
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
# Remove Second Entry
recent.remove(pathTwo)
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
]
# Entries missing a path or a title are skipped
cacheFile.write_text(
json.dumps({
"": {"uuid": "abc", "title": "No Path", "words": 1, "chars": 1, "time": 1},
str(pathTwo): {"uuid": "abc", "title": "", "words": 1, "chars": 1, "time": 1},
}),
encoding="utf-8",
)
assert recent.loadCache() is True
assert recent.listEntries() == []
# Entries without a UUID are not mapped
recent._setEntry("", str(pathOne), "No UUID", 10, 0, 0)
assert recent._map.get("") is None
recent.remove(pathOne)
# A moved project replaces the old entry
pathOld = fncPath / "projOld"
pathNew = fncPath / "projNew"
recent.update(pathOld, prjTwo.data, 1600002000)
assert recent.findCopy(pathNew, prjTwo.data.uuid) is None
recent.update(pathNew, prjTwo.data, 1600005600)
assert recent.listEntries() == [
(str(pathNew), "Proj Two", 200, 1600005600),
]
recent.remove(pathNew)
# A copied project keeps both entries
pathOld.mkdir()
(pathOld / nwFiles.PROJ_FILE).touch()
oldUuid = prjOne.data.uuid
recent.update(pathOld, prjOne.data, 1600002000)
assert recent.findCopy(pathOld, oldUuid) is None
assert recent.findCopy(pathNew, oldUuid) == str(pathOld)
recent.update(pathNew, prjOne.data, 1600005600)
assert recent.listEntries() == [
(str(pathOld), "Proj One", 100, 1600002000),
(str(pathNew), "Proj One", 100, 1600005600),
]
# Once the copy has a new UUID, the original is no longer a copy
prjOne.data.setUuid("")
recent.update(pathNew, prjOne.data, 1600005600)
assert recent.findCopy(pathOld, oldUuid) is None
assert len(recent.listEntries()) == 2
@pytest.mark.base
def testConfig_RecentPaths(monkeypatch: pytest.MonkeyPatch, tstPaths: TstPaths):
"""Test recent paths file."""
cacheFile = tstPaths.cnfDir / nwFiles.RECENT_PATH
recent = RecentPaths(CONFIG)
# Load when there is no file should pass, but load nothing
assert not cacheFile.exists()
assert recent.loadCache() is True
assert recent._data == {}
# Set valid paths
recent.setPath("default", tstPaths.cnfDir / "default")
recent.setPath("project", tstPaths.cnfDir / "project")
recent.setPath("import", tstPaths.cnfDir / "import")
recent.setPath("story", tstPaths.cnfDir / "story")
recent.setPath("stats", tstPaths.cnfDir / "stats")
# Set invalid path
recent.setPath("foobar", tstPaths.cnfDir / "foobar")
# Check valid paths
assert recent.getPath("default") == str(tstPaths.cnfDir / "default")
assert recent.getPath("project") == str(tstPaths.cnfDir / "project")
assert recent.getPath("import") == str(tstPaths.cnfDir / "import")
assert recent.getPath("story") == str(tstPaths.cnfDir / "story")
assert recent.getPath("stats") == str(tstPaths.cnfDir / "stats")
# Check invalid path
assert recent.getPath("foobar") is None
# Check file
expected = {
"default": str(tstPaths.cnfDir / "default"),
"project": str(tstPaths.cnfDir / "project"),
"import": str(tstPaths.cnfDir / "import"),
"story": str(tstPaths.cnfDir / "story"),
"stats": str(tstPaths.cnfDir / "stats"),
}
assert cacheFile.exists()
assert json.loads(cacheFile.read_text()) == expected
# Clear and reload
recent._data = {}
recent.loadCache()
assert recent._data == expected
# A non-dict cache file is ignored
cacheFile.write_text(json.dumps(["not", "a", "dict"]), encoding="utf-8")
recent._data = {}
assert recent.loadCache() is True
assert recent._data == {}
# Invalid keys and non-string values are skipped
cacheFile.write_text(
json.dumps({
"default": str(tstPaths.cnfDir / "default"),
"foobar": str(tstPaths.cnfDir / "foobar"),
"project": 1234,
}),
encoding="utf-8",
)
recent._data = {}
assert recent.loadCache() is True
assert recent._data == {"default": str(tstPaths.cnfDir / "default")}
# Check error handling
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert recent.saveCache() is False
assert recent.loadCache() is False
@pytest.mark.base
def testConfig_IOError(monkeypatch: pytest.MonkeyPatch):
"""Test handling of I/O errors when using pathlib."""
with monkeypatch.context() as mp:
mp.setattr(Path, "iterdir", causeOSError)
config = Config() # Loading manuals will fail if not handled
assert config.listLanguages(config.LANG_NW) == [("en_GB", "British English")]
# No assets folder found
with monkeypatch.context() as mp:
origIsDir = Path.is_dir
mp.setattr(Path, "is_dir", lambda self: False if self.name == "assets" else origIsDir(self))
config = Config()
assert config._manuals == {}
with monkeypatch.context() as mp:
config = Config()
mp.setattr(Path, "is_dir", causeOSError)
config.initConfig()
@pytest.mark.base
def testConfig_ConvertOldConfig(fncPath: Path, tstPaths: TstPaths):
"""Test that an old ini-format config file is converted to the new
TOML format when loaded.
"""
oldFile = fncPath / nwFiles.CONF_FILE_OLD
newFile = fncPath / nwFiles.CONF_FILE
copyfile(tstPaths.filesDir / "old_umbraedit.conf", oldFile)
assert oldFile.is_file()
assert not newFile.exists()
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
assert conf.loadConfig() is True
# The old file should have triggered a conversion to the new format
assert newFile.exists()
# A selection of values should have been picked up from the old file
assert conf.lightTheme == "default_light"
assert conf.darkTheme == "default_dark"
assert conf.themeMode == nwTheme.AUTO
assert conf.mainWinSize == [1200, 650]
assert conf.fontWinSize == [700, 550]
assert conf.autoSaveProj == 60
assert conf.autoSaveDoc == 30
assert conf.backupInterval == "session"
assert conf.textWidth == 700
assert conf.tabSize == 2.0
assert conf.dialogStyle == 2
assert conf.spellLanguage == "en"
assert conf.userIdleTime == 300
assert conf.showSessionTime is True
assert conf.searchRegEx is False
# Loading the newly converted file should produce the same values
newConf = Config()
newConf.initConfig(confPath=fncPath, dataPath=fncPath)
assert newConf.loadConfig() is True
assert newConf.mainWinSize == conf.mainWinSize
assert newConf.dialogStyle == conf.dialogStyle
assert newConf.spellLanguage == conf.spellLanguage
@pytest.mark.base
def testConfig_MovedConfigs(fncPath: Path):
"""Test that moved config options are saved to their new location
and read from their old location as a fallback.
"""
conf = Config()
conf.initConfig(confPath=fncPath, dataPath=fncPath)
assert conf.saveConfig() is True
saved = tomllib.loads((fncPath / nwFiles.CONF_FILE).read_text(encoding="utf-8"))
for (newSec, newOpt), (oldSec, oldOpt) in MOVED_CONFIGS.items():
assert newSec is not None
assert newOpt in saved[newSec]
assert oldOpt not in saved.get(oldSec, {})
data: T_TomlConfig = {oldSec: {oldOpt: "old"}}
toml = NTomlParser(confMap=MOVED_CONFIGS)
toml.readString(toml.writeString(data))
assert toml.getStr(newSec, newOpt, "") == "old"
ini = NConfigParser(confMap=MOVED_CONFIGS)
ini.read_dict(data)
assert ini.getStr(newSec, newOpt, "") == "old"
# The new location takes precedence
data.setdefault(newSec, {})[newOpt] = "new"
toml.readString(toml.writeString(data))
assert toml.getStr(newSec, newOpt, "") == "new"
ini.read_dict(data)
assert ini.getStr(newSec, newOpt, "") == "new"