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_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_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 testCoreIndex_ScanThis(nwMinimal, mockGUI): """Test the tag scanner function scanThis. """ theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) is True theIndex = NWIndex(theProject) isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") assert isValid is False isValid, theBits, thePos = theIndex.scanThis("@") assert isValid is False isValid, theBits, thePos = theIndex.scanThis("@:") assert isValid is False isValid, theBits, thePos = theIndex.scanThis(" @a: b") assert isValid is False isValid, theBits, thePos = theIndex.scanThis("@a:") assert isValid is True assert theBits == ["@a"] assert thePos == [0] isValid, theBits, thePos = theIndex.scanThis("@a:b") assert isValid is True assert theBits == ["@a", "b"] assert thePos == [0, 3] isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d") assert isValid is True assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 3, 5, 7] isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d") assert isValid is True assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 5, 9, 13] isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this") assert isValid is True assert theBits == ["@tag", "this", "and this"] assert thePos == [0, 6, 12] assert theProject.closeProject() is True
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 testCoreIndex_ScanThis(mockGUI): """Test the tag scanner function scanThis. """ theProject = NWProject(mockGUI) theIndex = theProject.index isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") assert isValid is False isValid, theBits, thePos = theIndex.scanThis("@") assert isValid is False isValid, theBits, thePos = theIndex.scanThis("@:") assert isValid is False isValid, theBits, thePos = theIndex.scanThis(" @a: b") assert isValid is False isValid, theBits, thePos = theIndex.scanThis("@a:") assert isValid is True assert theBits == ["@a"] assert thePos == [0] isValid, theBits, thePos = theIndex.scanThis("@a:b") assert isValid is True assert theBits == ["@a", "b"] assert thePos == [0, 3] isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d") assert isValid is True assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 3, 5, 7] isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d") assert isValid is True assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 5, 9, 13] isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this") assert isValid is True assert theBits == ["@tag", "this", "and this"] assert thePos == [0, 6, 12] assert theProject.closeProject() is True
def testCoreIndex_CheckThese(nwMinimal, mockGUI): """Test the tag checker function checkThese. """ theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) is True theIndex = NWIndex(theProject) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") nItem = theProject.projTree[nHandle] cItem = theProject.projTree[cHandle] assert theIndex.novelChangedSince(0) is False assert theIndex.notesChangedSince(0) is False assert theIndex.indexChangedSince(0) is False assert theIndex.scanText(cHandle, ("# Jane Smith\n" "@tag: Jane\n" "@tag:\n" "@:\n")) assert theIndex.scanText( nHandle, ( "# Hello World!\n" "@pov: Jane\n" "@invalid: John\n" # Checks for issue #688 )) assert theIndex._tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]} assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!" assert theIndex.getReferences(nHandle, "T000001") == { "@char": [], "@custom": [], "@entity": [], "@focus": [], "@location": [], "@object": [], "@plot": [], "@pov": ["Jane"], "@time": [] } assert theIndex.novelChangedSince(0) is True assert theIndex.notesChangedSince(0) is True assert theIndex.indexChangedSince(0) is True assert theIndex.getHandleHeaderLevel(cHandle) == "H1" assert theIndex.getHandleHeaderLevel(nHandle) == "H1" # Zero Items assert theIndex.checkThese([], cItem) == [] # One Item assert theIndex.checkThese(["@tag"], cItem) == [True] assert theIndex.checkThese(["@who"], cItem) == [False] # Two Items assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True] assert theIndex.checkThese(["@tag", "John"], cItem) == [True, True] assert theIndex.checkThese(["@tag", "Jane"], nItem) == [True, False] assert theIndex.checkThese(["@tag", "John"], nItem) == [True, True] assert theIndex.checkThese(["@pov", "John"], nItem) == [True, False] assert theIndex.checkThese(["@pov", "Jane"], nItem) == [True, True] assert theIndex.checkThese(["@ pov", "Jane"], nItem) == [False, False] assert theIndex.checkThese(["@what", "Jane"], nItem) == [False, False] # Three Items assert theIndex.checkThese(["@tag", "Jane", "John"], cItem) == [True, True, False] assert theIndex.checkThese(["@who", "Jane", "John"], cItem) == [False, False, False] assert theIndex.checkThese(["@pov", "Jane", "John"], nItem) == [True, True, False] assert theProject.closeProject() is True
def testCoreIndex_ScanText(nwMinimal, mockGUI): """Check the index text scanner. """ theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) is True theIndex = NWIndex(theProject) # Some items for fail to scan tests dHandle = theProject.newFolder("Folder", nwItemClass.NOVEL, "a508bb932959c") xHandle = theProject.newFile("No Layout", nwItemClass.NOVEL, "a508bb932959c") xItem = theProject.projTree[xHandle] xItem.setLayout(nwItemLayout.NO_LAYOUT) # Check invalid data assert theIndex.scanText(None, "Hello World!") is False assert theIndex.scanText(dHandle, "Hello World!") is False assert theIndex.scanText(xHandle, "Hello World!") is False xItem.setLayout(nwItemLayout.DOCUMENT) xItem.setParent(None) assert theIndex.scanText(xHandle, "Hello World!") is False # Create the trash folder tHandle = theProject.trashFolder() assert theProject.projTree[tHandle] is not None xItem.setParent(tHandle) assert theIndex.scanText(xHandle, "Hello World!") is False # Create the archive root aHandle = theProject.newRoot("Archive", nwItemClass.ARCHIVE) assert theProject.projTree[aHandle] is not None xItem.setParent(aHandle) assert theIndex.scanText(xHandle, "Hello World!") is False # Make some usable items tHandle = theProject.newFile("Title", nwItemClass.NOVEL, "a508bb932959c") pHandle = theProject.newFile("Page", nwItemClass.NOVEL, "a508bb932959c") nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") sHandle = theProject.newFile("Scene", nwItemClass.NOVEL, "a508bb932959c") # Text Indexing # ============= # Index correct text 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")) assert theIndex._tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]} assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!" # Title Indexing # ============== # Document File assert theIndex.scanText( nHandle, ( "# Title One\n\n" "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" "## Title Two\n\n" "% synopsis: Synopsis Two.\n\n" "Paragraph Two.\n\n" "### Title Three\n\n" "% synopsis: Synopsis Three.\n\n" "Paragraph Three.\n\n" "#### Title Four\n\n" "% synopsis: Synopsis Four.\n\n" "Paragraph Four.\n\n" "##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word "Paragraph Five.\n\n")) assert nHandle not in theIndex._refIndex assert theIndex._fileIndex[nHandle]["T000001"]["level"] == "H1" assert theIndex._fileIndex[nHandle]["T000007"]["level"] == "H2" assert theIndex._fileIndex[nHandle]["T000013"]["level"] == "H3" assert theIndex._fileIndex[nHandle]["T000019"]["level"] == "H4" assert theIndex._fileIndex[nHandle]["T000001"]["title"] == "Title One" assert theIndex._fileIndex[nHandle]["T000007"]["title"] == "Title Two" assert theIndex._fileIndex[nHandle]["T000013"]["title"] == "Title Three" assert theIndex._fileIndex[nHandle]["T000019"]["title"] == "Title Four" assert theIndex._fileIndex[nHandle]["T000001"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[nHandle]["T000007"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[nHandle]["T000013"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[nHandle]["T000019"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[nHandle]["T000001"]["cCount"] == 23 assert theIndex._fileIndex[nHandle]["T000007"]["cCount"] == 23 assert theIndex._fileIndex[nHandle]["T000013"]["cCount"] == 27 assert theIndex._fileIndex[nHandle]["T000019"]["cCount"] == 56 assert theIndex._fileIndex[nHandle]["T000001"]["wCount"] == 4 assert theIndex._fileIndex[nHandle]["T000007"]["wCount"] == 4 assert theIndex._fileIndex[nHandle]["T000013"]["wCount"] == 4 assert theIndex._fileIndex[nHandle]["T000019"]["wCount"] == 9 assert theIndex._fileIndex[nHandle]["T000001"]["pCount"] == 1 assert theIndex._fileIndex[nHandle]["T000007"]["pCount"] == 1 assert theIndex._fileIndex[nHandle]["T000013"]["pCount"] == 1 assert theIndex._fileIndex[nHandle]["T000019"]["pCount"] == 3 assert theIndex._fileIndex[nHandle]["T000001"][ "synopsis"] == "Synopsis One." assert theIndex._fileIndex[nHandle]["T000007"][ "synopsis"] == "Synopsis Two." assert theIndex._fileIndex[nHandle]["T000013"][ "synopsis"] == "Synopsis Three." assert theIndex._fileIndex[nHandle]["T000019"][ "synopsis"] == "Synopsis Four." # Note File assert theIndex.scanText(cHandle, ("# Title One\n\n" "@tag: One\n\n" "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n")) assert cHandle not in theIndex._refIndex assert theIndex._fileIndex[cHandle]["T000001"]["level"] == "H1" assert theIndex._fileIndex[cHandle]["T000001"]["title"] == "Title One" assert theIndex._fileIndex[cHandle]["T000001"]["layout"] == "NOTE" assert theIndex._fileIndex[cHandle]["T000001"]["cCount"] == 23 assert theIndex._fileIndex[cHandle]["T000001"]["wCount"] == 4 assert theIndex._fileIndex[cHandle]["T000001"]["pCount"] == 1 assert theIndex._fileIndex[cHandle]["T000001"][ "synopsis"] == "Synopsis One." # Valid and Invalid References assert theIndex.scanText( sHandle, ( "# Title One\n\n" "@pov: One\n\n" # Valid "@char: Two\n\n" # Invalid tag "@:\n\n" # Invalid line "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n")) assert theIndex._refIndex[sHandle]["T000001"] == ([[3, "@pov", "One"], [5, "@char", "Two"]]) # Special Titles # ============== assert theIndex.scanText(tHandle, ("#! My Project\n\n" ">> By Jane Doe <<\n\n")) assert tHandle not in theIndex._refIndex assert theIndex._fileIndex[tHandle]["T000001"]["level"] == "H1" assert theIndex._fileIndex[tHandle]["T000001"]["title"] == "My Project" assert theIndex._fileIndex[tHandle]["T000001"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[tHandle]["T000001"]["cCount"] == 21 assert theIndex._fileIndex[tHandle]["T000001"]["wCount"] == 5 assert theIndex._fileIndex[tHandle]["T000001"]["pCount"] == 1 assert theIndex._fileIndex[tHandle]["T000001"]["synopsis"] == "" assert theIndex.scanText(tHandle, ("##! Prologue\n\n" "In the beginning there was time ...\n\n")) assert tHandle not in theIndex._refIndex assert theIndex._fileIndex[tHandle]["T000001"]["level"] == "H2" assert theIndex._fileIndex[tHandle]["T000001"]["title"] == "Prologue" assert theIndex._fileIndex[tHandle]["T000001"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[tHandle]["T000001"]["cCount"] == 43 assert theIndex._fileIndex[tHandle]["T000001"]["wCount"] == 8 assert theIndex._fileIndex[tHandle]["T000001"]["pCount"] == 1 assert theIndex._fileIndex[tHandle]["T000001"]["synopsis"] == "" # Page wo/Title # ============= theProject.projTree[pHandle]._layout = nwItemLayout.DOCUMENT assert theIndex.scanText(pHandle, ("This is a page with some text on it.\n\n")) assert pHandle in theIndex._fileIndex assert theIndex._fileIndex[pHandle]["T000000"]["level"] == "H0" assert theIndex._fileIndex[pHandle]["T000000"]["title"] == "" assert theIndex._fileIndex[pHandle]["T000000"]["layout"] == "DOCUMENT" assert theIndex._fileIndex[pHandle]["T000000"]["cCount"] == 36 assert theIndex._fileIndex[pHandle]["T000000"]["wCount"] == 9 assert theIndex._fileIndex[pHandle]["T000000"]["pCount"] == 1 assert theIndex._fileIndex[pHandle]["T000000"]["synopsis"] == "" theProject.projTree[pHandle]._layout = nwItemLayout.NOTE assert theIndex.scanText(pHandle, ("This is a page with some text on it.\n\n")) assert pHandle in theIndex._fileIndex assert theIndex._fileIndex[pHandle]["T000000"]["level"] == "H0" assert theIndex._fileIndex[pHandle]["T000000"]["title"] == "" assert theIndex._fileIndex[pHandle]["T000000"]["layout"] == "NOTE" assert theIndex._fileIndex[pHandle]["T000000"]["cCount"] == 36 assert theIndex._fileIndex[pHandle]["T000000"]["wCount"] == 9 assert theIndex._fileIndex[pHandle]["T000000"]["pCount"] == 1 assert theIndex._fileIndex[pHandle]["T000000"]["synopsis"] == "" assert theProject.closeProject() is True
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_ScanText(mockGUI, fncDir, mockRnd): """Check the index text scanner. """ theProject = NWProject(mockGUI) buildTestProject(theProject, fncDir) theIndex = theProject.index # Some items for fail to scan tests dHandle = theProject.newFolder("Folder", "0000000000010") xHandle = theProject.newFile("No Layout", "0000000000010") xItem = theProject.tree[xHandle] xItem.setLayout(nwItemLayout.NO_LAYOUT) # Check invalid data assert theIndex.scanText(None, "Hello World!") is False assert theIndex.scanText(dHandle, "Hello World!") is False assert theIndex.scanText(xHandle, "Hello World!") is False xItem.setLayout(nwItemLayout.DOCUMENT) xItem.setParent(None) assert theIndex.scanText(xHandle, "Hello World!") is False # Create the trash folder tHandle = theProject.trashFolder() assert theProject.tree[tHandle] is not None xItem.setParent(tHandle) theProject.tree.updateItemData(xItem.itemHandle) assert xItem.itemRoot == tHandle assert xItem.itemClass == nwItemClass.TRASH assert theIndex.scanText(xHandle, "Hello World!") is False # Create the archive root aHandle = theProject.newRoot(nwItemClass.ARCHIVE) assert theProject.tree[aHandle] is not None xItem.setParent(aHandle) theProject.tree.updateItemData(xItem.itemHandle) assert theIndex.scanText(xHandle, "Hello World!") is False # Make some usable items tHandle = theProject.newFile("Title", "0000000000010") pHandle = theProject.newFile("Page", "0000000000010") nHandle = theProject.newFile("Hello", "0000000000010") cHandle = theProject.newFile("Jane", "0000000000012") sHandle = theProject.newFile("Scene", "0000000000010") # Text Indexing # ============= # Index correct text 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")) assert theIndex._tagsIndex.tagHandle("Jane") == cHandle assert theIndex._tagsIndex.tagHeading("Jane") == "T000001" assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER" assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!" # Title Indexing # ============== # Document File assert theIndex.scanText( nHandle, ( "# Title One\n\n" "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" "## Title Two\n\n" "% synopsis: Synopsis Two.\n\n" "Paragraph Two.\n\n" "### Title Three\n\n" "% synopsis: Synopsis Three.\n\n" "Paragraph Three.\n\n" "#### Title Four\n\n" "% synopsis: Synopsis Four.\n\n" "Paragraph Four.\n\n" "##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word "Paragraph Five.\n\n")) assert theIndex._itemIndex[nHandle]["T000001"].references == {} assert theIndex._itemIndex[nHandle]["T000007"].references == {} assert theIndex._itemIndex[nHandle]["T000013"].references == {} assert theIndex._itemIndex[nHandle]["T000019"].references == {} assert theIndex._itemIndex[nHandle]["T000001"].level == "H1" assert theIndex._itemIndex[nHandle]["T000007"].level == "H2" assert theIndex._itemIndex[nHandle]["T000013"].level == "H3" assert theIndex._itemIndex[nHandle]["T000019"].level == "H4" assert theIndex._itemIndex[nHandle]["T000001"].title == "Title One" assert theIndex._itemIndex[nHandle]["T000007"].title == "Title Two" assert theIndex._itemIndex[nHandle]["T000013"].title == "Title Three" assert theIndex._itemIndex[nHandle]["T000019"].title == "Title Four" assert theIndex._itemIndex[nHandle]["T000001"].charCount == 23 assert theIndex._itemIndex[nHandle]["T000007"].charCount == 23 assert theIndex._itemIndex[nHandle]["T000013"].charCount == 27 assert theIndex._itemIndex[nHandle]["T000019"].charCount == 56 assert theIndex._itemIndex[nHandle]["T000001"].wordCount == 4 assert theIndex._itemIndex[nHandle]["T000007"].wordCount == 4 assert theIndex._itemIndex[nHandle]["T000013"].wordCount == 4 assert theIndex._itemIndex[nHandle]["T000019"].wordCount == 9 assert theIndex._itemIndex[nHandle]["T000001"].paraCount == 1 assert theIndex._itemIndex[nHandle]["T000007"].paraCount == 1 assert theIndex._itemIndex[nHandle]["T000013"].paraCount == 1 assert theIndex._itemIndex[nHandle]["T000019"].paraCount == 3 assert theIndex._itemIndex[nHandle]["T000001"].synopsis == "Synopsis One." assert theIndex._itemIndex[nHandle]["T000007"].synopsis == "Synopsis Two." assert theIndex._itemIndex[nHandle][ "T000013"].synopsis == "Synopsis Three." assert theIndex._itemIndex[nHandle]["T000019"].synopsis == "Synopsis Four." # Note File assert theIndex.scanText(cHandle, ("# Title One\n\n" "@tag: One\n\n" "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n")) assert theIndex._itemIndex[cHandle]["T000001"].references == {} assert theIndex._itemIndex[cHandle]["T000001"].level == "H1" assert theIndex._itemIndex[cHandle]["T000001"].title == "Title One" assert theIndex._itemIndex[cHandle]["T000001"].charCount == 23 assert theIndex._itemIndex[cHandle]["T000001"].wordCount == 4 assert theIndex._itemIndex[cHandle]["T000001"].paraCount == 1 assert theIndex._itemIndex[cHandle]["T000001"].synopsis == "Synopsis One." # Valid and Invalid References assert theIndex.scanText( sHandle, ( "# Title One\n\n" "@pov: One\n\n" # Valid "@char: Two\n\n" # Invalid tag "@:\n\n" # Invalid line "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n")) assert theIndex._itemIndex[sHandle]["T000001"].references == { "One": {"@pov"}, "Two": {"@char"} } # Special Titles # ============== assert theIndex.scanText(tHandle, ("#! My Project\n\n" ">> By Jane Doe <<\n\n")) assert theIndex._itemIndex[cHandle]["T000001"].references == {} assert theIndex._itemIndex[tHandle]["T000001"].level == "H1" assert theIndex._itemIndex[tHandle]["T000001"].title == "My Project" assert theIndex._itemIndex[tHandle]["T000001"].charCount == 21 assert theIndex._itemIndex[tHandle]["T000001"].wordCount == 5 assert theIndex._itemIndex[tHandle]["T000001"].paraCount == 1 assert theIndex._itemIndex[tHandle]["T000001"].synopsis == "" assert theIndex.scanText(tHandle, ("##! Prologue\n\n" "In the beginning there was time ...\n\n")) assert theIndex._itemIndex[cHandle]["T000001"].references == {} assert theIndex._itemIndex[tHandle]["T000001"].level == "H2" assert theIndex._itemIndex[tHandle]["T000001"].title == "Prologue" assert theIndex._itemIndex[tHandle]["T000001"].charCount == 43 assert theIndex._itemIndex[tHandle]["T000001"].wordCount == 8 assert theIndex._itemIndex[tHandle]["T000001"].paraCount == 1 assert theIndex._itemIndex[tHandle]["T000001"].synopsis == "" # Page wo/Title # ============= theProject.tree[pHandle]._layout = nwItemLayout.DOCUMENT assert theIndex.scanText(pHandle, ("This is a page with some text on it.\n\n")) assert theIndex._itemIndex[pHandle]["T000000"].references == {} assert theIndex._itemIndex[pHandle]["T000000"].level == "H0" assert theIndex._itemIndex[pHandle]["T000000"].title == "" assert theIndex._itemIndex[pHandle]["T000000"].charCount == 36 assert theIndex._itemIndex[pHandle]["T000000"].wordCount == 9 assert theIndex._itemIndex[pHandle]["T000000"].paraCount == 1 assert theIndex._itemIndex[pHandle]["T000000"].synopsis == "" theProject.tree[pHandle]._layout = nwItemLayout.NOTE assert theIndex.scanText(pHandle, ("This is a page with some text on it.\n\n")) assert theIndex._itemIndex[pHandle]["T000000"].references == {} assert theIndex._itemIndex[pHandle]["T000000"].level == "H0" assert theIndex._itemIndex[pHandle]["T000000"].title == "" assert theIndex._itemIndex[pHandle]["T000000"].charCount == 36 assert theIndex._itemIndex[pHandle]["T000000"].wordCount == 9 assert theIndex._itemIndex[pHandle]["T000000"].paraCount == 1 assert theIndex._itemIndex[pHandle]["T000000"].synopsis == "" assert theProject.closeProject() is True
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir): """Test core functionality of scaning, saving, loading and checking the index cache file. """ projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json") compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") theProject = NWProject(mockGUI) assert theProject.openProject(nwLipsum) theIndex = NWIndex(theProject) assert repr(theIndex) == "<NWIndex project='Lorem Ipsum'>" notIndexable = { "b3643d0f92e32": False, # Novel ROOT "45e6b01ca35c1": False, # Chapter One FOLDER "6bd935d2490cd": False, # Chapter Two FOLDER "67a8707f2f249": False, # Character ROOT "6c6afb1247750": False, # Plot ROOT "60bdf227455cc": False, # World ROOT } for tItem in theProject.tree: assert theIndex.reIndexHandle(tItem.itemHandle) is notIndexable.get( tItem.itemHandle, True) assert theIndex.reIndexHandle(None) is False # Make the save fail with monkeypatch.context() as mp: mp.setattr("builtins.open", causeException) assert theIndex.saveIndex() is False # Make the save pass assert theIndex.saveIndex() is True # Take a copy of the index tagIndex = str(theIndex._tagsIndex.packData()) itemsIndex = str(theIndex._itemIndex.packData()) # Delete a handle assert theIndex._tagsIndex["Bod"] is not None assert theIndex._itemIndex["4c4f28287af27"] is not None theIndex.deleteHandle("4c4f28287af27") assert theIndex._tagsIndex["Bod"] is None assert theIndex._itemIndex["4c4f28287af27"] is None # Clear the index theIndex.clearIndex() assert theIndex._tagsIndex._tags == {} assert theIndex._itemIndex._items == {} # Make the load fail with monkeypatch.context() as mp: mp.setattr(json, "load", causeException) assert theIndex.loadIndex() is False assert theIndex.indexBroken is True # Make the load pass assert theIndex.loadIndex() is True assert theIndex.indexBroken is False assert str(theIndex._tagsIndex.packData()) == tagIndex assert str(theIndex._itemIndex.packData()) == itemsIndex # Check File copyfile(projFile, testFile) assert cmpFiles(testFile, compFile) # Write an emtpy index file and load it writeFile(projFile, "{}") assert theIndex.loadIndex() is False assert theIndex.indexBroken is True # Write an index file that passes loading, but is still empty writeFile(projFile, '{"tagsIndex": {}, "itemIndex": {}}') assert theIndex.loadIndex() is True assert theIndex.indexBroken is False # Check that the index is re-populated assert "04468803b92e1" in theIndex._itemIndex assert "2426c6f0ca922" in theIndex._itemIndex assert "441420a886d82" in theIndex._itemIndex assert "47666c91c7ccf" in theIndex._itemIndex assert "4c4f28287af27" in theIndex._itemIndex assert "846352075de7d" in theIndex._itemIndex assert "88243afbe5ed8" in theIndex._itemIndex assert "88d59a277361b" in theIndex._itemIndex assert "8c58a65414c23" in theIndex._itemIndex assert "db7e733775d4d" in theIndex._itemIndex assert "eb103bc70c90c" in theIndex._itemIndex assert "f8c0562e50f1b" in theIndex._itemIndex assert "f96ec11c6a3da" in theIndex._itemIndex assert "fb609cd8319dc" in theIndex._itemIndex assert "7a992350f3eb6" in theIndex._itemIndex # Finalise assert theProject.closeProject() is True
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!") ]
def testCoreProject_OldFormat(mockGUI, 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(mockGUI) # Create mock 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 mock 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, "ToC.txt"))
def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd): """Test the tag checker function checkThese. """ theProject = NWProject(mockGUI) buildTestProject(theProject, fncDir) theIndex = theProject.index nHandle = theProject.newFile("Hello", "0000000000010") cHandle = theProject.newFile("Jane", "0000000000012") nItem = theProject.tree[nHandle] cItem = theProject.tree[cHandle] assert theIndex.rootChangedSince("0000000000010", 0) is False assert theIndex.indexChangedSince(0) is False assert theIndex.scanText(cHandle, ("# Jane Smith\n" "@tag: Jane\n" "@tag:\n" "@:\n")) assert theIndex.scanText( nHandle, ( "# Hello World!\n" "@pov: Jane\n" "@invalid: John\n" # Checks for issue #688 )) assert theIndex._tagsIndex.tagHandle("Jane") == cHandle assert theIndex._tagsIndex.tagHeading("Jane") == "T000001" assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER" assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!" assert theIndex.getReferences(nHandle, "T000001") == { "@char": [], "@custom": [], "@entity": [], "@focus": [], "@location": [], "@object": [], "@plot": [], "@pov": ["Jane"], "@time": [] } assert theIndex.rootChangedSince("0000000000010", 0) is True assert theIndex.indexChangedSince(0) is True assert theIndex.getHandleHeaderLevel(cHandle) == "H1" assert theIndex.getHandleHeaderLevel(nHandle) == "H1" # Zero Items assert theIndex.checkThese([], cItem) == [] # One Item assert theIndex.checkThese(["@tag"], cItem) == [True] assert theIndex.checkThese(["@who"], cItem) == [False] # Two Items assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True] assert theIndex.checkThese(["@tag", "John"], cItem) == [True, True] assert theIndex.checkThese(["@tag", "Jane"], nItem) == [True, False] assert theIndex.checkThese(["@tag", "John"], nItem) == [True, True] assert theIndex.checkThese(["@pov", "John"], nItem) == [True, False] assert theIndex.checkThese(["@pov", "Jane"], nItem) == [True, True] assert theIndex.checkThese(["@ pov", "Jane"], nItem) == [False, False] assert theIndex.checkThese(["@what", "Jane"], nItem) == [False, False] # Three Items assert theIndex.checkThese(["@tag", "Jane", "John"], cItem) == [True, True, False] assert theIndex.checkThese(["@who", "Jane", "John"], cItem) == [False, False, False] assert theIndex.checkThese(["@pov", "Jane", "John"], nItem) == [True, True, False] assert theProject.closeProject() is True
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir): """Test core functionality of scaning, saving, loading and checking the index cache file. """ projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json") compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwLipsum) theIndex = NWIndex(theProject) notIndexable = { "b3643d0f92e32": False, # Novel ROOT "45e6b01ca35c1": False, # Chapter One FOLDER "6bd935d2490cd": False, # Chapter Two FOLDER "67a8707f2f249": False, # Character ROOT "6c6afb1247750": False, # Plot ROOT "60bdf227455cc": False, # World ROOT } for tItem in theProject.projTree: assert theIndex.reIndexHandle(tItem.itemHandle) is notIndexable.get( tItem.itemHandle, True) assert theIndex.reIndexHandle(None) is False # Make the save fail with monkeypatch.context() as mp: mp.setattr("builtins.open", causeException) assert theIndex.saveIndex() is False # Make the save pass assert theIndex.saveIndex() is True # Take a copy of the index tagIndex = str(theIndex._tagIndex) refIndex = str(theIndex._refIndex) fileIndex = str(theIndex._fileIndex) textCounts = str(theIndex._fileMeta) # Delete a handle assert theIndex._tagIndex.get("Bod", None) is not None assert theIndex._refIndex.get("4c4f28287af27", None) is not None assert theIndex._fileIndex.get("4c4f28287af27", None) is not None assert theIndex._fileMeta.get("4c4f28287af27", None) is not None theIndex.deleteHandle("4c4f28287af27") assert theIndex._tagIndex.get("Bod", None) is None assert theIndex._refIndex.get("4c4f28287af27", None) is None assert theIndex._fileIndex.get("4c4f28287af27", None) is None assert theIndex._fileMeta.get("4c4f28287af27", None) is None # Clear the index theIndex.clearIndex() assert theIndex._tagIndex == {} assert theIndex._refIndex == {} assert theIndex._fileIndex == {} assert theIndex._fileMeta == {} # Make the load fail with monkeypatch.context() as mp: mp.setattr(json, "load", causeException) assert theIndex.loadIndex() is False # Make the load pass assert theIndex.loadIndex() is True assert str(theIndex._tagIndex) == tagIndex assert str(theIndex._refIndex) == refIndex assert str(theIndex._fileIndex) == fileIndex assert str(theIndex._fileMeta) == textCounts # Break the index and check that we notice assert theIndex.indexBroken is False theIndex._tagIndex["Bod"].append("Stuff") theIndex._checkIndex() assert theIndex.indexBroken is True # Finalise assert theProject.closeProject() is True copyfile(projFile, testFile) assert cmpFiles(testFile, compFile)
def testCoreIndex_ExtractData(nwMinimal, mockGUI): """Check the index data extraction functions. """ theProject = NWProject(mockGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) is True theIndex = NWIndex(theProject) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") assert theIndex.getNovelData("", "") is None assert theIndex.getNovelData("a508bb932959c", "") 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 == ["%s:T000001" % nHandle] # Check that excluded files can be skipped theProject.projTree[nHandle].setExported(False) theKeys = [] for aKey, _, _, _ in theIndex.novelStructure(skipExcluded=False): theKeys.append(aKey) assert theKeys == ["%s:T000001" % nHandle] theKeys = [] for aKey, _, _, _ in theIndex.novelStructure(skipExcluded=True): theKeys.append(aKey) assert theKeys == [] theKeys = [] for aKey, _, _, _ in theIndex.novelStructure(): theKeys.append(aKey) assert theKeys == [] # 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 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, 2, "T000001") assert theIndex.getTagSource("John") == (None, 0, "T000000") # getCounts # ========= # For whole text and sections # 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", nwItemClass.NOVEL, "a508bb932959c") sHandle = theProject.newFile("Scene One", nwItemClass.NOVEL, "a508bb932959c") tHandle = theProject.newFile("Scene Two", nwItemClass.NOVEL, "a508bb932959c") theProject.projTree[hHandle].itemLayout == nwItemLayout.DOCUMENT theProject.projTree[sHandle].itemLayout == nwItemLayout.DOCUMENT theProject.projTree[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 theIndex._listNovelHandles(False) == [ nHandle, hHandle, sHandle, tHandle ] assert theIndex._listNovelHandles(True) == [hHandle, sHandle, tHandle] # Add a fake handle to the tree and check that it's ignored theProject.projTree._treeOrder.append("0000000000000") assert theIndex._listNovelHandles(False) == [ nHandle, hHandle, sHandle, tHandle ] theProject.projTree._treeOrder.remove("0000000000000") # Extract stats assert theIndex.getNovelWordCount(False) == 34 assert theIndex.getNovelWordCount(True) == 6 assert theIndex.getNovelTitleCounts(False) == [0, 2, 1, 2, 0] assert theIndex.getNovelTitleCounts(True) == [0, 0, 1, 2, 0] # Table of Contents assert theIndex.getTableOfContents(0, True) == [] assert theIndex.getTableOfContents(1, True) == [] assert theIndex.getTableOfContents(2, True) == [ ("%s:T000001" % hHandle, 2, "Chapter One", 6), ] assert theIndex.getTableOfContents(3, True) == [ ("%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, False) == [] assert theIndex.getTableOfContents(1, False) == [ ("%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 theProject.closeProject() # 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!") ]
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()