Add valid path check to the New Project Wizard (#1062)

This commit is contained in:
Veronica Berglyd Olsen
2022-05-15 16:43:43 +02:00
committed by GitHub
2 changed files with 212 additions and 144 deletions
+28
View File
@@ -185,6 +185,9 @@ class ProjWizardFolderPage(QWizardPage):
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
self.browseButton.clicked.connect(self._doBrowse)
self.errLabel = QLabel("")
self.errLabel.setWordWrap(True)
self.mainForm = QHBoxLayout()
self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0)
self.mainForm.addWidget(self.projPath, 1)
@@ -198,11 +201,36 @@ class ProjWizardFolderPage(QWizardPage):
self.outerBox.setSpacing(vS)
self.outerBox.addWidget(self.theText)
self.outerBox.addLayout(self.mainForm)
self.outerBox.addWidget(self.errLabel)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
return
def isComplete(self):
"""Check that the selected path isn't already being used.
"""
self.errLabel.setText("")
if not QWizardPage.isComplete(self):
return False
setPath = os.path.abspath(os.path.expanduser(self.projPath.text()))
parPath = os.path.dirname(setPath)
logger.verbose("Path is: %s", setPath)
if parPath and not os.path.isdir(parPath):
self.errLabel.setText(self.tr(
"Error: A project folder cannot be created using this path."
))
return False
if os.path.exists(setPath):
self.errLabel.setText(self.tr(
"Error: The selected path already exists."
))
return False
return True
##
# Slots
##
+184 -144
View File
@@ -19,14 +19,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
import sys
import pytest
from tools import getGuiItem
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox
from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox, QDialog
from novelwriter.enum import nwItemClass
from novelwriter.tools.projwizard import (
@@ -41,8 +41,8 @@ stepDelay = 20
@pytest.mark.gui
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the new project wizard.
def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the launch of the project wizard.
Disabled for macOS because the test segfaults on QWizard.show()
"""
# Block message box
@@ -55,173 +55,213 @@ def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
# New with a project open should cause an error
assert nwGUI.openProject(nwMinimal)
assert not nwGUI.newProject()
with monkeypatch.context() as mp:
mp.setattr(nwGUI, "closeProject", lambda *a: False)
assert nwGUI.newProject() is False
# Close project, but call with invalid path
assert nwGUI.closeProject()
with monkeypatch.context() as mp:
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: None)
assert not nwGUI.newProject()
assert nwGUI.newProject() is False
# Now, with an empty dictionary
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {})
assert not nwGUI.newProject()
assert nwGUI.newProject() is False
# Now, with a non-empty folder
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": nwMinimal})
assert not nwGUI.newProject()
assert nwGUI.newProject() is False
##
# Test the Wizard
# Test the Wizard Launching
##
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
nwGUI.mainConf.lastPath = " "
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
nwGUI.closeProject()
nwGUI.showNewProjectDialog()
result = nwGUI.showNewProjectDialog()
qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000)
nwWiz = getGuiItem("GuiProjectWizard")
assert isinstance(nwWiz, GuiProjectWizard)
nwWiz.show()
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.CancelButton), Qt.LeftButton)
assert result is None
for wStep in range(4):
# This does not actually create the project, it just generates the
# dictionary that defines it.
# Intro Page
introPage = nwWiz.currentPage()
assert isinstance(introPage, ProjWizardIntroPage)
assert not nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
for c in ("Test Minimal %d" % wStep):
qtbot.keyClick(introPage.projName, c, delay=typeDelay)
qtbot.wait(stepDelay)
for c in "Minimal Novel":
qtbot.keyClick(introPage.projTitle, c, delay=typeDelay)
qtbot.wait(stepDelay)
for c in "Jane Doe":
qtbot.keyClick(introPage.projAuthors, c, delay=typeDelay)
# Setting projName should activate the button
assert nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Folder Page
storagePage = nwWiz.currentPage()
assert isinstance(storagePage, ProjWizardFolderPage)
assert not nwWiz.button(QWizard.NextButton).isEnabled()
if wStep == 0:
# Check invalid path first, the first time we reach here
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: "")
qtbot.wait(stepDelay)
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
assert storagePage.projPath.text() == ""
# Then, we always return nwMinimal as path
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: nwMinimal)
qtbot.wait(stepDelay)
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
projPath = os.path.join(nwMinimal, "Test Minimal %d" % wStep)
assert storagePage.projPath.text() == projPath
# Setting projPath should activate the button
assert nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Populate Page
popPage = nwWiz.currentPage()
assert isinstance(popPage, ProjWizardPopulatePage)
assert nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
if wStep == 0:
popPage.popMinimal.setChecked(True)
elif wStep == 1:
popPage.popCustom.setChecked(True)
elif wStep == 2:
popPage.popCustom.setChecked(True)
elif wStep == 3:
popPage.popSample.setChecked(True)
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Custom Page
if wStep == 1 or wStep == 2:
customPage = nwWiz.currentPage()
assert isinstance(customPage, ProjWizardCustomPage)
assert nwWiz.button(QWizard.NextButton).isEnabled()
customPage.addPlot.setChecked(True)
customPage.addChar.setChecked(True)
customPage.addWorld.setChecked(True)
customPage.addTime.setChecked(True)
customPage.addObject.setChecked(True)
customPage.addEntity.setChecked(True)
if wStep == 2:
customPage.numChapters.setValue(0)
customPage.numScenes.setValue(10)
customPage.chFolders.setChecked(False)
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Final Page
finalPage = nwWiz.currentPage()
assert isinstance(finalPage, ProjWizardFinalPage)
assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it
# Check Data
projData = nwGUI._assembleProjectWizardData(nwWiz)
assert projData["projName"] == "Test Minimal %d" % wStep
assert projData["projTitle"] == "Minimal Novel"
assert projData["projAuthors"] == "Jane Doe"
assert projData["projPath"] == projPath
assert projData["popMinimal"] == (wStep == 0)
assert projData["popCustom"] == (wStep == 1 or wStep == 2)
assert projData["popSample"] == (wStep == 3)
if wStep == 1 or wStep == 2:
assert projData["addRoots"] == [
nwItemClass.PLOT,
nwItemClass.CHARACTER,
nwItemClass.WORLD,
nwItemClass.TIMELINE,
nwItemClass.OBJECT,
nwItemClass.ENTITY,
]
if wStep == 1:
assert projData["numChapters"] == 5
assert projData["numScenes"] == 5
assert projData["chFolders"]
else:
assert projData["numChapters"] == 0
assert projData["numScenes"] == 10
assert not projData["chFolders"]
else:
assert projData["addRoots"] == []
assert projData["numChapters"] == 0
assert projData["numScenes"] == 0
assert not projData["chFolders"]
# Restart the wizard for next iteration
nwWiz.restart()
with monkeypatch.context() as mp:
mp.setattr(GuiProjectWizard, "result", lambda *a: QDialog.Accepted)
result = nwGUI.showNewProjectDialog()
nwWiz.button(QWizard.CancelButton).click()
assert isinstance(result, dict)
nwWiz.reject()
nwWiz.close()
# qtbot.stopForInteraction()
# END Test testToolProjectWizard_Main
# END Test testToolProjectWizard_Handling
@pytest.mark.gui
@pytest.mark.parametrize("prjType", ["minimal", "custom1", "custom2", "sample"])
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
"""Test the new project wizard with a set of selection scenarios.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
nwGUI.mainConf.lastPath = " "
nwWiz = GuiProjectWizard(nwGUI)
nwWiz.show()
qtbot.wait(stepDelay)
# Intro Page
# ==========
introPage = nwWiz.currentPage()
assert isinstance(introPage, ProjWizardIntroPage)
assert not nwWiz.button(QWizard.NextButton).isEnabled()
introPage.projName.setText("Test Wizard")
introPage.projTitle.setText("My Novel")
introPage.projAuthors.setPlainText("Jane Doe")
# Setting projName should activate the button
assert nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Folder Page
# ===========
storagePage = nwWiz.currentPage()
assert isinstance(storagePage, ProjWizardFolderPage)
assert not nwWiz.button(QWizard.NextButton).isEnabled()
assert storagePage.errLabel.text() == ""
# Set an invalid path
storagePage.projPath.setText(os.path.join(fncDir, "not", "a", "path"))
assert not nwWiz.button(QWizard.NextButton).isEnabled()
assert storagePage.errLabel.text().startswith("Error")
# Set an existing path
storagePage.projPath.setText(fncDir)
assert not nwWiz.button(QWizard.NextButton).isEnabled()
assert storagePage.errLabel.text().startswith("Error")
# Return a non-result from browse
with monkeypatch.context() as mp:
mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "")
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
assert storagePage.errLabel.text() == ""
# Let the browse feature handle it
projPath = os.path.join(fncDir, "Test Wizard")
with monkeypatch.context() as mp:
mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: fncDir)
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
assert storagePage.projPath.text() == projPath
assert storagePage.errLabel.text() == ""
# Setting projPath should activate the button
assert nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Populate Page
# =============
popPage = nwWiz.currentPage()
assert isinstance(popPage, ProjWizardPopulatePage)
assert nwWiz.button(QWizard.NextButton).isEnabled()
qtbot.wait(stepDelay)
if prjType.startswith("minimal"):
popPage.popMinimal.setChecked(True)
elif prjType.startswith("custom"):
popPage.popCustom.setChecked(True)
elif prjType.startswith("sample"):
popPage.popSample.setChecked(True)
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Custom Page
# ===========
if prjType.startswith("custom"):
customPage = nwWiz.currentPage()
assert isinstance(customPage, ProjWizardCustomPage)
assert nwWiz.button(QWizard.NextButton).isEnabled()
customPage.addPlot.setChecked(True)
customPage.addChar.setChecked(True)
customPage.addWorld.setChecked(True)
customPage.addTime.setChecked(True)
customPage.addObject.setChecked(True)
customPage.addEntity.setChecked(True)
if prjType == "custom2":
customPage.numChapters.setValue(0)
customPage.numScenes.setValue(10)
customPage.chFolders.setChecked(False)
qtbot.wait(stepDelay)
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
# Final Page
# ==========
finalPage = nwWiz.currentPage()
assert isinstance(finalPage, ProjWizardFinalPage)
assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it
# Check Data
# ==========
projData = nwGUI._assembleProjectWizardData(nwWiz)
assert projData["projName"] == "Test Wizard"
assert projData["projTitle"] == "My Novel"
assert projData["projAuthors"] == "Jane Doe"
assert projData["projPath"] == projPath
assert projData["popMinimal"] == prjType.startswith("minimal")
assert projData["popCustom"] == prjType.startswith("custom")
assert projData["popSample"] == prjType.startswith("sample")
if prjType.startswith("custom"):
assert projData["addRoots"] == [
nwItemClass.PLOT,
nwItemClass.CHARACTER,
nwItemClass.WORLD,
nwItemClass.TIMELINE,
nwItemClass.OBJECT,
nwItemClass.ENTITY,
]
if prjType == "custom1":
assert projData["numChapters"] == 5
assert projData["numScenes"] == 5
assert projData["chFolders"]
else:
assert projData["numChapters"] == 0
assert projData["numScenes"] == 10
assert not projData["chFolders"]
else:
assert projData["addRoots"] == []
assert projData["numChapters"] == 0
assert projData["numScenes"] == 0
assert not projData["chFolders"]
# Cleanup
nwWiz.reject()
nwWiz.close()
# qtbot.stopForInteraction()
# END Test testToolProjectWizard_Run