Complete coverage of themes classes
This commit is contained in:
@@ -244,7 +244,7 @@ class GuiTheme:
|
|||||||
self.statSaved = self._parseColour(confParser, cnfSec, "statussaved")
|
self.statSaved = self._parseColour(confParser, cnfSec, "statussaved")
|
||||||
|
|
||||||
# Icons
|
# Icons
|
||||||
self.iconCache.updateTheme(self.themeIcons)
|
self.iconCache.loadTheme(self.themeIcons)
|
||||||
|
|
||||||
# CSS File
|
# CSS File
|
||||||
cssData = readTextFile(themeFile[:-5]+".qss")
|
cssData = readTextFile(themeFile[:-5]+".qss")
|
||||||
@@ -498,7 +498,7 @@ class GuiIcons:
|
|||||||
# Actions
|
# Actions
|
||||||
##
|
##
|
||||||
|
|
||||||
def updateTheme(self, iconTheme):
|
def loadTheme(self, iconTheme):
|
||||||
"""Update the theme map. This is more of an init, since many of
|
"""Update the theme map. This is more of an init, since many of
|
||||||
the GUI icons cannot really be replaced without writing specific
|
the GUI icons cannot really be replaced without writing specific
|
||||||
update functions for the classes where they're used.
|
update functions for the classes where they're used.
|
||||||
@@ -585,7 +585,7 @@ class GuiIcons:
|
|||||||
return QPixmap()
|
return QPixmap()
|
||||||
|
|
||||||
if not os.path.isfile(imgPath):
|
if not os.path.isfile(imgPath):
|
||||||
logger.error("Asset '%s' not found", self.IMAGE_MAP[decoKey])
|
logger.error("Asset not found: %s", imgPath)
|
||||||
return QPixmap()
|
return QPixmap()
|
||||||
|
|
||||||
theDeco = QPixmap(imgPath)
|
theDeco = QPixmap(imgPath)
|
||||||
@@ -598,7 +598,7 @@ class GuiIcons:
|
|||||||
|
|
||||||
return theDeco
|
return theDeco
|
||||||
|
|
||||||
def getIcon(self, iconKey, iconSize=None):
|
def getIcon(self, iconKey):
|
||||||
"""Return an icon from the icon buffer. If it doesn't exist,
|
"""Return an icon from the icon buffer. If it doesn't exist,
|
||||||
return, load it, and if it still doesn't exist, return an empty
|
return, load it, and if it still doesn't exist, return an empty
|
||||||
icon.
|
icon.
|
||||||
|
|||||||
@@ -26,13 +26,15 @@ import pytest
|
|||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
|
||||||
from mock import causeOSError
|
from mock import causeOSError
|
||||||
|
from novelwriter.constants import nwLabels
|
||||||
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||||
from tools import writeFile
|
from tools import writeFile
|
||||||
|
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QIcon, QPalette, QPixmap
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from novelwriter.config import Config
|
from novelwriter.config import Config
|
||||||
from novelwriter.gui.theme import GuiTheme
|
from novelwriter.gui.theme import GuiIcons, GuiTheme
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@@ -121,8 +123,8 @@ def testGuiTheme_Main(qtbot, nwGUI, fncDir):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir):
|
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir):
|
||||||
"""Test the theme class init.
|
"""Test the theme part of the class.
|
||||||
"""
|
"""
|
||||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||||
mainConf: Config = nwGUI.mainConf
|
mainConf: Config = nwGUI.mainConf
|
||||||
@@ -140,6 +142,11 @@ def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir):
|
|||||||
)
|
)
|
||||||
writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */")
|
writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */")
|
||||||
|
|
||||||
|
# Block the reading of the files
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert mainTheme.listThemes() == []
|
||||||
|
|
||||||
# Load the theme info
|
# Load the theme info
|
||||||
themesList = mainTheme.listThemes()
|
themesList = mainTheme.listThemes()
|
||||||
assert themesList[0] == ("default_dark", "Default Dark Theme")
|
assert themesList[0] == ("default_dark", "Default Dark Theme")
|
||||||
@@ -186,4 +193,254 @@ def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir):
|
|||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|
||||||
# END Test testGuiTheme_Themes
|
# END Test testGuiTheme_Theme
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir):
|
||||||
|
"""Test the syntax part of the class.
|
||||||
|
"""
|
||||||
|
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||||
|
mainConf: Config = nwGUI.mainConf
|
||||||
|
|
||||||
|
# List Themes
|
||||||
|
# ===========
|
||||||
|
|
||||||
|
shutil.copy(
|
||||||
|
os.path.join(mainConf.assetPath, "syntax", "default_dark.conf"),
|
||||||
|
os.path.join(fncDir, "syntax")
|
||||||
|
)
|
||||||
|
shutil.copy(
|
||||||
|
os.path.join(mainConf.assetPath, "syntax", "default_light.conf"),
|
||||||
|
os.path.join(fncDir, "syntax")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Block the reading of the files
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert mainTheme.listThemes() == []
|
||||||
|
|
||||||
|
# Load the syntax info
|
||||||
|
syntaxList = mainTheme.listSyntax()
|
||||||
|
assert syntaxList[0] == ("default_dark", "Default Dark")
|
||||||
|
assert syntaxList[1] == ("default_light", "Default Light")
|
||||||
|
|
||||||
|
# A second call should returned the cached list
|
||||||
|
assert mainTheme.listSyntax() == mainTheme._syntaxList
|
||||||
|
|
||||||
|
# Check handling of broken theme settings
|
||||||
|
mainConf.guiSyntax = "not_a_syntax"
|
||||||
|
assert mainTheme.loadSyntax() is False
|
||||||
|
|
||||||
|
# Check handling of unreadable file
|
||||||
|
mainConf.guiSyntax = "default_light"
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert mainTheme.loadSyntax() is False
|
||||||
|
|
||||||
|
# Load Default Light Syntax
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# Load the default syntax
|
||||||
|
mainConf.guiSyntax = "default_light"
|
||||||
|
assert mainTheme.loadSyntax() is True
|
||||||
|
|
||||||
|
# Check some values
|
||||||
|
assert mainTheme.syntaxName == "Default Light"
|
||||||
|
assert mainTheme.colBack == [255, 255, 255]
|
||||||
|
assert mainTheme.colText == [0, 0, 0]
|
||||||
|
assert mainTheme.colLink == [0, 0, 200]
|
||||||
|
|
||||||
|
# Load Default Dark Theme
|
||||||
|
# =======================
|
||||||
|
|
||||||
|
# Load the default syntax
|
||||||
|
mainConf.guiSyntax = "default_dark"
|
||||||
|
assert mainTheme.loadSyntax() is True
|
||||||
|
|
||||||
|
# Check some values
|
||||||
|
assert mainTheme.syntaxName == "Default Dark"
|
||||||
|
assert mainTheme.colBack == [54, 54, 54]
|
||||||
|
assert mainTheme.colText == [199, 207, 208]
|
||||||
|
assert mainTheme.colLink == [184, 200, 0]
|
||||||
|
|
||||||
|
# qtbot.stop()
|
||||||
|
|
||||||
|
# END Test testGuiTheme_Syntax
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||||
|
"""Test the icon cache class.
|
||||||
|
"""
|
||||||
|
iconCache: GuiIcons = nwGUI.mainTheme.iconCache
|
||||||
|
mainConf: Config = nwGUI.mainConf
|
||||||
|
|
||||||
|
# Load Theme
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
# Invalid theme name
|
||||||
|
assert iconCache.loadTheme("not_a_theme") is False
|
||||||
|
|
||||||
|
# Check handling of unreadable file
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert iconCache.loadTheme("typicons_dark") is False
|
||||||
|
|
||||||
|
# Load a broken theme file
|
||||||
|
iconsDir = os.path.join(fncDir, "icons")
|
||||||
|
os.mkdir(iconsDir)
|
||||||
|
os.mkdir(os.path.join(iconsDir, "testicons"))
|
||||||
|
writeFile(os.path.join(iconsDir, "testicons", "icons.conf"), (
|
||||||
|
"[Main]\n"
|
||||||
|
"name = Test Icons\n"
|
||||||
|
"\n"
|
||||||
|
"[Map]\n"
|
||||||
|
"add = add.svg\n"
|
||||||
|
"stuff = stuff.svg\n"
|
||||||
|
))
|
||||||
|
|
||||||
|
assetPath = mainConf.assetPath
|
||||||
|
mainConf.assetPath = fncDir
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
assert iconCache.loadTheme("testicons") is True
|
||||||
|
assert "Unknown icon name 'stuff' in config file" in caplog.text
|
||||||
|
assert "Icon file 'add.svg' not in theme folder" in caplog.text
|
||||||
|
|
||||||
|
mainConf.assetPath = assetPath
|
||||||
|
|
||||||
|
# Load working theme file
|
||||||
|
assert iconCache.loadTheme("typicons_dark") is True
|
||||||
|
assert "add" in iconCache._themeMap
|
||||||
|
|
||||||
|
# Load Decorations
|
||||||
|
# ================
|
||||||
|
|
||||||
|
# Invalid name should return empty pixmap
|
||||||
|
qPix = iconCache.loadDecoration("stuff")
|
||||||
|
assert qPix.isNull() is True
|
||||||
|
|
||||||
|
# Load an image
|
||||||
|
qPix = iconCache.loadDecoration("wiz-back")
|
||||||
|
assert qPix.isNull() is False
|
||||||
|
|
||||||
|
# Fail finding the file
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("os.path.isfile", lambda *a: False)
|
||||||
|
qPix = iconCache.loadDecoration("wiz-back")
|
||||||
|
assert qPix.isNull() is True
|
||||||
|
|
||||||
|
# Test image sizes
|
||||||
|
qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=None)
|
||||||
|
assert qPix.isNull() is False
|
||||||
|
assert qPix.width() == 100
|
||||||
|
assert qPix.height() > 100
|
||||||
|
|
||||||
|
qPix = iconCache.loadDecoration("wiz-back", pxW=None, pxH=100)
|
||||||
|
assert qPix.isNull() is False
|
||||||
|
assert qPix.width() < 100
|
||||||
|
assert qPix.height() == 100
|
||||||
|
|
||||||
|
qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=100)
|
||||||
|
assert qPix.isNull() is False
|
||||||
|
assert qPix.width() == 100
|
||||||
|
assert qPix.height() == 100
|
||||||
|
|
||||||
|
# Load Icons
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
# Load an unknown icon
|
||||||
|
qIcon = iconCache.getIcon("stuff")
|
||||||
|
assert isinstance(qIcon, QIcon)
|
||||||
|
assert qIcon.isNull() is True
|
||||||
|
|
||||||
|
# Load an icon, it is likelyu already cached
|
||||||
|
qIcon = iconCache.getIcon("add")
|
||||||
|
assert isinstance(qIcon, QIcon)
|
||||||
|
assert qIcon.isNull() is False
|
||||||
|
|
||||||
|
# Load it as a pixmap with a size
|
||||||
|
qPix = iconCache.getPixmap("add", (50, 50))
|
||||||
|
assert isinstance(qPix, QPixmap)
|
||||||
|
assert qPix.isNull() is False
|
||||||
|
assert qPix.width() == 50
|
||||||
|
assert qPix.height() == 50
|
||||||
|
|
||||||
|
# Load app icon
|
||||||
|
qIcon = iconCache.getIcon("novelwriter")
|
||||||
|
assert isinstance(qIcon, QIcon)
|
||||||
|
assert qIcon.isNull() is False
|
||||||
|
|
||||||
|
# Load mime icon
|
||||||
|
qIcon = iconCache.getIcon("proj_nwx")
|
||||||
|
assert isinstance(qIcon, QIcon)
|
||||||
|
assert qIcon.isNull() is False
|
||||||
|
|
||||||
|
# Load Item Icons
|
||||||
|
# ===============
|
||||||
|
|
||||||
|
# Root -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.ROOT, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0"
|
||||||
|
) == iconCache.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
||||||
|
|
||||||
|
# Folder -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FOLDER, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0"
|
||||||
|
) == iconCache.getIcon("proj_folder")
|
||||||
|
|
||||||
|
# Document H0 -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0"
|
||||||
|
) == iconCache.getIcon("proj_document")
|
||||||
|
|
||||||
|
# Document H1 -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H1"
|
||||||
|
) == iconCache.getIcon("proj_title")
|
||||||
|
|
||||||
|
# Document H2 -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H2"
|
||||||
|
) == iconCache.getIcon("proj_chapter")
|
||||||
|
|
||||||
|
# Document H3 -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H3"
|
||||||
|
) == iconCache.getIcon("proj_scene")
|
||||||
|
|
||||||
|
# Document H4 -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H4"
|
||||||
|
) == iconCache.getIcon("proj_section")
|
||||||
|
|
||||||
|
# Document H5 -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H4"
|
||||||
|
) == iconCache.getIcon("proj_document")
|
||||||
|
|
||||||
|
# Note -> Not Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NOTE, hLevel="H5"
|
||||||
|
) == iconCache.getIcon("proj_note")
|
||||||
|
|
||||||
|
# No Type -> Null
|
||||||
|
assert iconCache.getItemIcon(
|
||||||
|
nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0"
|
||||||
|
).isNull() is True
|
||||||
|
|
||||||
|
# Header Decorations
|
||||||
|
# ==================
|
||||||
|
|
||||||
|
assert iconCache.getHeaderDecoration(-1) == iconCache._headerDec[0]
|
||||||
|
assert iconCache.getHeaderDecoration(0) == iconCache._headerDec[0]
|
||||||
|
assert iconCache.getHeaderDecoration(1) == iconCache._headerDec[1]
|
||||||
|
assert iconCache.getHeaderDecoration(2) == iconCache._headerDec[2]
|
||||||
|
assert iconCache.getHeaderDecoration(3) == iconCache._headerDec[3]
|
||||||
|
assert iconCache.getHeaderDecoration(4) == iconCache._headerDec[4]
|
||||||
|
assert iconCache.getHeaderDecoration(5) == iconCache._headerDec[4]
|
||||||
|
|
||||||
|
# qtbot.stop()
|
||||||
|
|
||||||
|
# END Test testGuiTheme_Icons
|
||||||
|
|||||||
Reference in New Issue
Block a user