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_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir): """Check that we can create a new project can be created from the provided sample project folder. """ projData = { "projName": "Test Sample", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", "projPath": fncDir, "popSample": True, "popMinimal": False, "popCustom": False, } theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) # Make sure we do not pick up the novelwriter/assets/sample.zip file tmpConf.assetPath = tmpDir # Set a fake project file name monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx") assert not theProject.newProject(projData) monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx") assert theProject.newProject(projData) is True assert theProject.openProject(fncDir) is True assert theProject.projName == "Sample Project" assert theProject.saveProject() is True assert theProject.closeProject() is True # Misdirect the appRoot path so neither is possible tmpConf.appRoot = tmpDir assert not theProject.newProject(projData)
def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI): """Create a new project from a project wizard dictionary. With default setting, creating a Minimal project. """ projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx") theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) # Setting no data should fail assert theProject.newProject({}) is False # Wrong type should also fail assert theProject.newProject("stuff") is False # Try again with a proper path assert theProject.newProject({"projPath": fncDir}) is True assert theProject.saveProject() is True assert theProject.closeProject() is True # Creating the project once more should fail assert theProject.newProject({"projPath": fncDir}) is False # Open again assert theProject.openProject(projFile) is True # Save and close 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 # Open a second time assert theProject.openProject(projFile) is True assert theProject.openProject(projFile) is False assert theProject.openProject(projFile, overrideLock=True) is True assert theProject.saveProject() is True assert theProject.closeProject() is True copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
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_NewCustomA(fncDir, outDir, refDir, mockGUI): """Create a new project from a project wizard dictionary. Custom type with chapters and scenes. """ projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewCustomA_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewCustomA_nwProject.nwx") projData = { "projName": "Test Custom", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", "projPath": fncDir, "popSample": False, "popMinimal": False, "popCustom": True, "addRoots": [ nwItemClass.PLOT, nwItemClass.CHARACTER, nwItemClass.WORLD, nwItemClass.TIMELINE, nwItemClass.OBJECT, nwItemClass.ENTITY, ], "numChapters": 3, "numScenes": 3, "chFolders": True, } theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.newProject(projData) is True assert theProject.saveProject() is True assert theProject.closeProject() is True copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir): """Check that we can create a new project can be created from the provided sample project via a zip file. """ projData = { "projName": "Test Sample", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", "projPath": fncDir, "popSample": True, "popMinimal": False, "popCustom": False, } theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) # Sample set, but no path assert not theProject.newProject({"popSample": True}) # Force the lookup path for assets to our temp folder srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample")) dstSample = os.path.join(tmpDir, "sample.zip") tmpConf.assetPath = tmpDir # Create and open a defective zip file with open(dstSample, mode="w+") as outFile: outFile.write("foo") assert not theProject.newProject(projData) os.unlink(dstSample) # Create a real zip file, and unpack it with ZipFile(dstSample, "w") as zipObj: zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx") for docFile in os.listdir(os.path.join(srcSample, "content")): srcDoc = os.path.join(srcSample, "content", docFile) zipObj.write(srcDoc, "content/" + docFile) assert theProject.newProject(projData) is True assert theProject.openProject(fncDir) is True assert theProject.projName == "Sample Project" assert theProject.saveProject() is True assert theProject.closeProject() is True os.unlink(dstSample)
def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir): """Test saving a project. """ theProject = NWProject(mockGUI) testFile = os.path.join(nwMinimal, "nwProject.nwx") backFile = os.path.join(nwMinimal, "nwProject.bak") compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx") # Nothing to save assert theProject.saveProject() is False # Open test project assert theProject.openProject(nwMinimal) # Fail on folder structure check with monkeypatch.context() as mp: mp.setattr("os.path.isdir", lambda *a: False) assert theProject.saveProject() is False # Fail on open file with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert theProject.saveProject() is False # Fail on creating .bak file with monkeypatch.context() as mp: mp.setattr("os.replace", causeOSError) assert theProject.saveProject() is False assert os.path.isfile(backFile) is False # Successful save saveCount = theProject.saveCount autoCount = theProject.autoCount assert theProject.saveProject() is True assert theProject.saveCount == saveCount + 1 assert theProject.autoCount == autoCount assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9]) # Check that a second save creates a .bak file assert os.path.isfile(backFile) is True # Successful autosave saveCount = theProject.saveCount autoCount = theProject.autoCount assert theProject.saveProject(autoSave=True) is True assert theProject.saveCount == saveCount assert theProject.autoCount == autoCount + 1 assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9]) # Close test project assert theProject.closeProject()
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 testCoreIndex_ExtractData(mockGUI, fncDir, mockRnd): """Check the index data extraction functions. """ theProject = NWProject(mockGUI) buildTestProject(theProject, fncDir) theIndex = theProject.index theIndex.reIndexHandle("0000000000010") theIndex.reIndexHandle("0000000000011") theIndex.reIndexHandle("0000000000012") theIndex.reIndexHandle("0000000000013") theIndex.reIndexHandle("0000000000014") theIndex.reIndexHandle("0000000000015") theIndex.reIndexHandle("0000000000016") theIndex.reIndexHandle("0000000000017") nHandle = theProject.newFile("Hello", "0000000000010") cHandle = theProject.newFile("Jane", "0000000000012") assert theIndex.getNovelData("", "") is None assert theIndex.getNovelData("0000000000010", "") is None assert theIndex.scanText(cHandle, ("# Jane Smith\n" "@tag: Jane\n")) assert theIndex.scanText(nHandle, ("# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" "% this is a comment\n\n" "This is a story about Jane Smith.\n\n" "Well, not really.\n")) # The novel structure should contain the pointer to the novel file header theKeys = [] for aKey, _, _, _ in theIndex.novelStructure(): theKeys.append(aKey) assert theKeys == [ "0000000000014:T000001", "0000000000016:T000001", "0000000000017:T000001", "%s:T000001" % nHandle, ] # Check that excluded files can be skipped theProject.tree[nHandle].setExported(False) theKeys = [] for aKey, _, _, _ in theIndex.novelStructure(skipExcl=False): theKeys.append(aKey) assert theKeys == [ "0000000000014:T000001", "0000000000016:T000001", "0000000000017:T000001", "%s:T000001" % nHandle, ] theKeys = [] for aKey, _, _, _ in theIndex.novelStructure(skipExcl=True): theKeys.append(aKey) assert theKeys == [ "0000000000014:T000001", "0000000000016:T000001", "0000000000017:T000001", ] # The novel file should have the correct counts cC, wC, pC = theIndex.getCounts(nHandle) assert cC == 62 # Characters in text and title only assert wC == 12 # Words in text and title only assert pC == 2 # Paragraphs in text only # getReferences # ============= # Look up an ivalid handle theRefs = theIndex.getReferences("Not a handle") assert theRefs["@pov"] == [] assert theRefs["@char"] == [] # The novel file should now refer to Jane as @pov and @char theRefs = theIndex.getReferences(nHandle) assert theRefs["@pov"] == ["Jane"] assert theRefs["@char"] == ["Jane"] # getBackReferenceList # ==================== # None handle should return an empty dict assert theIndex.getBackReferenceList(None) == {} # The Title Page file should have no references as it has no tag assert theIndex.getBackReferenceList("0000000000014") == {} # The character file should have a record of the reference from the novel file theRefs = theIndex.getBackReferenceList(cHandle) assert theRefs == {nHandle: "T000001"} # getTagSource # ============ assert theIndex.getTagSource("Jane") == (cHandle, "T000001") assert theIndex.getTagSource("John") == (None, "T000000") # getCounts # ========= # For whole text and sections # Invalid handle or title should return 0s assert theIndex.getCounts("stuff") == (0, 0, 0) assert theIndex.getCounts(nHandle, "stuff") == (0, 0, 0) # Get section counts for a novel file assert theIndex.scanText( nHandle, ("# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" "% this is a comment\n\n" "This is a story about Jane Smith.\n\n" "Well, not really.\n\n" "# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" "% this is a comment\n\n" "This is a story about Jane Smith.\n\n" "Well, not really. She's still awesome though.\n")) # Whole document cC, wC, pC = theIndex.getCounts(nHandle) assert cC == 152 assert wC == 28 assert pC == 4 # First part cC, wC, pC = theIndex.getCounts(nHandle, "T000001") assert cC == 62 assert wC == 12 assert pC == 2 # Second part cC, wC, pC = theIndex.getCounts(nHandle, "T000011") assert cC == 90 assert wC == 16 assert pC == 2 # Get section counts for a note file assert theIndex.scanText( cHandle, ("# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" "% this is a comment\n\n" "This is a story about Jane Smith.\n\n" "Well, not really.\n\n" "# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" "% this is a comment\n\n" "This is a story about Jane Smith.\n\n" "Well, not really. She's still awesome though.\n")) # Whole document cC, wC, pC = theIndex.getCounts(cHandle) assert cC == 152 assert wC == 28 assert pC == 4 # First part cC, wC, pC = theIndex.getCounts(cHandle, "T000001") assert cC == 62 assert wC == 12 assert pC == 2 # Second part cC, wC, pC = theIndex.getCounts(cHandle, "T000011") assert cC == 90 assert wC == 16 assert pC == 2 # Novel Stats # =========== hHandle = theProject.newFile("Chapter", "0000000000010") sHandle = theProject.newFile("Scene One", "0000000000010") tHandle = theProject.newFile("Scene Two", "0000000000010") theProject.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT theProject.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT theProject.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT assert theIndex.scanText(hHandle, "## Chapter One\n\n") assert theIndex.scanText(sHandle, "### Scene One\n\n") assert theIndex.scanText(tHandle, "### Scene Two\n\n") assert [ (h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False) ] == [ ("0000000000014", "T000001"), ("0000000000016", "T000001"), ("0000000000017", "T000001"), (nHandle, "T000001"), (nHandle, "T000011"), (hHandle, "T000001"), (sHandle, "T000001"), (tHandle, "T000001"), ] assert [ (h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=True) ] == [ ("0000000000014", "T000001"), ("0000000000016", "T000001"), ("0000000000017", "T000001"), (hHandle, "T000001"), (sHandle, "T000001"), (tHandle, "T000001"), ] # Add a fake handle to the tree and check that it's ignored theProject.tree._treeOrder.append("0000000000000") assert [ (h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False) ] == [ ("0000000000014", "T000001"), ("0000000000016", "T000001"), ("0000000000017", "T000001"), (nHandle, "T000001"), (nHandle, "T000011"), (hHandle, "T000001"), (sHandle, "T000001"), (tHandle, "T000001"), ] theProject.tree._treeOrder.remove("0000000000000") # Extract stats assert theIndex.getNovelWordCount(skipExcl=False) == 43 assert theIndex.getNovelWordCount(skipExcl=True) == 15 assert theIndex.getNovelTitleCounts(skipExcl=False) == [0, 3, 2, 3, 0] assert theIndex.getNovelTitleCounts(skipExcl=True) == [0, 1, 2, 3, 0] # Table of Contents assert theIndex.getTableOfContents(0, skipExcl=True) == [] assert theIndex.getTableOfContents(1, skipExcl=True) == [ ("0000000000014:T000001", 1, "New Novel", 15), ] assert theIndex.getTableOfContents(2, skipExcl=True) == [ ("0000000000014:T000001", 1, "New Novel", 5), ("0000000000016:T000001", 2, "New Chapter", 4), ("%s:T000001" % hHandle, 2, "Chapter One", 6), ] assert theIndex.getTableOfContents(3, skipExcl=True) == [ ("0000000000014:T000001", 1, "New Novel", 5), ("0000000000016:T000001", 2, "New Chapter", 2), ("0000000000017:T000001", 3, "New Scene", 2), ("%s:T000001" % hHandle, 2, "Chapter One", 2), ("%s:T000001" % sHandle, 3, "Scene One", 2), ("%s:T000001" % tHandle, 3, "Scene Two", 2), ] assert theIndex.getTableOfContents(0, skipExcl=False) == [] assert theIndex.getTableOfContents(1, skipExcl=False) == [ ("0000000000014:T000001", 1, "New Novel", 9), ("%s:T000001" % nHandle, 1, "Hello World!", 12), ("%s:T000011" % nHandle, 1, "Hello World!", 22), ] # Header Word Counts bHandle = "0000000000000" assert theIndex.getHandleWordCounts(bHandle) == [] assert theIndex.getHandleWordCounts(hHandle) == [("%s:T000001" % hHandle, 2)] assert theIndex.getHandleWordCounts(sHandle) == [("%s:T000001" % sHandle, 2)] assert theIndex.getHandleWordCounts(tHandle) == [("%s:T000001" % tHandle, 2)] assert theIndex.getHandleWordCounts(nHandle) == [ ("%s:T000001" % nHandle, 12), ("%s:T000011" % nHandle, 16) ] assert theIndex.saveIndex() is True assert theProject.saveProject() is True assert theProject.closeProject() is True # Header Record bHandle = "0000000000000" assert theIndex.getHandleHeaders(bHandle) == [] assert theIndex.getHandleHeaders(hHandle) == [("T000001", "H2", "Chapter One")] assert theIndex.getHandleHeaders(sHandle) == [("T000001", "H3", "Scene One")] assert theIndex.getHandleHeaders(tHandle) == [("T000001", "H3", "Scene Two")] assert theIndex.getHandleHeaders(nHandle) == [ ("T000001", "H1", "Hello World!"), ("T000011", "H1", "Hello World!") ]