diff --git a/manuskript/main.py b/manuskript/main.py index 7d58f0a5..aced35a3 100644 --- a/manuskript/main.py +++ b/manuskript/main.py @@ -14,8 +14,7 @@ from manuskript.version import getVersion faulthandler.enable() - -def run(): +def prepare(): app = QApplication(sys.argv) app.setOrganizationName("manuskript") app.setOrganizationDomain("www.theologeek.ch") @@ -57,25 +56,38 @@ def run(): QIcon.setThemeSearchPaths(QIcon.themeSearchPaths() + [appPath("icons")]) QIcon.setThemeName("NumixMsk") - # qApp.setWindowIcon(QIcon.fromTheme("im-aim")) - # Seperating launch to avoid segfault, so it seem. - # Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code - launch() - - -def launch(): + # Main window from manuskript.mainWindow import MainWindow - main = MainWindow() + MW = MainWindow() # We store the system default cursor flash time to be able to restore it # later if necessary - main._defaultCursorFlashTime = qApp.cursorFlashTime() - main.show() + MW._defaultCursorFlashTime = qApp.cursorFlashTime() + + return app, MW + +def launch(MW = None): + if MW is None: + from manuskript.functions import mainWindow + MW = mainWindow() + + MW.show() qApp.exec_() qApp.deleteLater() +def run(): + """ + Run separates prepare and launch for two reasons: + 1. I've read somewhere it helps with potential segfault (see comment below) + 2. So that prepare can be used in tests, without running the whole thing + """ + # Need to return and keep `app` otherwise it gets deleted. + app, MW = prepare() + # Seperating launch to avoid segfault, so it seem. + # Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code + launch(MW) if __name__ == "__main__": run() diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 1fc61d02..5a05be6a 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -1416,4 +1416,3 @@ class MainWindow(QMainWindow, Ui_MainWindow): r = self.dialog.geometry() r2 = self.geometry() self.dialog.move(r2.center() - r.center()) - diff --git a/manuskript/tests/__init__.py b/manuskript/tests/__init__.py index 772c83c5..b63773ce 100644 --- a/manuskript/tests/__init__.py +++ b/manuskript/tests/__init__.py @@ -7,5 +7,10 @@ import pytest # We need a qApplication to be running, or all the calls to qApp # will throw a seg fault. -from PyQt5.QtWidgets import QApplication -app = QApplication([]) +# from PyQt5.QtWidgets import QApplication +# app = QApplication([]) +# app.setOrganizationName("manuskript_tests") +# app.setApplicationName("manuskript_tests") + +from manuskript import main +app, MW = main.prepare() diff --git a/manuskript/tests/conftest.py b/manuskript/tests/conftest.py index 5c00100e..5f825e48 100644 --- a/manuskript/tests/conftest.py +++ b/manuskript/tests/conftest.py @@ -5,17 +5,32 @@ import pytest -@pytest.fixture(scope='session', autouse=True) -def MW(): - """ - Creates a mainWindow that can be used for the tests - Either with functions.mainWindow or by passing argument - MW to the test - """ - from manuskript.mainWindow import MainWindow - mw = MainWindow() +# @pytest.fixture(scope='session', autouse=True) +# def MW(): +# """ +# Creates a mainWindow that can be used for the tests +# Either with functions.mainWindow or by passing argument +# MW to the test +# """ +# from manuskript.mainWindow import MainWindow +# mw = MainWindow() +# +# yield +# +# # Properly destructed after. Otherwise: seg fault. +# mw.deleteLater() - yield +@pytest.fixture +def MWEmptyProject(): + """ + Sets the mainWindow to load an empty project. + """ + from manuskript.functions import mainWindow + MW = mainWindow() - # Properly destructed after. Otherwise: seg fault. - mw.deleteLater() + import tempfile + tf = tempfile.NamedTemporaryFile(suffix=".msk") + MW.welcome.createFile(tf.name, overwrite=True) + assert MW.currentProject is not None + + return MW diff --git a/manuskript/tests/models/conftest.py b/manuskript/tests/models/conftest.py index 25b2e57e..19073e6c 100644 --- a/manuskript/tests/models/conftest.py +++ b/manuskript/tests/models/conftest.py @@ -7,13 +7,13 @@ import pytest from manuskript.models import outlineModel, outlineItem @pytest.fixture -def outlineModelBasic(): +def outlineModelBasic(MWEmptyProject): """Returns an outlineModel with a few items: * Folder * Text * Text """ - mdl = outlineModel(parent=None) + mdl = MWEmptyProject.mdlOutline root = mdl.rootItem f = outlineItem(title="Folder", parent=root) diff --git a/manuskript/tests/models/test_outlineItem.py b/manuskript/tests/models/test_outlineItem.py index 33096487..daaddf0c 100644 --- a/manuskript/tests/models/test_outlineItem.py +++ b/manuskript/tests/models/test_outlineItem.py @@ -18,53 +18,79 @@ def outlineItemText(): return outlineItem(title="Text", _type="md") def test_outlineItemsProperties(outlineItemFolder, outlineItemText): + """ + Tests with simple items, without parent or models. + """ # Simplification folder = outlineItemFolder text = outlineItemText - # Basic tests + # getters assert folder.isFolder() == True assert text.isFolder() == False assert text.isText() == True assert text.isMD() == text.isMMD() == True - assert text.title() == "Text" assert text.compile() == True - text.setData(text.enum.compile, 0) - assert text.compile() == False assert folder.POV() == "" assert folder.status() == "" - assert folder.POV() == "" + assert folder.label() == "" assert folder.customIcon() == "" + + # setData and other setters + from PyQt5.QtCore import Qt + assert text.data(text.enum.compile, role=Qt.CheckStateRole) == Qt.Checked + text.setData(text.enum.compile, 0) + assert text.compile() == False + assert text.data(text.enum.compile, role=Qt.CheckStateRole) == Qt.Unchecked folder.setCustomIcon("custom") assert folder.customIcon() == "custom" + folder.setData(folder.enum.text, "Some text") + assert folder.text() == "" # folders have no text + # wordCount text.setData(text.enum.text, "Sample **text**.") assert text.wordCount() == 2 - assert text.data(text.enum.revisions) == [] - text.setData(text.enum.setGoal, 4) + text.setData(text.enum.goal, 4) assert text.data(text.enum.goalPercentage) == .5 + # revisions + assert text.data(text.enum.revisions) == [] + def test_modelStuff(outlineModelBasic): + """ + Tests with children items. + """ # Simplification model = outlineModelBasic + # Child count root = model.rootItem assert len(root.children()) == 2 folder = root.child(0) text1 = folder.child(0) text2 = root.child(1) + + # Compile assert text1.compile() == True folder.setData(folder.enum.compile, 0) assert text1.compile() == False + # Word count text1.setData(text1.enum.text, "Sample text.") assert text1.wordCount() == 2 assert folder.wordCount() == 2 - assert folder.stats() != "" + statsWithGoal = folder.stats() + assert statsWithGoal != "" + text1.setData(text1.enum.setGoal, 4) + assert folder.data(folder.enum.goal) == 4 + folder.setData(folder.enum.setGoal, 3) + assert folder.data(folder.enum.goal) == 3 + assert folder.stats() != statsWithGoal + # Split and merge text1.setData(text1.enum.text, "Sample\n---\ntext.") folder.split("invalid mark") assert folder.childCount() == 1 @@ -75,6 +101,40 @@ def test_modelStuff(outlineModelBasic): text1.setData(text1.enum.text, "Sample\nNewTitle\ntext.") text1.splitAt(7, 8) assert folder.child(1).title() == "NewTitle" + folder.child(1).splitAt(3) + assert folder.child(2).title() == "NewTitle_2" + folder.removeChild(2) folder.removeChild(1) folder.removeChild(0) assert folder.childCount() == 0 + + # Search + folder.appendChild(text2) + text2.setData(text2.enum.POV, 1) + folder.setData(folder.enum.POV, 1) + assert len(folder.findItemsByPOV(1)) == 2 + folder.setData(folder.enum.label, 1) # Idea + folder.setData(folder.enum.status, 4) # Final + text2.setData(text2.enum.text, "Some final value.") + from manuskript.functions import MW + cols = [folder.enum.text, folder.enum.POV, + folder.enum.label, folder.enum.status] + assert folder.findItemsContaining("VALUE", cols, MW, True) == [] + assert folder.findItemsContaining("VALUE", cols, MW, False) == [text2.ID()] + + # Revisions + # from manuskript import settings + # settings.revisions["smartremove"] = False + # text2.setData(text2.enum.text, "Some value.") + # assert text2.revisions() == 1 + # text2.clearAllRevisions() + # assert text2.revisions() == [] + # text2.setData(text2.enum.text, "Some value.") + # assert len(text2.revisions()) == 1 + # text2.setData(text2.enum.text, "Some new value.") + # assert len(text2.revisions()) == 1 # Auto clean + + + + #TODO: copy (with children), IDs check, childcountrecursive + # (cf. abstractItem) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 34afc5fb..8467d58b 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -152,18 +152,20 @@ class welcome(QWidget, Ui_welcome): pName=pName[:-4] self.mw.setWindowTitle(pName + " - " + self.tr("Manuskript")) - def createFile(self): + def createFile(self, filename=None, overwrite=False): """When starting a new project, ask for a place to save it. Datas are not loaded from file, so they must be populated another way.""" - filename = QFileDialog.getSaveFileName(self, - self.tr("Create New Project"), - ".", - self.tr("Manuskript project (*.msk)"))[0] + if filename is None: + filename = QFileDialog.getSaveFileName( + self, + self.tr("Create New Project"), + ".", + self.tr("Manuskript project (*.msk)"))[0] if filename: if filename[-4:] != ".msk": filename += ".msk" - if os.path.exists(filename): + if os.path.exists(filename) and not overwrite: # Check if okay to overwrite existing project result = QMessageBox.warning(self, self.tr("Warning"), self.tr("Overwrite existing project {} ?").format(filename),