Add tests for sidebar and themes

This commit is contained in:
Veronica Berglyd Olsen
2025-06-03 22:36:53 +02:00
parent 5fee3ecdb8
commit bd2353f7bc
3 changed files with 389 additions and 333 deletions
+21 -22
View File
@@ -240,8 +240,8 @@ class GuiTheme:
return hint.colorScheme() == Qt.ColorScheme.Dark
palette = QPalette()
text = palette.color(QPalette.ColorRole.WindowText)
window = palette.color(QPalette.ColorRole.Window)
text = palette.windowText().color()
window = palette.window().color()
return text.lightnessF() > window.lightnessF()
def parseColor(self, value: str, default: QColor = QtBlack) -> QColor:
@@ -270,7 +270,7 @@ class GuiTheme:
return QColor(*result)
return default
def loadTheme(self, force: bool = False) -> bool:
def loadTheme(self, force: bool = False) -> None:
"""Load the currently specified GUI theme."""
match CONFIG.themeMode:
case nwTheme.LIGHT:
@@ -282,7 +282,7 @@ class GuiTheme:
theme = CONFIG.darkTheme if darkMode else CONFIG.lightTheme
if theme not in self._allThemes:
logger.error("Could not find theme for key '%s'", theme)
logger.error("Could not find theme '%s'", theme)
if darkMode:
theme = DEF_GUI_DARK
CONFIG.darkTheme = DEF_GUI_DARK
@@ -292,23 +292,22 @@ class GuiTheme:
if theme == self._currentTheme and not force:
logger.info("Theme '%s' is already loaded", theme)
return False
return
entry = self._allThemes.get(theme)
if not entry:
logger.error("Could not load GUI theme")
return False
return
CONFIG.splashMessage(f"Loading colour theme: {entry.name}")
logger.info("Loading GUI theme '%s'", theme)
parser = ConfigParser()
try:
with open(entry.path, mode="r", encoding="utf-8") as fo:
parser.read_file(fo)
parser.read(entry.path, encoding="utf-8")
except Exception:
logger.error("Could not read file: %s", entry.path)
logException()
return False
return
# Reset Palette
self._resetTheme()
@@ -458,15 +457,15 @@ class GuiTheme:
# Set project override colours
if (override := CONFIG.iconColTree) != "theme":
color = self._svgColors.get(override, b"#000000")
self._svgColors["root"] = color
self._svgColors["folder"] = color
color = self._qColors.get(override, QtBlack)
self._setBaseColor("root", color)
self._setBaseColor("folder", color)
if not CONFIG.iconColDocs:
self._svgColors["file"] = color
self._svgColors["title"] = color
self._svgColors["chapter"] = color
self._svgColors["scene"] = color
self._svgColors["note"] = color
self._setBaseColor("file", color)
self._setBaseColor("title", color)
self._setBaseColor("chapter", color)
self._setBaseColor("scene", color)
self._setBaseColor("note", color)
self.isDarkTheme = darkMode
self._currentTheme = theme
@@ -478,7 +477,7 @@ class GuiTheme:
QApplication.setPalette(self._guiPalette)
self._buildStyleSheets(self._guiPalette)
return True
return
def getStyleSheet(self, name: str) -> str:
"""Load a standard style sheet."""
@@ -685,7 +684,7 @@ class GuiIcons:
self._scanThemes(icons)
return
def loadTheme(self, theme: str) -> bool:
def loadTheme(self, theme: str) -> None:
"""Update the theme map. This is more of an init, since many of
the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used.
@@ -698,7 +697,7 @@ class GuiIcons:
entry = self._allThemes.get(theme)
if not entry:
logger.error("Could not load icon theme")
return False
return
CONFIG.splashMessage(f"Loading icon theme: {entry.name}")
logger.info("Loading icon theme '%s'", theme)
@@ -722,14 +721,14 @@ class GuiIcons:
except Exception:
logger.error("Could not read file: %s", entry.path)
logException()
return False
return
# Populate generated icons cache
CONFIG.splashMessage("Generating additional icons ...")
self.getHeaderDecoration(0)
self.getHeaderDecorationNarrow(0)
return True
return
##
# Access Functions
+53
View File
@@ -0,0 +1,53 @@
"""
novelWriter Side Bar Class Tester
===================================
This file is a part of novelWriter
Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter 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/>.
"""
from __future__ import annotations
import pytest
from novelwriter import CONFIG
from novelwriter.constants import nwLabels
from novelwriter.enum import nwTheme
@pytest.mark.gui
def testGuiSideBar_CycleColourTheme(nwGUI):
"""Test theme cycle feature on the side bar."""
CONFIG.themeMode = nwTheme.AUTO
sidebar = nwGUI.sideBar
sidebar.mainGui.checkThemeUpdate = lambda *a: None
# Run 3 Cycles
for _ in range(3):
# Cycle Light
sidebar._cycleColurTheme()
assert CONFIG.themeMode == nwTheme.LIGHT
assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.LIGHT]
# Cycle Dark
sidebar._cycleColurTheme()
assert CONFIG.themeMode == nwTheme.DARK
assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.DARK]
# Cycle Auto
sidebar._cycleColurTheme()
assert CONFIG.themeMode == nwTheme.AUTO
assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.AUTO]
+315 -311
View File
@@ -20,99 +20,31 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import sys
from configparser import ConfigParser
from pathlib import Path
from unittest.mock import MagicMock, Mock
import pytest
from PyQt6.QtGui import QColor, QIcon, QPalette, QPixmap
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QColor, QFont, QFontDatabase, QIcon, QPalette, QPixmap, QStyleHints
from novelwriter import CONFIG, SHARED
from novelwriter.config import DEF_GUI_LIGHT
from novelwriter import CONFIG
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.gui.theme import _listContent
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
from novelwriter.gui.theme import (
STYLES_BIG_TOOLBUTTON, STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON, GuiTheme,
ThemeMeta, _listContent
)
from tests.mocked import causeOSError
from tests.tools import writeFile
@pytest.mark.gui
def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
"""Test the theme class init."""
theme = SHARED.theme
# Methods
# =======
mSize = theme.getTextWidth("m")
assert mSize > 0
assert theme.getTextWidth("m", theme.guiFont) == mSize
# Scan for Themes
# ===============
result = []
_listContent(result, Path("not_a_path"), ".conf")
assert result == []
themeOne = tstPaths.cnfDir / "themes" / "themeone.conf"
themeTwo = tstPaths.cnfDir / "themes" / "themetwo.conf"
writeFile(themeOne, "# Stuff")
writeFile(themeTwo, "# Stuff")
_listContent(result, tstPaths.cnfDir / "themes", ".conf")
assert result == [themeOne, themeTwo]
# Parse Colours
# =============
parser = ConfigParser()
parser["Palette"] = {
"colour1": "100, 150, 200", # Valid
"colour2": "100, 150, 200, 250", # With alpha
"colour3": "100, 150, 200, 250, 300", # Too many values
"colour4": "250, 250", # Missing blue
"colour5": "-10, 127, 300", # Invalid red and blue
"colour6": "bob, 127, 255", # Invalid red
}
# Test the parser for several valid and invalid values
assert theme._readColor(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255)
assert theme._readColor(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250)
assert theme._readColor(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250)
assert theme._readColor(parser, "Palette", "colour4").getRgb() == (250, 250, 0, 255)
assert theme._readColor(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0)
assert theme._readColor(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255)
# The palette should load with the parsed values
theme._setPalette(parser, "Palette", "colour1", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 255)
theme._setPalette(parser, "Palette", "colour2", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
theme._setPalette(parser, "Palette", "colour3", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
theme._setPalette(parser, "Palette", "colour4", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (250, 250, 0, 255)
theme._setPalette(parser, "Palette", "colour5", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 0)
theme._setPalette(parser, "Palette", "colour6", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 127, 255, 255)
# Non-existing value should return default colour
theme._setBaseColor("default", QColor(64, 64, 64, 255))
theme._setPalette(parser, "Palette", "stuff", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (64, 64, 64, 255)
# qtbot.stop()
@pytest.mark.gui
def testGuiTheme_ParseColor(qtbot, nwGUI):
def testGuiTheme_ParseColor():
"""Test the colour parsing."""
theme = SHARED.theme
theme = GuiTheme()
# Pre-Populate
theme._qColors["red"] = QColor(255, 0, 0)
@@ -144,258 +76,333 @@ def testGuiTheme_ParseColor(qtbot, nwGUI):
@pytest.mark.gui
@pytest.mark.skip
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
"""Test the theme part of the class."""
theme = SHARED.theme
def testGuiTheme_ScanThemes(monkeypatch):
"""Test the themes scanning."""
theme = GuiTheme()
# List Themes
# Load built-in themes
files = []
_listContent(files, CONFIG.assetPath("themes"), ".conf")
assert len(files) > 0
# Block reading theme files
with monkeypatch.context() as mp:
mp.setattr(ConfigParser, "read", causeOSError)
theme._scanThemes(files)
assert theme.colourThemes == {}
# Read all themes correctly
theme._scanThemes(files)
assert len(theme.colourThemes) > 0
dark = theme.colourThemes[DEF_GUI_DARK]
light = theme.colourThemes[DEF_GUI_LIGHT]
assert dark.name == "Default Dark Theme"
assert dark.dark is True
assert light.name == "Default Light Theme"
assert light.dark is False
@pytest.mark.gui
def testGuiTheme_LoadThemes(monkeypatch):
"""Test loading themes."""
theme = GuiTheme()
theme.iconCache = MagicMock()
CONFIG.lightTheme = DEF_GUI_LIGHT
CONFIG.darkTheme = DEF_GUI_DARK
# Load built-in themes
files = []
_listContent(files, CONFIG.assetPath("themes"), ".conf")
theme._scanThemes(files)
assert DEF_GUI_LIGHT in theme._allThemes
assert DEF_GUI_DARK in theme._allThemes
assert theme._currentTheme == ""
# Load light theme
CONFIG.themeMode = nwTheme.LIGHT
theme.loadTheme()
assert theme._currentTheme == DEF_GUI_LIGHT
# Load dark theme
CONFIG.themeMode = nwTheme.DARK
theme.loadTheme()
assert theme._currentTheme == DEF_GUI_DARK
# Let auto switch back to light, then dark
with monkeypatch.context() as mp:
mp.setattr(CONFIG, "verQtValue", 0x060500)
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Light)
CONFIG.themeMode = nwTheme.AUTO
theme.loadTheme()
assert theme._currentTheme == DEF_GUI_LIGHT
with monkeypatch.context() as mp:
mp.setattr(CONFIG, "verQtValue", 0x060500)
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Dark)
CONFIG.themeMode = nwTheme.AUTO
theme.loadTheme()
assert theme._currentTheme == DEF_GUI_DARK
# Error Cases
# ===========
# Block the reading of the files
# Invalid light theme
CONFIG.lightTheme = "not_a_theme"
CONFIG.themeMode = nwTheme.LIGHT
theme.loadTheme()
assert theme._currentTheme == DEF_GUI_LIGHT
# Invalid dark theme
CONFIG.darkTheme = "not_a_theme"
CONFIG.themeMode = nwTheme.DARK
theme.loadTheme()
assert theme._currentTheme == DEF_GUI_DARK
# Clear meta and check exit early cases
theme._meta = ThemeMeta()
assert theme._meta.name == ""
# Reload dark should not load anything
CONFIG.darkTheme = DEF_GUI_DARK
CONFIG.themeMode = nwTheme.DARK
theme.loadTheme()
assert theme._meta.name == ""
# Force reload, but fail parsing
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
theme._themeList = []
assert theme.getColourThemes() == []
mp.setattr(ConfigParser, "read", causeOSError)
CONFIG.darkTheme = DEF_GUI_DARK
CONFIG.themeMode = nwTheme.DARK
theme.loadTheme(force=True)
assert theme._meta.name == ""
# Load the theme info, default themes first
themesList = theme.getColourThemes()
assert themesList[0] == ("default_dark", "Default Dark Theme")
assert themesList[1] == ("default_light", "Default Light Theme")
assert themesList[2] == ("cyberpunk_night", "Cyberpunk Night")
assert themesList[3] == ("dracula", "Dracula")
# Invalid theme, and defaults are missing
del theme._allThemes[DEF_GUI_DARK]
del theme._allThemes[DEF_GUI_LIGHT]
# A second call should returned the cached list
assert theme.getColourThemes() == theme._themeList
CONFIG.lightTheme = "not_a_theme"
CONFIG.themeMode = nwTheme.LIGHT
theme.loadTheme(force=True)
assert theme._meta.name == ""
# Check handling of broken theme settings
CONFIG.guiTheme = "not_a_theme"
availThemes = theme._availThemes
theme._availThemes = {}
assert theme.loadTheme() is False
theme._availThemes = availThemes
CONFIG.darkTheme = "not_a_theme"
CONFIG.themeMode = nwTheme.DARK
theme.loadTheme(force=True)
assert theme._meta.name == ""
# Check handling of unreadable file
CONFIG.guiTheme = DEF_GUI_LIGHT
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert theme.loadTheme() is False
# Load Default Theme
# ==================
@pytest.mark.gui
def testGuiTheme_SpecialColors(tstPaths):
"""Test handling special cases for colours."""
theme = GuiTheme()
theme.iconCache = MagicMock()
if sys.platform != "win32":
# Set a mock colour for the window background
theme._guiPalette.color(QPalette.ColorRole.Window).setRgb(0, 0, 0, 0)
# Load the default theme
CONFIG.guiTheme = DEF_GUI_LIGHT
assert theme.loadTheme() is True
# This should load a standard palette
wCol = QPalette().color(QPalette.ColorRole.Window).getRgb()
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == wCol
# Mock Dark Theme
# ===============
mockTheme: Path = tstPaths.cnfDir / "themes" / "test.conf"
mockTheme.write_text(
testTheme: Path = tstPaths.cnfDir / "themes" / "test.conf"
testTheme.write_text(
"[Main]\n"
"name = Test\n"
"mode = light\n"
"\n"
"[Base]\n"
"default = #cccccc\n"
"faded = #949494\n"
"red = #ff0000\n"
"orange = #ff7f00\n"
"yellow = #ffff00\n"
"green = #00ff00\n"
"cyan = #00ffff\n"
"blue = #0000ff\n"
"purple = #ff00ff\n"
"\n"
"[Project]\n"
"root = blue\n"
"folder = yellow\n"
"file = default\n"
"title = green\n"
"chapter = red\n"
"scene = blue\n"
"note = yellow\n"
"\n"
"[Palette]\n"
"window = 0, 0, 0\n"
"text = 255, 255, 255\n"
"window = #000000\n"
"text = #ffffff\n"
)
theme._availThemes["test"] = mockTheme
theme._scanThemes([testTheme])
assert len(theme.colourThemes) == 1
CONFIG.themeMode = nwTheme.LIGHT
CONFIG.lightTheme = "test"
CONFIG.guiTheme = "test"
assert theme.loadTheme() is True
assert theme._guiPalette.window().color().getRgb() == (0, 0, 0, 255)
assert theme._guiPalette.text().color().getRgb() == (255, 255, 255, 255)
# Load theme
theme.loadTheme()
# Since window is black, a lighter version should be generated
assert theme._guiPalette.light().color().getRgb() == (57, 57, 57, 255)
assert theme.isDarkTheme is True
# Load Default Light Theme
# ========================
# Reload with project override to red
CONFIG.iconColTree = "red"
CONFIG.iconColDocs = True
theme.loadTheme(force=True)
CONFIG.guiTheme = "default_light"
assert theme.loadTheme() is True
assert theme.getBaseColor("root").getRgb() == (255, 0, 0, 255)
assert theme.getBaseColor("folder").getRgb() == (255, 0, 0, 255)
assert theme.getBaseColor("file").getRgb() == (204, 204, 204, 255)
assert theme.getBaseColor("title").getRgb() == (0, 255, 0, 255)
assert theme.getBaseColor("chapter").getRgb() == (255, 0, 0, 255)
assert theme.getBaseColor("scene").getRgb() == (0, 0, 255, 255)
assert theme.getBaseColor("note").getRgb() == (255, 255, 0, 255)
# Check a few values
assert theme._guiPalette.color(
QPalette.ColorRole.Window
).getRgb() == (239, 239, 239, 255)
assert theme._guiPalette.color(
QPalette.ColorRole.WindowText
).getRgb() == (0, 0, 0, 255)
assert theme._guiPalette.color(
QPalette.ColorRole.Base
).getRgb() == (255, 255, 255, 255)
assert theme._guiPalette.color(
QPalette.ColorRole.AlternateBase
).getRgb() == (224, 224, 224, 255)
# Load Default Dark Theme
# =======================
CONFIG.guiTheme = "default_dark"
assert theme.loadTheme() is True
# Check a few values
assert theme._guiPalette.color(
QPalette.ColorRole.Window).getRgb() == (54, 54, 54, 255)
assert theme._guiPalette.color(
QPalette.ColorRole.WindowText).getRgb() == (204, 204, 204, 255)
assert theme._guiPalette.color(
QPalette.ColorRole.Base).getRgb() == (62, 62, 62, 255)
assert theme._guiPalette.color(
QPalette.ColorRole.AlternateBase).getRgb() == (78, 78, 78, 255)
# qtbot.stop()
@pytest.mark.skip
@pytest.mark.gui
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
"""Test the syntax part of the class."""
mainTheme = SHARED.theme
# List Themes
# ===========
# 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
availSyntax = mainTheme._availSyntax
mainTheme._availSyntax = {}
CONFIG.guiSyntax = "not_a_syntax"
assert mainTheme.loadSyntax() is False
mainTheme._availSyntax = availSyntax
# Check handling of unreadable file
CONFIG.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
CONFIG.guiSyntax = "default_light"
assert mainTheme.loadSyntax() is True
# Check some values
assert mainTheme.syntaxMeta.name == "Default Light"
assert mainTheme.syntaxTheme.back == QColor(255, 255, 255)
assert mainTheme.syntaxTheme.text == QColor(0, 0, 0)
assert mainTheme.syntaxTheme.link == QColor(0, 0, 200)
# Load Default Dark Theme
# =======================
# Load the default syntax
CONFIG.guiSyntax = "default_dark"
assert mainTheme.loadSyntax() is True
# Check some values
assert mainTheme.syntaxMeta.name == "Default Dark"
assert mainTheme.syntaxTheme.back == QColor(42, 42, 42)
assert mainTheme.syntaxTheme.text == QColor(204, 204, 204)
assert mainTheme.syntaxTheme.link == QColor(102, 153, 204)
# qtbot.stop()
@pytest.mark.skip
@pytest.mark.gui
def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
"""Test the icon cache class."""
iconCache = SHARED.theme.iconCache
# Load Theme
# ==========
# Invalid theme name
availThemes = iconCache._availThemes
iconCache._availThemes = {}
assert iconCache.loadTheme("not_a_theme") is False
iconCache._availThemes = availThemes
# Check handling of unreadable file
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert iconCache.loadTheme("material_rounded_normal") is False
# Load working theme file
assert iconCache.loadTheme("material_rounded_normal") is True
assert iconCache.themeMeta.name == "Material Symbols - Rounded Medium"
# Load with project colour override
purple = iconCache._svgColors["purple"]
assert iconCache._svgColors["root"] != purple
assert iconCache._svgColors["folder"] != purple
assert iconCache._svgColors["file"] != purple
assert iconCache._svgColors["title"] != purple
assert iconCache._svgColors["chapter"] != purple
assert iconCache._svgColors["scene"] != purple
assert iconCache._svgColors["note"] != purple
assert theme.getRawBaseColor("root") == b"#ff0000"
assert theme.getRawBaseColor("folder") == b"#ff0000"
assert theme.getRawBaseColor("file") == b"#cccccc"
assert theme.getRawBaseColor("title") == b"#00ff00"
assert theme.getRawBaseColor("chapter") == b"#ff0000"
assert theme.getRawBaseColor("scene") == b"#0000ff"
assert theme.getRawBaseColor("note") == b"#ffff00"
# Reload with project override to purple, also for docs
CONFIG.iconColTree = "purple"
assert iconCache.loadTheme("material_rounded_normal") is True
assert iconCache._svgColors["root"] == purple
assert iconCache._svgColors["folder"] == purple
assert iconCache._svgColors["file"] == purple
assert iconCache._svgColors["title"] == purple
assert iconCache._svgColors["chapter"] == purple
assert iconCache._svgColors["scene"] == purple
assert iconCache._svgColors["note"] == purple
CONFIG.iconColDocs = False
theme.loadTheme(force=True)
# Change some colours
iconCache.setIconColor("root", QColor(255, 255, 255))
assert iconCache._svgColors["root"] != purple
assert iconCache._svgColors["root"] == b"#ffffff"
assert theme.getBaseColor("root").getRgb() == (255, 0, 255, 255)
assert theme.getBaseColor("folder").getRgb() == (255, 0, 255, 255)
assert theme.getBaseColor("file").getRgb() == (255, 0, 255, 255)
assert theme.getBaseColor("title").getRgb() == (255, 0, 255, 255)
assert theme.getBaseColor("chapter").getRgb() == (255, 0, 255, 255)
assert theme.getBaseColor("scene").getRgb() == (255, 0, 255, 255)
assert theme.getBaseColor("note").getRgb() == (255, 0, 255, 255)
# List Themes
# ===========
# Load error returns empty list
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
themes = iconCache.getIconThemes()
assert themes == []
# Successful read
themes = iconCache.getIconThemes()
assert len(themes) > 1
assert "material_rounded_normal" in dict(themes)
# Load error doesn't matter on second read since list is cached
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert iconCache.getIconThemes() == themes
# qtbot.stop()
assert theme.getRawBaseColor("root") == b"#ff00ff"
assert theme.getRawBaseColor("folder") == b"#ff00ff"
assert theme.getRawBaseColor("file") == b"#ff00ff"
assert theme.getRawBaseColor("title") == b"#ff00ff"
assert theme.getRawBaseColor("chapter") == b"#ff00ff"
assert theme.getRawBaseColor("scene") == b"#ff00ff"
assert theme.getRawBaseColor("note") == b"#ff00ff"
@pytest.mark.gui
def testGuiTheme_LoadIcons(qtbot, nwGUI):
def testGuiTheme_Methods(monkeypatch):
"""Test other themes methods."""
theme = GuiTheme()
theme.iconCache = MagicMock()
CONFIG.darkTheme = DEF_GUI_DARK
CONFIG.themeMode = nwTheme.DARK
# Init theme
assert theme.colourThemes == {}
theme.initThemes()
assert theme._meta.name == "Default Dark Theme"
# Text width
theme.guiFont = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
assert theme.getTextWidth("MMMMM") > theme.getTextWidth("MMM")
font = QFont(theme.guiFont)
font.setPointSizeF(0.5*font.pointSizeF())
assert theme.getTextWidth("MMMMM", font) < theme.getTextWidth("MMMMM")
# Detect desktop mode Qt 6.5+
with monkeypatch.context() as mp:
mp.setattr(CONFIG, "verQtValue", 0x060500)
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Light)
assert theme.isDesktopDarkMode() is False
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Dark)
assert theme.isDesktopDarkMode() is True
# Detect desktop mode Qt 6.4
mockWhite = Mock()
mockWhite.color.return_value = QColor(255, 255, 255)
mockBlack = Mock()
mockBlack.color.return_value = QColor(0, 0, 0)
with monkeypatch.context() as mp:
mp.setattr(CONFIG, "verQtValue", 0x060400)
mp.setattr(QPalette, "window", lambda *a: mockWhite)
mp.setattr(QPalette, "windowText", lambda *a: mockBlack)
assert theme.isDesktopDarkMode() is False
mp.setattr(QPalette, "window", lambda *a: mockBlack)
mp.setattr(QPalette, "windowText", lambda *a: mockWhite)
assert theme.isDesktopDarkMode() is True
# Stylesheets
assert theme.getStyleSheet(STYLES_FLAT_TABS) != ""
assert theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) != ""
assert theme.getStyleSheet(STYLES_BIG_TOOLBUTTON) != ""
assert theme.getStyleSheet("stuff") == ""
@pytest.mark.gui
def testGuiTheme_ScanIcons(monkeypatch):
"""Test the icon theme scanning."""
theme = GuiTheme()
# Load built-in themes
files = []
_listContent(files, CONFIG.assetPath("icons"), ".icons")
assert len(files) > 0
# Block reading theme files
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
theme.iconCache._scanThemes(files)
assert theme.iconCache.iconThemes == {}
# Read all themes correctly
theme.iconCache._scanThemes(files)
assert len(theme.iconCache.iconThemes) > 0
@pytest.mark.gui
def testGuiTheme_IconThemes(monkeypatch):
"""Test loading icon theme."""
CONFIG.lightTheme = DEF_GUI_LIGHT
CONFIG.themeMode = nwTheme.LIGHT
CONFIG.iconTheme = DEF_ICONS
theme = GuiTheme()
# Init should load default theme
theme.initThemes()
assert theme.iconCache._meta.name == "Material Symbols - Rounded Medium"
assert DEF_ICONS in theme.iconCache.iconThemes
# Load default theme directly
theme.iconCache._meta = ThemeMeta()
theme.iconCache.loadTheme("DEF_ICONS")
assert theme.iconCache._meta.name == "Material Symbols - Rounded Medium"
# Failed loading should load nothing
theme.iconCache._meta = ThemeMeta()
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
theme.iconCache.loadTheme("DEF_ICONS")
assert theme.iconCache._meta.name == ""
# Reload with non-existent theme should reload default
theme.iconCache._meta = ThemeMeta()
theme.iconCache.loadTheme("not_a_theme")
assert theme.iconCache._meta.name == "Material Symbols - Rounded Medium"
# If default theme is missing, load nothing
del theme.iconCache._allThemes[DEF_ICONS]
theme.iconCache._meta = ThemeMeta()
theme.iconCache.loadTheme("not_a_theme")
assert theme.iconCache._meta.name == ""
@pytest.mark.gui
def testGuiTheme_LoadIcons():
"""Test the icon cache class."""
iconCache = SHARED.theme.iconCache
assert iconCache.loadTheme("material_rounded_normal") is True
theme = GuiTheme()
theme.initThemes()
iconCache = theme.iconCache
# Load Icons
# ==========
@@ -493,14 +500,13 @@ def testGuiTheme_LoadIcons(qtbot, nwGUI):
nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0"
) == iconCache._noIcon
# qtbot.stop()
@pytest.mark.gui
def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI):
def testGuiTheme_LoadDecorations(monkeypatch):
"""Test the icon cache class."""
iconCache = SHARED.theme.iconCache
assert iconCache.loadTheme("material_rounded_normal") is True
theme = GuiTheme()
theme.initThemes()
iconCache = theme.iconCache
# Load Decorations
# ================
@@ -557,5 +563,3 @@ def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI):
assert iconCache.getHeaderDecorationNarrow(4) == iconCache._headerDecNarrow[4]
assert iconCache.getHeaderDecorationNarrow(5) == iconCache._headerDecNarrow[5]
assert iconCache.getHeaderDecorationNarrow(6) == iconCache._headerDecNarrow[5]
# qtbot.stop()