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 testCoreTree_MakeHandles(mockGUI): """Test generating item handles. """ random.seed(42) theProject = NWProject(mockGUI) theTree = NWTree(theProject) handles = [ "1c803a3b1799d", "bdd6406671ad1", "3eb1346685257", "23b8c392456de" ] random.seed(42) tHandle = theTree._makeHandle() assert tHandle == handles[0] theTree._projTree[handles[0]] = None # Add the next in line to the project to force duplicate theTree._projTree[handles[1]] = None tHandle = theTree._makeHandle() assert tHandle == handles[2] theTree._projTree[handles[2]] = None # Reset the seed to force collissions, which should still end up # returning the next handle in the sequence random.seed(42) tHandle = theTree._makeHandle() assert tHandle == handles[3]
def testCoreIndex_CheckTagIndex(mockGUI): """Test the tag index checker. """ theProject = NWProject(mockGUI) theIndex = NWIndex(theProject) # Valid Index theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], "Jane": [3, "bb2c23b3c42cc", "CHARACTER", "T000001"], } assert theIndex._checkTagIndex() is None # Wrong Key Type theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], 123456: [3, "bb2c23b3c42cc", "CHARACTER", "T000001"], } with pytest.raises(KeyError): theIndex._checkTagIndex() # Wrong Length theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], "Jane": [3, "bb2c23b3c42cc", "CHARACTER", "T000001", "Stuff"], } with pytest.raises(IndexError): theIndex._checkTagIndex() # Wrong Type of Entry 0 theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], "Jane": ["3", "bb2c23b3c42cc", "CHARACTER", "T000001"], } with pytest.raises(ValueError): theIndex._checkTagIndex() # Wrong Type of Entry 1 theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], "Jane": [3, 0xbb2c23b3c42cc, "CHARACTER", "T000001"], } with pytest.raises(ValueError): theIndex._checkTagIndex() # Wrong Type of Entry 2 theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], "Jane": [3, "bb2c23b3c42cc", "INVALID_CLASS", "T000001"], } with pytest.raises(ValueError): theIndex._checkTagIndex() # Wrong Type of Entry 3 theIndex._tagIndex = { "John": [3, "14298de4d9524", "CHARACTER", "T000001"], "Jane": [3, "bb2c23b3c42cc", "CHARACTER", "INVALID"], } with pytest.raises(ValueError): theIndex._checkTagIndex()
def testCoreTree_Reorder(mockGUI, mockItems): """Test changing tree order. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) aHandle = [] for tHandle, pHandle, nwItem in mockItems: aHandle.append(tHandle) theTree.append(tHandle, pHandle, nwItem) assert len(theTree) == len(mockItems) bHandle = aHandle.copy() bHandle[2], bHandle[3] = bHandle[3], bHandle[2] assert aHandle != bHandle assert theTree.handles() == aHandle theTree.setOrder(bHandle) assert theTree.handles() == bHandle theTree.setOrder(bHandle + ["stuff"]) assert theTree.handles() == bHandle theTree._treeOrder.append("stuff") theTree.setOrder(bHandle) assert theTree.handles() == bHandle
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 testCoreIndex_CheckFileMeta(mockGUI): """Test the file meta checker. """ theProject = NWProject(mockGUI) theIndex = NWIndex(theProject) # Valid Index theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "974e400180a99": ["H0", 210, 40, 2], } assert theIndex._checkFileMeta() is None # Invalid Handle theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "h74e400180a99": ["H0", 210, 40, 2], } with pytest.raises(KeyError): theIndex._checkFileMeta() # Wrong Length theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "974e400180a99": ["H0", 210, 40, 2, 8], } with pytest.raises(IndexError): theIndex._checkFileMeta() # Content of Entry 0 theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "974e400180a99": ["XXX", 210, 40, 2], } with pytest.raises(ValueError): theIndex._checkFileMeta() # Type of Entry 1 theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "974e400180a99": ["H0", "210", 40, 2], } with pytest.raises(ValueError): theIndex._checkFileMeta() # Type of Entry 2 theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "974e400180a99": ["H0", 210, "40", 2], } with pytest.raises(ValueError): theIndex._checkFileMeta() # Type of Entry 3 theIndex._fileMeta = { "53b69b83cdafc": ["H0", 72, 15, 2], "974e400180a99": ["H0", 210, 40, "2"], } with pytest.raises(ValueError): theIndex._checkFileMeta()
def testCoreTree_Methods(mockGUI, mockItems): """Test various class methods. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) for tHandle, pHandle, nwItem in mockItems: theTree.append(tHandle, pHandle, nwItem) assert len(theTree) == len(mockItems) # Chech type assert theTree.checkType("blabla", nwItemType.FILE) is False assert theTree.checkType("b000000000001", nwItemType.FILE) is False assert theTree.checkType("c000000000001", nwItemType.FILE) is True # Root item lookup theTree._treeRoots.append("stuff") assert theTree.findRoot(nwItemClass.WORLD) is None assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001" assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004" # Check for root uniqueness assert theTree.checkRootUnique(nwItemClass.CUSTOM) assert theTree.checkRootUnique(nwItemClass.WORLD) assert not theTree.checkRootUnique(nwItemClass.NOVEL) assert not theTree.checkRootUnique(nwItemClass.CHARACTER) # Find root item of child item assert theTree.getRootItem("b000000000001").itemHandle == "a000000000001" assert theTree.getRootItem("c000000000001").itemHandle == "a000000000001" assert theTree.getRootItem("c000000000002").itemHandle == "a000000000001" assert theTree.getRootItem("stuff") is None # Get item path assert theTree.getItemPath("stuff") == [] assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001", "a000000000001" ] # Break the folder parent handle theTree["b000000000001"]._parent = "stuff" assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001" ] theTree["b000000000001"]._parent = "a000000000001" assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001", "a000000000001" ] # Change file layout assert theTree.setFileItemLayout("stuff", nwItemLayout.DOCUMENT) is False assert theTree.setFileItemLayout("b000000000001", nwItemLayout.DOCUMENT) is False assert theTree.setFileItemLayout("c000000000001", "stuff") is False assert theTree.setFileItemLayout("c000000000001", nwItemLayout.NOTE) is True assert theTree["c000000000001"].itemLayout == nwItemLayout.NOTE
def testCoreProject_AccessItems(nwMinimal, mockGUI): """Test helper functions for the project folder. """ theProject = NWProject(mockGUI) theProject.openProject(nwMinimal) # Move Novel ROOT to after its files oldOrder = [ "a508bb932959c", # ROOT: Novel "a35baf2e93843", # FILE: Title Page "a6d311a93600a", # FOLDER: New Chapter "f5ab3e30151e1", # FILE: New Chapter "8c659a11cd429", # FILE: New Scene "7695ce551d265", # ROOT: Plot "afb3043c7b2b3", # ROOT: Characters "9d5247ab588e0", # ROOT: World ] newOrder = [ "a35baf2e93843", # FILE: Title Page "f5ab3e30151e1", # FILE: New Chapter "8c659a11cd429", # FILE: New Scene "a6d311a93600a", # FOLDER: New Chapter "a508bb932959c", # ROOT: Novel "7695ce551d265", # ROOT: Plot "afb3043c7b2b3", # ROOT: Characters "9d5247ab588e0", # ROOT: World ] assert theProject.projTree.handles() == oldOrder assert theProject.setTreeOrder(newOrder) assert theProject.projTree.handles() == newOrder # Add a non-existing item theProject.projTree._treeOrder.append("01234567789abc") # Add an item with a non-existent parent nHandle = theProject.newFile("Test File", nwItemClass.NOVEL, "a6d311a93600a") theProject.projTree[nHandle].setParent("cba9876543210") assert theProject.projTree[nHandle].itemParent == "cba9876543210" retOrder = [] for tItem in theProject.getProjectItems(): retOrder.append(tItem.itemHandle) assert retOrder == [ "a508bb932959c", # ROOT: Novel "7695ce551d265", # ROOT: Plot "afb3043c7b2b3", # ROOT: Characters "9d5247ab588e0", # ROOT: World nHandle, # FILE: Test File "a35baf2e93843", # FILE: Title Page "a6d311a93600a", # FOLDER: New Chapter "f5ab3e30151e1", # FILE: New Chapter "8c659a11cd429", # FILE: New Scene ] assert theProject.projTree[nHandle].itemParent is None
def testCoreProject_LockFile(monkeypatch, fncDir, mockGUI): """Test lock file functions for the project folder. """ theProject = NWProject(mockGUI) lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK) # No project assert theProject._writeLockFile() is False assert theProject._readLockFile() == ["ERROR"] assert theProject._clearLockFile() is False theProject.projPath = fncDir theProject.mainConf.hostName = "TestHost" theProject.mainConf.osType = "TestOS" theProject.mainConf.kernelVer = "1.0" # Block open with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert theProject._writeLockFile() is False # Write lock file with monkeypatch.context() as mp: mp.setattr("novelwriter.core.project.time", lambda: 123.4) assert theProject._writeLockFile() is True assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n" # Block open with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert theProject._readLockFile() == ["ERROR"] # Read lock file assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"] # Block unlink with monkeypatch.context() as mp: mp.setattr("os.unlink", causeOSError) assert os.path.isfile(lockFile) assert theProject._clearLockFile() is False assert os.path.isfile(lockFile) # Clear file assert os.path.isfile(lockFile) assert theProject._clearLockFile() is True assert not os.path.isfile(lockFile) # Read again, no file assert theProject._readLockFile() == [] # Read an invalid lock file writeFile(lockFile, "A\nB") assert theProject._readLockFile() == ["ERROR"] assert theProject._clearLockFile() is True
def testCoreTree_XMLPackUnpack(mockGUI, mockItems): """Test packing and unpacking the tree to and from XML. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) for tHandle, pHandle, nwItem in mockItems: theTree.append(tHandle, pHandle, nwItem) theTree.updateItemData(tHandle) assert len(theTree) == len(mockItems) nwXML = etree.Element("novelWriterXML") theTree.packXML(nwXML) assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == ( b'<novelWriterXML>' b'<content count="8">' b'<item handle="a000000000001" parent="None" root="a000000000001" order="0" type="ROOT" ' b'class="NOVEL"><meta expanded="True"/><name status="s000000" ' b'import="i000004">Novel</name></item>' b'<item handle="b000000000001" parent="a000000000001" root="a000000000001" order="0" ' b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="s000000" ' b'import="i000004">Act One</name></item>' b'<item handle="c000000000001" parent="b000000000001" root="a000000000001" order="0" ' b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta expanded="False" charCount="300" ' b'wordCount="50" paraCount="2" cursorPos="0"/><name status="s000000" import="i000004" ' b'exported="True">Chapter One</name></item>' b'<item handle="c000000000002" parent="b000000000001" root="a000000000001" order="0" ' b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta expanded="False" charCount="3000" ' b'wordCount="500" paraCount="20" cursorPos="0"/><name status="s000000" import="i000004" ' b'exported="True">Scene One</name></item>' b'<item handle="a000000000002" parent="None" root="a000000000002" order="0" type="ROOT" ' b'class="ARCHIVE"><meta expanded="False"/><name status="s000000" ' b'import="i000004">Outtakes</name></item>' b'<item handle="a000000000003" parent="None" root="a000000000003" order="0" type="ROOT" ' b'class="TRASH"><meta expanded="False"/><name status="s000000" ' b'import="i000004">Trash</name></item>' b'<item handle="a000000000004" parent="None" root="a000000000004" order="0" type="ROOT" ' b'class="CHARACTER"><meta expanded="True"/><name status="s000000" ' b'import="i000004">Characters</name></item>' b'<item handle="b000000000002" parent="a000000000004" root="a000000000004" order="0" ' b'type="FILE" class="CHARACTER" layout="NOTE"><meta expanded="False" charCount="2000" ' b'wordCount="400" paraCount="16" cursorPos="0"/><name status="s000000" import="i000004" ' b'exported="True">Jane Doe</name></item>' b'</content>' b'</novelWriterXML>') theTree.clear() assert len(theTree) == 0 assert not theTree.unpackXML(nwXML) assert theTree.unpackXML(nwXML[0]) assert len(theTree) == len(mockItems)
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 testCoreTree_Stats(mockGUI, mockItems): """Test project stats methods. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) for tHandle, pHandle, nwItem in mockItems: theTree.append(tHandle, pHandle, nwItem) assert len(theTree) == len(mockItems) theTree._treeOrder.append("stuff") # Count Words novelWords, noteWords = theTree.sumWords() assert novelWords == 550 assert noteWords == 400
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_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 testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir): """Test writing the ToC.txt file. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) for tHandle, pHandle, nwItem in mockItems: theTree.append(tHandle, pHandle, nwItem) theTree.updateItemData(tHandle) assert len(theTree) == len(mockItems) theTree._treeOrder.append("stuff") def mockIsFile(fileName): """Return True for items that are files in novelWriter and should thus also be files in the project folder structure. """ dItem = theTree[fileName[8:21]] assert dItem is not None return dItem.itemType == nwItemType.FILE monkeypatch.setattr("os.path.isfile", mockIsFile) theProject.projContent = "content" theProject.projPath = None assert not theTree.writeToCFile() theProject.projPath = tmpDir assert theTree.writeToCFile() pathA = os.path.join("content", "c000000000001.nwd") pathB = os.path.join("content", "c000000000002.nwd") pathC = os.path.join("content", "b000000000002.nwd") assert readFile(os.path.join(tmpDir, nwFiles.TOC_TXT)) == ( "\n" "Table of Contents\n" "=================\n" "\n" "File Name Class Layout Document Label\n" "--------------------------------------------------------------\n" f"{pathA} NOVEL DOCUMENT Chapter One\n" f"{pathB} NOVEL DOCUMENT Scene One\n" f"{pathC} CHARACTER NOTE Jane Doe\n")
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 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_Helpers(monkeypatch, fncDir, mockGUI): """Test helper functions for the project folder. """ theProject = NWProject(mockGUI) # No path assert theProject.ensureFolderStructure() is False # Set the correct dir theProject.projPath = fncDir # Block user's home folder with monkeypatch.context() as mp: mp.setattr("os.path.expanduser", lambda *a, **k: fncDir) assert theProject.ensureFolderStructure() is False # Create a file to block meta folder metaDir = os.path.join(fncDir, "meta") writeFile(metaDir, "stuff") assert theProject.ensureFolderStructure() is False os.unlink(metaDir) # Create a file to block cache folder cacheDir = os.path.join(fncDir, "cache") writeFile(cacheDir, "stuff") assert theProject.ensureFolderStructure() is False os.unlink(cacheDir) # Create a file to block content folder contentDir = os.path.join(fncDir, "content") writeFile(contentDir, "stuff") assert theProject.ensureFolderStructure() is False os.unlink(contentDir) # Now, do it right assert theProject.ensureFolderStructure() is True assert os.path.isdir(metaDir) assert os.path.isdir(cacheDir) assert os.path.isdir(contentDir)
def testCoreTree_MakeHandles(monkeypatch, mockGUI): """Test generating item handles. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) theTree.setSeed(42) tHandle = theTree._makeHandle() assert tHandle == "73475cb40a568" # Add the next in line to the project to force duplicate theTree._projTree["44cb730c42048"] = None tHandle = theTree._makeHandle() assert tHandle == "71ee45a3c0db9" # Fix the time() function and force a handle collission theTree.setSeed(None) theTree._handleCount = 0 monkeypatch.setattr("novelwriter.core.tree.time", lambda: 123.4) tHandle = theTree._makeHandle() theTree._projTree[tHandle] = None newSeed = "123.4_0_" assert tHandle == sha256(newSeed.encode()).hexdigest()[0:13] tHandle = theTree._makeHandle() theTree._projTree[tHandle] = None newSeed = "123.4_1_" assert tHandle == sha256(newSeed.encode()).hexdigest()[0:13] # Reset the count and the handle for 0 and 1 should be duplicates # which forces the function to add the '!' theTree._handleCount = 0 tHandle = theTree._makeHandle() theTree._projTree[tHandle] = None newSeed = "123.4_1_!" assert tHandle == sha256(newSeed.encode()).hexdigest()[0:13]
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 mockItems(mockGUI): """Create a list of mock items. """ theProject = NWProject(mockGUI) itemA = NWItem(theProject) itemA._name = "Novel" itemA._type = nwItemType.ROOT itemA._class = nwItemClass.NOVEL itemA._expanded = True itemB = NWItem(theProject) itemB._name = "Act One" itemB._type = nwItemType.FOLDER itemB._class = nwItemClass.NOVEL itemB._expanded = True itemC = NWItem(theProject) itemC._name = "Chapter One" itemC._type = nwItemType.FILE itemC._class = nwItemClass.NOVEL itemC._layout = nwItemLayout.DOCUMENT itemC._charCount = 300 itemC._wordCount = 50 itemC._paraCount = 2 itemD = NWItem(theProject) itemD._name = "Scene One" itemD._type = nwItemType.FILE itemD._class = nwItemClass.NOVEL itemD._layout = nwItemLayout.DOCUMENT itemD._charCount = 3000 itemD._wordCount = 500 itemD._paraCount = 20 itemE = NWItem(theProject) itemE._name = "Outtakes" itemE._type = nwItemType.ROOT itemE._class = nwItemClass.ARCHIVE itemE._expanded = False itemF = NWItem(theProject) itemF._name = "Trash" itemF._type = nwItemType.TRASH itemF._class = nwItemClass.TRASH itemF._expanded = False itemG = NWItem(theProject) itemG._name = "Characters" itemG._type = nwItemType.ROOT itemG._class = nwItemClass.CHARACTER itemG._expanded = True itemH = NWItem(theProject) itemH._name = "Jane Doe" itemH._type = nwItemType.FILE itemH._class = nwItemClass.CHARACTER itemH._layout = nwItemLayout.NOTE itemH._charCount = 2000 itemH._wordCount = 400 itemH._paraCount = 16 theItems = [ ("a000000000001", None, itemA), ("b000000000001", "a000000000001", itemB), ("c000000000001", "b000000000001", itemC), ("c000000000002", "b000000000001", itemD), ("a000000000002", None, itemE), ("a000000000003", None, itemF), ("a000000000004", None, itemG), ("b000000000002", "a000000000002", itemH), ] return theItems
def testCoreTree_BuildTree(mockGUI, mockItems): """Test building a project tree from a list of items. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) theTree.setSeed(42) assert theTree._handleSeed == 42 # Check that tree is empty (calls NWTree.__bool__) assert not theTree # Check for archive and trash folders assert theTree.trashRoot() is None assert theTree.archiveRoot() is None assert not theTree.isTrashRoot("a000000000003") aHandles = [] for tHandle, pHandle, nwItem in mockItems: aHandles.append(tHandle) assert theTree.append(tHandle, pHandle, nwItem) assert theTree._treeChanged # Check that tree is not empty (calls __bool__) assert theTree # Check the number of elements (calls __len__) assert len(theTree) == len(mockItems) # Check that we have the correct handles assert theTree.handles() == aHandles # Check by iterator (calls __iter__, __next__ and __getitem__) for theItem, theHandle in zip(theTree, aHandles): assert theItem.itemHandle == theHandle # Check that we have the correct archive and trash folders assert theTree.trashRoot() == "a000000000003" assert theTree.archiveRoot() == "a000000000002" assert theTree.isTrashRoot("a000000000003") # Try to add another trash folder itemT = NWItem(theProject) itemT._name = "Trash" itemT._type = nwItemType.TRASH itemT._class = nwItemClass.TRASH itemT._expanded = False assert not theTree.append("1234567890abc", None, itemT) assert len(theTree) == len(mockItems) # Generate handle automatically itemT = NWItem(theProject) itemT._name = "New File" itemT._type = nwItemType.FILE itemT._class = nwItemClass.NOVEL itemT._layout = nwItemLayout.DOCUMENT assert theTree.append(None, None, itemT) assert len(theTree) == len(mockItems) + 1 theList = theTree.handles() assert theList[-1] == "73475cb40a568" # Try to add existing handle assert not theTree.append("73475cb40a568", None, itemT) assert len(theTree) == len(mockItems) + 1 # Delete a non-existing item del theTree["stuff"] assert len(theTree) == len(mockItems) + 1 # Delete the last item del theTree["73475cb40a568"] assert len(theTree) == len(mockItems) assert "73475cb40a568" not in theTree # Delete the Novel, Archive and Trash folders del theTree["a000000000001"] assert len(theTree) == len(mockItems) - 1 assert "a000000000001" not in theTree del theTree["a000000000002"] assert len(theTree) == len(mockItems) - 2 assert "a000000000002" not in theTree assert theTree.archiveRoot() is None del theTree["a000000000003"] assert len(theTree) == len(mockItems) - 3 assert "a000000000003" not in theTree assert theTree.trashRoot() is None
def testCoreIndex_CheckFileIndex(mockGUI): """Test the file index checker. """ theProject = NWProject(mockGUI) theIndex = NWIndex(theProject) # Valid Index theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } theIndex._fileIndex = theIndex._fileIndex.copy() assert theIndex._checkFileIndex() is None # Invalid Handle theIndex._fileIndex = { "H3b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Invalid Title theIndex._fileIndex = { "53b69b83cdafc": { "INVALID": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Wrong Length theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", "stuff": None } } } with pytest.raises(IndexError): theIndex._checkFileIndex() # Missing Keys # ============ # Missing 'level' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "stuff": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Missing 'title' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "stuff": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Missing 'layout' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "stuff": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Missing 'cCount' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "stuff": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Missing 'wCount' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "stuff": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Missing 'pCount' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "stuff": 2, "synopsis": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Missing 'synopsis' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "stuff": "text", } } } with pytest.raises(KeyError): theIndex._checkFileIndex() # Wrong Types # =========== # Wrong Type for 'level' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "XX", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(ValueError): theIndex._checkFileIndex() # Wrong Type for 'title' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": 12345678, "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(ValueError): theIndex._checkFileIndex() # Wrong Type for 'layout' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "INVALID", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(ValueError): theIndex._checkFileIndex() # Wrong Type for 'cCount' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": "72", "wCount": 15, "pCount": 2, "synopsis": "text", } } } with pytest.raises(ValueError): theIndex._checkFileIndex() # Wrong Type for 'wCount' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": "15", "pCount": 2, "synopsis": "text", } } } with pytest.raises(ValueError): theIndex._checkFileIndex() # Wrong Type for 'pCount' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": "2", "synopsis": "text", } } } with pytest.raises(ValueError): theIndex._checkFileIndex() # Wrong Type for 'synopsis' theIndex._fileIndex = { "53b69b83cdafc": { "T000001": { "level": "H1", "title": "My Novel", "layout": "DOCUMENT", "cCount": 72, "wCount": 15, "pCount": 2, "synopsis": 123456, } } } with pytest.raises(ValueError): theIndex._checkFileIndex()
def testCoreIndex_CheckRefIndex(mockGUI): """Test the reference index checker. """ theProject = NWProject(mockGUI) theIndex = NWIndex(theProject) # Valid Index theIndex._refIndex = { "6a2d6d5f4f401": { "T000000": [], "T000001": [[3, "@pov", "Jane"], [4, "@location", "Earth"]], } } assert theIndex._checkRefIndex() is None # Invalid Handle theIndex._refIndex = { "Ha2d6d5f4f401": { "T000000": [], "T000001": [[3, "@pov", "Jane"], [4, "@location", "Earth"]], } } with pytest.raises(KeyError): theIndex._checkRefIndex() # Invalid Title theIndex._refIndex = { "6a2d6d5f4f401": { "T000000": [], "INVALID": [[3, "@pov", "Jane"], [4, "@location", "Earth"]], } } with pytest.raises(KeyError): theIndex._checkRefIndex() # Wrong Length theIndex._refIndex = { "6a2d6d5f4f401": { "T000000": [], "T000001": [[3, "@pov", "Jane"], [4, "@location", "Earth", "Stuff"]], } } with pytest.raises(IndexError): theIndex._checkRefIndex() # Wrong Type of Entry 0 theIndex._refIndex = { "6a2d6d5f4f401": { "T000000": [], "T000001": [[3, "@pov", "Jane"], ["4", "@location", "Earth"]], } } with pytest.raises(ValueError): theIndex._checkRefIndex() # Wrong Type of Entry 1 theIndex._refIndex = { "6a2d6d5f4f401": { "T000000": [], "T000001": [[3, "@pov", "Jane"], [4, "@stuff", "Earth"]], } } with pytest.raises(ValueError): theIndex._checkRefIndex() # Wrong Type of Entry 2 theIndex._refIndex = { "6a2d6d5f4f401": { "T000000": [], "T000001": [[3, "@pov", "Jane"], [4, "@location", 123456]], } } with pytest.raises(ValueError): theIndex._checkRefIndex()
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 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_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 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 testCoreTree_Methods(mockGUI, mockItems): """Test various class methods. """ theProject = NWProject(mockGUI) theTree = NWTree(theProject) for tHandle, pHandle, nwItem in mockItems: theTree.append(tHandle, pHandle, nwItem) theTree.updateItemData(tHandle) assert len(theTree) == len(mockItems) # Update item data, nonsense handle assert theTree.updateItemData("stuff") is False # Update item data, invalid item parent corrParent = theTree["b000000000001"].itemParent theTree["b000000000001"].setParent("0000000000000") assert theTree.updateItemData("b000000000001") is False # Update item data, valid item parent theTree["b000000000001"].setParent(corrParent) assert theTree.updateItemData("b000000000001") is True # Update item data, root is unreachable maxDepth = theTree.MAX_DEPTH theTree.MAX_DEPTH = 0 with pytest.raises(RecursionError): theTree.updateItemData("b000000000001") theTree.MAX_DEPTH = maxDepth # Chech type assert theTree.checkType("blabla", nwItemType.FILE) is False assert theTree.checkType("b000000000001", nwItemType.FILE) is False assert theTree.checkType("c000000000001", nwItemType.FILE) is True # Root item lookup assert theTree.findRoot(nwItemClass.WORLD) is None assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001" assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004" # Add a fake item to root and check that it can handle it theTree._treeRoots["0000000000000"] = NWItem(theProject) assert theTree.findRoot(nwItemClass.WORLD) is None del theTree._treeRoots["0000000000000"] # Get item path assert theTree.getItemPath("stuff") == [] assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001", "a000000000001" ] # Cause recursion error maxDepth = theTree.MAX_DEPTH theTree.MAX_DEPTH = 0 with pytest.raises(RecursionError): theTree.getItemPath("c000000000001") theTree.MAX_DEPTH = maxDepth # Break the folder parent handle theTree["b000000000001"]._parent = "stuff" assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001" ] theTree["b000000000001"]._parent = "a000000000001" assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001", "a000000000001" ] # Change file layout assert theTree.setFileItemLayout("stuff", nwItemLayout.DOCUMENT) is False assert theTree.setFileItemLayout("b000000000001", nwItemLayout.DOCUMENT) is False assert theTree.setFileItemLayout("c000000000001", "stuff") is False assert theTree.setFileItemLayout("c000000000001", nwItemLayout.NOTE) is True assert theTree["c000000000001"].itemLayout == nwItemLayout.NOTE