Add tests for sidebar and themes
This commit is contained in:
+21
-22
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user