Add creating new project from zipped existing project (#1684)
This commit is contained in:
@@ -32,6 +32,7 @@ import logging
|
|||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from zipfile import ZipFile, is_zipfile
|
||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
@@ -459,7 +460,7 @@ class ProjectBuilder:
|
|||||||
"""
|
"""
|
||||||
source = data.get("template")
|
source = data.get("template")
|
||||||
if not (isinstance(source, Path) and source.is_file()
|
if not (isinstance(source, Path) and source.is_file()
|
||||||
and source.name == nwFiles.PROJ_FILE):
|
and (source.name == nwFiles.PROJ_FILE or is_zipfile(source))):
|
||||||
logger.error("Could not access source project: %s", source)
|
logger.error("Could not access source project: %s", source)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -476,12 +477,24 @@ class ProjectBuilder:
|
|||||||
dstPath = path.resolve()
|
dstPath = path.resolve()
|
||||||
srcCont = srcPath / "content"
|
srcCont = srcPath / "content"
|
||||||
dstCont = dstPath / "content"
|
dstCont = dstPath / "content"
|
||||||
dstPath.mkdir(exist_ok=True)
|
try:
|
||||||
dstCont.mkdir(exist_ok=True)
|
dstPath.mkdir(exist_ok=True)
|
||||||
shutil.copy2(srcPath / nwFiles.PROJ_FILE, dstPath)
|
dstCont.mkdir(exist_ok=True)
|
||||||
for contFile in srcCont.iterdir():
|
if is_zipfile(source):
|
||||||
if contFile.is_file() and contFile.suffix == ".nwd" and isHandle(contFile.stem):
|
with ZipFile(source) as zipObj:
|
||||||
shutil.copy2(contFile, dstCont)
|
for member in zipObj.namelist():
|
||||||
|
if member == nwFiles.PROJ_FILE:
|
||||||
|
zipObj.extract(member, dstPath)
|
||||||
|
elif member.startswith("content") and member.endswith(".nwd"):
|
||||||
|
zipObj.extract(member, dstPath)
|
||||||
|
else:
|
||||||
|
shutil.copy2(srcPath / nwFiles.PROJ_FILE, dstPath)
|
||||||
|
for item in srcCont.iterdir():
|
||||||
|
if item.is_file() and item.suffix == ".nwd" and isHandle(item.stem):
|
||||||
|
shutil.copy2(item, dstCont)
|
||||||
|
except Exception as exc:
|
||||||
|
SHARED.error(self.tr("Could not copy project files."), exc=exc)
|
||||||
|
return False
|
||||||
|
|
||||||
# Open the copied project and update settings
|
# Open the copied project and update settings
|
||||||
project = NWProject()
|
project = NWProject()
|
||||||
@@ -505,14 +518,11 @@ class ProjectBuilder:
|
|||||||
"""Make a copy of the sample project by extracting the
|
"""Make a copy of the sample project by extracting the
|
||||||
sample.zip file to the new path.
|
sample.zip file to the new path.
|
||||||
"""
|
"""
|
||||||
pkgSample = CONFIG.assetPath("sample.zip")
|
if (sample := CONFIG.assetPath("sample.zip")).is_file():
|
||||||
if pkgSample.is_file():
|
|
||||||
try:
|
try:
|
||||||
shutil.unpack_archive(pkgSample, path)
|
shutil.unpack_archive(sample, path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
SHARED.error(self.tr(
|
SHARED.error(self.tr("Failed to create a new example project."), exc=exc)
|
||||||
"Failed to create a new example project."
|
|
||||||
), exc=exc)
|
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
SHARED.error(self.tr(
|
SHARED.error(self.tr(
|
||||||
|
|||||||
@@ -221,16 +221,15 @@ class SharedData(QObject):
|
|||||||
def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
|
def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
|
||||||
allowZip: bool = False) -> Path | None:
|
allowZip: bool = False) -> Path | None:
|
||||||
"""Open the file dialog and select a novelWriter project file."""
|
"""Open the file dialog and select a novelWriter project file."""
|
||||||
ext = [
|
label = (self.tr("novelWriter Project File or Zip")
|
||||||
self.tr("novelWriter Project File ({0})").format(nwFiles.PROJ_FILE),
|
if allowZip else self.tr("novelWriter Project File"))
|
||||||
self.tr("All files ({0})").format("*"),
|
ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
|
||||||
]
|
selected, _ = QFileDialog.getOpenFileName(
|
||||||
if allowZip:
|
parent, self.tr("Open Project"), str(path or ""), filter=";;".join(
|
||||||
ext.insert(1, self.tr("Zip Archives ({0})").format("*.zip"))
|
[f"{label} ({ext})", "{0} (*)".format(self.tr("All Files"))]
|
||||||
projFile, _ = QFileDialog.getOpenFileName(
|
)
|
||||||
parent, self.tr("Open Project"), str(path or ""), filter=";;".join(ext)
|
|
||||||
)
|
)
|
||||||
return Path(projFile) if projFile else None
|
return Path(selected) if selected else None
|
||||||
|
|
||||||
def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
|
def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
|
||||||
"""Find a top level widget."""
|
"""Find a top level widget."""
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
import pytest
|
import pytest
|
||||||
|
import shutil
|
||||||
|
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -501,7 +502,7 @@ def testCoreTools_ProjectBuilderB(monkeypatch, fncPath, tstPaths, mockRnd):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreTools_ProjectBuilderTemplate(monkeypatch, mockGUI, prjLipsum, fncPath):
|
def testCoreTools_ProjectBuilderCopyPlain(monkeypatch, caplog, mockGUI, prjLipsum, fncPath):
|
||||||
"""Create a new project copied from existing project."""
|
"""Create a new project copied from existing project."""
|
||||||
srcPath = prjLipsum / nwFiles.PROJ_FILE
|
srcPath = prjLipsum / nwFiles.PROJ_FILE
|
||||||
dstPath = fncPath / "lipsum"
|
dstPath = fncPath / "lipsum"
|
||||||
@@ -524,6 +525,14 @@ def testCoreTools_ProjectBuilderTemplate(monkeypatch, mockGUI, prjLipsum, fncPat
|
|||||||
# Cannot copy to existing folder
|
# Cannot copy to existing folder
|
||||||
assert builder.buildProject({"path": fncPath, "template": srcPath}) is False
|
assert builder.buildProject({"path": fncPath, "template": srcPath}) is False
|
||||||
|
|
||||||
|
# Valid data, but copy fails
|
||||||
|
caplog.clear()
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("pathlib.Path.mkdir", lambda *a, **k: causeOSError)
|
||||||
|
assert builder.buildProject(data) is False
|
||||||
|
assert "Could not copy project files." in caplog.text
|
||||||
|
dstPath.unlink(missing_ok=True) # The failed mkdir leaves an empty file
|
||||||
|
|
||||||
# Copy project properly
|
# Copy project properly
|
||||||
assert builder.buildProject(data) is True
|
assert builder.buildProject(data) is True
|
||||||
|
|
||||||
@@ -556,7 +565,85 @@ def testCoreTools_ProjectBuilderTemplate(monkeypatch, mockGUI, prjLipsum, fncPat
|
|||||||
assert dstProject.data.autoCount < 5
|
assert dstProject.data.autoCount < 5
|
||||||
assert dstProject.data.editTime < 10
|
assert dstProject.data.editTime < 10
|
||||||
|
|
||||||
# END Test testCoreTools_ProjectBuilderTemplate
|
# END Test testCoreTools_ProjectBuilderCopyPlain
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreTools_ProjectBuilderCopyZipped(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||||
|
"""Create a new project copied from existing zipped project."""
|
||||||
|
|
||||||
|
# Create a project
|
||||||
|
origPath = fncPath / "original"
|
||||||
|
srcProject = NWProject()
|
||||||
|
buildTestProject(srcProject, origPath)
|
||||||
|
|
||||||
|
# Zip it
|
||||||
|
shutil.make_archive(str(fncPath / "original"), "zip", origPath)
|
||||||
|
|
||||||
|
# Make fake zip file
|
||||||
|
fakeZip = fncPath / "broken.zip"
|
||||||
|
fakeZip.write_bytes(b"stuff")
|
||||||
|
|
||||||
|
# Set up the builder
|
||||||
|
srcPath = fncPath / "original.zip"
|
||||||
|
dstPath = fncPath / "copy"
|
||||||
|
data = {
|
||||||
|
"name": "Test Project",
|
||||||
|
"author": "Jane Doe",
|
||||||
|
"language": "en_US",
|
||||||
|
"path": dstPath,
|
||||||
|
"template": srcPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = ProjectBuilder()
|
||||||
|
|
||||||
|
# No path set
|
||||||
|
assert builder.buildProject({"template": srcPath}) is False
|
||||||
|
|
||||||
|
# No project at path
|
||||||
|
assert builder.buildProject({"path": fncPath, "template": fncPath}) is False
|
||||||
|
|
||||||
|
# Cannot copy to existing folder
|
||||||
|
assert builder.buildProject({"path": fncPath, "template": srcPath}) is False
|
||||||
|
|
||||||
|
# Cannot copy to existing folder
|
||||||
|
caplog.clear()
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("novelwriter.core.coretools.is_zipfile", lambda *a: True)
|
||||||
|
assert builder.buildProject({"path": dstPath, "template": fakeZip}) is False
|
||||||
|
assert "Could not copy project files." in caplog.text
|
||||||
|
shutil.rmtree(dstPath, ignore_errors=True)
|
||||||
|
|
||||||
|
# Copy project properly
|
||||||
|
assert builder.buildProject(data) is True
|
||||||
|
|
||||||
|
# Check Copy
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
dstProject = NWProject()
|
||||||
|
dstProject.openProject(dstPath)
|
||||||
|
|
||||||
|
# UUID should be different
|
||||||
|
assert srcProject.data.uuid != dstProject.data.uuid
|
||||||
|
|
||||||
|
# Name should be different
|
||||||
|
assert srcProject.data.name == "New Project"
|
||||||
|
assert dstProject.data.name == "Test Project"
|
||||||
|
|
||||||
|
# Author should be different
|
||||||
|
assert srcProject.data.author == "Jane Doe"
|
||||||
|
assert dstProject.data.author == "Jane Doe"
|
||||||
|
|
||||||
|
# Language should be different
|
||||||
|
assert srcProject.data.language is None
|
||||||
|
assert dstProject.data.language == "en_US"
|
||||||
|
|
||||||
|
# Counts should be more or less zeroed
|
||||||
|
assert dstProject.data.saveCount < 5
|
||||||
|
assert dstProject.data.autoCount < 5
|
||||||
|
assert dstProject.data.editTime < 10
|
||||||
|
|
||||||
|
# END Test testCoreTools_ProjectBuilderCopyZipped
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
|
|||||||
@@ -24,10 +24,9 @@ import pytest
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from tools import getGuiItem
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||||
|
|
||||||
|
from novelwriter import SHARED
|
||||||
from novelwriter.dialogs.about import GuiAbout
|
from novelwriter.dialogs.about import GuiAbout
|
||||||
|
|
||||||
|
|
||||||
@@ -37,8 +36,8 @@ def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
|
|||||||
# NW About
|
# NW About
|
||||||
nwGUI.showAboutNWDialog(showNotes=True)
|
nwGUI.showAboutNWDialog(showNotes=True)
|
||||||
|
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiAbout) is not None, timeout=1000)
|
||||||
msgAbout = getGuiItem("GuiAbout")
|
msgAbout = SHARED.findTopLevelWidget(GuiAbout)
|
||||||
assert isinstance(msgAbout, GuiAbout)
|
assert isinstance(msgAbout, GuiAbout)
|
||||||
|
|
||||||
assert msgAbout.pageAbout.document().characterCount() > 100
|
assert msgAbout.pageAbout.document().characterCount() > 100
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tools import getGuiItem
|
|
||||||
|
|
||||||
from PyQt5.QtGui import QFontDatabase, QKeyEvent
|
from PyQt5.QtGui import QFontDatabase, QKeyEvent
|
||||||
from PyQt5.QtCore import QEvent, Qt
|
from PyQt5.QtCore import QEvent, Qt
|
||||||
from PyQt5.QtWidgets import QAction, QDialogButtonBox, QFileDialog, QFontDialog
|
from PyQt5.QtWidgets import QAction, QDialogButtonBox, QFileDialog, QFontDialog
|
||||||
@@ -44,8 +42,8 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
|
|||||||
|
|
||||||
# Load GUI with standard values
|
# Load GUI with standard values
|
||||||
nwGUI.mainMenu.aPreferences.activate(QAction.ActionEvent.Trigger)
|
nwGUI.mainMenu.aPreferences.activate(QAction.ActionEvent.Trigger)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiPreferences) is not None, timeout=1000)
|
||||||
prefs = getGuiItem("GuiPreferences")
|
prefs = SHARED.findTopLevelWidget(GuiPreferences)
|
||||||
assert isinstance(prefs, GuiPreferences)
|
assert isinstance(prefs, GuiPreferences)
|
||||||
prefs.show()
|
prefs.show()
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tools import C, getGuiItem, buildTestProject
|
from tools import C, buildTestProject
|
||||||
|
|
||||||
from PyQt5.QtGui import QColor
|
from PyQt5.QtGui import QColor
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@@ -47,7 +47,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
|
|||||||
|
|
||||||
# Check that we cannot open when there is no project
|
# Check that we cannot open when there is no project
|
||||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||||
assert getGuiItem("GuiProjectSettings") is None
|
assert SHARED.findTopLevelWidget(GuiProjectSettings) is None
|
||||||
|
|
||||||
# Pretend we have a project
|
# Pretend we have a project
|
||||||
SHARED.project._valid = True
|
SHARED.project._valid = True
|
||||||
@@ -55,9 +55,11 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
|
|||||||
|
|
||||||
# Get the dialog object
|
# Get the dialog object
|
||||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
qtbot.waitUntil(
|
||||||
|
lambda: SHARED.findTopLevelWidget(GuiProjectSettings) is not None, timeout=1000
|
||||||
|
)
|
||||||
|
|
||||||
projSettings = getGuiItem("GuiProjectSettings")
|
projSettings = SHARED.findTopLevelWidget(GuiProjectSettings)
|
||||||
assert isinstance(projSettings, GuiProjectSettings)
|
assert isinstance(projSettings, GuiProjectSettings)
|
||||||
projSettings.show()
|
projSettings.show()
|
||||||
qtbot.addWidget(projSettings)
|
qtbot.addWidget(projSettings)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import pytest
|
|||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import QDialog, QAction
|
from PyQt5.QtWidgets import QDialog, QAction
|
||||||
|
|
||||||
from tools import buildTestProject, getGuiItem
|
from tools import buildTestProject
|
||||||
|
|
||||||
from novelwriter import SHARED
|
from novelwriter import SHARED
|
||||||
from novelwriter.core.spellcheck import UserDictionary
|
from novelwriter.core.spellcheck import UserDictionary
|
||||||
@@ -47,9 +47,9 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath):
|
|||||||
|
|
||||||
# Load the dialog
|
# Load the dialog
|
||||||
nwGUI.mainMenu.aEditWordList.activate(QAction.Trigger)
|
nwGUI.mainMenu.aEditWordList.activate(QAction.Trigger)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiWordList") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWordList) is not None, timeout=1000)
|
||||||
|
|
||||||
wList = getGuiItem("GuiWordList")
|
wList = SHARED.findTopLevelWidget(GuiWordList)
|
||||||
assert isinstance(wList, GuiWordList)
|
assert isinstance(wList, GuiWordList)
|
||||||
wList.show()
|
wList.show()
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import pytest
|
|||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
|
||||||
from tools import (
|
from tools import (
|
||||||
C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem
|
C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE
|
||||||
)
|
)
|
||||||
|
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
@@ -93,16 +93,16 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
|
|||||||
nwGUI.closeProject()
|
nwGUI.closeProject()
|
||||||
|
|
||||||
# Check that release notes opened
|
# Check that release notes opened
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiAbout) is not None, timeout=1000)
|
||||||
msgAbout = getGuiItem("GuiAbout")
|
msgAbout = SHARED.findTopLevelWidget(GuiAbout)
|
||||||
assert isinstance(msgAbout, GuiAbout)
|
assert isinstance(msgAbout, GuiAbout)
|
||||||
assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
|
assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
|
||||||
msgAbout.accept()
|
msgAbout.accept()
|
||||||
|
|
||||||
# Check that project open dialog launches
|
# Check that project open dialog launches
|
||||||
nwGUI.postLaunchTasks(None)
|
nwGUI.postLaunchTasks(None)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiWelcome") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWelcome) is not None, timeout=1000)
|
||||||
assert isinstance(welcome := getGuiItem("GuiWelcome"), GuiWelcome)
|
assert isinstance(welcome := SHARED.findTopLevelWidget(GuiWelcome), GuiWelcome)
|
||||||
welcome.show()
|
welcome.show()
|
||||||
welcome.close()
|
welcome.close()
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import enchant
|
|||||||
|
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from tools import getGuiItem
|
|
||||||
from mocked import causeException
|
from mocked import causeException
|
||||||
|
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
@@ -48,9 +47,9 @@ def testToolDictionaries_Main(qtbot, monkeypatch, nwGUI, fncPath):
|
|||||||
|
|
||||||
# Open the tool
|
# Open the tool
|
||||||
nwGUI.showDictionariesDialog()
|
nwGUI.showDictionariesDialog()
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiDictionaries") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiDictionaries) is not None, timeout=1000)
|
||||||
|
|
||||||
nwDicts = getGuiItem("GuiDictionaries")
|
nwDicts = SHARED.findTopLevelWidget(GuiDictionaries)
|
||||||
assert isinstance(nwDicts, GuiDictionaries)
|
assert isinstance(nwDicts, GuiDictionaries)
|
||||||
assert nwDicts.isVisible()
|
assert nwDicts.isVisible()
|
||||||
assert nwDicts.inPath.text() == str(fncPath)
|
assert nwDicts.inPath.text() == str(fncPath)
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tools import C, getGuiItem, buildTestProject
|
from tools import C, buildTestProject
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QAction
|
from PyQt5.QtWidgets import QAction
|
||||||
|
|
||||||
|
from novelwriter import SHARED
|
||||||
from novelwriter.tools.lipsum import GuiLipsum
|
from novelwriter.tools.lipsum import GuiLipsum
|
||||||
|
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ def testToolLipsum_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
"""Test the Lorem Ipsum tool."""
|
"""Test the Lorem Ipsum tool."""
|
||||||
# Check that we cannot open when there is no project
|
# Check that we cannot open when there is no project
|
||||||
nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger)
|
nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger)
|
||||||
assert getGuiItem("GuiLipsum") is None
|
assert SHARED.findTopLevelWidget(GuiLipsum) is None
|
||||||
|
|
||||||
buildTestProject(nwGUI, projPath)
|
buildTestProject(nwGUI, projPath)
|
||||||
nwLipsum = GuiLipsum(nwGUI)
|
nwLipsum = GuiLipsum(nwGUI)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import pytest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pytestqt.qtbot import QtBot
|
from pytestqt.qtbot import QtBot
|
||||||
|
|
||||||
from tools import C, buildTestProject, getGuiItem
|
from tools import C, buildTestProject
|
||||||
from mocked import causeOSError
|
from mocked import causeOSError
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSlot
|
from PyQt5.QtCore import Qt, pyqtSlot
|
||||||
@@ -50,8 +50,8 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat
|
|||||||
allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi\n* * *"
|
allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi\n* * *"
|
||||||
|
|
||||||
nwGUI.mainMenu.aBuildManuscript.activate(QAction.Trigger)
|
nwGUI.mainMenu.aBuildManuscript.activate(QAction.Trigger)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiManuscript") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiManuscript) is not None, timeout=1000)
|
||||||
manus = getGuiItem("GuiManuscript")
|
manus = SHARED.findTopLevelWidget(GuiManuscript)
|
||||||
assert isinstance(manus, GuiManuscript)
|
assert isinstance(manus, GuiManuscript)
|
||||||
manus.show()
|
manus.show()
|
||||||
assert manus.docPreview.toPlainText().strip() == ""
|
assert manus.docPreview.toPlainText().strip() == ""
|
||||||
@@ -104,7 +104,7 @@ def testManuscript_Builds(qtbot: QtBot, nwGUI: GuiMain, projPath: Path):
|
|||||||
|
|
||||||
# Create a new build
|
# Create a new build
|
||||||
manus.tbAdd.click()
|
manus.tbAdd.click()
|
||||||
bSettings = getGuiItem("GuiBuildSettings")
|
bSettings = SHARED.findTopLevelWidget(GuiBuildSettings)
|
||||||
assert isinstance(bSettings, GuiBuildSettings)
|
assert isinstance(bSettings, GuiBuildSettings)
|
||||||
bSettings.editBuildName.setText("Test Build")
|
bSettings.editBuildName.setText("Test Build")
|
||||||
build = None
|
build = None
|
||||||
@@ -127,7 +127,7 @@ def testManuscript_Builds(qtbot: QtBot, nwGUI: GuiMain, projPath: Path):
|
|||||||
manus.buildList.setCurrentRow(0)
|
manus.buildList.setCurrentRow(0)
|
||||||
manus.tbEdit.click()
|
manus.tbEdit.click()
|
||||||
|
|
||||||
bSettings = getGuiItem("GuiBuildSettings")
|
bSettings = SHARED.findTopLevelWidget(GuiBuildSettings)
|
||||||
assert isinstance(bSettings, GuiBuildSettings)
|
assert isinstance(bSettings, GuiBuildSettings)
|
||||||
build = None
|
build = None
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tools import getGuiItem
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QAction
|
from PyQt5.QtWidgets import QAction
|
||||||
|
|
||||||
from novelwriter import SHARED
|
from novelwriter import SHARED
|
||||||
@@ -49,8 +47,8 @@ def testToolNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
|
|||||||
|
|
||||||
# Create the dialog
|
# Create the dialog
|
||||||
nwGUI.mainMenu.aNovelDetails.activate(QAction.ActionEvent.Trigger)
|
nwGUI.mainMenu.aNovelDetails.activate(QAction.ActionEvent.Trigger)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiNovelDetails") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiNovelDetails) is not None, timeout=1000)
|
||||||
details = getGuiItem("GuiNovelDetails")
|
details = SHARED.findTopLevelWidget(GuiNovelDetails)
|
||||||
assert isinstance(details, GuiNovelDetails)
|
assert isinstance(details, GuiNovelDetails)
|
||||||
|
|
||||||
# Overview Page
|
# Overview Page
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import pytest
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from tools import getGuiItem, buildTestProject
|
from tools import buildTestProject
|
||||||
from mocked import causeOSError
|
from mocked import causeOSError
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@@ -38,8 +38,7 @@ from novelwriter.tools.writingstats import GuiWritingStats
|
|||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
|
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
|
||||||
"""Test the full writing stats tool.
|
"""Test the full writing stats tool."""
|
||||||
"""
|
|
||||||
# Create a project to work on
|
# Create a project to work on
|
||||||
buildTestProject(nwGUI, projPath)
|
buildTestProject(nwGUI, projPath)
|
||||||
project = SHARED.project
|
project = SHARED.project
|
||||||
@@ -50,9 +49,9 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
|
|||||||
|
|
||||||
# Open the Writing Stats dialog
|
# Open the Writing Stats dialog
|
||||||
nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
|
nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
|
||||||
qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWritingStats) is not None, timeout=1000)
|
||||||
|
|
||||||
sessLog = getGuiItem("GuiWritingStats")
|
sessLog = SHARED.findTopLevelWidget(GuiWritingStats)
|
||||||
assert isinstance(sessLog, GuiWritingStats)
|
assert isinstance(sessLog, GuiWritingStats)
|
||||||
|
|
||||||
# Test Loading
|
# Test Loading
|
||||||
|
|||||||
+1
-9
@@ -25,7 +25,7 @@ import shutil
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget, qApp
|
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget
|
||||||
|
|
||||||
XML_IGNORE = ("<novelWriterXML", "<project")
|
XML_IGNORE = ("<novelWriterXML", "<project")
|
||||||
ODT_IGNORE = ("<meta:generator", "<meta:creation-date", "<dc:date", "<meta:editing")
|
ODT_IGNORE = ("<meta:generator", "<meta:creation-date", "<dc:date", "<meta:editing")
|
||||||
@@ -114,14 +114,6 @@ def cmpFiles(
|
|||||||
return not diffFound
|
return not diffFound
|
||||||
|
|
||||||
|
|
||||||
def getGuiItem(name: str):
|
|
||||||
"""Returns a QtWidget based on its objectName."""
|
|
||||||
for qWidget in qApp.topLevelWidgets():
|
|
||||||
if qWidget.objectName() == name:
|
|
||||||
return qWidget
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def readFile(fileName: str | Path):
|
def readFile(fileName: str | Path):
|
||||||
"""Returns the content of a file as a string."""
|
"""Returns the content of a file as a string."""
|
||||||
with open(fileName, mode="r", encoding="utf-8") as inFile:
|
with open(fileName, mode="r", encoding="utf-8") as inFile:
|
||||||
|
|||||||
Reference in New Issue
Block a user