def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj): """Test creating a new project. """ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) # No data with monkeypatch.context() as mp: mp.setattr(GuiProjectWizard, "exec_", lambda *a: None) assert nwGUI.newProject(projData=None) is False # Close project with monkeypatch.context() as mp: nwGUI.hasProject = True mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No) assert nwGUI.newProject(projData={"projPath": fncProj}) is False # No project path assert nwGUI.newProject(projData={}) is False # Project file already exists projFile = os.path.join(fncProj, nwGUI.theProject.projFile) writeFile(projFile, "Stuff") assert nwGUI.newProject(projData={"projPath": fncProj}) is False os.unlink(projFile) # An unreachable path should also fail projPath = os.path.join(fncProj, "stuff", "stuff", "stuff") assert nwGUI.newProject(projData={"projPath": projPath}) is False # This one should work just fine assert nwGUI.newProject(projData={"projPath": fncProj}) is True assert os.path.isfile(os.path.join(fncProj, nwGUI.theProject.projFile)) assert os.path.isdir(os.path.join(fncProj, "content"))
def fillAspect(aspectj_setup_dir, monitor_setup_dir, gen_monitor_setup_dir): """ Fills the user-defined parts of a generated .aj file. The file should be called unittestMonitorAspect.aj file. Args: aspectj_setup_dir (str): The destination directory for the filled .aj file. monitor_setup_dir (str): Input directory that contains two files: * import.txt: should contain the code that replaces the "// add your own imports." comment in the .aj file. * ajcode.txt: should contain the code that replaces the "// Implement your code here." comment in the .aj file. gen_monitor_dir (str): The input directory, which must a a single unittestMonitorAspect.aj file. """ tools.progress("Fill in AspectJ template") aspect_file_name = "unittestMonitorAspect.aj" aspect_file_path = os.path.join(gen_monitor_setup_dir, aspect_file_name) aspectContent = tools.readFile(aspect_file_path) aspectContent = aspectContent.replace( "// add your own imports.", tools.readFile(os.path.join(monitor_setup_dir, "import.txt"))) aspectContent = aspectContent.replace( "// Implement your code here.", tools.readFile(os.path.join(monitor_setup_dir, "ajcode.txt"))) tools.writeFile(os.path.join(aspectj_setup_dir, aspect_file_name), aspectContent)
def addRvmExceptions(rvm_file_path): """ Changes the getState functions to throw an exception with the state name. The input file will be changed in-place. The getState function should have the following format: private void somename_getState() throws GotoStmtException, RaiseStmtException { } The resulting getState function will look like this: private void somename_getState() throws GotoStmtException, RaiseStmtException { throw new StateNameException(state.getName()); } Args: rvm_file_path (str): The path to the rvm file. """ rvm = re.sub( r"([ ]*)private void ([a-zA-Z_0-9]+)_getState\(\) throws GotoStmtException, RaiseStmtException \{", r"""\1private void \2_getState() throws GotoStmtException, RaiseStmtException, StateNameException { \1 throw new StateNameException(state.getName());""", tools.readFile(rvm_file_path)) tools.writeFile(rvm_file_path, rvm)
def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI): """Test lock file functions for the project folder. """ theProject = NWProject(dummyGUI) lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK) # No project assert theProject._writeLockFile() is False assert theProject._readLockFile() == ["ERROR"] assert theProject._clearLockFile() is False theProject.projPath = fncDir theProject.mainConf.hostName = "TestHost" theProject.mainConf.osType = "TestOS" theProject.mainConf.kernelVer = "1.0" # Block open monkeypatch.setattr("builtins.open", causeOSError) assert theProject._writeLockFile() is False monkeypatch.undo() # Write lock file monkeypatch.setattr("nw.core.project.time", lambda: 123.4) assert theProject._writeLockFile() is True monkeypatch.undo() assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n" # Block open monkeypatch.setattr("builtins.open", causeOSError) assert theProject._readLockFile() == ["ERROR"] monkeypatch.undo() # Read lock file assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"] # Block unlink monkeypatch.setattr("os.unlink", causeOSError) assert os.path.isfile(lockFile) assert theProject._clearLockFile() is False assert os.path.isfile(lockFile) monkeypatch.undo() # Clear file assert os.path.isfile(lockFile) assert theProject._clearLockFile() is True assert not os.path.isfile(lockFile) # Read again, no file assert theProject._readLockFile() == [] # Read an invalid lock file writeFile(lockFile, "A\nB") assert theProject._readLockFile() == ["ERROR"] assert theProject._clearLockFile() is True
def testBaseCommon_ReadTextFile(monkeypatch, fncDir, ipsumText): """Test the readTextFile function. """ testText = "\n\n".join(ipsumText) + "\n" testFile = os.path.join(fncDir, "ipsum.txt") writeFile(testFile, testText) assert readTextFile(os.path.join(fncDir, "not_a_file.txt")) == "" assert readTextFile(testFile) == testText with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert readTextFile(testFile) == ""
def testCoreSpell_Enchant(monkeypatch, tmpDir, tmpConf): """Test the pyenchant spell checker """ wList = os.path.join(tmpDir, "wordlist.txt") writeFile(wList, "a_word\nb_word\nc_word\n") # Block the enchant package (and trigger the dummy class) monkeypatch.setitem(sys.modules, "enchant", None) spChk = NWSpellEnchant() spChk.setLanguage("en", wList) assert spChk.setLanguage("", "") is None assert spChk.checkWord("") assert spChk.suggestWords("") == [] assert spChk.listDictionaries() == [] assert spChk.describeDict() == ("", "") monkeypatch.undo() # Load the proper enchant package spChk = NWSpellEnchant() spChk.mainConf = tmpConf spChk.setLanguage("en", wList) assert spChk.checkWord("a_word") assert spChk.checkWord("b_word") assert spChk.checkWord("c_word") assert not spChk.checkWord("d_word") spChk.addWord("d_word") assert spChk.checkWord("d_word") wSuggest = spChk.suggestWords("wrod") assert len(wSuggest) > 0 assert "word" in wSuggest dList = spChk.listDictionaries() assert len(dList) > 0 aTag, aName = spChk.describeDict() assert aTag == "en" assert aName != ""
def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf): """Test the spell checker super class """ wList = os.path.join(tmpDir, "wordlist.txt") writeFile(wList, "a_word\nb_word\nc_word\n") spChk = NWSpellCheck() spChk.mainConf = tmpConf # Check that dummy functions return results that reflects that spell # checking is effectively disabled assert spChk.setLanguage("", "") is None assert spChk.checkWord("") assert spChk.suggestWords("") == [] assert spChk.listDictionaries() == [] assert spChk.describeDict() == ("", "") # Check language info assert NWSpellCheck.expandLanguage("en") == "English" assert NWSpellCheck.expandLanguage("en_GB") == "English (GB)" # Add a word to the user's dictionary assert spChk._readProjectDictionary("dummy") is False monkeypatch.setattr("builtins.open", causeOSError) assert spChk._readProjectDictionary(wList) is False monkeypatch.undo() assert spChk._readProjectDictionary(wList) is True assert spChk.projectDict == wList # Cannot write to file monkeypatch.setattr("builtins.open", causeOSError) assert spChk.addWord("d_word") is False monkeypatch.undo() assert readFile(wList) == "a_word\nb_word\nc_word\n" # First time, OK assert spChk.addWord("d_word") is True assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n" # But not added twice assert spChk.addWord("d_word") is False assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n"
def testBaseCommon_Sha256Sum(monkeypatch, fncDir, ipsumText): """Test the sha256sum function. """ longText = 50 * (" ".join(ipsumText) + " ") shortText = "This is a short file" noneText = "" assert len(longText) == 175650 longFile = os.path.join(fncDir, "long_file.txt") shortFile = os.path.join(fncDir, "short_file.txt") noneFile = os.path.join(fncDir, "none_file.txt") writeFile(longFile, longText) writeFile(shortFile, shortText) writeFile(noneFile, noneText) # Taken with sha256sum command on command line longHash = "9b22aee35660da4fae204acbe96aec7f563022746ca2b7a3831f5e44544765eb" shortHash = "6d7c9b2722364c471b8a8666bcb35d18500272d05b23b3427288e2e34c6618f0" noneHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" assert sha256sum(longFile) == longHash assert sha256sum(shortFile) == shortHash assert sha256sum(noneFile) == noneHash assert hashlib.sha256(longText.encode("utf-8")).hexdigest() == longHash assert hashlib.sha256(shortText.encode("utf-8")).hexdigest() == shortHash assert hashlib.sha256(noneText.encode("utf-8")).hexdigest() == noneHash with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert sha256sum(longFile) is None assert sha256sum(shortFile) is None assert sha256sum(noneFile) is None
def fillAspect(aspectj_dir, gen_monitor_dir): """ Fills the user-defined parts of a generated .aj file. The current directory should contain two files: * import.txt: should contain the code that replaces the "// add your own imports." comment in the .aj file. * ajcode.txt: should contain the code that replaces the "// Implement your code here." comment in the .aj file. The fillAspect function replaces the comments mentioned above in the .aj file, and copies the result to the destination directory. Args: aspectj_dir (str): The destination directory for the filled .aj file. gen_monitor_dir (str): The input directory, which must contain a single .aj file Raises: Exception if the input directory does not contain exactly one .aj file. """ tools.progress("Fill in AspectJ template") aspect_file_paths = glob.glob(os.path.join(gen_monitor_dir, "*.aj")) if len(aspect_file_paths) != 1: raise Exception("Expected a single aspectJ template") aspect_file_path = aspect_file_paths[0] aspectContent = tools.readFile(aspect_file_path) aspectContent = aspectContent.replace("// add your own imports.", tools.readFile("import.txt")) aspectContent = aspectContent.replace("// Implement your code here.", tools.readFile("ajcode.txt")) aspect_file_name = os.path.basename(aspect_file_path) tools.writeFile(os.path.join(aspectj_dir, aspect_file_name), aspectContent)
def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI): """Test helper functions for the project folder. """ theProject = NWProject(dummyGUI) # No path assert theProject.ensureFolderStructure() is False # Set the correct dir theProject.projPath = fncDir # Block user's home folder monkeypatch.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir) assert theProject.ensureFolderStructure() is False monkeypatch.undo() # Create a file to block meta folder metaDir = os.path.join(fncDir, "meta") writeFile(metaDir, "dummy") assert theProject.ensureFolderStructure() is False os.unlink(metaDir) # Create a file to block cache folder cacheDir = os.path.join(fncDir, "cache") writeFile(cacheDir, "dummy") assert theProject.ensureFolderStructure() is False os.unlink(cacheDir) # Create a file to block content folder contentDir = os.path.join(fncDir, "content") writeFile(contentDir, "dummy") assert theProject.ensureFolderStructure() is False os.unlink(contentDir) # Now, do it right assert theProject.ensureFolderStructure() is True assert os.path.isdir(metaDir) assert os.path.isdir(cacheDir) assert os.path.isdir(contentDir)
def testCoreProject_OldFormat(dummyGUI, nwOldProj): """Test that a project folder structure of version 1.0 can be converted to the latest folder structure. Version 1.0 split the documents into 'data_0' ... 'data_f' folders, which are now all contained in a single 'content' folder. """ theProject = NWProject(dummyGUI) # Create dummy files for known legacy files deleteFiles = [ os.path.join(nwOldProj, "cache", "nwProject.nwx.0"), os.path.join(nwOldProj, "cache", "nwProject.nwx.1"), os.path.join(nwOldProj, "cache", "nwProject.nwx.2"), os.path.join(nwOldProj, "cache", "nwProject.nwx.3"), os.path.join(nwOldProj, "cache", "nwProject.nwx.4"), os.path.join(nwOldProj, "cache", "nwProject.nwx.5"), os.path.join(nwOldProj, "cache", "nwProject.nwx.6"), os.path.join(nwOldProj, "cache", "nwProject.nwx.7"), os.path.join(nwOldProj, "cache", "nwProject.nwx.8"), os.path.join(nwOldProj, "cache", "nwProject.nwx.9"), os.path.join(nwOldProj, "meta", "mainOptions.json"), os.path.join(nwOldProj, "meta", "exportOptions.json"), os.path.join(nwOldProj, "meta", "outlineOptions.json"), os.path.join(nwOldProj, "meta", "timelineOptions.json"), os.path.join(nwOldProj, "meta", "docMergeOptions.json"), os.path.join(nwOldProj, "meta", "sessionLogOptions.json"), ] # Add some files that shouldn't be there deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.nwd")) deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.txt")) # Add some folders that shouldn't be there os.mkdir(os.path.join(nwOldProj, "stuff")) os.mkdir(os.path.join(nwOldProj, "data_1", "stuff")) # Create dummy files os.mkdir(os.path.join(nwOldProj, "cache")) for aFile in deleteFiles: writeFile(aFile, "Hi") for aFile in deleteFiles: assert os.path.isfile(aFile) # Open project and check that files that are not supposed to be # there have been removed assert theProject.openProject(nwOldProj) for aFile in deleteFiles: assert not os.path.isfile(aFile) assert not os.path.isdir(os.path.join(nwOldProj, "data_1", "stuff")) assert not os.path.isdir(os.path.join(nwOldProj, "data_1")) assert not os.path.isdir(os.path.join(nwOldProj, "data_7")) assert not os.path.isdir(os.path.join(nwOldProj, "data_8")) assert not os.path.isdir(os.path.join(nwOldProj, "data_9")) assert not os.path.isdir(os.path.join(nwOldProj, "data_a")) assert not os.path.isdir(os.path.join(nwOldProj, "data_f")) # Check stuff that has been moved assert os.path.isdir(os.path.join(nwOldProj, "junk")) assert os.path.isdir(os.path.join(nwOldProj, "junk", "stuff")) assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.nwd")) assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.txt")) # Check that files we want to keep are in the right place assert os.path.isdir(os.path.join(nwOldProj, "cache")) assert os.path.isdir(os.path.join(nwOldProj, "content")) assert os.path.isdir(os.path.join(nwOldProj, "meta")) assert os.path.isfile( os.path.join(nwOldProj, "content", "f528d831f5b24.nwd")) assert os.path.isfile( os.path.join(nwOldProj, "content", "88124a4292d8b.nwd")) assert os.path.isfile( os.path.join(nwOldProj, "content", "91239bf2f8b69.nwd")) assert os.path.isfile( os.path.join(nwOldProj, "content", "19752e7f9d8af.nwd")) assert os.path.isfile( os.path.join(nwOldProj, "content", "a764d5acf5a21.nwd")) assert os.path.isfile( os.path.join(nwOldProj, "content", "9058ae29f0dfd.nwd")) assert os.path.isfile( os.path.join(nwOldProj, "content", "7ff63b8afc4cd.nwd")) assert os.path.isfile(os.path.join(nwOldProj, "meta", "tagsIndex.json")) assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionInfo.log")) # Close the project theProject.closeProject() # Check that new files have been created assert os.path.isfile(os.path.join(nwOldProj, "meta", "guiOptions.json")) assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionStats.log")) assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt"))
def testCoreProject_OrphanedFiles(mockGUI, nwLipsum): """Check that files in the content folder that are not tracked in the project XML file are handled correctly by the orphaned files function. It should also restore as much meta data as possible from the meta line at the top of the document file. """ theProject = NWProject(mockGUI) assert theProject.openProject(nwLipsum) assert theProject.projTree["636b6aa9b697b"] is None assert theProject.closeProject() # First Item with Meta Data orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd") writeFile(orphPath, ("%%~name:[Recovered] Mars\n" "%%~path:5eaea4e8cdee8/636b6aa9b697b\n" "%%~kind:WORLD/NOTE\n" "%%~invalid\n" "\n")) # Second Item without Meta Data orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd") writeFile(orphPath, "\n") # Invalid File Name tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt") writeFile(tstPath, "\n") # Invalid File Name tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd") writeFile(tstPath, "\n") # Invalid File Name tstPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd") writeFile(tstPath, "\n") assert theProject.openProject(nwLipsum) assert theProject.projPath is not None assert theProject.projTree["636b6aa9b697bb"] is None assert theProject.projTree["abcdefghijklm"] is None # First Item with Meta Data oItem = theProject.projTree["636b6aa9b697b"] assert oItem is not None assert oItem.itemName == "[Recovered] Mars" assert oItem.itemHandle == "636b6aa9b697b" assert oItem.itemParent == "60bdf227455cc" assert oItem.itemClass == nwItemClass.WORLD assert oItem.itemType == nwItemType.FILE assert oItem.itemLayout == nwItemLayout.NOTE # Second Item without Meta Data oItem = theProject.projTree["736b6aa9b697b"] assert oItem is not None assert oItem.itemName == "Recovered File 1" assert oItem.itemHandle == "736b6aa9b697b" assert oItem.itemParent == "b3643d0f92e32" assert oItem.itemClass == nwItemClass.NOVEL assert oItem.itemType == nwItemType.FILE assert oItem.itemLayout == nwItemLayout.NOTE assert theProject.saveProject(nwLipsum) assert theProject.closeProject() # Finally, check that the orphaned files function returns # if no project is open and no path is set assert not theProject._scanProjectFolder()
def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal): """Test adding and removing items from the project tree. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes) monkeypatch.setattr(GuiMain, "editItem", lambda *args: None) nwGUI.theProject.projTree.setSeed(42) nwTree = nwGUI.treeView ## # Add New Items ## # Try to add and move item with no project assert not nwTree.newTreeItem(nwItemType.FILE, None) assert not nwTree.moveTreeItem(1) # Open a project assert nwGUI.openProject(nwMinimal) # No location selected for new item nwTree.clearSelection() assert not nwTree.newTreeItem(nwItemType.FILE, None) assert not nwTree.newTreeItem(nwItemType.FOLDER, None) assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL) # No itemType set or ROOT, but no class assert not nwTree.newTreeItem(None, None) assert not nwTree.newTreeItem(nwItemType.ROOT, None) # Select a location chItem = nwTree._getTreeItem("a6d311a93600a") nwTree.setCurrentItem(chItem, QItemSelectionModel.Current) chItem.setExpanded(True) # Create new item with no class set (defaults to NOVEL) assert nwTree.newTreeItem(nwItemType.FILE, None) assert nwTree.newTreeItem(nwItemType.FOLDER, None) # Check that we have the correct tree order assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9" ] # Add roots assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid # Change max depth and try to add a subfolder that is too deep monkeypatch.setattr("nw.constants.nwConst.MAX_DEPTH", 2) chItem = nwTree._getTreeItem("71ee45a3c0db9") nwTree.setCurrentItem(chItem, QItemSelectionModel.Current) assert not nwTree.newTreeItem(nwItemType.FOLDER, None) ## # Move Items ## nwTree.setSelectedHandle("8c659a11cd429") # Shift focus and try to move item monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: False) assert not nwTree.moveTreeItem(1) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9" ] monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True) # Move second item up twice (should give same result) nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9" ] nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9" ] # Move it back down four times (last two should be the same) nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9" ] nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "8c659a11cd429", "71ee45a3c0db9" ] nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429" ] nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429" ] # Move up twice, and undo nwTree._lastMove = {} nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger) nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger) nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger) assert nwTree.getTreeFromHandle("a6d311a93600a") == [ "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429" ] # Move a root item (top level items are different) twice nwTree.flushTreeOrder() assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10 nwTree.setSelectedHandle("9d5247ab588e0") nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) nwTree.flushTreeOrder() assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11 nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) nwTree.flushTreeOrder() assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11 ## # Delete and Trash ## # Add some content to the new file nwGUI.openDocument("73475cb40a568") nwGUI.docEditor.setText("# Hello World\n") nwGUI.saveDocument() nwGUI.saveProject() assert os.path.isfile( os.path.join(nwMinimal, "content", "73475cb40a568.nwd")) # Delete the items we added earlier nwTree.clearSelection() assert not nwTree.emptyTrash() # No folder yet assert not nwTree.deleteItem(None) assert not nwTree.deleteItem("1111111111111") assert nwTree.deleteItem("73475cb40a568") # New File assert nwTree.deleteItem("71ee45a3c0db9") # New Folder assert nwTree.deleteItem("811786ad1ae74") # Custom Root assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder assert "811786ad1ae74" not in nwGUI.theProject.projTree._treeOrder # The file is in trash, empty it assert os.path.isfile( os.path.join(nwMinimal, "content", "73475cb40a568.nwd")) assert nwTree.emptyTrash() assert not nwTree.emptyTrash() # Already empty assert not os.path.isfile( os.path.join(nwMinimal, "content", "73475cb40a568.nwd")) assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder # Should not be allowed to add files and folders to Trash trashHandle = nwGUI.theProject.projTree.trashRoot() chItem = nwTree._getTreeItem(trashHandle) nwTree.setCurrentItem(chItem, QItemSelectionModel.Current) assert not nwTree.newTreeItem(nwItemType.FILE, None) assert not nwTree.newTreeItem(nwItemType.FOLDER, None) # Close the project nwGUI.closeProject() ## # Orphaned Files ## # Add an orphaned file orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd") writeFile(orphFile, "# Hello World\n") # Open the project again nwGUI.openProject(nwMinimal) # Check that the orphaned file was found and added to the tree nwTree.flushTreeOrder() assert "1234567890abc" in nwGUI.theProject.projTree._treeOrder orItem = nwTree._getTreeItem("1234567890abc") assert orItem.text(nwTree.C_NAME) == "Recovered File 1" ## # Unexpected Error Handling ## # Add an item with an invalid type assert not nwTree.newTreeItem(nwItemType.NO_TYPE, nwItemClass.NOVEL) assert "Failed to add new item" in caplog.messages[-1] # Add new file after one that has no parent handle chItem = nwTree._getTreeItem("44cb730c42048") nwTree.setCurrentItem(chItem, QItemSelectionModel.Current) nwTree.theProject.projTree["44cb730c42048"].itemParent = None assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL) nwTree.clearSelection() # Add a file with no parent, and fail to find a suitable parent item monkeypatch.setattr("nw.core.tree.NWTree.findRoot", lambda *args: None) assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL) assert not nwTree.newTreeItem(nwItemType.FOLDER, nwItemClass.NOVEL) # qtbot.stopForInteraction() nwGUI.closeProject()
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test the split document tool. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) # Create a new project buildTestProject(nwGUI, fncProj) # Handles for new objects hNovelRoot = "0000000000008" hChapterDir = "000000000000d" hToSplit = "0000000000010" hNewFolder = "0000000000021" hPartition = "0000000000022" hChapterOne = "0000000000023" hSceneOne = "0000000000024" hSceneTwo = "0000000000025" hSceneThree = "0000000000026" hSceneFour = "0000000000027" hSceneFive = "0000000000028" # Add Project Content nwGUI.switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hNovelRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) assert nwGUI.saveProject() is True assert nwGUI.closeProject() is True tPartition = "# Nantucket" tChapterOne = "## Chapter One\n\n% Chapter one comment" tSceneOne = "### Scene One\n\nThere once was a man from Nantucket" tSceneTwo = "### Scene Two\n\nWho kept all his cash in a bucket." tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan, \n\tRan away with a man" tSceneFour = "### Scene Four\n\nAnd as for the bucket, Nantucket." tSceneFive = "#### The End\n\nend" tToSplit = (f"{tPartition}\n\n{tChapterOne}\n\n" f"{tSceneOne}\n\n{tSceneTwo}\n\n" f"{tSceneThree}\n\n{tSceneFour}\n\n" f"{tSceneFive}\n\n") contentDir = os.path.join(fncProj, "content") writeFile(os.path.join(contentDir, hToSplit + ".nwd"), tToSplit) assert nwGUI.openProject(fncProj) is True # Open the Split tool nwGUI.switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hToSplit).setSelected(True) monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None) nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000) nwSplit = getGuiItem("GuiDocSplit") assert isinstance(nwSplit, GuiDocSplit) nwSplit.show() qtbot.wait(50) # Populate List # ============= nwSplit.listBox.clear() assert nwSplit.listBox.count() == 0 # No item selected nwSplit.sourceItem = None nwGUI.projView.projTree.clearSelection() assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Non-existing item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) nwSplit.sourceItem = None nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hToSplit).setSelected(True) assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Select a non-file nwSplit.sourceItem = None nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True) assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Error when reading documents with monkeypatch.context() as mp: mp.setattr(NWDoc, "readDocument", lambda *a: None) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Read properly, and check split levels # Level 1 nwSplit.splitLevel.setCurrentIndex(0) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 1 # Level 2 nwSplit.splitLevel.setCurrentIndex(1) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 2 # Level 3 nwSplit.splitLevel.setCurrentIndex(2) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 6 # Level 4 nwSplit.splitLevel.setCurrentIndex(3) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 7 # Split Document # ============== # Test a proper split first with monkeypatch.context() as mp: mp.setattr(GuiDocSplit, "_doClose", lambda *a: None) assert nwSplit._doSplit() is True assert nwGUI.saveProject() assert readFile(os.path.join( contentDir, hPartition + ".nwd")) == ("%%%%~name: Nantucket\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hPartition, tPartition) assert readFile(os.path.join( contentDir, hChapterOne + ".nwd")) == ("%%%%~name: Chapter One\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hChapterOne, tChapterOne) assert readFile(os.path.join( contentDir, hSceneOne + ".nwd")) == ("%%%%~name: Scene One\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hSceneOne, tSceneOne) assert readFile(os.path.join( contentDir, hSceneTwo + ".nwd")) == ("%%%%~name: Scene Two\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hSceneTwo, tSceneTwo) assert readFile(os.path.join( contentDir, hSceneThree + ".nwd")) == ("%%%%~name: Scene Three\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hSceneThree, tSceneThree) assert readFile(os.path.join( contentDir, hSceneFour + ".nwd")) == ("%%%%~name: Scene Four\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hSceneFour, tSceneFour) assert readFile(os.path.join( contentDir, hSceneFive + ".nwd")) == ("%%%%~name: The End\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n") % (hNewFolder, hSceneFive, tSceneFive) # OS error with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert nwSplit._doSplit() is False # Select to not split with monkeypatch.context() as mp: mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No) assert nwSplit._doSplit() is False # Clear the list nwSplit.listBox.clear() assert nwSplit._doSplit() is False # Can't find sourcv item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) assert nwSplit._doSplit() is False # No source item set nwSplit.sourceItem = None assert nwSplit._doSplit() is False # Close nwSplit._doClose()
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): """Test config intialisation. """ tstConf = Config() confFile = os.path.join(tmpDir, "novelwriter.conf") testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") # Make sure we don't have any old conf file if os.path.isfile(confFile): os.unlink(confFile) # Let the config class figure out the path with monkeypatch.context() as mp: mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *a: fncDir) tstConf.verQtValue = 50600 tstConf.initConfig() assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert not os.path.isfile(confFile) tstConf.verQtValue = 50000 tstConf.initConfig() assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert not os.path.isfile(confFile) # Fail to make folders with monkeypatch.context() as mp: mp.setattr("os.mkdir", causeOSError) tstConfDir = os.path.join(fncDir, "test_conf") tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir) assert tstConf.confPath is None assert tstConf.dataPath == tmpDir assert not os.path.isfile(confFile) tstDataDir = os.path.join(fncDir, "test_data") tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath is None assert os.path.isfile(confFile) os.unlink(confFile) # Test load/save with no path tstConf.confPath = None assert tstConf.loadConfig() is False assert tstConf.saveConfig() is False # Run again and set the paths directly and correctly # This should create a config file as well with monkeypatch.context() as mp: mp.setattr("os.path.expanduser", lambda *a: "") tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath == tmpDir assert os.path.isfile(confFile) copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang")) # Load and save with OSError with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert not tstConf.loadConfig() assert tstConf.hasError is True assert tstConf.errData != [] assert tstConf.getErrData().startswith("Could not") assert tstConf.hasError is False assert tstConf.errData == [] assert not tstConf.saveConfig() assert tstConf.hasError is True assert tstConf.errData != [] assert tstConf.getErrData().startswith("Could not") assert tstConf.hasError is False assert tstConf.errData == [] # Check handling of novelWriter as a package with monkeypatch.context() as mp: tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath == tmpDir appRoot = tstConf.appRoot mp.setattr("os.path.isfile", lambda *a: True) tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath == tmpDir assert tstConf.appRoot == os.path.dirname(appRoot) assert tstConf.appPath == os.path.dirname(appRoot) assert tstConf.loadConfig() is True assert tstConf.saveConfig() is True # Test Correcting Quote Settings origDbl = tstConf.fmtDoubleQuotes origSng = tstConf.fmtSingleQuotes orDoDbl = tstConf.doReplaceDQuote orDoSng = tstConf.doReplaceSQuote tstConf.fmtDoubleQuotes = ["\"", "\""] tstConf.fmtSingleQuotes = ["'", "'"] tstConf.doReplaceDQuote = True tstConf.doReplaceSQuote = True assert tstConf.saveConfig() is True assert tstConf.loadConfig() is True assert tstConf.doReplaceDQuote is False assert tstConf.doReplaceSQuote is False tstConf.fmtDoubleQuotes = origDbl tstConf.fmtSingleQuotes = origSng tstConf.doReplaceDQuote = orDoDbl tstConf.doReplaceSQuote = orDoSng assert tstConf.saveConfig() is True # Test Correcting icon theme origIcons = tstConf.guiIcons tstConf.guiIcons = "typicons_colour_dark" assert tstConf.saveConfig() is True assert tstConf.loadConfig() is True assert tstConf.guiIcons == "typicons_dark" tstConf.guiIcons = "typicons_grey_dark" assert tstConf.saveConfig() is True assert tstConf.loadConfig() is True assert tstConf.guiIcons == "typicons_dark" tstConf.guiIcons = "typicons_colour_light" assert tstConf.saveConfig() is True assert tstConf.loadConfig() is True assert tstConf.guiIcons == "typicons_light" tstConf.guiIcons = "typicons_grey_light" assert tstConf.saveConfig() is True assert tstConf.loadConfig() is True assert tstConf.guiIcons == "typicons_light" tstConf.guiIcons = origIcons assert tstConf.saveConfig() # Localisation # ============ i18nDir = os.path.join(fncDir, "i18n") os.mkdir(i18nDir) os.mkdir(os.path.join(i18nDir, "stuff")) tstConf.nwLangPath = i18nDir copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_en_GB.qm")) writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "") writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "") tstApp = MockApp() tstConf.initLocalisation(tstApp) # Check Lists theList = tstConf.listLanguages(tstConf.LANG_NW) assert theList == [("en_GB", "British English")] theList = tstConf.listLanguages(tstConf.LANG_PROJ) assert theList == [("en_GB", "British English")] theList = tstConf.listLanguages(None) assert theList == [] # Add Language copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_fr.qm")) writeFile(os.path.join(i18nDir, "nw_fr.ts"), "") theList = tstConf.listLanguages(tstConf.LANG_NW) assert theList == [("en_GB", "British English"), ("fr", "Français")] copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test navigating the novel tree. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) buildTestProject(nwGUI, fncProj) nwGUI.switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) writeFile( os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"), "# Jane Doe\n\n@tag: Jane\n\n" ) writeFile( os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"), ( "### Scene One\n\n" "@pov: Jane\n" "@focus: Jane\n\n" "% Synopsis: This is a scene." ) ) novelView = nwGUI.novelView novelTree = novelView.novelTree novelBar = novelView.novelBar # Show/Hide Scrollbars # ==================== nwGUI.mainConf.hideVScroll = True nwGUI.mainConf.hideHScroll = True novelView.initSettings() assert not novelTree.verticalScrollBar().isVisible() assert not novelTree.horizontalScrollBar().isVisible() nwGUI.mainConf.hideVScroll = False nwGUI.mainConf.hideHScroll = False novelView.initSettings() assert novelTree.verticalScrollBar().isEnabled() assert novelTree.horizontalScrollBar().isEnabled() # Populate Tree # ============= nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView) nwGUI.rebuildIndex() novelTree._populateTree(rootHandle=None) assert novelTree.topLevelItemCount() == 3 # Rebuild should preserve selection topItem = novelTree.topLevelItem(0) assert not topItem.isSelected() topItem.setSelected(True) assert novelTree.selectedItems()[0] == topItem assert novelView.getSelectedHandle() == ("000000000000c", 0) # Refresh using the slot for the butoom novelBar._refreshNovelTree() assert novelTree.topLevelItem(0).isSelected() # Open Items # ========== # Clear selection novelTree.clearSelection() scItem = novelTree.topLevelItem(2) scItem.setSelected(True) assert scItem.isSelected() # Clear selection with mouse vPort = novelTree.viewport() qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10) assert not scItem.isSelected() # Double-click item scItem.setSelected(True) assert scItem.isSelected() assert nwGUI.docEditor.docHandle() is None novelTree._treeDoubleClick(scItem, 0) assert nwGUI.docEditor.docHandle() == "000000000000f" # Open item with middle mouse button scItem.setSelected(True) assert scItem.isSelected() assert nwGUI.docViewer.docHandle() is None qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10) assert nwGUI.docViewer.docHandle() is None scRect = novelTree.visualItemRect(scItem) oldData = scItem.data(novelTree.C_TITLE, novelTree.D_HANDLE) scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, None) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() is None scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, oldData) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() == "000000000000f" # Last Column # =========== novelBar.setLastColType(NovelTreeColumn.HIDDEN) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True assert novelTree.lastColType == NovelTreeColumn.HIDDEN assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "") novelBar.setLastColType(NovelTreeColumn.POV) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.POV assert novelTree._getLastColumnText("000000000000f", "T000001") == ( "Jane", "Point of View: Jane" ) novelBar.setLastColType(NovelTreeColumn.FOCUS) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.FOCUS assert novelTree._getLastColumnText("000000000000f", "T000001") == ( "Jane", "Focus: Jane" ) novelBar.setLastColType(NovelTreeColumn.PLOT) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.PLOT assert novelTree._getLastColumnText("000000000000f", "T000001") == ( "", "Plot: " ) novelTree._lastCol = None assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "") # Item Meta # ========= ttText = "" def showText(pos, text): nonlocal ttText ttText = text mIndex = novelTree.model().index(2, novelTree.C_MORE) with monkeypatch.context() as mp: mp.setattr(QToolTip, "showText", showText) novelTree._treeItemClicked(mIndex) assert ttText == ( "<p><b>Point of View</b>: Jane<br><b>Focus</b>: Jane</p>" "<p><b>Synopsis</b>: This is a scene.</p>" ) # Other Checks # ============ scItem = novelTree.topLevelItem(2) scItem.setSelected(True) assert scItem.isSelected() novelTree.focusOutEvent(QFocusEvent(QEvent.None_, Qt.MouseFocusReason)) assert not scItem.isSelected() # Close # ===== # qtbot.stop() nwGUI.closeProject()
def startSearch(status_passed): """ Invokes the search on the file. Central bit of the core - examines the source file byte by byte and checks against the provided start sequences of files (provided by the signatures). Ones, a start sequence has been found, the further actions depend on the type of signature (in fact, how the end of the file is identified - by end sequence, file size info inside file or manual by additional module). The status object is constantly updated. The frequency of updating the status instance with progress within a source file depends on the value in the settings instance ( ExecutionSettings.ExecutionSettings.output_frequency) In fact, this variable says how often a message shall be sent to the status object in total for the current source file. @param status_passed: Reference to the status instance for applying runtime information and gaining settings for the running. @type status_passed: ExecutionSettings.ExecutionStatus return: Active Signatures; Overall Counter rtype: C{List} of C{Signatures}; C{int} """ global binfile, start, skipped, size, maxlength global status status= status_passed st = binfile.read(maxlength-1) dx = size / status.settings.output_frequency # for user output only x = dx # same here file_pos = binfile.tell() status.startedOneSourceFile(size) while 1: c = '' if file_pos < status.file_end: c = binfile.read(1) file_pos = binfile.tell() if c!='': # end of file st += c if file_pos-status.file_start >= x: status.updateFineshedForCurrent(file_pos-status.file_start) if status.settings.output_level == 0: pass elif status.settings.output_level == 3 and size!=0: print "Pos: 0x%x - %d / %d KB (%d %%)" %(file_pos, (file_pos-status.file_start) / 1024 , size / 1024, (file_pos-status.file_start)*100/size) elif status.settings.output_level == 2: print "%d %%" %((file_pos-status.file_start)*100/size) elif status.settings.output_level == 1: print '#' , x += dx for sig in status.settings.signatures: if start[sig[signatures.name]] == -1: if sig[signatures.start_seq][0] == ord(st[0]): if checkString(st, sig[signatures.start_seq]): start_pos = file_pos-len(st) if status.settings.output_level == 3: print ('Found start at 0x%x for %s' %(start_pos, sig[signatures.description])) if sig[signatures.filesize_type] == signatures.TYPE_FILE_SIZE: offsets = sig[signatures.filesize_address_offsets] ofs = 0 for i in offsets: binfile.seek(start_pos + i) val = ord(binfile.read(1)) ofs = ofs * 256 + val end_pos = start_pos + ofs correction = sig[signatures.filesize_info_correction] end_pos = end_pos + correction writeFile(sig[signatures.name],status.counterr[sig[signatures.name]]+status.settings.counterstart_global, sig[signatures.extension],binfile, start_pos, end_pos-1, status.settings.dest_folder, status.settings.output_level == 3, status) status.counter[sig[signatures.name]] += 1 status.counterr[sig[signatures.name]] += 1 status.foundFile() binfile.seek(file_pos) elif sig[signatures.filesize_type] == signatures.TYPE_MANUAL: function = sig[signatures.filesizemanual_functionname] if status.settings.output_level == 3: print ('-- Enter signature defined function for end address determination for this file') end_address = function(binfile, start_pos, status.settings.output_level == 3) if (end_address < start_pos): if status.settings.output_level == 3: print ('-- No valid end address found - skip this file.') continue writeFile(sig[signatures.name],status.counterr[sig[signatures.name]]+status.settings.counterstart_global, sig[signatures.extension],binfile, start_pos, end_address, status.settings.dest_folder, status.settings.output_level == 3, status) status.counter[sig[signatures.name]] += 1 status.counterr[sig[signatures.name]] += 1 status.foundFile() binfile.seek(file_pos) else: start[sig[signatures.name]] = start_pos else: if file_pos < start[sig[signatures.name]] + len(sig[signatures.start_seq]): continue if sig[signatures.end_seq][0] == ord(st[0]): if tools.checkString(st, sig[signatures.end_seq]): end_pos = file_pos-len(st)+len(sig[signatures.end_seq])-1 if skipped[sig[signatures.name]] < sig[signatures.skip_end_seqs]: skipped[sig[signatures.name]] +=1 if status.settings.output_level == 3: print ('Found end at 0x%x for %s - skipped' %(end_pos, sig[signatures.description])) continue if status.settings.output_level == 3: print ('Found end at 0x%x for %s' %(end_pos, sig[signatures.description])) writeFile(sig[signatures.name],status.counterr[sig[signatures.name]]+status.settings.counterstart_global, sig[signatures.extension],binfile, start[sig[signatures.name]], end_pos, status.settings.dest_folder, status.settings.output_level == 3, status) start[sig[signatures.name]] = -1 status.counter[sig[signatures.name]] += 1 status.counterr[sig[signatures.name]] += 1 status.foundFile() skipped[sig[signatures.name]] = 0 if len(st)==1: break st = st[1:] status.finishedOneSourceFile() binfile.close() return status.settings.signatures, status.counterr
def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir): """Test loading and saving from the OptionState class. """ theProject = NWProject(mockGUI) theOpts = OptionState(theProject) # Write a test file optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE) writeFile(optFile, json.dumps({ "GuiBuildNovel": { "winWidth": 1000, "winHeight": 700, "addNovel": True, "addNotes": False, "textFont": "Cantarell", "mockItem": None, }, "MockGroup": { "mockItem": None, }, })) # Load and save with no path set theProject.projMeta = None assert not theOpts.loadSettings() assert not theOpts.saveSettings() # Set path theProject.projMeta = tmpDir assert theProject.projMeta == tmpDir # Cause open() to fail with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert not theOpts.loadSettings() assert not theOpts.saveSettings() # Load proper assert theOpts.loadSettings() # Check that unwanted items have been removed assert theOpts._theState == { "GuiBuildNovel": { "winWidth": 1000, "winHeight": 700, "addNovel": True, "addNotes": False, "textFont": "Cantarell", }, } # Save proper assert theOpts.saveSettings() # Load again to check we get the values back assert theOpts.loadSettings() assert theOpts._theState == { "GuiBuildNovel": { "winWidth": 1000, "winHeight": 700, "addNovel": True, "addNotes": False, "textFont": "Cantarell", }, }
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): """Test config intialisation. """ tstConf = Config() confFile = os.path.join(tmpDir, "novelwriter.conf") testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") # Make sure we don't have any old conf file if os.path.isfile(confFile): os.unlink(confFile) # Let the config class figure out the path with monkeypatch.context() as mp: mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir) tstConf.verQtValue = 50600 tstConf.initConfig() assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert not os.path.isfile(confFile) tstConf.verQtValue = 50000 tstConf.initConfig() assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert not os.path.isfile(confFile) # Fail to make folders with monkeypatch.context() as mp: mp.setattr("os.mkdir", causeOSError) tstConfDir = os.path.join(fncDir, "test_conf") tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir) assert tstConf.confPath is None assert tstConf.dataPath == tmpDir assert not os.path.isfile(confFile) tstDataDir = os.path.join(fncDir, "test_data") tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath is None assert os.path.isfile(confFile) os.unlink(confFile) # Test load/save with no path tstConf.confPath = None assert not tstConf.loadConfig() assert not tstConf.saveConfig() # Run again and set the paths directly and correctly # This should create a config file as well with monkeypatch.context() as mp: mp.setattr("os.path.expanduser", lambda *args: "") tstConf.spellTool = nwConst.SP_INTERNAL tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf.confPath == tmpDir assert tstConf.dataPath == tmpDir assert os.path.isfile(confFile) copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9, 10]) # Load and save with OSError with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert not tstConf.loadConfig() assert tstConf.hasError is True assert tstConf.errData != [] assert tstConf.getErrData().startswith("Could not") assert tstConf.hasError is False assert tstConf.errData == [] assert not tstConf.saveConfig() assert tstConf.hasError is True assert tstConf.errData != [] assert tstConf.getErrData().startswith("Could not") assert tstConf.hasError is False assert tstConf.errData == [] assert tstConf.loadConfig() assert tstConf.saveConfig() # Test Correcting Quote Settings origDbl = tstConf.fmtDoubleQuotes origSng = tstConf.fmtSingleQuotes orDoDbl = tstConf.doReplaceDQuote orDoSng = tstConf.doReplaceSQuote tstConf.fmtDoubleQuotes = ["\"", "\""] tstConf.fmtSingleQuotes = ["'", "'"] tstConf.doReplaceDQuote = True tstConf.doReplaceSQuote = True assert tstConf.saveConfig() assert tstConf.loadConfig() assert not tstConf.doReplaceDQuote assert not tstConf.doReplaceSQuote tstConf.fmtDoubleQuotes = origDbl tstConf.fmtSingleQuotes = origSng tstConf.doReplaceDQuote = orDoDbl tstConf.doReplaceSQuote = orDoSng assert tstConf.saveConfig() # Localisation i18nDir = os.path.join(fncDir, "i18n") os.mkdir(i18nDir) os.mkdir(os.path.join(i18nDir, "stuff")) tstConf.nwLangPath = i18nDir copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(fncDir, "nw_en_GB.qm")) writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "") writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "") tstApp = DummyApp() tstConf.initLocalisation(tstApp) theList = tstConf.listLanguages(tstConf.LANG_NW) assert theList == [("en_GB", "British English")] copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9, 10])
def testCoreSpell_Enchant(monkeypatch, tmpDir): """Test the pyenchant spell checker """ wList = os.path.join(tmpDir, "wordlist.txt") writeFile(wList, "a_word\nb_word\nc_word\n") # Block the enchant package (and trigger the default class) with monkeypatch.context() as mp: mp.setitem(sys.modules, "enchant", None) spChk = NWSpellEnchant() spChk.setLanguage("en", wList) assert spChk.setLanguage("", "") is None assert spChk.checkWord("") is True assert spChk.suggestWords("") == [] assert spChk.listDictionaries() == [] assert spChk.describeDict() == ("", "") # Break the enchant package, and check error handling spChk = NWSpellEnchant() spChk.theDict = None assert spChk.checkWord("word") is True assert spChk.suggestWords("word") == [] assert spChk.addWord("word") is False # Load the proper enchant package (twice) spChk = NWSpellEnchant() spChk.setLanguage("en", wList) spChk.setLanguage("en", wList) # Add a word to the user's dictionary assert spChk._readProjectDictionary("stuff") is False with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert spChk._readProjectDictionary(wList) is False assert spChk._readProjectDictionary(None) is False assert spChk._readProjectDictionary(wList) is True assert spChk._projectDict == wList # Cannot write to file with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert spChk.addWord("d_word") is False assert readFile(wList) == "a_word\nb_word\nc_word\n" assert spChk.addWord("d_word") is True assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n" assert spChk.addWord("d_word") is False # Check words assert spChk.checkWord("a_word") is True assert spChk.checkWord("b_word") is True assert spChk.checkWord("c_word") is True assert spChk.checkWord("d_word") is True assert spChk.checkWord("e_word") is False spChk.addWord("d_word") assert spChk.checkWord("d_word") is True wSuggest = spChk.suggestWords("wrod") assert len(wSuggest) > 0 assert "word" in wSuggest dList = spChk.listDictionaries() assert len(dList) > 0 aTag, aName = spChk.describeDict() assert aTag == "en" assert aName != ""
def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf): """Test the fallback simple spell checker """ wList = os.path.join(tmpDir, "wordlist.txt") wDict = os.path.join(tmpDir, "en.dict") writeFile(wList, "a_word\nb_word\nc_word\n") writeFile(wDict, "# Comment\ne_word\nf_word\ng_word\n") spChk = NWSpellSimple() spChk.mainConf = tmpConf spChk.mainConf.dictPath = tmpDir # Load dictionary, but fail monkeypatch.setattr("builtins.open", causeOSError) spChk.setLanguage("en", wList) assert spChk.spellLanguage is None assert spChk.WORDS == spChk.projDict monkeypatch.undo() # Load dictionary properly spChk.setLanguage("en", wList) assert spChk.projDict == ["a_word", "b_word", "c_word"] assert spChk.WORDS == [ "e_word", "f_word", "g_word", "a_word", "b_word", "c_word" ] # Check words assert spChk.checkWord("a_word") assert spChk.checkWord("b_word") assert spChk.checkWord("c_word") assert not spChk.checkWord("d_word") assert spChk.checkWord("e_word") assert spChk.checkWord("f_word") assert spChk.checkWord("g_word") # Add word spChk.addWord("d_word") assert spChk.checkWord("d_word") # Check spelling assert spChk.suggestWords(" \t") == [] wSuggest = spChk.suggestWords("d_wrod") assert len(wSuggest) > 0 assert "d_word" in wSuggest # Break the matching monkeypatch.setattr("difflib.get_close_matches", lambda *args, **kwargs: [""]) assert spChk.suggestWords("word") == [] monkeypatch.undo() # Capitalisation wSuggest = spChk.suggestWords("D_wrod") assert len(wSuggest) > 0 assert "D_word" in wSuggest # List dictionaries assert spChk.listDictionaries() == [("en", "English [%s]" % nwConst.SP_INTERNAL)] # Description aTag, aName = spChk.describeDict() assert aTag == "en" assert aName == nwConst.SP_INTERNAL
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj): """Test the Insert menu. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) nwGUI.theProject.projTree.setSeed(42) assert nwGUI.newProject({"projPath": fncProj}) assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None assert nwGUI.openDocument("0e17daca5f3e1") is True nwGUI.docEditor.clear() # Test Faulty Inserts assert nwGUI.docEditor.insertText("hello world") assert nwGUI.docEditor.getText() == "hello world" nwGUI.docEditor.clear() assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) assert nwGUI.docEditor.isEmpty() assert not nwGUI.docEditor.insertText(None) assert nwGUI.docEditor.isEmpty() # qtbot.stopForInteraction() # Check Menu Entries nwGUI.mainMenu.aInsENDash.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_ENDASH nwGUI.docEditor.clear() nwGUI.mainMenu.aInsEMDash.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_EMDASH nwGUI.docEditor.clear() nwGUI.mainMenu.aInsHorBar.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_HBAR nwGUI.docEditor.clear() nwGUI.mainMenu.aInsFigDash.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_FGDASH nwGUI.docEditor.clear() nwGUI.mainMenu.aInsQuoteLS.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[0] nwGUI.docEditor.clear() nwGUI.mainMenu.aInsQuoteRS.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[1] nwGUI.docEditor.clear() nwGUI.mainMenu.aInsQuoteLD.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[0] nwGUI.docEditor.clear() nwGUI.mainMenu.aInsQuoteRD.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[1] nwGUI.docEditor.clear() nwGUI.mainMenu.aInsMSApos.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS nwGUI.docEditor.clear() nwGUI.mainMenu.aInsEllipsis.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_HELLIP nwGUI.docEditor.clear() nwGUI.mainMenu.aInsPrime.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_PRIME nwGUI.docEditor.clear() nwGUI.mainMenu.aInsDPrime.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_DPRIME nwGUI.docEditor.clear() nwGUI.mainMenu.aInsBullet.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_BULL nwGUI.docEditor.clear() nwGUI.mainMenu.aInsHyBull.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_HYBULL nwGUI.docEditor.clear() nwGUI.mainMenu.aInsFlower.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_FLOWER nwGUI.docEditor.clear() nwGUI.mainMenu.aInsPerMille.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_PERMIL nwGUI.docEditor.clear() nwGUI.mainMenu.aInsDegree.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_DEGREE nwGUI.docEditor.clear() nwGUI.mainMenu.aInsMinus.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_MINUS nwGUI.docEditor.clear() nwGUI.mainMenu.aInsTimes.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_TIMES nwGUI.docEditor.clear() nwGUI.mainMenu.aInsDivide.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_DIVIDE nwGUI.docEditor.clear() nwGUI.mainMenu.aInsNBSpace.activate(QAction.Trigger) if nwGUI.mainConf.verQtValue >= 50900: assert nwGUI.docEditor.getText() == nwUnicode.U_NBSP else: assert nwGUI.docEditor.getText() == " " nwGUI.docEditor.clear() nwGUI.mainMenu.aInsThinSpace.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == nwUnicode.U_THSP nwGUI.docEditor.clear() nwGUI.mainMenu.aInsThinNBSpace.activate(QAction.Trigger) if nwGUI.mainConf.verQtValue >= 50900: assert nwGUI.docEditor.getText() == nwUnicode.U_THNBSP else: assert nwGUI.docEditor.getText() == " " nwGUI.docEditor.clear() ## # Insert Keywords ## nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.TAG_KEY][0].activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TAG_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.POV_KEY][0].activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.POV_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.FOCUS_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.FOCUS_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.CHAR_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CHAR_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.PLOT_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.PLOT_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.TIME_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TIME_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.WORLD_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.WORLD_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.OBJECT_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.OBJECT_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.ENTITY_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.ENTITY_KEY nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.CUSTOM_KEY][0].activate( QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CUSTOM_KEY # Faulty Keyword Inserts assert not nwGUI.docEditor.insertKeyWord("blabla") with monkeypatch.context() as mp: mp.setattr(QTextBlock, "isValid", lambda *a, **k: False) assert not nwGUI.docEditor.insertKeyWord(nwKeyWords.TAG_KEY) nwGUI.docEditor.clear() ## # Insert Break or Space ## nwGUI.docEditor.setText("### Stuff\n") nwGUI.mainMenu.aInsNewPage.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "[NEW PAGE]\n### Stuff\n" nwGUI.docEditor.setText("### Stuff\n") nwGUI.mainMenu.aInsVSpaceS.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "[VSPACE]\n### Stuff\n" nwGUI.docEditor.setText("### Stuff\n") nwGUI.mainMenu.aInsVSpaceM.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "[VSPACE:2]\n### Stuff\n" nwGUI.docEditor.clear() ## # Insert text from file ## nwGUI.closeDocument() # First, with no path monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: ("", "")) assert not nwGUI.importDocument() # Then with a path, but an invalid one monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (" ", "")) assert not nwGUI.importDocument() # Then a valid path, but bot a file that exists theFile = os.path.join(fncDir, "import.txt") monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (theFile, "")) assert not nwGUI.importDocument() # Create the file and try again, but with no target document open writeFile(theFile, "Foo") assert not nwGUI.importDocument() # Open the document from before, and add some text to it nwGUI.openDocument("0e17daca5f3e1") nwGUI.docEditor.setText("Bar") assert nwGUI.docEditor.getText() == "Bar" # The document isn't empty, so the message box should pop monkeypatch.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No) assert not nwGUI.importDocument() assert nwGUI.docEditor.getText() == "Bar" # Finally, accept the replaced text, this time we use the menu entry to trigger it monkeypatch.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.Yes) nwGUI.mainMenu.aImportFile.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "Foo" ## # Reveal file location ## theMessage = "" def recordMsg(*args): nonlocal theMessage theMessage = args[3] return None assert not theMessage monkeypatch.setattr(QMessageBox, "information", recordMsg) nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger) theBits = theMessage.split("<br>") assert len(theBits) == 2 assert theBits[0] == "The currently open file is saved in:" assert theBits[1] == os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal): """Test loading and saving a document with the NWDoc class. """ theProject = NWProject(mockGUI) assert theProject.openProject(nwMinimal) is True assert theProject.projPath == nwMinimal sHandle = "8c659a11cd429" # Read Document # ============= # Not a valid handle theDoc = NWDoc(theProject, "stuff") assert bool(theDoc) is False assert theDoc.readDocument() is None # Non-existent handle theDoc = NWDoc(theProject, "0000000000000") assert theDoc.readDocument() is None assert theDoc._currHash is None # Cause open() to fail while loading with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) theDoc = NWDoc(theProject, sHandle) assert theDoc.readDocument() is None assert theDoc.getError() == "OSError: Mock OSError" # Load the text theDoc = NWDoc(theProject, sHandle) assert theDoc.readDocument() == "### New Scene\n\n" # Try to open a new (non-existent) file nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL) assert nHandle is not None xHandle = theProject.newFile("New File", nwItemClass.NOVEL, nHandle) theDoc = NWDoc(theProject, xHandle) assert bool(theDoc) is True assert repr(theDoc) == f"<NWDoc handle={xHandle}>" assert theDoc.readDocument() == "" # Write Document # ============== # Set handle and save again theText = "### Test File\n\nText ...\n\n" theDoc = NWDoc(theProject, xHandle) assert theDoc.readDocument(xHandle) == "" assert theDoc.writeDocument(theText) is True # Save again to ensure temp file and previous file is handled assert theDoc.writeDocument(theText) # Check file content docPath = os.path.join(nwMinimal, "content", xHandle + ".nwd") assert readFile(docPath) == ("%%~name: New File\n" f"%%~path: a508bb932959c/{xHandle}\n" "%%~kind: NOVEL/DOCUMENT\n" "### Test File\n\n" "Text ...\n\n") # Alter the document on disk and save again writeFile(docPath, "blablabla") assert theDoc.writeDocument(theText) is False # Force the overwrite assert theDoc.writeDocument(theText, forceWrite=True) is True # Force no meta data theDoc._theItem = None assert theDoc.writeDocument(theText) is True assert readFile(docPath) == theText # Cause open() to fail while saving with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert theDoc.writeDocument(theText) is False assert theDoc.getError() == "OSError: Mock OSError" theDoc._docError = "" assert theDoc.getError() == "" # Cause os.replace() to fail while saving with monkeypatch.context() as mp: mp.setattr("os.replace", causeOSError) assert theDoc.writeDocument(theText) is False assert theDoc.getError() == "OSError: Mock OSError" theDoc._docError = "" assert theDoc.getError() == "" # Saving with no handle theDoc._docHandle = None assert theDoc.writeDocument(theText) is False # Delete Document # =============== # Delete the last document theDoc = NWDoc(theProject, "stuff") assert theDoc.deleteDocument() is False assert os.path.isfile(docPath) # Cause the delete to fail with monkeypatch.context() as mp: mp.setattr("os.unlink", causeOSError) theDoc = NWDoc(theProject, xHandle) assert theDoc.deleteDocument() is False assert theDoc.getError() == "OSError: Mock OSError" # Make the delete pass theDoc = NWDoc(theProject, xHandle) assert theDoc.deleteDocument() is True assert not os.path.isfile(docPath)
def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir): """Test the functins that handle legacy data folders and structure with additional tests of failure handling. """ theProject = NWProject(dummyGUI) theProject.setProjectPath(fncDir) # assert theProject.newProject({"projPath": fncDir}) # assert theProject.saveProject() # assert theProject.closeProject() # Check behaviour of deprecated files function on OSError tstFile = os.path.join(fncDir, "ToC.json") writeFile(tstFile, "dummy") assert os.path.isfile(tstFile) monkeypatch.setattr("os.unlink", causeOSError) assert not theProject._deprecatedFiles() monkeypatch.undo() assert theProject._deprecatedFiles() assert not os.path.isfile(tstFile) # Check processing non-folders tstFile = os.path.join(fncDir, "data_0") writeFile(tstFile, "dummy") assert os.path.isfile(tstFile) errList = [] errList = theProject._legacyDataFolder(tstFile, errList) assert len(errList) > 0 # Move folder in data folder, shouldn't be there tstData = os.path.join(fncDir, "data_1") errItem = os.path.join(fncDir, "data_1", "stuff") os.mkdir(tstData) os.mkdir(errItem) assert os.path.isdir(tstData) assert os.path.isdir(errItem) # This causes a failure to create the 'junk' folder monkeypatch.setattr("os.mkdir", causeOSError) errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) > 0 monkeypatch.undo() # This causes a failure to move 'stuff' to 'junk' monkeypatch.setattr("os.rename", causeOSError) errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) > 0 monkeypatch.undo() # This should be successful errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) == 0 assert os.path.isdir(os.path.join(fncDir, "junk", "stuff")) # Check renaming/deleting of old document files tstData = os.path.join(fncDir, "data_2") tstDoc1m = os.path.join(tstData, "000000000001_main.nwd") tstDoc1b = os.path.join(tstData, "000000000001_main.bak") tstDoc2m = os.path.join(tstData, "000000000002_main.nwd") tstDoc2b = os.path.join(tstData, "000000000002_main.bak") tstDoc3m = os.path.join(tstData, "tooshort003_main.nwd") tstDoc3b = os.path.join(tstData, "tooshort003_main.bak") os.mkdir(tstData) writeFile(tstDoc1m, "dummy") writeFile(tstDoc1b, "dummy") writeFile(tstDoc2m, "dummy") writeFile(tstDoc2b, "dummy") writeFile(tstDoc3m, "dummy") writeFile(tstDoc3b, "dummy") # Make the above fail monkeypatch.setattr("os.rename", causeOSError) monkeypatch.setattr("os.unlink", causeOSError) errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) > 0 assert os.path.isfile(tstDoc1m) assert os.path.isfile(tstDoc1b) assert os.path.isfile(tstDoc2m) assert os.path.isfile(tstDoc2b) assert os.path.isfile(tstDoc3m) assert os.path.isfile(tstDoc3b) monkeypatch.undo() # And succeed ... errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) == 0 assert not os.path.isdir(tstData) assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd")) assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd")) assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.nwd")) assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.bak"))
def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI): """Test opening a project. """ theProject = NWProject(dummyGUI) # Rename the project file to check handling rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE) wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE + "_sdfghj") os.rename(rName, wName) assert theProject.openProject(nwMinimal) is False os.rename(wName, rName) # Fail on folder structure check monkeypatch.setattr("os.mkdir", causeOSError) assert theProject.openProject(nwMinimal) is False monkeypatch.undo() # Fail on lock file theProject.setProjectPath(nwMinimal) assert theProject._writeLockFile() assert theProject.openProject(nwMinimal) is False # Fail to read lockfile (which still opens the project) monkeypatch.setattr("builtins.open", causeOSError) assert theProject.openProject(nwMinimal) is True monkeypatch.undo() assert theProject.closeProject() # Force open with lockfile theProject.setProjectPath(nwMinimal) assert theProject._writeLockFile() assert theProject.openProject(nwMinimal, overrideLock=True) is True assert theProject.closeProject() # Make a junk XML file oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3] + "orig") bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3] + "bak") os.rename(rName, oName) writeFile(rName, "dummy") assert theProject.openProject(nwMinimal) is False # Also write a jun XML backup file writeFile(bName, "dummy") assert theProject.openProject(nwMinimal) is False # Wrong root item writeFile(rName, "<not_novelWriterXML></not_novelWriterXML>\n") assert theProject.openProject(nwMinimal) is False # Wrong file version writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0x01000000\" " "fileVersion=\"1.0\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "</novelWriterXML>\n")) dummyGUI.askResponse = False assert theProject.openProject(nwMinimal) is False dummyGUI.undo() # Future file version writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0x01000000\" " "fileVersion=\"99.99\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "</novelWriterXML>\n")) assert theProject.openProject(nwMinimal) is False # Larger hex version writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0xffffffff\" " "fileVersion=\"1.2\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "</novelWriterXML>\n")) dummyGUI.askResponse = False assert theProject.openProject(nwMinimal) is False dummyGUI.undo() # Test skipping XML entries writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0x01000000\" " "fileVersion=\"1.2\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "<project><stuff/></project>\n" "<settings><stuff/></settings>\n" "</novelWriterXML>\n")) assert theProject.openProject(nwMinimal) is True assert theProject.closeProject() # Test deprecated XML entries writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0x01000000\" " "fileVersion=\"1.2\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "<settings>\n" "<autoReplace>\n" "<A>B</A>\n" "</autoReplace>\n" "</settings>\n" "</novelWriterXML>\n")) assert theProject.openProject(nwMinimal) is True assert theProject.autoReplace == {"A": "B"} assert theProject.closeProject() # Clean up XML files os.unlink(rName) os.unlink(bName) os.rename(oName, rName) # Add some legacy stuff that cannot be removed writeFile(os.path.join(nwMinimal, "junk"), "dummy") os.mkdir(os.path.join(nwMinimal, "data_0")) writeFile(os.path.join(nwMinimal, "data_0", "junk"), "dummy") dummyGUI.clear() assert theProject.openProject(nwMinimal) is True assert "data_0" in dummyGUI.lastAlert assert theProject.closeProject()
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): """Test navigating the novel tree. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) nwGUI.openProject(nwMinimal) nwGUI.theProject.projTree.setSeed(42) nwTree = nwGUI.novelView ## # Show/Hide Scrollbars ## nwTree.mainConf.hideVScroll = True nwTree.mainConf.hideHScroll = True nwTree.initTree() assert not nwTree.verticalScrollBar().isVisible() assert not nwTree.horizontalScrollBar().isVisible() nwTree.mainConf.hideVScroll = False nwTree.mainConf.hideHScroll = False nwTree.initTree() assert nwTree.verticalScrollBar().isEnabled() assert nwTree.horizontalScrollBar().isEnabled() ## # Populate Tree ## nwGUI.projTabs.setCurrentIndex(nwGUI.idxNovelView) nwGUI.rebuildIndex() nwTree._populateTree() assert nwTree.topLevelItemCount() == 1 # Rebuild should preserve selection topItem = nwTree.topLevelItem(0) assert not topItem.isSelected() topItem.setSelected(True) assert nwTree.selectedItems()[0] == topItem assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0) nwTree.refreshTree() assert nwTree.topLevelItem(0).isSelected() ## # Open Items ## # Clear selection nwTree.clearSelection() scItem = nwTree.topLevelItem(0).child(0).child(0) scItem.setSelected(True) assert scItem.isSelected() # Clear selection with mouse vPort = nwTree.viewport() qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10) assert not scItem.isSelected() # Double-click item scItem.setSelected(True) assert scItem.isSelected() assert nwGUI.docEditor.docHandle() is None nwTree._treeDoubleClick(scItem, 0) assert nwGUI.docEditor.docHandle() == "8c659a11cd429" # Open item with middle mouse button scItem.setSelected(True) assert scItem.isSelected() assert nwGUI.docViewer.docHandle() is None qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10) assert nwGUI.docViewer.docHandle() is None scRect = nwTree.visualItemRect(scItem) oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole) scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", "")) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() is None scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() == "8c659a11cd429" ## # Populate Tree ## # Add weird titles to first file to check hnadling of non-standard # order of title levels. writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), ("#### Section wo/Scene\n\n" "### Scene wo/Chapter\n\n" "## Chapter wo/Title\n\n" "# Title\n\n" "#### Section w/Title, wo/Scene\n\n" "### Scene w/Title, wo/Chapter\n\n" "## Chapter\n\n" "#### Section w/Chapter, wo/Scene\n\n" "### Scene\n\n" "#### Section\n\n")) nwGUI.rebuildIndex() nwTree._populateTree() assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene" assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter" assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title" assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title" tTitle = nwTree.topLevelItem(3) assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene" assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter" assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter" tChap = tTitle.child(2) assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene" assert tChap.child(1).text(nwTree.C_TITLE) == "Scene" tScene = tChap.child(1) assert tScene.child(0).text(nwTree.C_TITLE) == "Section" ## # Close ## # qtbot.stopForInteraction() nwGUI.closeProject()
def testBaseCommon_NWConfigParser(fncDir): """Test the NWConfigParser subclass. """ tstConf = os.path.join(fncDir, "test.cfg") writeFile(tstConf, ("[main]\n" "stropt = value\n" "intopt1 = 42\n" "intopt2 = 42.43\n" "boolopt1 = true\n" "boolopt2 = TRUE\n" "boolopt3 = 1\n" "boolopt4 = 0\n" "list1 = a, b, c\n" "list2 = 17, 18, 19\n" "float1 = 4.2\n")) cfgParser = NWConfigParser() cfgParser.read(tstConf) # Readers # ======= # Read String assert cfgParser.rdStr("main", "stropt", "stuff") == "value" assert cfgParser.rdStr("main", "boolopt1", "stuff") == "true" assert cfgParser.rdStr("main", "intopt1", "stuff") == "42" assert cfgParser.rdStr("nope", "stropt", "stuff") == "stuff" assert cfgParser.rdStr("main", "blabla", "stuff") == "stuff" # Read Boolean assert cfgParser.rdBool("main", "boolopt1", None) is True assert cfgParser.rdBool("main", "boolopt2", None) is True assert cfgParser.rdBool("main", "boolopt3", None) is True assert cfgParser.rdBool("main", "boolopt4", None) is False assert cfgParser.rdBool("main", "intopt1", None) is None assert cfgParser.rdBool("nope", "boolopt1", None) is None assert cfgParser.rdBool("main", "blabla", None) is None # Read Integer assert cfgParser.rdInt("main", "intopt1", 13) == 42 assert cfgParser.rdInt("main", "intopt2", 13) == 13 assert cfgParser.rdInt("main", "stropt", 13) == 13 assert cfgParser.rdInt("nope", "intopt1", 13) == 13 assert cfgParser.rdInt("main", "blabla", 13) == 13 # Read Float assert cfgParser.rdFlt("main", "intopt1", 13.0) == 42.0 assert cfgParser.rdFlt("main", "float1", 13.0) == 4.2 assert cfgParser.rdInt("main", "stropt", 13.0) == 13.0 assert cfgParser.rdInt("nope", "intopt1", 13.0) == 13.0 assert cfgParser.rdInt("main", "blabla", 13.0) == 13.0 # Read String List assert cfgParser.rdStrList("main", "list1", []) == [] assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"] assert cfgParser.rdStrList("main", "list1", ["x", "y"]) == ["a", "b"] assert cfgParser.rdStrList("main", "list1", ["x", "y", "z"]) == ["a", "b", "c"] assert cfgParser.rdStrList("main", "list1", ["x", "y", "z", "w"]) == ["a", "b", "c", "w"] assert cfgParser.rdStrList("main", "stropt", ["x"]) == ["value"] assert cfgParser.rdStrList("main", "intopt1", ["x"]) == ["42"] assert cfgParser.rdStrList("nope", "list1", ["x"]) == ["x"] assert cfgParser.rdStrList("main", "blabla", ["x"]) == ["x"] # Read Integer List assert cfgParser.rdIntList("main", "list2", []) == [] assert cfgParser.rdIntList("main", "list2", [1]) == [17] assert cfgParser.rdIntList("main", "list2", [1, 2]) == [17, 18] assert cfgParser.rdIntList("main", "list2", [1, 2, 3]) == [17, 18, 19] assert cfgParser.rdIntList("main", "list2", [1, 2, 3, 4]) == [17, 18, 19, 4] assert cfgParser.rdIntList("main", "stropt", [1]) == [1] assert cfgParser.rdIntList("main", "boolopt1", [1]) == [1] assert cfgParser.rdIntList("nope", "list2", [1]) == [1] assert cfgParser.rdIntList("main", "blabla", [1]) == [1] # Internal # ======== assert cfgParser._parseLine("main", "stropt", None, 999) is None
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test the merge documents tool. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) # Create a new project buildTestProject(nwGUI, fncProj) # Handles for new objects hNovelRoot = "0000000000008" hChapterDir = "000000000000d" hChapterOne = "000000000000e" hSceneOne = "000000000000f" hSceneTwo = "0000000000010" hSceneThree = "0000000000011" hSceneFour = "0000000000012" hMergedDoc = "0000000000023" # Add Project Content nwGUI.switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) assert nwGUI.saveProject() is True assert nwGUI.closeProject() is True tChapterOne = "## Chapter One\n\n% Chapter one comment\n" tSceneOne = "### Scene One\n\nThere once was a man from Nantucket" tSceneTwo = "### Scene Two\n\nWho kept all his cash in a bucket." tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan, \n\tRan away with a man" tSceneFour = "### Scene Four\n\nAnd as for the bucket, Nantucket." contentDir = os.path.join(fncProj, "content") writeFile(os.path.join(contentDir, hChapterOne+".nwd"), tChapterOne) writeFile(os.path.join(contentDir, hSceneOne+".nwd"), tSceneOne) writeFile(os.path.join(contentDir, hSceneTwo+".nwd"), tSceneTwo) writeFile(os.path.join(contentDir, hSceneThree+".nwd"), tSceneThree) writeFile(os.path.join(contentDir, hSceneFour+".nwd"), tSceneFour) assert nwGUI.openProject(fncProj) is True # Open the Merge tool nwGUI.switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True) monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None) nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000) nwMerge = getGuiItem("GuiDocMerge") assert isinstance(nwMerge, GuiDocMerge) nwMerge.show() qtbot.wait(50) # Populate List # ============= nwMerge.listBox.clear() assert nwMerge.listBox.count() == 0 # No item selected nwGUI.projView.projTree.clearSelection() assert nwMerge._populateList() is False assert nwMerge.listBox.count() == 0 # Non-existing item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True) assert nwMerge._populateList() is False assert nwMerge.listBox.count() == 0 # Select a non-folder nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hChapterOne).setSelected(True) assert nwMerge._populateList() is False assert nwMerge.listBox.count() == 0 # Select the chapter folder nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True) assert nwMerge._populateList() is True assert nwMerge.listBox.count() == 5 # Merge Documents # =============== # First, a successful merge with monkeypatch.context() as mp: mp.setattr(GuiDocMerge, "_doClose", lambda *a: None) assert nwMerge._doMerge() is True assert nwGUI.saveProject() is True mergedFile = os.path.join(contentDir, hMergedDoc+".nwd") assert os.path.isfile(mergedFile) assert readFile(mergedFile) == ( "%%%%~name: New Chapter\n" "%%%%~path: %s/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" "%s\n\n" "%s\n\n" "%s\n\n" "%s\n\n" ) % ( hNovelRoot, hMergedDoc, tChapterOne.strip(), tSceneOne.strip(), tSceneTwo.strip(), tSceneThree.strip(), tSceneFour.strip(), ) # OS error with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert nwMerge._doMerge() is False # Can't find the source item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) assert nwMerge._doMerge() is False # No source handle set nwMerge.sourceItem = None assert nwMerge._doMerge() is False # No documents to merge nwMerge.listBox.clear() assert nwMerge._doMerge() is False # Close up nwMerge._doClose()
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj): """Test the merge documents tool. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) # Create a new project nwGUI.theProject.projTree.setSeed(42) assert nwGUI.newProject({"projPath": fncProj}) # Handles for new objects hChapterDir = "31489056e0916" hChapterOne = "98010bd9270f9" hSceneOne = "0e17daca5f3e1" hSceneTwo = "1a6562590ef19" hSceneThree = "031b4af5197ec" hSceneFour = "41cfc0d1f2d12" hMergedDoc = "2858dcd1057d3" # Add Project Content monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted) nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) assert nwGUI.saveProject() is True assert nwGUI.closeProject() is True tChapterOne = "## Chapter One\n\n% Chapter one comment\n" tSceneOne = "### Scene One\n\nThere once was a man from Nantucket" tSceneTwo = "### Scene Two\n\nWho kept all his cash in a bucket." tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan, \n\tRan away with a man" tSceneFour = "### Scene Four\n\nAnd as for the bucket, Nantucket." contentDir = os.path.join(fncProj, "content") writeFile(os.path.join(contentDir, hChapterOne+".nwd"), tChapterOne) writeFile(os.path.join(contentDir, hSceneOne+".nwd"), tSceneOne) writeFile(os.path.join(contentDir, hSceneTwo+".nwd"), tSceneTwo) writeFile(os.path.join(contentDir, hSceneThree+".nwd"), tSceneThree) writeFile(os.path.join(contentDir, hSceneFour+".nwd"), tSceneFour) assert nwGUI.openProject(fncProj) is True # Open the Merge tool nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True) monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None) nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000) nwMerge = getGuiItem("GuiDocMerge") assert isinstance(nwMerge, GuiDocMerge) nwMerge.show() qtbot.wait(50) # Populate List # ============= nwMerge.listBox.clear() assert nwMerge.listBox.count() == 0 # No item selected nwGUI.treeView.clearSelection() assert nwMerge._populateList() is False assert nwMerge.listBox.count() == 0 # Non-existing item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True) assert nwMerge._populateList() is False assert nwMerge.listBox.count() == 0 # Select a non-folder nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hChapterOne).setSelected(True) assert nwMerge._populateList() is False assert nwMerge.listBox.count() == 0 # Select the chapter folder nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True) assert nwMerge._populateList() is True assert nwMerge.listBox.count() == 5 # Merge Documents # =============== # First, a successful merge with monkeypatch.context() as mp: mp.setattr(GuiDocMerge, "_doClose", lambda *a: None) assert nwMerge._doMerge() is True assert nwGUI.saveProject() is True mergedFile = os.path.join(contentDir, hMergedDoc+".nwd") assert os.path.isfile(mergedFile) assert readFile(mergedFile) == ( "%%%%~name: New Chapter\n" "%%%%~path: 73475cb40a568/2858dcd1057d3\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" "%s\n\n" "%s\n\n" "%s\n\n" "%s\n\n" ) % ( tChapterOne.strip(), tSceneOne.strip(), tSceneTwo.strip(), tSceneThree.strip(), tSceneFour.strip(), ) # OS error with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert nwMerge._doMerge() is False # Can't find the source item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) assert nwMerge._doMerge() is False # No source handle set nwMerge.sourceItem = None assert nwMerge._doMerge() is False # No documents to merge nwMerge.listBox.clear() assert nwMerge._doMerge() is False # Close up nwMerge._doClose()
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): """Test the full writing stats tool. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) # Create a project to work on assert nwGUI.newProject({"projPath": fncProj}) qtbot.wait(100) assert nwGUI.saveProject() sessFile = os.path.join(fncProj, "meta", nwFiles.SESS_STATS) # Open the Writing Stats dialog nwGUI.mainConf.lastPath = "" nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000) sessLog = getGuiItem("GuiWritingStats") assert isinstance(sessLog, GuiWritingStats) qtbot.wait(stepDelay) # Test Loading # ============ # No initial logfile assert not os.path.isfile(sessFile) assert not sessLog._loadLogFile() # Make a test log file writeFile(sessFile, ( "# Offset 123\n" "# Start Time End Time Novel Notes Idle\n" "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n" "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n" "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n" "2020-01-06 21:00:00 2020-01-06 21:00:10 125 5\n")) assert os.path.isfile(sessFile) assert sessLog._loadLogFile() assert sessLog.wordOffset == 123 assert len(sessLog.logData) == 4 # Make sure a faulty file can still be read writeFile(sessFile, ( "# Offset abc123\n" "# Start Time End Time Novel Notes Idle\n" "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0 50\n" "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n" "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n" "2020-01-06 21:00:00 2020-01-06 21:00:10 125\n")) assert sessLog._loadLogFile() assert sessLog.wordOffset == 0 assert len(sessLog.logData) == 3 # Test Exporting # ============== writeFile(sessFile, ( "# Offset 1075\n" "# Start Time End Time Novel Notes Idle\n" "2021-01-31 19:00:00 2021-01-31 19:30:00 700 375 0\n" "2021-02-01 19:00:00 2021-02-01 19:30:00 700 375 10\n" "2021-02-01 20:00:00 2021-02-01 20:30:00 600 275 20\n" "2021-02-02 19:00:00 2021-02-02 19:30:00 750 425 30\n" "2021-02-02 20:00:00 2021-02-02 20:30:00 690 365 40\n" "2021-02-03 19:00:00 2021-02-03 19:30:00 680 355 50\n" "2021-02-04 19:00:00 2021-02-04 19:30:00 700 375 60\n" "2021-02-05 19:00:00 2021-02-05 19:30:00 500 175 70\n" "2021-02-06 19:00:00 2021-02-06 19:30:00 600 275 80\n" "2021-02-07 19:00:00 2021-02-07 19:30:00 600 275 90\n" )) sessLog.populateGUI() # Make the saving fail monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *a, **k: ("", "")) assert not sessLog._saveData(sessLog.FMT_CSV) assert not sessLog._saveData(sessLog.FMT_JSON) assert not sessLog._saveData(None) # Make the save succeed monkeypatch.setattr("os.path.expanduser", lambda *a: fncDir) monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: (pp, "")) sessLog.listBox.sortByColumn(sessLog.C_TIME, 0) assert sessLog.novelWords.text() == "{:n}".format(600) assert sessLog.notesWords.text() == "{:n}".format(275) assert sessLog.totalWords.text() == "{:n}".format(875) assert sessLog.listBox.topLevelItem(0).text( sessLog.C_COUNT) == "{:n}".format(1) assert sessLog.listBox.topLevelItem(1).text( sessLog.C_COUNT) == "{:n}".format(-200) assert sessLog.listBox.topLevelItem(2).text( sessLog.C_COUNT) == "{:n}".format(300) assert sessLog.listBox.topLevelItem(3).text( sessLog.C_COUNT) == "{:n}".format(-120) assert sessLog.listBox.topLevelItem(4).text( sessLog.C_COUNT) == "{:n}".format(-20) assert sessLog.listBox.topLevelItem(5).text( sessLog.C_COUNT) == "{:n}".format(40) assert sessLog.listBox.topLevelItem(6).text( sessLog.C_COUNT) == "{:n}".format(-400) assert sessLog.listBox.topLevelItem(7).text( sessLog.C_COUNT) == "{:n}".format(200) assert sessLog._saveData(sessLog.FMT_CSV) qtbot.wait(100) assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(100) assert nwGUI.mainConf.lastPath == fncDir # Check the exported files jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) assert jsonData == [{ "date": "2021-01-31 19:00:00", "length": 1800.0, "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0 }, { "date": "2021-02-01 20:00:00", "length": 1800.0, "newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 20 }, { "date": "2021-02-02 19:00:00", "length": 1800.0, "newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30 }, { "date": "2021-02-02 20:00:00", "length": 1800.0, "newWords": -120, "novelWords": 690, "noteWords": 365, "idleTime": 40 }, { "date": "2021-02-03 19:00:00", "length": 1800.0, "newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50 }, { "date": "2021-02-04 19:00:00", "length": 1800.0, "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60 }, { "date": "2021-02-05 19:00:00", "length": 1800.0, "newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70 }, { "date": "2021-02-06 19:00:00", "length": 1800.0, "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80 }] # Test Filters # ============ # No Novel Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) assert sessLog.listBox.topLevelItem(0).text( sessLog.C_COUNT) == "{:n}".format(1) assert sessLog.listBox.topLevelItem(1).text( sessLog.C_COUNT) == "{:n}".format(-100) assert sessLog.listBox.topLevelItem(2).text( sessLog.C_COUNT) == "{:n}".format(150) assert sessLog.listBox.topLevelItem(3).text( sessLog.C_COUNT) == "{:n}".format(-60) assert sessLog.listBox.topLevelItem(4).text( sessLog.C_COUNT) == "{:n}".format(-10) assert sessLog.listBox.topLevelItem(5).text( sessLog.C_COUNT) == "{:n}".format(20) assert sessLog.listBox.topLevelItem(6).text( sessLog.C_COUNT) == "{:n}".format(-200) assert sessLog.listBox.topLevelItem(7).text( sessLog.C_COUNT) == "{:n}".format(100) assert jsonData == [{ "date": "2021-01-31 19:00:00", "length": 1800.0, "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0 }, { "date": "2021-02-01 20:00:00", "length": 1800.0, "newWords": -100, "novelWords": 600, "noteWords": 275, "idleTime": 20 }, { "date": "2021-02-02 19:00:00", "length": 1800.0, "newWords": 150, "novelWords": 750, "noteWords": 425, "idleTime": 30 }, { "date": "2021-02-02 20:00:00", "length": 1800.0, "newWords": -60, "novelWords": 690, "noteWords": 365, "idleTime": 40 }, { "date": "2021-02-03 19:00:00", "length": 1800.0, "newWords": -10, "novelWords": 680, "noteWords": 355, "idleTime": 50 }, { "date": "2021-02-04 19:00:00", "length": 1800.0, "newWords": 20, "novelWords": 700, "noteWords": 375, "idleTime": 60 }, { "date": "2021-02-05 19:00:00", "length": 1800.0, "newWords": -200, "novelWords": 500, "noteWords": 175, "idleTime": 70 }, { "date": "2021-02-06 19:00:00", "length": 1800.0, "newWords": 100, "novelWords": 600, "noteWords": 275, "idleTime": 80 }] # No Note Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton) qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) assert sessLog.listBox.topLevelItem(0).text( sessLog.C_COUNT) == "{:n}".format(1) assert sessLog.listBox.topLevelItem(1).text( sessLog.C_COUNT) == "{:n}".format(-100) assert sessLog.listBox.topLevelItem(2).text( sessLog.C_COUNT) == "{:n}".format(150) assert sessLog.listBox.topLevelItem(3).text( sessLog.C_COUNT) == "{:n}".format(-60) assert sessLog.listBox.topLevelItem(4).text( sessLog.C_COUNT) == "{:n}".format(-10) assert sessLog.listBox.topLevelItem(5).text( sessLog.C_COUNT) == "{:n}".format(20) assert sessLog.listBox.topLevelItem(6).text( sessLog.C_COUNT) == "{:n}".format(-200) assert sessLog.listBox.topLevelItem(7).text( sessLog.C_COUNT) == "{:n}".format(100) assert jsonData == [{ "date": "2021-01-31 19:00:00", "length": 1800.0, "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0 }, { "date": "2021-02-01 20:00:00", "length": 1800.0, "newWords": -100, "novelWords": 600, "noteWords": 275, "idleTime": 20 }, { "date": "2021-02-02 19:00:00", "length": 1800.0, "newWords": 150, "novelWords": 750, "noteWords": 425, "idleTime": 30 }, { "date": "2021-02-02 20:00:00", "length": 1800.0, "newWords": -60, "novelWords": 690, "noteWords": 365, "idleTime": 40 }, { "date": "2021-02-03 19:00:00", "length": 1800.0, "newWords": -10, "novelWords": 680, "noteWords": 355, "idleTime": 50 }, { "date": "2021-02-04 19:00:00", "length": 1800.0, "newWords": 20, "novelWords": 700, "noteWords": 375, "idleTime": 60 }, { "date": "2021-02-05 19:00:00", "length": 1800.0, "newWords": -200, "novelWords": 500, "noteWords": 175, "idleTime": 70 }, { "date": "2021-02-06 19:00:00", "length": 1800.0, "newWords": 100, "novelWords": 600, "noteWords": 275, "idleTime": 80 }] # No Negative Entries qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton) qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton) qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) # qtbot.stopForInteraction() jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) assert sessLog.listBox.topLevelItem(0).text( sessLog.C_COUNT) == "{:n}".format(1) assert sessLog.listBox.topLevelItem(1).text( sessLog.C_COUNT) == "{:n}".format(300) assert sessLog.listBox.topLevelItem(2).text( sessLog.C_COUNT) == "{:n}".format(40) assert sessLog.listBox.topLevelItem(3).text( sessLog.C_COUNT) == "{:n}".format(200) assert jsonData == [{ "date": "2021-01-31 19:00:00", "length": 1800.0, "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0 }, { "date": "2021-02-02 19:00:00", "length": 1800.0, "newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30 }, { "date": "2021-02-04 19:00:00", "length": 1800.0, "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60 }, { "date": "2021-02-06 19:00:00", "length": 1800.0, "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80 }] # Un-hide Zero Entries qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton) qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton) qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) assert sessLog.listBox.topLevelItem(0).text( sessLog.C_COUNT) == "{:n}".format(1) assert sessLog.listBox.topLevelItem(1).text( sessLog.C_COUNT) == "{:n}".format(0) assert sessLog.listBox.topLevelItem(2).text( sessLog.C_COUNT) == "{:n}".format(-200) assert sessLog.listBox.topLevelItem(3).text( sessLog.C_COUNT) == "{:n}".format(300) assert sessLog.listBox.topLevelItem(4).text( sessLog.C_COUNT) == "{:n}".format(-120) assert sessLog.listBox.topLevelItem(5).text( sessLog.C_COUNT) == "{:n}".format(-20) assert sessLog.listBox.topLevelItem(6).text( sessLog.C_COUNT) == "{:n}".format(40) assert sessLog.listBox.topLevelItem(7).text( sessLog.C_COUNT) == "{:n}".format(-400) assert sessLog.listBox.topLevelItem(8).text( sessLog.C_COUNT) == "{:n}".format(200) assert sessLog.listBox.topLevelItem(9).text( sessLog.C_COUNT) == "{:n}".format(0) assert jsonData == [{ "date": "2021-01-31 19:00:00", "length": 1800.0, "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0 }, { "date": "2021-02-01 19:00:00", "length": 1800.0, "newWords": 0, "novelWords": 700, "noteWords": 375, "idleTime": 10 }, { "date": "2021-02-01 20:00:00", "length": 1800.0, "newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 20 }, { "date": "2021-02-02 19:00:00", "length": 1800.0, "newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30 }, { "date": "2021-02-02 20:00:00", "length": 1800.0, "newWords": -120, "novelWords": 690, "noteWords": 365, "idleTime": 40 }, { "date": "2021-02-03 19:00:00", "length": 1800.0, "newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50 }, { "date": "2021-02-04 19:00:00", "length": 1800.0, "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60 }, { "date": "2021-02-05 19:00:00", "length": 1800.0, "newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70 }, { "date": "2021-02-06 19:00:00", "length": 1800.0, "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80 }, { "date": "2021-02-07 19:00:00", "length": 1800.0, "newWords": 0, "novelWords": 600, "noteWords": 275, "idleTime": 90 }] # Group by Day qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton) qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) assert sessLog.listBox.topLevelItem(0).text( sessLog.C_COUNT) == "{:n}".format(1) assert sessLog.listBox.topLevelItem(1).text( sessLog.C_COUNT) == "{:n}".format(-200) assert sessLog.listBox.topLevelItem(2).text( sessLog.C_COUNT) == "{:n}".format(180) assert sessLog.listBox.topLevelItem(3).text( sessLog.C_COUNT) == "{:n}".format(-20) assert sessLog.listBox.topLevelItem(4).text( sessLog.C_COUNT) == "{:n}".format(40) assert sessLog.listBox.topLevelItem(5).text( sessLog.C_COUNT) == "{:n}".format(-400) assert sessLog.listBox.topLevelItem(6).text( sessLog.C_COUNT) == "{:n}".format(200) assert sessLog.listBox.topLevelItem(7).text( sessLog.C_COUNT) == "{:n}".format(0) assert jsonData == [{ "date": "2021-01-31", "length": 1800.0, "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0 }, { "date": "2021-02-01", "length": 3600.0, "newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 30 }, { "date": "2021-02-02", "length": 3600.0, "newWords": 180, "novelWords": 690, "noteWords": 365, "idleTime": 70 }, { "date": "2021-02-03", "length": 1800.0, "newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50 }, { "date": "2021-02-04", "length": 1800.0, "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60 }, { "date": "2021-02-05", "length": 1800.0, "newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70 }, { "date": "2021-02-06", "length": 1800.0, "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80 }, { "date": "2021-02-07", "length": 1800.0, "newWords": 0, "novelWords": 600, "noteWords": 275, "idleTime": 90 }] # IOError # ======= monkeypatch.setattr("builtins.open", causeOSError) assert not sessLog._loadLogFile() assert not sessLog._saveData(sessLog.FMT_CSV) # qtbot.stopForInteraction() sessLog._doClose() assert nwGUI.closeProject() qtbot.wait(stepDelay)
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj): """Test the split document tool. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) # Create a new project nwGUI.theProject.projTree.setSeed(42) assert nwGUI.newProject({"projPath": fncProj}) is True # Handles for new objects hNovelRoot = "73475cb40a568" hChapterDir = "31489056e0916" hToSplit = "1a6562590ef19" hPartition = "41cfc0d1f2d12" hChapterOne = "2858dcd1057d3" hSceneOne = "2fca346db6561" hSceneTwo = "02d20bbd7e394" hSceneThree = "7688b6ef52555" hSceneFour = "c837649cce43f" hSceneFive = "6208ef0f7750c" # Add Project Content monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted) nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hNovelRoot).setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) assert nwGUI.saveProject() is True assert nwGUI.closeProject() is True tPartition = "# Nantucket" tChapterOne = "## Chapter One\n\n% Chapter one comment" tSceneOne = "### Scene One\n\nThere once was a man from Nantucket" tSceneTwo = "### Scene Two\n\nWho kept all his cash in a bucket." tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan, \n\tRan away with a man" tSceneFour = "### Scene Four\n\nAnd as for the bucket, Nantucket." tSceneFive = "#### The End\n\nend" tToSplit = ( f"{tPartition}\n\n{tChapterOne}\n\n" f"{tSceneOne}\n\n{tSceneTwo}\n\n" f"{tSceneThree}\n\n{tSceneFour}\n\n" f"{tSceneFive}\n\n" ) contentDir = os.path.join(fncProj, "content") writeFile(os.path.join(contentDir, hToSplit+".nwd"), tToSplit) assert nwGUI.openProject(fncProj) is True # Open the Split tool nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hToSplit).setSelected(True) monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None) nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000) nwSplit = getGuiItem("GuiDocSplit") assert isinstance(nwSplit, GuiDocSplit) nwSplit.show() qtbot.wait(50) # Populate List # ============= nwSplit.listBox.clear() assert nwSplit.listBox.count() == 0 # No item selected nwSplit.sourceItem = None nwGUI.treeView.clearSelection() assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Non-existing item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) nwSplit.sourceItem = None nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hToSplit).setSelected(True) assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Select a non-file nwSplit.sourceItem = None nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True) assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Error when reading documents with monkeypatch.context() as mp: mp.setattr(NWDoc, "readDocument", lambda *a: None) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is False assert nwSplit.listBox.count() == 0 # Read properly, and check split levels # Level 1 nwSplit.splitLevel.setCurrentIndex(0) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 1 # Level 2 nwSplit.splitLevel.setCurrentIndex(1) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 2 # Level 3 nwSplit.splitLevel.setCurrentIndex(2) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 6 # Level 4 nwSplit.splitLevel.setCurrentIndex(3) nwSplit.sourceItem = hToSplit assert nwSplit._populateList() is True assert nwSplit.listBox.count() == 7 # Split Document # ============== # Test a proper split first with monkeypatch.context() as mp: mp.setattr(GuiDocSplit, "_doClose", lambda *a: None) assert nwSplit._doSplit() is True assert nwGUI.saveProject() assert readFile(os.path.join(contentDir, hPartition+".nwd")) == ( "%%%%~name: Nantucket\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hPartition, tPartition) assert readFile(os.path.join(contentDir, hChapterOne+".nwd")) == ( "%%%%~name: Chapter One\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hChapterOne, tChapterOne) assert readFile(os.path.join(contentDir, hSceneOne+".nwd")) == ( "%%%%~name: Scene One\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hSceneOne, tSceneOne) assert readFile(os.path.join(contentDir, hSceneTwo+".nwd")) == ( "%%%%~name: Scene Two\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hSceneTwo, tSceneTwo) assert readFile(os.path.join(contentDir, hSceneThree+".nwd")) == ( "%%%%~name: Scene Three\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hSceneThree, tSceneThree) assert readFile(os.path.join(contentDir, hSceneFour+".nwd")) == ( "%%%%~name: Scene Four\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hSceneFour, tSceneFour) assert readFile(os.path.join(contentDir, hSceneFive+".nwd")) == ( "%%%%~name: The End\n" "%%%%~path: 031b4af5197ec/%s\n" "%%%%~kind: NOVEL/DOCUMENT\n" "%s\n\n" ) % (hSceneFive, tSceneFive) # OS error with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert nwSplit._doSplit() is False # Select to not split with monkeypatch.context() as mp: mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No) assert nwSplit._doSplit() is False # Block folder creation by returning that the folder has a depth # of 50 items in the tree with monkeypatch.context() as mp: mp.setattr(NWTree, "getItemPath", lambda *a: [""]*50) assert nwSplit._doSplit() is False # Clear the list nwSplit.listBox.clear() assert nwSplit._doSplit() is False # Can't find sourcv item with monkeypatch.context() as mp: mp.setattr(NWTree, "__getitem__", lambda *a: None) assert nwSplit._doSplit() is False # No source item set nwSplit.sourceItem = None assert nwSplit._doSplit() is False # Close nwSplit._doClose()
def makeGnuplotFile(inFile, outFile): inLines = [l.replace("\n", "") for l in readFile(inFile)] output = "\n".join(createGnuplotFile(inLines)) print("--------------------------") print(output) writeFile(outFile, output)