def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI): """Check that new files can be added to the project. """ projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx") theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.newProject({"projPath": fncDir}) is True assert theProject.setProjectPath(fncDir) is True assert theProject.saveProject() is True assert theProject.closeProject() is True assert theProject.openProject(projFile) is True assert isinstance( theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str) assert isinstance( theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) assert theProject.projChanged assert theProject.saveProject() is True assert theProject.closeProject() is True copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert theProject.projChanged is False
def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI): """Check that new root folders can be added to the project. """ projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx") theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.newProject({"projPath": fncDir}) is True assert theProject.setProjectPath(fncDir) is True assert theProject.saveProject() is True assert theProject.closeProject() is True assert theProject.openProject(projFile) is True assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None)) assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None)) assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None)) assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), type(None)) assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str) assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str) assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str) assert isinstance(theProject.newRoot("Custom2", nwItemClass.CUSTOM), str) assert theProject.projChanged is True assert theProject.saveProject() is True assert theProject.closeProject() is True copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert theProject.projChanged is False
def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir): """Test other project class methods and functions. """ theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) assert theProject.projPath == nwMinimal # Setting project path assert theProject.setProjectPath(None) assert theProject.projPath is None assert theProject.setProjectPath("") assert theProject.projPath is None assert theProject.setProjectPath("~") assert theProject.projPath == os.path.expanduser("~") # Create a new folder and populate it projPath = os.path.join(nwMinimal, "mock1") assert theProject.setProjectPath(projPath, newProject=True) # Make os.mkdir fail monkeypatch.setattr("os.mkdir", causeOSError) projPath = os.path.join(nwMinimal, "mock2") assert not theProject.setProjectPath(projPath, newProject=True) # Set back assert theProject.setProjectPath(nwMinimal) # Project Name assert theProject.setProjectName(" A Name ") assert theProject.projName == "A Name" # Project Title assert theProject.setBookTitle(" A Title ") assert theProject.bookTitle == "A Title" # Project Authors # Check that the list is cleaned up and that it can be extracted as # a properly formatted string, depending on number of names assert not theProject.setBookAuthors([]) assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.bookAuthors == ["Jane Doe", "John Doh"] assert theProject.setBookAuthors("") assert theProject.getAuthors() == "" assert theProject.setBookAuthors("Jane Doe") assert theProject.getAuthors() == "Jane Doe" assert theProject.setBookAuthors("Jane Doe\nJohn Doh") assert theProject.getAuthors() == "Jane Doe and John Doh" assert theProject.setBookAuthors("Jane Doe\nJohn Doh\nBod Owens") assert theProject.getAuthors() == "Jane Doe, John Doh and Bod Owens" # Edit Time theProject.editTime = 1234 theProject.projOpened = 1600000000 with monkeypatch.context() as mp: mp.setattr("novelwriter.core.project.time", lambda: 1600005600) assert theProject.getCurrentEditTime() == 6834 # Trash folder # Should create on first call, and just returned on later calls assert theProject.projTree["73475cb40a568"] is None assert theProject.trashFolder() == "73475cb40a568" assert theProject.trashFolder() == "73475cb40a568" # Project backup assert theProject.doBackup is True assert theProject.setProjBackup(False) assert theProject.doBackup is False assert not theProject.setProjBackup(True) theProject.mainConf.backupPath = tmpDir assert theProject.setProjBackup(True) assert theProject.setProjectName("") assert not theProject.setProjBackup(True) assert theProject.setProjectName("A Name") assert theProject.setProjBackup(True) # Spell check theProject.projChanged = False assert theProject.setSpellCheck(True) assert not theProject.setSpellCheck(False) assert theProject.projChanged # Spell language theProject.projChanged = False assert theProject.setSpellLang(None) assert theProject.projSpell is None assert theProject.setSpellLang("None") assert theProject.projSpell is None assert theProject.setSpellLang("en_GB") assert theProject.projSpell == "en_GB" assert theProject.projChanged # Project Language theProject.projChanged = False theProject.projLang = "en" assert theProject.setProjectLang(None) is True assert theProject.projLang is None assert theProject.setProjectLang("en_GB") is True assert theProject.projLang == "en_GB" # Automatic outline update theProject.projChanged = False assert theProject.setAutoOutline(True) assert not theProject.setAutoOutline(False) assert theProject.projChanged # Last edited theProject.projChanged = False assert theProject.setLastEdited("0123456789abc") assert theProject.lastEdited == "0123456789abc" assert theProject.projChanged # Last viewed theProject.projChanged = False assert theProject.setLastViewed("0123456789abc") assert theProject.lastViewed == "0123456789abc" assert theProject.projChanged # Autoreplace theProject.projChanged = False assert theProject.setAutoReplace({"A": "B", "C": "D"}) assert theProject.autoReplace == {"A": "B", "C": "D"} assert theProject.projChanged # Change project tree order oldOrder = [ "a508bb932959c", "a35baf2e93843", "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265", "afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568", ] newOrder = [ "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265", "a508bb932959c", "a35baf2e93843", "a6d311a93600a", "afb3043c7b2b3", "9d5247ab588e0", ] assert theProject.projTree.handles() == oldOrder assert theProject.setTreeOrder(newOrder) assert theProject.projTree.handles() == newOrder assert theProject.setTreeOrder(oldOrder) assert theProject.projTree.handles() == oldOrder # Change status theProject.projTree["a35baf2e93843"].setStatus("Finished") theProject.projTree["a6d311a93600a"].setStatus("Draft") theProject.projTree["f5ab3e30151e1"].setStatus("Note") theProject.projTree["8c659a11cd429"].setStatus("Finished") newList = [ ("New", 1, 1, 1, "New"), ("Draft", 2, 2, 2, "Note"), # These are swapped ("Note", 3, 3, 3, "Draft"), # These are swapped ("Edited", 4, 4, 4, "Finished"), # Renamed ("Finished", 5, 5, 5, None), # New, with reused name ] assert theProject.setStatusColours(newList) assert theProject.statusItems._theLabels == [ "New", "Draft", "Note", "Edited", "Finished" ] assert theProject.statusItems._theColours == [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)] assert theProject.projTree[ "a35baf2e93843"].itemStatus == "Edited" # Renamed assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped assert theProject.projTree[ "8c659a11cd429"].itemStatus == "Edited" # Renamed # Change importance fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3") theProject.projTree[fHandle].setImport("Main") newList = [ ("New", 1, 1, 1, "New"), ("Minor", 2, 2, 2, "Minor"), ("Major", 3, 3, 3, "Major"), ("Min", 4, 4, 4, "Main"), ("Max", 5, 5, 5, None), ] assert theProject.setImportColours(newList) assert theProject.importItems._theLabels == [ "New", "Minor", "Major", "Min", "Max" ] assert theProject.importItems._theColours == [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)] assert theProject.projTree[fHandle].itemImport == "Min" # Check status counts assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0] assert theProject.importItems._theCounts == [0, 0, 0, 0, 0] theProject.countStatus() assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0] assert theProject.importItems._theCounts == [3, 0, 0, 1, 0] # Session stats theProject.currWCount = 200 theProject.lastWCount = 100 with monkeypatch.context() as mp: mp.setattr("os.path.isdir", lambda *a, **k: False) assert not theProject._appendSessionStats(idleTime=0) # Block open with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert not theProject._appendSessionStats(idleTime=0) # Write entry assert theProject.projMeta == os.path.join(nwMinimal, "meta") statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS) theProject.projOpened = 1600002000 theProject.currNovelWC = 200 theProject.currNotesWC = 100 with monkeypatch.context() as mp: mp.setattr("novelwriter.core.project.time", lambda: 1600005600) assert theProject._appendSessionStats(idleTime=99) assert readFile(statsFile) == ( "# Offset 100\n" "# Start Time End Time Novel Notes Idle\n" "%s %s 200 100 99\n") % ( formatTimeStamp(1600002000), formatTimeStamp(1600005600)) # Pack XML Value xElem = etree.Element("element") theProject._packProjectValue(xElem, "A", "B", allowNone=False) assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (b"<element><A>B</A></element>") xElem = etree.Element("element") theProject._packProjectValue(xElem, "A", "", allowNone=False) assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (b"<element/>") # Pack XML Key/Value xElem = etree.Element("element") theProject._packProjectKeyValue(xElem, "item", {"A": "B", "C": "D"}) assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (b"<element>" b"<item>" b"<entry key=\"A\">B</entry>" b"<entry key=\"C\">D</entry>" b"</item>" b"</element>")
def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI): """Test opening a project. """ theProject = NWProject(mockGUI) # 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 with monkeypatch.context() as mp: mp.setattr("os.mkdir", causeOSError) assert theProject.openProject(nwMinimal) is False # 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) with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert theProject.openProject(nwMinimal) is True 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, "stuff") assert theProject.openProject(nwMinimal) is False # Also write a jun XML backup file writeFile(bName, "stuff") 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='0.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0x01000000\" " "fileVersion=\"1.0\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "</novelWriterXML>\n")) mockGUI.askResponse = False assert theProject.openProject(nwMinimal) is False mockGUI.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 # Update file 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")) mockGUI.askResponse = False assert theProject.openProject(nwMinimal) is False assert mockGUI.lastQuestion[0] == "File Version" mockGUI.undo() # Larger hex version writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n" "<novelWriterXML " "appVersion=\"1.0\" " "hexVersion=\"0xffffffff\" " "fileVersion=\"%s\" " "timeStamp=\"2020-01-01 00:00:00\">\n" "</novelWriterXML>\n") % theProject.FILE_VERSION) mockGUI.askResponse = False assert theProject.openProject(nwMinimal) is False assert mockGUI.lastQuestion[0] == "Version Conflict" mockGUI.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() # 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"), "stuff") os.mkdir(os.path.join(nwMinimal, "data_0")) writeFile(os.path.join(nwMinimal, "data_0", "junk"), "stuff") mockGUI.clear() assert theProject.openProject(nwMinimal) is True assert "data_0" in mockGUI.lastAlert assert theProject.closeProject()
def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir): """Test the functins that handle legacy data folders and structure with additional tests of failure handling. """ theProject = NWProject(mockGUI) 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, "stuff") assert os.path.isfile(tstFile) with monkeypatch.context() as mp: mp.setattr("os.unlink", causeOSError) assert not theProject._deprecatedFiles() assert theProject._deprecatedFiles() assert not os.path.isfile(tstFile) # Check processing non-folders tstFile = os.path.join(fncDir, "data_0") writeFile(tstFile, "stuff") 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 with monkeypatch.context() as mp: mp.setattr("os.mkdir", causeOSError) errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) > 0 # This causes a failure to move 'stuff' to 'junk' with monkeypatch.context() as mp: mp.setattr("os.rename", causeOSError) errList = [] errList = theProject._legacyDataFolder(tstData, errList) assert len(errList) > 0 # 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, "stuff") writeFile(tstDoc1b, "stuff") writeFile(tstDoc2m, "stuff") writeFile(tstDoc2b, "stuff") writeFile(tstDoc3m, "stuff") writeFile(tstDoc3b, "stuff") # Make the above fail with monkeypatch.context() as mp: mp.setattr("os.rename", causeOSError) mp.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) # 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"))