Merge branch 'dev' into project_handling

This commit is contained in:
Veronica Berglyd Olsen
2024-01-27 14:42:43 +01:00
68 changed files with 2045 additions and 2222 deletions
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="1" timeStamp="2022-11-07 13:00:48">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="2" timeStamp="2022-11-07 13:00:48">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0.7" hexVersion="0x020007f0" fileVersion="1.5" fileRevision="1" timeStamp="2023-06-03 18:18:23">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:50:57">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0.7" hexVersion="0x020007f0" fileVersion="1.5" fileRevision="1" timeStamp="2023-06-03 18:18:23">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:50:57">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.1-beta1" hexVersion="0x020100b1" fileVersion="1.5" fileRevision="1" timeStamp="2023-07-20 20:33:41">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:53:40">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3-alpha1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-12-29 14:34:07">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:53:40">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Project A</name>
<title>Test Project A</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3-alpha1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-12-29 14:36:07">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:53:40">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Project B</name>
<title>Test Project B</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.2-alpha1" hexVersion="0x020200a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-10-17 20:52:34">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="4">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:48:13">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="3">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0.7" hexVersion="0x020007f0" fileVersion="1.5" fileRevision="1" timeStamp="2023-06-03 18:18:30">
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:45:36">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
</project>
<settings>
@@ -1,10 +1,9 @@
{
"meta": {
"projectName": "Lorem Ipsum",
"novelTitle": "Lorem Ipsum",
"novelAuthor": "lipsum.com",
"buildTime": 1687620769,
"buildTimeStr": "2023-06-24 17:32:49"
"buildTime": 1706306020,
"buildTimeStr": "2024-01-26 22:53:40"
},
"text": {
"css": [
@@ -1,10 +1,9 @@
{
"meta": {
"projectName": "Lorem Ipsum",
"novelTitle": "Lorem Ipsum",
"novelAuthor": "lipsum.com",
"buildTime": 1687621222,
"buildTimeStr": "2023-06-24 17:40:22"
"buildTime": 1706306020,
"buildTimeStr": "2024-01-26 22:53:40"
},
"text": {
"nwd": [
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="1" timeStamp="2020-05-28 09:59:15">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="2" timeStamp="2020-05-28 09:59:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jay Doh</author>
</project>
<settings>
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="1" timeStamp="2020-06-26 21:20:24">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="2" timeStamp="2020-06-26 21:20:24">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jay Doh</author>
</project>
<settings>
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="1" timeStamp="2021-08-30 23:33:44">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="2" timeStamp="2021-08-30 23:33:44">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jay Doh</author>
</project>
<settings>
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="1" timeStamp="2022-10-25 18:26:15">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="2" timeStamp="2022-10-25 18:26:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jay Doh</author>
</project>
<settings>
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="1" timeStamp="2022-10-15 12:12:59">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="2" timeStamp="2022-10-15 12:12:59">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jay Doh</author>
</project>
<settings>
+12 -1
View File
@@ -35,7 +35,7 @@ from PyQt5.QtCore import QUrl
from novelwriter.common import (
checkBool, checkFloat, checkHandle, checkInt, checkIntTuple, checkPath,
checkString, checkStringNone, checkUuid, formatInt, formatTime,
formatTimeStamp, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax,
numberToRoman, NWConfigParser, openExternalPath, readTextFile, simplified,
transferCase, xmlIndent, yesNo
@@ -348,6 +348,17 @@ def testBaseCommon_formatTime():
# END Test testBaseCommon_formatTime
@pytest.mark.base
def testBaseCommon_formatVersion():
"""Test the formatVersion function."""
assert formatVersion("1.2") == "1.2"
assert formatVersion("1.2a1") == "1.2 Alpha 1"
assert formatVersion("1.2b2") == "1.2 Beta 2"
assert formatVersion("1.2rc3") == "1.2 RC 3"
# END Test testBaseCommon_formatVersion
@pytest.mark.base
def testBaseCommon_simplified():
"""Test the simplified function."""
+2 -2
View File
@@ -230,7 +230,7 @@ def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
assert error == []
copyfile(docFile, tstFile)
assert cmpFiles(tstFile, cmpFile, ignoreLines=[6, 7])
assert cmpFiles(tstFile, cmpFile, ignoreLines=[5, 6])
# Check Error Handling
# ====================
@@ -370,7 +370,7 @@ def testCoreDocBuild_NWD(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
assert error == []
copyfile(docFile, tstFile)
assert cmpFiles(tstFile, cmpFile, ignoreLines=[6, 7])
assert cmpFiles(tstFile, cmpFile, ignoreLines=[5, 6])
# Check Error Handling
# ====================
+5
View File
@@ -695,6 +695,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
"This is a story about Jane Smith.\n\n"
"Well, not really. She's still awesome though.\n"
))
# Whole document
cC, wC, pC = index.getCounts(nHandle)
assert cC == 152
@@ -798,6 +799,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
# Extract stats
assert index.getNovelWordCount(activeOnly=False) == 43
assert index.getNovelWordCount(activeOnly=True) == 15
assert index.getNovelWordCount(rootHandle=C.hNovelRoot, activeOnly=False) == 43
assert index.getNovelWordCount(rootHandle=C.hNovelRoot, activeOnly=True) == 15
assert index.getNovelWordCount(rootHandle=C.hWorldRoot, activeOnly=False) == 0
assert index.getNovelWordCount(rootHandle=C.hWorldRoot, activeOnly=True) == 0
assert index.getNovelTitleCounts(activeOnly=False) == [0, 3, 2, 3, 0]
assert index.getNovelTitleCounts(activeOnly=True) == [0, 1, 2, 3, 0]
+16 -16
View File
@@ -121,27 +121,27 @@ def testCoreOptions_SetGet(mockGUI):
assert options.setValue("GuiProjectSettings", "winWidth", 100) is True
# Set some values of different types
assert options.setValue("GuiProjectDetails", "winWidth", 100) is True
assert options.setValue("GuiProjectDetails", "winHeight", 12.34) is True
assert options.setValue("GuiProjectDetails", "clearDouble", True) is True
assert options.setValue("GuiNovelDetails", "winWidth", 100) is True
assert options.setValue("GuiNovelDetails", "winHeight", 12.34) is True
assert options.setValue("GuiNovelDetails", "clearDouble", True) is True
assert options.setValue("GuiNovelView", "lastCol", nwColHidden) is True
# Generic get, doesn't check type
assert options.getValue("GuiProjectDetails", "winWidth", None) == 100
assert options.getValue("GuiProjectDetails", "winHeight", None) == 12.34
assert options.getValue("GuiProjectDetails", "clearDouble", None) is True
assert options.getValue("GuiProjectDetails", "mockItem", None) is None
assert options.getValue("GuiNovelDetails", "winWidth", None) == 100
assert options.getValue("GuiNovelDetails", "winHeight", None) == 12.34
assert options.getValue("GuiNovelDetails", "clearDouble", None) is True
assert options.getValue("GuiNovelDetails", "mockItem", None) is None
# Get type-specific
assert options.getString("GuiProjectDetails", "winWidth", None) is None # type: ignore
assert options.getString("GuiProjectDetails", "mockItem", None) is None # type: ignore
assert options.getInt("GuiProjectDetails", "winWidth", None) == 100 # type: ignore
assert options.getInt("GuiProjectDetails", "textFont", None) is None # type: ignore
assert options.getInt("GuiProjectDetails", "mockItem", None) is None # type: ignore
assert options.getFloat("GuiProjectDetails", "winWidth", None) == 100.0 # type: ignore
assert options.getFloat("GuiProjectDetails", "mockItem", None) is None # type: ignore
assert options.getBool("GuiProjectDetails", "clearDouble", None) is True # type: ignore
assert options.getBool("GuiProjectDetails", "mockItem", None) is None # type: ignore
assert options.getString("GuiNovelDetails", "winWidth", None) is None # type: ignore
assert options.getString("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getInt("GuiNovelDetails", "winWidth", None) == 100 # type: ignore
assert options.getInt("GuiNovelDetails", "textFont", None) is None # type: ignore
assert options.getInt("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getFloat("GuiNovelDetails", "winWidth", None) == 100.0 # type: ignore
assert options.getFloat("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getBool("GuiNovelDetails", "clearDouble", None) is True # type: ignore
assert options.getBool("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, nwColHidden) == nwColHidden
# Get from non-existent groups
-4
View File
@@ -482,10 +482,6 @@ def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
project.data.setName(" A Name ")
assert project.data.name == "A Name"
# Project Title
project.data.setTitle(" A Title ")
assert project.data.title == "A Title"
# Project Author
project.data.setAuthor(" Jane\tDoe ")
assert project.data.author == "Jane Doe"
+1 -7
View File
@@ -131,13 +131,12 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0105
assert xmlReader.xmlRevision == 1
assert xmlReader.xmlRevision == 2
assert xmlReader.appVersion == "2.0-rc1"
assert xmlReader.hexVersion == 0x020000c1
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.author == "Jane Smith"
assert data.saveCount == 5
assert data.autoCount == 10
@@ -256,7 +255,6 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.author == "Jay Doh" # Only last author is preserved
assert data.saveCount == 0 # Doesn't exist in 1.0
assert data.autoCount == 0 # Doesn't exist in 1.0
@@ -391,7 +389,6 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.author == "Jay Doh" # Only last author is preserved
assert data.saveCount == 5
assert data.autoCount == 10
@@ -526,7 +523,6 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.author == "Jay Doh" # Only last author is preserved
assert data.saveCount == 5
assert data.autoCount == 10
@@ -664,7 +660,6 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.author == "Jay Doh" # Only last author is preserved
assert data.saveCount == 5
assert data.autoCount == 10
@@ -802,7 +797,6 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.author == "Jay Doh" # Only last author is preserved
assert data.saveCount == 5
assert data.autoCount == 10
+12 -6
View File
@@ -22,9 +22,11 @@ from __future__ import annotations
import pytest
from tools import getGuiItem
from PyQt5.QtGui import QFontDatabase, QKeyEvent
from PyQt5.QtCore import QEvent, Qt
from PyQt5.QtWidgets import QDialogButtonBox, QFileDialog, QFontDialog
from PyQt5.QtWidgets import QAction, QDialogButtonBox, QFileDialog, QFontDialog
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwConst, nwUnicode
@@ -38,9 +40,13 @@ KEY_DELAY = 1
def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
"""Test the preferences dialog loading."""
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
monkeypatch.setattr(GuiPreferences, "exec_", lambda *a: None)
# Load GUI with standard values
prefs = GuiPreferences(nwGUI)
nwGUI.mainMenu.aPreferences.activate(QAction.ActionEvent.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
prefs = getGuiItem("GuiPreferences")
assert isinstance(prefs, GuiPreferences)
prefs.show()
# Check Languages
@@ -95,10 +101,6 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
# Check Navigation
vBar = prefs.mainForm.verticalScrollBar()
old = -1
with qtbot.waitSignal(vBar.valueChanged) as value:
prefs.sidebar.button(0).click()
assert value.args[0] > old
old = value.args[0]
with qtbot.waitSignal(vBar.valueChanged) as value:
prefs.sidebar.button(1).click()
assert value.args[0] > old
@@ -107,6 +109,10 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
prefs.sidebar.button(2).click()
assert value.args[0] > old
old = value.args[0]
with qtbot.waitSignal(vBar.valueChanged) as value:
prefs.sidebar.button(3).click()
assert value.args[0] > old
old = value.args[0]
# Check Search
prefs.searchText.setText("Display language")
-109
View File
@@ -1,109 +0,0 @@
"""
novelWriter Project Details Dialog Class Tester
=================================================
This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import pytest
from tools import getGuiItem
from PyQt5.QtWidgets import QAction
from novelwriter import SHARED
from novelwriter.dialogs.projdetails import GuiProjectDetails
@pytest.mark.gui
def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum):
"""Test the project details dialog.
"""
# Create a project to work on
assert nwGUI.openProject(prjLipsum)
assert nwGUI.rebuildIndex(beQuiet=True)
qtbot.wait(100)
# Open the Writing Stats dialog
nwGUI.mainMenu.aProjectDetails.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectDetails") is not None, timeout=1000)
projDet = getGuiItem("GuiProjectDetails")
assert isinstance(projDet, GuiProjectDetails)
# Overview Page
# =============
assert projDet.tabMain.bookTitle.text() == "Lorem Ipsum"
assert projDet.tabMain.projName.text()[-11:] == "Lorem Ipsum"
assert projDet.tabMain.bookAuthors.text()[-10:] == "lipsum.com"
assert projDet.tabMain.wordCountVal.text() == f"{3000:n}"
assert projDet.tabMain.chapCountVal.text() == f"{3:n}"
assert projDet.tabMain.sceneCountVal.text() == f"{5:n}"
assert projDet.tabMain.revCountVal.text() == f"{SHARED.project.data.saveCount:n}"
assert projDet.tabMain.projPathVal.text() == str(prjLipsum)
# Contents Page
# =============
tocTab = projDet.tabContents
tocTree = tocTab.tocTree
assert tocTree.topLevelItemCount() == 7
assert tocTree.topLevelItem(0).text(tocTab.C_TITLE) == "Lorem Ipsum" # type: ignore
assert tocTree.topLevelItem(2).text(tocTab.C_TITLE) == "Prologue" # type: ignore
assert tocTree.topLevelItem(3).text(tocTab.C_TITLE) == "Act One" # type: ignore
assert tocTree.topLevelItem(4).text(tocTab.C_TITLE) == "Chapter One" # type: ignore
assert tocTree.topLevelItem(5).text(tocTab.C_TITLE) == "Chapter Two" # type: ignore
assert tocTree.topLevelItem(6).text(tocTab.C_TITLE) == "END" # type: ignore
# Count Pages
tocTab.wpValue.setValue(100)
tocTab.poValue.setValue(4)
tocTab.dblValue.setChecked(False)
tocTab._populateTree()
thePages = ["1", "2", "1", "1", "11", "17", "0"]
thePage = ["i", "ii", "1", "2", "3", "14", "31"]
for i in range(7):
assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] # type: ignore
assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] # type: ignore
tocTab.poValue.setValue(5)
tocTab.dblValue.setChecked(True)
tocTab._populateTree()
thePages = ["2", "2", "2", "2", "12", "18", "0"]
thePage = ["i", "iii", "1", "3", "5", "17", "35"]
for i in range(7):
assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] # type: ignore
assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] # type: ignore
# Re-populate
assert tocTab._currentRoot is None
tocTab._novelValueChanged("7a992350f3eb6") # Not a root
assert tocTab._currentRoot == "b3643d0f92e32" # The actual novel root
# qtbot.stop()
# Clean Up
projDet.close()
nwGUI.closeMain()
# END Test testDlgProjDetails_Dialog
@@ -31,7 +31,7 @@ from PyQt5.QtWidgets import QDialog, QAction, QColorDialog
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemType
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.dialogs.projectsettings import GuiProjectSettings
KEY_DELAY = 1
@@ -63,17 +63,17 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
qtbot.addWidget(projSettings)
# Switch Tabs
projSettings._focusTab(GuiProjectSettings.TAB_REPLACE)
assert projSettings._tabBox.currentWidget() == projSettings.tabReplace
projSettings.sidebar.button(GuiProjectSettings.PAGE_SETTINGS).click()
assert projSettings.mainStack.currentWidget() == projSettings.settingsPage
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
assert projSettings._tabBox.currentWidget() == projSettings.tabImport
projSettings.sidebar.button(GuiProjectSettings.PAGE_STATUS).click()
assert projSettings.mainStack.currentWidget() == projSettings.statusPage
projSettings._focusTab(GuiProjectSettings.TAB_STATUS)
assert projSettings._tabBox.currentWidget() == projSettings.tabStatus
projSettings.sidebar.button(GuiProjectSettings.PAGE_IMPORT).click()
assert projSettings.mainStack.currentWidget() == projSettings.importPage
projSettings._focusTab(GuiProjectSettings.TAB_MAIN)
assert projSettings._tabBox.currentWidget() == projSettings.tabMain
projSettings.sidebar.button(GuiProjectSettings.PAGE_REPLACE).click()
assert projSettings.mainStack.currentWidget() == projSettings.replacePage
# Clean Up
projSettings.close()
@@ -83,9 +83,11 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
@pytest.mark.gui
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the main tab of the project settings dialog."""
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the settings page of the dialog."""
languages = [("en", "English"), ("de", "German")]
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda *a: languages)
monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages)
# Create new project
buildTestProject(nwGUI, projPath)
@@ -94,72 +96,57 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR
# Set some values
project = SHARED.project
project.data.setLanguage("en")
project.data.setSpellLang("en")
project.data.setAuthor("Jane Smith")
project.data.setAutoReplace({"A": "B", "C": "D"})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN)
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_SETTINGS)
projSettings.show()
qtbot.addWidget(projSettings)
# Settings Tab
# ============
settings = projSettings.settingsPage
tabMain = projSettings.tabMain
assert settings.projName.text() == "New Project"
assert settings.projAuthor.text() == "Jane Smith"
assert settings.projLang.currentData() == "en"
assert settings.spellLang.currentData() == "en"
assert settings.doBackup.isChecked() is False
assert tabMain.editName.text() == "New Project"
assert tabMain.editTitle.text() == "New Novel"
assert tabMain.editAuthor.text() == "Jane Smith"
assert tabMain.spellLang.currentData() == "en"
assert tabMain.doBackup.isChecked() is False
tabMain.editName.setText("")
for c in "Project Name":
qtbot.keyClick(tabMain.editName, c, delay=KEY_DELAY)
tabMain.editTitle.setText("")
for c in "Project Title":
qtbot.keyClick(tabMain.editTitle, c, delay=KEY_DELAY)
tabMain.editAuthor.clear()
for c in "Jane Doe":
qtbot.keyClick(tabMain.editAuthor, c, delay=KEY_DELAY)
assert tabMain.editName.text() == "Project Name"
assert tabMain.editTitle.text() == "Project Title"
assert tabMain.editAuthor.text() == "Jane Doe"
settings.projName.setText("Project Name")
settings.projAuthor.setText("Jane Doe")
settings.projLang.setCurrentIndex(settings.projLang.findData("de"))
settings.spellLang.setCurrentIndex(settings.spellLang.findData("de"))
settings.doBackup.setChecked(True)
projSettings._doSave()
assert project.data.name == "Project Name"
assert project.data.title == "Project Title"
assert project.data.author == "Jane Doe"
assert project.data.language == "de"
assert project.data.spellLang == "de"
assert project.data.doBackup is False
nwGUI._processProjectSettingsChanges()
nwGUI._processProjectSettingsChanges(False)
assert nwGUI.windowTitle() == "novelWriter - Project Name"
# qtbot.stop()
# END Test testDlgProjSettings_Main
# END Test testDlgProjSettings_SettingsPage
@pytest.mark.gui
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the status and importance tabs of the project settings
dialog.
"""
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the status and importance pages of the dialog."""
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
# Create new project
mockRnd.reset()
buildTestProject(nwGUI, projPath)
CONFIG.setBackupPath(fncPath)
# Set some values
theProject = SHARED.project
theProject.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
theProject.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
theProject.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
project = SHARED.project
project.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
project.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
project.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
@@ -172,51 +159,54 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
hCharNote = "0000000000011"
hWorldNote = "0000000000012"
theProject.tree[hPlotNote].setImport(C.iMajor) # type: ignore
theProject.tree[hCharNote].setImport(C.iMajor) # type: ignore
theProject.tree[hWorldNote].setImport(C.iMain) # type: ignore
project.tree[hPlotNote].setImport(C.iMajor) # type: ignore
project.tree[hCharNote].setImport(C.iMajor) # type: ignore
project.tree[hWorldNote].setImport(C.iMain) # type: ignore
nwGUI.rebuildTrees()
project.countStatus()
assert [e["count"] for _, e in project.data.itemStatus.items()] == [2, 0, 2, 1]
assert [e["count"] for _, e in project.data.itemImport.items()] == [3, 0, 2, 1]
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS)
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS)
projSettings.show()
qtbot.addWidget(projSettings)
# Status Tab
# ==========
tabStatus = projSettings.tabStatus
status = projSettings.statusPage
assert tabStatus.colChanged is False
assert tabStatus.getNewList() == ([], [])
assert tabStatus.listBox.topLevelItemCount() == 4
assert status.wasChanged is False
assert status.getNewList() == ([], [])
assert status.listBox.topLevelItemCount() == 4
# Can't delete the first item (it's in use)
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 4
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
qtbot.mouseClick(status.delButton, Qt.LeftButton)
assert status.listBox.topLevelItemCount() == 4
# Can delete the second item
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(1))
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 3
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(1))
qtbot.mouseClick(status.delButton, Qt.LeftButton)
assert status.listBox.topLevelItemCount() == 3
# Add a new item
with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(tabStatus.addButton, Qt.LeftButton)
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
for _ in range(8):
qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=KEY_DELAY)
for c in "Final":
qtbot.keyClick(tabStatus.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton)
qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 4
status.addButton.click()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status.editName.setText("Final")
status.colButton.click()
status.saveButton.click()
assert status.listBox.topLevelItemCount() == 4
assert tabStatus.colChanged is True
assert tabStatus.getNewList() == (
assert status.wasChanged is True
assert status.getNewList() == (
[
{
"key": C.sNew,
@@ -241,62 +231,62 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
)
# Move items, none selected -> no change
tabStatus.listBox.clearSelection()
tabStatus._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status.listBox.clearSelection()
status._moveItem(1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
]
# Move items, first selected, move up -> no change
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
tabStatus._moveItem(-1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
status._moveItem(-1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
]
# Move items, last selected, move up -> allowed
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
tabStatus._moveItem(-1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status._moveItem(-1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, None, C.sFinished
]
# Move items, same selected, move down -> allowed
tabStatus._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status._moveItem(1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
]
# Importance Tab
# ==============
tabImport = projSettings.tabImport
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
importance = projSettings.importPage
projSettings._sidebarClicked(GuiProjectSettings.PAGE_IMPORT)
# Delete unused entry
tabImport.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(1))
qtbot.mouseClick(tabImport.delButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 3
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1))
qtbot.mouseClick(importance.delButton, Qt.LeftButton)
assert importance.listBox.topLevelItemCount() == 3
# Add a new entry
with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(tabImport.addButton, Qt.LeftButton)
tabImport.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(3))
qtbot.mouseClick(importance.addButton, Qt.LeftButton)
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3))
for _ in range(8):
qtbot.keyClick(tabImport.editName, Qt.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(importance.editName, Qt.Key_Backspace, delay=KEY_DELAY)
for c in "Final":
qtbot.keyClick(tabImport.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(tabImport.colButton, Qt.LeftButton)
qtbot.mouseClick(tabImport.saveButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 4
qtbot.keyClick(importance.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(importance.colButton, Qt.LeftButton)
qtbot.mouseClick(importance.saveButton, Qt.LeftButton)
assert importance.listBox.topLevelItemCount() == 4
assert tabImport.colChanged is True
assert tabImport.getNewList() == (
assert importance.wasChanged is True
assert importance.getNewList() == (
[
{
"key": C.iNew,
@@ -323,13 +313,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
# Check Project
projSettings._doSave()
statusItems = dict(theProject.data.itemStatus.items())
statusItems = dict(project.data.itemStatus.items())
assert statusItems[C.sNew]["name"] == "New"
assert statusItems[C.sDraft]["name"] == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished"
assert statusItems["s000013"]["name"] == "Final"
importItems = dict(theProject.data.itemImport.items())
importItems = dict(project.data.itemImport.items())
assert importItems[C.iNew]["name"] == "New"
assert importItems[C.iMajor]["name"] == "Major"
assert importItems[C.iMain]["name"] == "Main"
@@ -341,83 +331,78 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
@pytest.mark.gui
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the auto-replace tab of the project settings dialog."""
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the auto-replace page of the dialog."""
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
# Create new project
mockRnd.reset()
buildTestProject(nwGUI, projPath)
CONFIG.setBackupPath(fncPath)
# Set some values
theProject = SHARED.project
theProject.data.setAutoReplace({
project = SHARED.project
project.data.setAutoReplace({
"A": "B", "C": "D"
})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE)
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_REPLACE)
projSettings.show()
qtbot.addWidget(projSettings)
# Auto-Replace Tab
# ================
tabReplace = projSettings.tabReplace
replace = projSettings.replacePage
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert tabReplace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert tabReplace.listBox.topLevelItemCount() == 2
assert replace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert replace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert replace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert replace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert replace.listBox.topLevelItemCount() == 2
# Nothing to save or delete
tabReplace.listBox.clearSelection()
tabReplace._saveEntry()
tabReplace._delEntry()
assert tabReplace.listBox.topLevelItemCount() == 2
replace.listBox.clearSelection()
replace._saveEntry()
replace._delEntry()
assert replace.listBox.topLevelItemCount() == 2
# Create a new entry
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "" # type: ignore
qtbot.mouseClick(replace.addButton, Qt.LeftButton)
assert replace.listBox.topLevelItemCount() == 3
assert replace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert replace.listBox.topLevelItem(2).text(1) == "" # type: ignore
# Edit the entry
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2))
tabReplace.editKey.setText("")
replace.listBox.setCurrentItem(replace.listBox.topLevelItem(2))
replace.editKey.setText("")
for c in "Th is ":
qtbot.keyClick(tabReplace.editKey, c, delay=KEY_DELAY)
tabReplace.editValue.setText("")
qtbot.keyClick(replace.editKey, c, delay=KEY_DELAY)
replace.editValue.setText("")
for c in "With This Stuff ":
qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(replace.saveButton, Qt.LeftButton)
assert replace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
# Create a new entry again
tabReplace.listBox.clearSelection()
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 4
replace.listBox.clearSelection()
qtbot.mouseClick(replace.addButton, Qt.LeftButton)
assert replace.listBox.topLevelItemCount() == 4
# The list is sorted, so we must find it
newIdx = -1
for i in range(tabReplace.listBox.topLevelItemCount()):
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore
for i in range(replace.listBox.topLevelItemCount()):
if replace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore
newIdx = i
break
assert newIdx >= 0
# Then delete the new item
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(newIdx))
qtbot.mouseClick(tabReplace.delButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
replace.listBox.setCurrentItem(replace.listBox.topLevelItem(newIdx))
qtbot.mouseClick(replace.delButton, Qt.LeftButton)
assert replace.listBox.topLevelItemCount() == 3
# Check Project
projSettings._doSave()
assert theProject.data.autoReplace == {
assert project.data.autoReplace == {
"A": "B", "C": "D", "This": "With This Stuff"
}
+2 -4
View File
@@ -29,7 +29,7 @@ from tools import (
C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem
)
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMenu, QInputDialog
@@ -170,7 +170,7 @@ def testGuiMain_UpdateTheme(qtbot, nwGUI):
mainTheme.loadSyntax()
nwGUI._processConfigChanges(True, True, True, True)
syntaxBack = QColor(*SHARED.theme.colBack)
syntaxBack = SHARED.theme.colBack
assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntaxBack
assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack
@@ -200,7 +200,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
assert len(SHARED.project.tree._roots) == 0
assert SHARED.project.tree.trashRoot is None
assert SHARED.project.data.name == ""
assert SHARED.project.data.title == ""
assert SHARED.project.data.author == ""
assert SHARED.project.data.spellCheck is False
@@ -220,7 +219,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
assert len(SHARED.project.tree._roots) == 4
assert SHARED.project.tree.trashRoot is None
assert SHARED.project.data.name == "New Project"
assert SHARED.project.data.title == "New Novel"
assert SHARED.project.data.author == "Jane Doe"
assert SHARED.project.data.spellCheck is False
+28 -19
View File
@@ -27,7 +27,7 @@ from pathlib import Path
from mocked import causeOSError
from tools import writeFile
from PyQt5.QtGui import QIcon, QPalette, QPixmap
from PyQt5.QtGui import QColor, QIcon, QPalette, QPixmap
from PyQt5.QtWidgets import QApplication
from novelwriter import CONFIG, SHARED
@@ -89,29 +89,38 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
parser = NWConfigParser()
parser["Palette"] = {
"colour1": "100, 150, 200",
"colour2": "100, 150, 200, 250",
"colour3": "250, 250",
"colour4": "-10, 127, 300",
"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 mainTheme._parseColour(parser, "Palette", "colour1") == [100, 150, 200]
assert mainTheme._parseColour(parser, "Palette", "colour2") == [100, 150, 200]
assert mainTheme._parseColour(parser, "Palette", "colour3") == [0, 0, 0]
assert mainTheme._parseColour(parser, "Palette", "colour4") == [0, 127, 255]
assert mainTheme._parseColour(parser, "Palette", "colour5") == [0, 0, 0]
assert mainTheme._parseColour(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255)
assert mainTheme._parseColour(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250)
assert mainTheme._parseColour(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250)
assert mainTheme._parseColour(parser, "Palette", "colour4").getRgb() == (250, 250, 0, 255)
assert mainTheme._parseColour(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0)
assert mainTheme._parseColour(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255)
# The palette should load with the parsed values
mainTheme._setPalette(parser, "Palette", "colour1", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 255)
mainTheme._setPalette(parser, "Palette", "colour2", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 255)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 250)
mainTheme._setPalette(parser, "Palette", "colour3", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 255)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 250)
mainTheme._setPalette(parser, "Palette", "colour4", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 127, 255, 255)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (250, 250, 0, 255)
mainTheme._setPalette(parser, "Palette", "colour5", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 0)
mainTheme._setPalette(parser, "Palette", "colour6", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 127, 255, 255)
# Non-existing value should return default colour
mainTheme._setPalette(parser, "Palette", "stuff", QPalette.Window)
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 255)
# qtbot.stop()
@@ -240,9 +249,9 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
# 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]
assert mainTheme.colBack == QColor(255, 255, 255)
assert mainTheme.colText == QColor(0, 0, 0)
assert mainTheme.colLink == QColor(0, 0, 200)
# Load Default Dark Theme
# =======================
@@ -253,9 +262,9 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
# 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]
assert mainTheme.colBack == QColor(54, 54, 54)
assert mainTheme.colText == QColor(199, 207, 208)
assert mainTheme.colLink == QColor(184, 200, 0)
# qtbot.stop()
+136
View File
@@ -0,0 +1,136 @@
"""
novelWriter Novel Details Tool Tester
=======================================
This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import pytest
from tools import getGuiItem
from PyQt5.QtWidgets import QAction
from novelwriter import SHARED
from novelwriter.enum import nwItemClass
from novelwriter.tools.noveldetails import GuiNovelDetails
@pytest.mark.gui
def testToolNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
"""Test the Novel Details main dialog."""
nwGUI.openProject(prjLipsum)
nHandle = "b3643d0f92e32"
# Add a second Novel folder
project = SHARED.project
secondText = "#! Second\n\n" + "\n\n".join(ipsumText)
sHandle = project.newRoot(nwItemClass.NOVEL, "Second")
dHandle = project.newFile("Document", sHandle)
project.storage.getDocument(dHandle).writeDocument(secondText)
project.index.reIndexHandle(dHandle)
nwGUI.projView.projTree.revealNewTreeItem(sHandle)
nwGUI.projView.projTree.revealNewTreeItem(dHandle)
# Create the dialog
nwGUI.mainMenu.aNovelDetails.activate(QAction.ActionEvent.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiNovelDetails") is not None, timeout=1000)
details = getGuiItem("GuiNovelDetails")
assert isinstance(details, GuiNovelDetails)
# Overview Page
# =============
overview = details.overviewPage
# The selector should default to the first entry
assert details.novelSelector.handle == nHandle
# Check project data
assert overview.projName.text() == "Lorem Ipsum"
assert overview.projWords.text() == f"{4376:n}"
assert overview.projNovels.text() == f"{3638:n}"
assert overview.projNotes.text() == f"{738:n}"
assert overview.projRevisions.text() != ""
assert overview.projEditTime.text() != ""
# Check novel data for "Novel"
assert overview.novelName.text() == "Novel"
assert overview.novelWords.text() == f"{3000:n}"
assert overview.novelChapters.text() == f"{3:n}"
assert overview.novelScenes.text() == f"{5:n}"
# Check novel data for "Second"
details.novelSelector.setHandle(sHandle, blockSignal=False)
assert overview.novelName.text() == "Second"
assert overview.novelWords.text() == f"{529:n}"
assert overview.novelChapters.text() == f"{0:n}"
assert overview.novelScenes.text() == f"{0:n}"
# Contents Page
# =============
details.novelSelector.setHandle(nHandle, blockSignal=False)
details.sidebar.button(details.PAGE_CONTENTS).click()
assert details.mainStack.currentIndex() == 1
contents = details.contentsPage
# Check defaults
words = [f"{v:n}" for v in [40, 176, 92, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [2, 2, 2, 2, 4, 6, 0]]
page = [f"{v:n}" for v in [1, 3, 5, 7, 9, 13, 19]]
for i in range(6):
item = contents.tocTree.topLevelItem(i)
assert item is not None
assert item.text(contents.C_WORDS) == words[i]
assert item.text(contents.C_PAGES) == pages[i]
assert item.text(contents.C_PAGE) == page[i]
# Change Settings
contents.poValue.setValue(7)
contents.wpValue.setValue(50)
words = [f"{v:n}" for v in [40, 176, 92, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [2, 4, 2, 2, 22, 34, 0]]
page = ["i", "iii"] + [f"{v:n}" for v in [1, 3, 5, 27, 61]]
for i in range(6):
item = contents.tocTree.topLevelItem(i)
assert item is not None
assert item.text(contents.C_WORDS) == words[i]
assert item.text(contents.C_PAGES) == pages[i]
assert item.text(contents.C_PAGE) == page[i]
# Turn off use odd pages
contents.dblValue.setChecked(False)
contents.poValue.setValue(0)
contents.wpValue.setValue(100)
words = [f"{v:n}" for v in [40, 176, 92, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [1, 2, 1, 1, 11, 17, 0]]
page = [f"{v:n}" for v in [1, 2, 4, 5, 6, 17, 34]]
for i in range(6):
item = contents.tocTree.topLevelItem(i)
assert item is not None
assert item.text(contents.C_WORDS) == words[i]
assert item.text(contents.C_PAGES) == pages[i]
assert item.text(contents.C_PAGE) == page[i]
# Revert to Overview page
details.sidebar.button(details.PAGE_OVERVIEW).click()
assert details.mainStack.currentIndex() == 0
# qtbot.stop()
details.close()
# END Test testToolNovelDetails_Main
+20 -18
View File
@@ -156,52 +156,54 @@ def cleanProject(path: str | Path):
return
def buildTestProject(obj, projPath):
def buildTestProject(obj: object, projPath: Path) -> None:
"""Build a standard test project in projPath using the project
object as the parent.
"""
from novelwriter.enum import nwItemClass
from novelwriter.guimain import GuiMain
from novelwriter.core.project import NWProject
if isinstance(obj, NWProject):
nwGUI = None
project = obj
else:
elif isinstance(obj, GuiMain):
from novelwriter import SHARED
nwGUI = obj
project = SHARED.project
else:
return
project.storage.createNewProject(projPath)
project.setDefaultStatusImport()
project.data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
project.data.setName("New Project")
project.data.setTitle("New Novel")
project.data.setAuthor("Jane Doe")
# Creating a minimal project with a few root folders and a
# single chapter folder with a single file.
xHandle = {}
xHandle[1] = project.newRoot(nwItemClass.NOVEL, "Novel")
xHandle[2] = project.newRoot(nwItemClass.PLOT, "Plot")
xHandle[3] = project.newRoot(nwItemClass.CHARACTER, "Characters")
xHandle[4] = project.newRoot(nwItemClass.WORLD, "World")
xHandle[5] = project.newFile("Title Page", xHandle[1])
xHandle[6] = project.newFolder("New Chapter", xHandle[1])
xHandle[7] = project.newFile("New Chapter", xHandle[6])
xHandle[8] = project.newFile("New Scene", xHandle[6])
nrHandle = project.newRoot(nwItemClass.NOVEL, "Novel")
project.newRoot(nwItemClass.PLOT, "Plot")
project.newRoot(nwItemClass.CHARACTER, "Characters")
project.newRoot(nwItemClass.WORLD, "World")
aDoc = project.storage.getDocument(xHandle[5])
tdHandle = project.newFile("Title Page", nrHandle)
cfHandle = project.newFolder("New Chapter", nrHandle) or ""
cdHandle = project.newFile("New Chapter", cfHandle)
sdHandle = project.newFile("New Scene", cfHandle)
aDoc = project.storage.getDocument(tdHandle)
aDoc.writeDocument("#! New Novel\n\n>> By Jane Doe <<\n")
project.index.reIndexHandle(xHandle[5])
project.index.reIndexHandle(tdHandle)
aDoc = project.storage.getDocument(xHandle[7])
aDoc = project.storage.getDocument(cdHandle)
aDoc.writeDocument("## %s\n\n" % project.tr("New Chapter"))
project.index.reIndexHandle(xHandle[7])
project.index.reIndexHandle(cdHandle)
aDoc = project.storage.getDocument(xHandle[8])
aDoc = project.storage.getDocument(sdHandle)
aDoc.writeDocument("### %s\n\n" % project.tr("New Scene"))
project.index.reIndexHandle(xHandle[8])
project.index.reIndexHandle(sdHandle)
project.session.startSession()
project.setProjectChanged(True)