def testIndexCheckThese(nwMinimal, nwDummy): """Test the tag checker function checkThese. """ theProject = NWProject(nwDummy) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) theIndex = NWIndex(theProject, nwDummy) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") nItem = theProject.projTree[nHandle] cItem = theProject.projTree[cHandle] assert theIndex.scanText(cHandle, ( "# Jane Smith\n" "@tag: Jane" )) assert theIndex.scanText(nHandle, ( "# Hello World!\n" "@pov: Jane" )) assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]" assert str(theIndex.checkThese(["@tag", "John"], cItem)) == "[True, True]" assert str(theIndex.checkThese(["@tag", "Jane"], nItem)) == "[True, False]" assert str(theIndex.checkThese(["@tag", "John"], nItem)) == "[True, True]" assert str(theIndex.checkThese(["@pov", "John"], nItem)) == "[True, False]" assert str(theIndex.checkThese(["@pov", "Jane"], nItem)) == "[True, True]" assert str(theIndex.checkThese(["@ pov", "Jane"], nItem)) == "[False, False]" assert str(theIndex.checkThese(["@what", "Jane"], nItem)) == "[False, False]" assert theProject.closeProject()
def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI): """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(dummyGUI) theProject.projTree.setSeed(42) assert theProject.newProject({"projPath": fncDir}) assert theProject.setProjectPath(fncDir) assert theProject.saveProject() assert theProject.closeProject() assert theProject.openProject(projFile) assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str) assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) assert theProject.projChanged assert theProject.saveProject() assert theProject.closeProject() copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert not theProject.projChanged
def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy): """Check that new files can be added to the project. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "3_nwProject.nwx") refFile = os.path.join(nwRef, "proj", "3_nwProject.nwx") theProject = NWProject(nwDummy) theProject.projTree.setSeed(42) assert theProject.newProject({"projPath": nwFuncTemp}) assert theProject.setProjectPath(nwFuncTemp) assert theProject.saveProject() assert theProject.closeProject() assert theProject.openProject(projFile) assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str) assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) assert theProject.projChanged assert theProject.saveProject() assert theProject.closeProject() copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) assert not theProject.projChanged
def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, 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(dummyGUI) theProject.projTree.setSeed(42) theProject.mainConf = tmpConf # Make sure we do not pick up the nw/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) assert theProject.openProject(fncDir) assert theProject.projName == "Sample Project" assert theProject.saveProject() assert theProject.closeProject() # Misdirect the appRoot path so neither is possible tmpConf.appRoot = tmpDir assert not theProject.newProject(projData)
def testCoreTree_Reorder(dummyGUI, dummyItems): """Test changing tree order. """ theProject = NWProject(dummyGUI) theTree = NWTree(theProject) aHandle = [] for tHandle, pHande, nwItem in dummyItems: aHandle.append(tHandle) theTree.append(tHandle, pHande, nwItem) assert len(theTree) == len(dummyItems) 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 + ["dummy"]) assert theTree.handles() == bHandle theTree._treeOrder.append("dummy") theTree.setOrder(bHandle) assert theTree.handles() == bHandle
def testItemLayoutSetter(nwDummy): theProject = NWProject(nwDummy) theItem = NWItem(theProject) # Layout theItem.setLayout(None) assert theItem.itemLayout == nwItemLayout.NO_LAYOUT theItem.setLayout("NONSENSE") assert theItem.itemLayout == nwItemLayout.NO_LAYOUT theItem.setLayout("NO_LAYOUT") assert theItem.itemLayout == nwItemLayout.NO_LAYOUT theItem.setLayout("TITLE") assert theItem.itemLayout == nwItemLayout.TITLE theItem.setLayout("BOOK") assert theItem.itemLayout == nwItemLayout.BOOK theItem.setLayout("PAGE") assert theItem.itemLayout == nwItemLayout.PAGE theItem.setLayout("PARTITION") assert theItem.itemLayout == nwItemLayout.PARTITION theItem.setLayout("UNNUMBERED") assert theItem.itemLayout == nwItemLayout.UNNUMBERED theItem.setLayout("CHAPTER") assert theItem.itemLayout == nwItemLayout.CHAPTER theItem.setLayout("SCENE") assert theItem.itemLayout == nwItemLayout.SCENE theItem.setLayout("NOTE") assert theItem.itemLayout == nwItemLayout.NOTE
def testCoreTree_MakeHandles(monkeypatch, dummyGUI): """Test generating item handles. """ theProject = NWProject(dummyGUI) theTree = NWTree(theProject) theTree.setSeed(42) tHandle = theTree._makeHandle() assert tHandle == "73475cb40a568" # Add the next in line to the project to foprce duplicate theTree._projTree["44cb730c42048"] = None tHandle = theTree._makeHandle() assert tHandle == "71ee45a3c0db9" # Fix the time() function and force a handle collission theTree.setSeed(None) monkeypatch.setattr("nw.core.tree.time", lambda: 123.4) tHandle = theTree._makeHandle() theTree._projTree[tHandle] = None assert tHandle == "5f466d7afa48b" tHandle = theTree._makeHandle() theTree._projTree[tHandle] = None assert tHandle == "a79acf4c634a7" monkeypatch.undo()
def testItemClassSetter(nwDummy): theProject = NWProject(nwDummy) theItem = NWItem(theProject) # Class theItem.setClass(None) assert theItem.itemClass == nwItemClass.NO_CLASS theItem.setClass("NONSENSE") assert theItem.itemClass == nwItemClass.NO_CLASS theItem.setClass("NO_CLASS") assert theItem.itemClass == nwItemClass.NO_CLASS theItem.setClass("NOVEL") assert theItem.itemClass == nwItemClass.NOVEL theItem.setClass("PLOT") assert theItem.itemClass == nwItemClass.PLOT theItem.setClass("CHARACTER") assert theItem.itemClass == nwItemClass.CHARACTER theItem.setClass("WORLD") assert theItem.itemClass == nwItemClass.WORLD theItem.setClass("TIMELINE") assert theItem.itemClass == nwItemClass.TIMELINE theItem.setClass("OBJECT") assert theItem.itemClass == nwItemClass.OBJECT theItem.setClass("ENTITY") assert theItem.itemClass == nwItemClass.ENTITY theItem.setClass("CUSTOM") assert theItem.itemClass == nwItemClass.CUSTOM theItem.setClass("ARCHIVE") assert theItem.itemClass == nwItemClass.ARCHIVE theItem.setClass("TRASH") assert theItem.itemClass == nwItemClass.TRASH
def testCoreIndex_CheckTagIndex(dummyGUI): """Test the tag index checker. """ theProject = NWProject(dummyGUI) theIndex = NWIndex(theProject, dummyGUI) # 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 testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): """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(dummyGUI) theProject.projTree.setSeed(42) assert theProject.newProject(projData) assert theProject.saveProject() assert theProject.closeProject() copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
def testProjectBackup(nwDummy, nwMinimal, nwTemp): """Test the automated backup feature of the project class. The test creates a backup of the Minimal test project, and then unzips the backupd file and checks that the project XML file is identical to the original file. """ theProject = NWProject(nwDummy) assert theProject.openProject(nwMinimal) # Test faulty settings # Invalid path theProject.mainConf.backupPath = None assert not theProject.zipIt(doNotify=False) # Missing project name theProject.mainConf.backupPath = nwTemp theProject.projName = "" assert not theProject.zipIt(doNotify=False) # Non-existent folder theProject.mainConf.backupPath = os.path.join(nwTemp, "nonexistent") theProject.projName = "Test Minimal" assert not theProject.zipIt(doNotify=False) # Same folder as project (causes infinite loop in zipping) theProject.mainConf.backupPath = nwMinimal assert not theProject.zipIt(doNotify=False) # Test correct settings theProject.mainConf.backupPath = nwTemp assert theProject.zipIt(doNotify=False) theFiles = os.listdir(os.path.join(nwTemp, "Test Minimal")) assert len(theFiles) == 1 theZip = theFiles[0] assert theZip[:12] == "Backup from " assert theZip[-4:] == ".zip" # Extract the archive with ZipFile(os.path.join(nwTemp, "Test Minimal", theZip), "r") as inZip: inZip.extractall(os.path.join(nwTemp, "extract")) # Check that the main project file was restored assert cmpFiles( os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(nwTemp, "extract", "nwProject.nwx") )
def testCoreIndex_ScanThis(nwMinimal, dummyGUI): """Test the tag scanner function scanThis. """ theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) theIndex = NWIndex(theProject, dummyGUI) isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") assert not isValid isValid, theBits, thePos = theIndex.scanThis("@") assert not isValid isValid, theBits, thePos = theIndex.scanThis("@:") assert not isValid isValid, theBits, thePos = theIndex.scanThis(" @a: b") assert not isValid isValid, theBits, thePos = theIndex.scanThis("@a:") assert isValid assert theBits == ["@a"] assert thePos == [0] isValid, theBits, thePos = theIndex.scanThis("@a:b") assert isValid assert theBits == ["@a", "b"] assert thePos == [0, 3] isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d") assert isValid assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 3, 5, 7] isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d") assert isValid assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 5, 9, 13] isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this") assert isValid assert theBits == ["@tag", "this", "and this"] assert thePos == [0, 6, 12] assert theProject.closeProject()
def testIndexScanThis(nwMinimal, nwDummy): """Test the tag scanner function scanThis. """ theProject = NWProject(nwDummy) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) theIndex = NWIndex(theProject, nwDummy) isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") assert not isValid isValid, theBits, thePos = theIndex.scanThis("@") assert not isValid isValid, theBits, thePos = theIndex.scanThis("@:") assert not isValid isValid, theBits, thePos = theIndex.scanThis(" @a: b") assert not isValid isValid, theBits, thePos = theIndex.scanThis("@a:") assert isValid assert str(theBits) == "['@a']" assert str(thePos) == "[0]" isValid, theBits, thePos = theIndex.scanThis("@a:b") assert isValid assert str(theBits) == "['@a', 'b']" assert str(thePos) == "[0, 3]" isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d") assert isValid assert str(theBits) == "['@a', 'b', 'c', 'd']" assert str(thePos) == "[0, 3, 5, 7]" isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d") assert isValid assert str(theBits) == "['@a', 'b', 'c', 'd']" assert str(thePos) == "[0, 5, 9, 13]" isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this") assert isValid assert str(theBits) == "['@tag', 'this', 'and this']" assert str(thePos) == "[0, 6, 12]" assert theProject.closeProject()
def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir): """Test writing the ToC.txt file. """ theProject = NWProject(dummyGUI) theTree = NWTree(theProject) for tHandle, pHande, nwItem in dummyItems: theTree.append(tHandle, pHande, nwItem) assert len(theTree) == len(dummyItems) theTree._treeOrder.append("dummy") def dummyIsFile(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", dummyIsFile) 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") with open(os.path.join(tmpDir, nwFiles.TOC_TXT), mode="r", encoding="utf8") as inFile: assert inFile.read() == ( "\n" "Table of Contents\n" "=================\n" "\n" "File Name Class Layout Document Label\n" "-------------------------------------------------------------\n" f"{pathA} NOVEL CHAPTER Chapter One\n" f"{pathB} NOVEL SCENE Scene One\n" f"{pathC} CHARACTER NOTE Jane Doe\n")
def testCoreProject_AccessItems(nwMinimal, dummyGUI): """Test helper functions for the project folder. """ theProject = NWProject(dummyGUI) 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 testCoreTree_Methods(dummyGUI, dummyItems): """Test building a project tree from a list of items. """ theProject = NWProject(dummyGUI) theTree = NWTree(theProject) for tHandle, pHande, nwItem in dummyItems: theTree.append(tHandle, pHande, nwItem) assert len(theTree) == len(dummyItems) # Root item lookup theTree._treeRoots.append("dummy") 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("dummy") is None # Get item path assert theTree.getItemPath("dummy") == [] assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001", "a000000000001" ] # Break the folder parent handle theTree["b000000000001"].itemParent = "dummy" assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001" ] theTree["b000000000001"].itemParent = "a000000000001" assert theTree.getItemPath("c000000000001") == [ "c000000000001", "b000000000001", "a000000000001" ] # Change file layout assert not theTree.setFileItemLayout("dummy", nwItemLayout.UNNUMBERED) assert not theTree.setFileItemLayout("b000000000001", nwItemLayout.UNNUMBERED) assert not theTree.setFileItemLayout("c000000000001", "stuff") assert theTree.setFileItemLayout("c000000000001", nwItemLayout.UNNUMBERED) assert theTree["c000000000001"].itemLayout == nwItemLayout.UNNUMBERED
def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, 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(dummyGUI) theProject.projTree.setSeed(42) theProject.mainConf = tmpConf # 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) assert theProject.openProject(fncDir) assert theProject.projName == "Sample Project" assert theProject.saveProject() assert theProject.closeProject() os.unlink(dstSample)
def testCoreIndex_CheckThese(nwMinimal, dummyGUI): """Test the tag checker function checkThese. """ theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) theIndex = NWIndex(theProject, dummyGUI) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") nItem = theProject.projTree[nHandle] cItem = theProject.projTree[cHandle] assert theIndex.scanText(cHandle, ("# Jane Smith\n" "@tag: Jane")) assert theIndex.scanText(nHandle, ("# Hello World!\n" "@pov: Jane")) assert theIndex.tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]} assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" assert theIndex.checkThese([], cItem) == [] 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] assert theProject.closeProject()
def testCoreIndex_CheckTextCounts(dummyGUI): """Test the text counts checker. """ theProject = NWProject(dummyGUI) theIndex = NWIndex(theProject, dummyGUI) # Valid Index theIndex._textCounts = { "53b69b83cdafc": [72, 15, 2], "974e400180a99": [210, 40, 2], } assert theIndex._checkTextCounts() is None # Invalid Handle theIndex._textCounts = { "53b69b83cdafc": [72, 15, 2], "h74e400180a99": [210, 40, 2], } with pytest.raises(KeyError): theIndex._checkTextCounts() # Wrong Length theIndex._textCounts = { "53b69b83cdafc": [72, 15, 2], "974e400180a99": [210, 40, 2, 8], } with pytest.raises(IndexError): theIndex._checkTextCounts() # Type of Entry 0 theIndex._textCounts = { "53b69b83cdafc": [72, 15, 2], "974e400180a99": ["210", 40, 2], } with pytest.raises(ValueError): theIndex._checkTextCounts() # Type of Entry 1 theIndex._textCounts = { "53b69b83cdafc": [72, 15, 2], "974e400180a99": [210, "40", 2], } with pytest.raises(ValueError): theIndex._checkTextCounts() # Type of Entry 2 theIndex._textCounts = { "53b69b83cdafc": [72, 15, 2], "974e400180a99": [210, 40, "2"], } with pytest.raises(ValueError): theIndex._checkTextCounts()
def testDocMeta(nwDummy, nwLipsum): """Check that the document meta data string is parsed correctly. """ theProject = NWProject(nwDummy) theProject.projTree.setSeed(42) assert theProject.openProject(nwLipsum) aDoc = NWDoc(theProject, nwDummy) assert aDoc.openDocument("47666c91c7ccf") theName, theParent, theClass, theLayout = aDoc.getMeta() assert theName == "Scene Five" assert theParent == "6bd935d2490cd" assert theClass == nwItemClass.NOVEL assert theLayout == nwItemLayout.SCENE aDoc._docMeta = {"stuff": None} theName, theParent, theClass, theLayout = aDoc.getMeta() assert theName == "" assert theParent is None assert theClass is None assert theLayout is None
def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems): """Test changing tree order. """ theProject = NWProject(dummyGUI) theTree = NWTree(theProject) for tHandle, pHande, nwItem in dummyItems: theTree.append(tHandle, pHande, nwItem) assert len(theTree) == len(dummyItems) 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\" order=\"0\" parent=\"None\">" b"<name>Novel</name><type>ROOT</type><class>NOVEL</class><status>None</status>" b"<expanded>True</expanded></item>" b"<item handle=\"b000000000001\" order=\"0\" parent=\"a000000000001\">" b"<name>Act One</name><type>FOLDER</type><class>NOVEL</class><status>None</status>" b"<expanded>True</expanded></item>" b"<item handle=\"c000000000001\" order=\"0\" parent=\"b000000000001\">" b"<name>Chapter One</name><type>FILE</type><class>NOVEL</class><status>None</status>" b"<exported>True</exported><layout>CHAPTER</layout><charCount>300</charCount>" b"<wordCount>50</wordCount><paraCount>2</paraCount><cursorPos>0</cursorPos></item>" b"<item handle=\"c000000000002\" order=\"0\" parent=\"b000000000001\">" b"<name>Scene One</name><type>FILE</type><class>NOVEL</class><status>None</status>" b"<exported>True</exported><layout>SCENE</layout><charCount>3000</charCount>" b"<wordCount>500</wordCount><paraCount>20</paraCount><cursorPos>0</cursorPos></item>" b"<item handle=\"a000000000002\" order=\"0\" parent=\"None\">" b"<name>Outtakes</name><type>ROOT</type><class>ARCHIVE</class><status>None</status>" b"<expanded>False</expanded></item>" b"<item handle=\"a000000000003\" order=\"0\" parent=\"None\">" b"<name>Trash</name><type>TRASH</type><class>TRASH</class><status>None</status>" b"<expanded>False</expanded></item>" b"<item handle=\"a000000000004\" order=\"0\" parent=\"None\">" b"<name>Characters</name><type>ROOT</type><class>CHARACTER</class><status>None</status>" b"<expanded>True</expanded></item>" b"<item handle=\"b000000000002\" order=\"0\" parent=\"a000000000002\">" b"<name>Jane Doe</name><type>FILE</type><class>CHARACTER</class><status>None</status>" b"<exported>True</exported><layout>NOTE</layout><charCount>2000</charCount>" b"<wordCount>400</wordCount><paraCount>16</paraCount><cursorPos>0</cursorPos></item>" b"</content></novelWriterXML>" ) theTree.clear() assert len(theTree) == 0 assert not theTree.unpackXML(nwXML) assert theTree.unpackXML(nwXML[0]) assert len(theTree) == len(dummyItems)
def testCoreIndex_CheckThese(nwMinimal, dummyGUI): """Test the tag checker function checkThese. """ theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) theIndex = NWIndex(theProject, dummyGUI) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") nItem = theProject.projTree[nHandle] cItem = theProject.projTree[cHandle] assert not theIndex.novelChangedSince(0) assert not theIndex.notesChangedSince(0) assert not theIndex.indexChangedSince(0) 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) assert theIndex.notesChangedSince(0) assert theIndex.indexChangedSince(0) assert theIndex.checkThese([], cItem) == [] 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] assert theProject.closeProject()
def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, nwDummy): """Create a new project from a project wizard dictionary. Custom type without chapters, but with scenes. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "5_nwProject.nwx") refFile = os.path.join(nwRef, "proj", "5_nwProject.nwx") projData = { "projName": "Test Custom", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", "projPath": nwFuncTemp, "popSample": False, "popMinimal": False, "popCustom": True, "addRoots": [ nwItemClass.PLOT, nwItemClass.CHARACTER, nwItemClass.WORLD, nwItemClass.TIMELINE, nwItemClass.OBJECT, nwItemClass.ENTITY, ], "numChapters": 0, "numScenes": 6, "chFolders": True, } theProject = NWProject(nwDummy) theProject.projTree.setSeed(42) assert theProject.newProject(projData) assert theProject.saveProject() assert theProject.closeProject() copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir): """Test saving a project. """ theProject = NWProject(dummyGUI) testFile = os.path.join(nwMinimal, "nwProject.nwx") 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 monkeypatch.setattr("os.path.isdir", lambda *args: False) assert theProject.saveProject() is False monkeypatch.undo() # Fail on open file monkeypatch.setattr("builtins.open", causeOSError) assert theProject.saveProject() is False monkeypatch.undo() # 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]) # 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_Helpers(monkeypatch, fncDir, dummyGUI): """Test helper functions for the project folder. """ theProject = NWProject(dummyGUI) # No path assert theProject.ensureFolderStructure() is False # Set the correct dir theProject.projPath = fncDir # Block user's home folder monkeypatch.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir) assert theProject.ensureFolderStructure() is False monkeypatch.undo() # Create a file to block meta folder metaDir = os.path.join(fncDir, "meta") writeFile(metaDir, "dummy") assert theProject.ensureFolderStructure() is False os.unlink(metaDir) # Create a file to block cache folder cacheDir = os.path.join(fncDir, "cache") writeFile(cacheDir, "dummy") assert theProject.ensureFolderStructure() is False os.unlink(cacheDir) # Create a file to block content folder contentDir = os.path.join(fncDir, "content") writeFile(contentDir, "dummy") assert theProject.ensureFolderStructure() is False os.unlink(contentDir) # Now, do it right assert theProject.ensureFolderStructure() is True assert os.path.isdir(metaDir) assert os.path.isdir(cacheDir) assert os.path.isdir(contentDir)
def testItemTypeSetter(nwDummy): theProject = NWProject(nwDummy) theItem = NWItem(theProject) # Type theItem.setType(None) assert theItem.itemType == nwItemType.NO_TYPE theItem.setType("NONSENSE") assert theItem.itemType == nwItemType.NO_TYPE theItem.setType("NO_TYPE") assert theItem.itemType == nwItemType.NO_TYPE theItem.setType("ROOT") assert theItem.itemType == nwItemType.ROOT theItem.setType("FOLDER") assert theItem.itemType == nwItemType.FOLDER theItem.setType("FILE") assert theItem.itemType == nwItemType.FILE theItem.setType("TRASH") assert theItem.itemType == nwItemType.TRASH
def testCoreTree_Stats(dummyGUI, dummyItems): """Test project stats methods. """ theProject = NWProject(dummyGUI) theTree = NWTree(theProject) for tHandle, pHande, nwItem in dummyItems: theTree.append(tHandle, pHande, nwItem) assert len(theTree) == len(dummyItems) theTree._treeOrder.append("dummy") # Count Words novelWords, noteWords = theTree.sumWords() assert novelWords == 550 assert noteWords == 400 # Count types nRoot, nFolder, nFile = theTree.countTypes() assert nRoot == 3 assert nFolder == 1 assert nFile == 3
def testCoreTree_MakeHandles(monkeypatch, dummyGUI): """Test generating item handles. """ theProject = NWProject(dummyGUI) 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("nw.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 testCoreTree_BuildTree(dummyGUI, dummyItems): """Test building a project tree from a list of items. """ theProject = NWProject(dummyGUI) 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, pHande, nwItem in dummyItems: aHandles.append(tHandle) assert theTree.append(tHandle, pHande, 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(dummyItems) # 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.itemName = "Trash" itemT.itemType = nwItemType.TRASH itemT.itemClass = nwItemClass.TRASH itemT.isExpanded = False assert not theTree.append("1234567890abc", None, itemT) assert len(theTree) == len(dummyItems) # Generate handle automatically itemT = NWItem(theProject) itemT.itemName = "New File" itemT.itemType = nwItemType.FILE itemT.itemClass = nwItemClass.NOVEL itemT.itemLayout = nwItemLayout.SCENE assert theTree.append(None, None, itemT) assert len(theTree) == len(dummyItems) + 1 theList = theTree.handles() assert theList[-1] == "73475cb40a568" # Try to add existing handle assert not theTree.append("73475cb40a568", None, itemT) assert len(theTree) == len(dummyItems) + 1 # Delete a non-existing item del theTree["dummy"] assert len(theTree) == len(dummyItems) + 1 # Delete the last item del theTree["73475cb40a568"] assert len(theTree) == len(dummyItems) assert "73475cb40a568" not in theTree # Delete the Novel, Archive and Trash folders del theTree["a000000000001"] assert len(theTree) == len(dummyItems) - 1 assert "a000000000001" not in theTree del theTree["a000000000002"] assert len(theTree) == len(dummyItems) - 2 assert "a000000000002" not in theTree assert theTree.archiveRoot() is None del theTree["a000000000003"] assert len(theTree) == len(dummyItems) - 3 assert "a000000000003" not in theTree assert theTree.trashRoot() is None
def dummyItems(dummyGUI): """Create a list of dummy items. """ theProject = NWProject(dummyGUI) itemA = NWItem(theProject) itemA.itemName = "Novel" itemA.itemType = nwItemType.ROOT itemA.itemClass = nwItemClass.NOVEL itemA.isExpanded = True itemB = NWItem(theProject) itemB.itemName = "Act One" itemB.itemType = nwItemType.FOLDER itemB.itemClass = nwItemClass.NOVEL itemB.isExpanded = True itemC = NWItem(theProject) itemC.itemName = "Chapter One" itemC.itemType = nwItemType.FILE itemC.itemClass = nwItemClass.NOVEL itemC.itemLayout = nwItemLayout.CHAPTER itemC.charCount = 300 itemC.wordCount = 50 itemC.paraCount = 2 itemD = NWItem(theProject) itemD.itemName = "Scene One" itemD.itemType = nwItemType.FILE itemD.itemClass = nwItemClass.NOVEL itemD.itemLayout = nwItemLayout.SCENE itemD.charCount = 3000 itemD.wordCount = 500 itemD.paraCount = 20 itemE = NWItem(theProject) itemE.itemName = "Outtakes" itemE.itemType = nwItemType.ROOT itemE.itemClass = nwItemClass.ARCHIVE itemE.isExpanded = False itemF = NWItem(theProject) itemF.itemName = "Trash" itemF.itemType = nwItemType.TRASH itemF.itemClass = nwItemClass.TRASH itemF.isExpanded = False itemG = NWItem(theProject) itemG.itemName = "Characters" itemG.itemType = nwItemType.ROOT itemG.itemClass = nwItemClass.CHARACTER itemG.isExpanded = True itemH = NWItem(theProject) itemH.itemName = "Jane Doe" itemH.itemType = nwItemType.FILE itemH.itemClass = nwItemClass.CHARACTER itemH.itemLayout = 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