Example #1
0
def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
    """Test creating a new project.
    """
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)

    # No data
    with monkeypatch.context() as mp:
        mp.setattr(GuiProjectWizard, "exec_", lambda *a: None)
        assert nwGUI.newProject(projData=None) is False

    # Close project
    with monkeypatch.context() as mp:
        nwGUI.hasProject = True
        mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
        assert nwGUI.newProject(projData={"projPath": fncProj}) is False

    # No project path
    assert nwGUI.newProject(projData={}) is False

    # Project file already exists
    projFile = os.path.join(fncProj, nwGUI.theProject.projFile)
    writeFile(projFile, "Stuff")
    assert nwGUI.newProject(projData={"projPath": fncProj}) is False
    os.unlink(projFile)

    # An unreachable path should also fail
    projPath = os.path.join(fncProj, "stuff", "stuff", "stuff")
    assert nwGUI.newProject(projData={"projPath": projPath}) is False

    # This one should work just fine
    assert nwGUI.newProject(projData={"projPath": fncProj}) is True
    assert os.path.isfile(os.path.join(fncProj, nwGUI.theProject.projFile))
    assert os.path.isdir(os.path.join(fncProj, "content"))
Example #2
0
def fillAspect(aspectj_setup_dir, monitor_setup_dir, gen_monitor_setup_dir):
    """
    Fills the user-defined parts of a generated .aj file.

    The file should be called unittestMonitorAspect.aj file.

    Args:
        aspectj_setup_dir (str): The destination directory for the
            filled .aj file.

        monitor_setup_dir (str): Input directory that contains two files:
            * import.txt: should contain the code that replaces the
              "// add your own imports." comment in the .aj file.
            * ajcode.txt: should contain the code that replaces the
              "// Implement your code here." comment in the .aj file.

        gen_monitor_dir (str): The input directory, which must a
            a single unittestMonitorAspect.aj file.
    """
    tools.progress("Fill in AspectJ template")
    aspect_file_name = "unittestMonitorAspect.aj"
    aspect_file_path = os.path.join(gen_monitor_setup_dir, aspect_file_name)
    aspectContent = tools.readFile(aspect_file_path)
    aspectContent = aspectContent.replace(
        "// add your own imports.",
        tools.readFile(os.path.join(monitor_setup_dir, "import.txt")))
    aspectContent = aspectContent.replace(
        "// Implement your code here.",
        tools.readFile(os.path.join(monitor_setup_dir, "ajcode.txt")))
    tools.writeFile(os.path.join(aspectj_setup_dir, aspect_file_name),
                    aspectContent)
Example #3
0
def addRvmExceptions(rvm_file_path):
    """
    Changes the getState functions to throw an exception with the state name.

    The input file will be changed in-place.

    The getState function should have the following format:
    private void somename_getState() throws GotoStmtException, RaiseStmtException {
    }

    The resulting getState function will look like this:
    private void somename_getState() throws GotoStmtException, RaiseStmtException {
        throw new StateNameException(state.getName());
    }


    Args:
        rvm_file_path (str): The path to the rvm file.
    """
    rvm = re.sub(
        r"([ ]*)private void ([a-zA-Z_0-9]+)_getState\(\) throws GotoStmtException, RaiseStmtException \{",
        r"""\1private void \2_getState() throws GotoStmtException, RaiseStmtException, StateNameException {
\1    throw new StateNameException(state.getName());""",
        tools.readFile(rvm_file_path))
    tools.writeFile(rvm_file_path, rvm)
Example #4
0
def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
    """Test lock file functions for the project folder.
    """
    theProject = NWProject(dummyGUI)

    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
    monkeypatch.setattr("builtins.open", causeOSError)
    assert theProject._writeLockFile() is False
    monkeypatch.undo()

    # Write lock file
    monkeypatch.setattr("nw.core.project.time", lambda: 123.4)
    assert theProject._writeLockFile() is True
    monkeypatch.undo()
    assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n"

    # Block open
    monkeypatch.setattr("builtins.open", causeOSError)
    assert theProject._readLockFile() == ["ERROR"]
    monkeypatch.undo()

    # Read lock file
    assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"]

    # Block unlink
    monkeypatch.setattr("os.unlink", causeOSError)
    assert os.path.isfile(lockFile)
    assert theProject._clearLockFile() is False
    assert os.path.isfile(lockFile)
    monkeypatch.undo()

    # 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
Example #5
0
def testBaseCommon_ReadTextFile(monkeypatch, fncDir, ipsumText):
    """Test the readTextFile function.
    """
    testText = "\n\n".join(ipsumText) + "\n"
    testFile = os.path.join(fncDir, "ipsum.txt")
    writeFile(testFile, testText)

    assert readTextFile(os.path.join(fncDir, "not_a_file.txt")) == ""
    assert readTextFile(testFile) == testText

    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert readTextFile(testFile) == ""
def testCoreSpell_Enchant(monkeypatch, tmpDir, tmpConf):
    """Test the pyenchant spell checker
    """
    wList = os.path.join(tmpDir, "wordlist.txt")
    writeFile(wList, "a_word\nb_word\nc_word\n")

    # Block the enchant package (and trigger the dummy class)
    monkeypatch.setitem(sys.modules, "enchant", None)
    spChk = NWSpellEnchant()

    spChk.setLanguage("en", wList)
    assert spChk.setLanguage("", "") is None
    assert spChk.checkWord("")
    assert spChk.suggestWords("") == []
    assert spChk.listDictionaries() == []
    assert spChk.describeDict() == ("", "")

    monkeypatch.undo()

    # Load the proper enchant package
    spChk = NWSpellEnchant()
    spChk.mainConf = tmpConf
    spChk.setLanguage("en", wList)

    assert spChk.checkWord("a_word")
    assert spChk.checkWord("b_word")
    assert spChk.checkWord("c_word")
    assert not spChk.checkWord("d_word")

    spChk.addWord("d_word")
    assert spChk.checkWord("d_word")

    wSuggest = spChk.suggestWords("wrod")
    assert len(wSuggest) > 0
    assert "word" in wSuggest

    dList = spChk.listDictionaries()
    assert len(dList) > 0

    aTag, aName = spChk.describeDict()
    assert aTag == "en"
    assert aName != ""
def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf):
    """Test the spell checker super class
    """
    wList = os.path.join(tmpDir, "wordlist.txt")
    writeFile(wList, "a_word\nb_word\nc_word\n")

    spChk = NWSpellCheck()
    spChk.mainConf = tmpConf

    # Check that dummy functions return results that reflects that spell
    # checking is effectively disabled
    assert spChk.setLanguage("", "") is None
    assert spChk.checkWord("")
    assert spChk.suggestWords("") == []
    assert spChk.listDictionaries() == []
    assert spChk.describeDict() == ("", "")

    # Check language info
    assert NWSpellCheck.expandLanguage("en") == "English"
    assert NWSpellCheck.expandLanguage("en_GB") == "English (GB)"

    # Add a word to the user's dictionary
    assert spChk._readProjectDictionary("dummy") is False
    monkeypatch.setattr("builtins.open", causeOSError)
    assert spChk._readProjectDictionary(wList) is False
    monkeypatch.undo()
    assert spChk._readProjectDictionary(wList) is True
    assert spChk.projectDict == wList

    # Cannot write to file
    monkeypatch.setattr("builtins.open", causeOSError)
    assert spChk.addWord("d_word") is False
    monkeypatch.undo()
    assert readFile(wList) == "a_word\nb_word\nc_word\n"

    # First time, OK
    assert spChk.addWord("d_word") is True
    assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n"

    # But not added twice
    assert spChk.addWord("d_word") is False
    assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n"
Example #8
0
def testBaseCommon_Sha256Sum(monkeypatch, fncDir, ipsumText):
    """Test the sha256sum function.
    """
    longText = 50 * (" ".join(ipsumText) + " ")
    shortText = "This is a short file"
    noneText = ""

    assert len(longText) == 175650

    longFile = os.path.join(fncDir, "long_file.txt")
    shortFile = os.path.join(fncDir, "short_file.txt")
    noneFile = os.path.join(fncDir, "none_file.txt")

    writeFile(longFile, longText)
    writeFile(shortFile, shortText)
    writeFile(noneFile, noneText)

    # Taken with sha256sum command on command line
    longHash = "9b22aee35660da4fae204acbe96aec7f563022746ca2b7a3831f5e44544765eb"
    shortHash = "6d7c9b2722364c471b8a8666bcb35d18500272d05b23b3427288e2e34c6618f0"
    noneHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

    assert sha256sum(longFile) == longHash
    assert sha256sum(shortFile) == shortHash
    assert sha256sum(noneFile) == noneHash

    assert hashlib.sha256(longText.encode("utf-8")).hexdigest() == longHash
    assert hashlib.sha256(shortText.encode("utf-8")).hexdigest() == shortHash
    assert hashlib.sha256(noneText.encode("utf-8")).hexdigest() == noneHash

    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert sha256sum(longFile) is None
        assert sha256sum(shortFile) is None
        assert sha256sum(noneFile) is None
Example #9
0
def fillAspect(aspectj_dir, gen_monitor_dir):
    """
    Fills the user-defined parts of a generated .aj file.

    The current directory should contain two files:
        * import.txt: should contain the code that replaces the
          "// add your own imports." comment in the .aj file.
        * ajcode.txt: should contain the code that replaces the
          "// Implement your code here." comment in the .aj file.
    
    The fillAspect function replaces the comments mentioned above in
    the .aj file, and copies the result to the destination directory.

    Args:
        aspectj_dir (str): The destination directory for the
            filled .aj file.

        gen_monitor_dir (str): The input directory, which must contain
            a single .aj file

    Raises:
        Exception if the input directory does not contain exactly
            one .aj file.
    """
    tools.progress("Fill in AspectJ template")
    aspect_file_paths = glob.glob(os.path.join(gen_monitor_dir, "*.aj"))
    if len(aspect_file_paths) != 1:
        raise Exception("Expected a single aspectJ template")
    aspect_file_path = aspect_file_paths[0]
    aspectContent = tools.readFile(aspect_file_path)
    aspectContent = aspectContent.replace("// add your own imports.",
                                          tools.readFile("import.txt"))
    aspectContent = aspectContent.replace("// Implement your code here.",
                                          tools.readFile("ajcode.txt"))
    aspect_file_name = os.path.basename(aspect_file_path)
    tools.writeFile(os.path.join(aspectj_dir, aspect_file_name), aspectContent)
Example #10
0
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)
Example #11
0
def testCoreProject_OldFormat(dummyGUI, nwOldProj):
    """Test that a project folder structure of version 1.0 can be
    converted to the latest folder structure. Version 1.0 split the
    documents into 'data_0' ... 'data_f' folders, which are now all
    contained in a single 'content' folder.
    """
    theProject = NWProject(dummyGUI)

    # Create dummy files for known legacy files
    deleteFiles = [
        os.path.join(nwOldProj, "cache", "nwProject.nwx.0"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.1"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.2"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.3"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.4"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.5"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.6"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.7"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.8"),
        os.path.join(nwOldProj, "cache", "nwProject.nwx.9"),
        os.path.join(nwOldProj, "meta", "mainOptions.json"),
        os.path.join(nwOldProj, "meta", "exportOptions.json"),
        os.path.join(nwOldProj, "meta", "outlineOptions.json"),
        os.path.join(nwOldProj, "meta", "timelineOptions.json"),
        os.path.join(nwOldProj, "meta", "docMergeOptions.json"),
        os.path.join(nwOldProj, "meta", "sessionLogOptions.json"),
    ]

    # Add some files that shouldn't be there
    deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.nwd"))
    deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.txt"))

    # Add some folders that shouldn't be there
    os.mkdir(os.path.join(nwOldProj, "stuff"))
    os.mkdir(os.path.join(nwOldProj, "data_1", "stuff"))

    # Create dummy files
    os.mkdir(os.path.join(nwOldProj, "cache"))
    for aFile in deleteFiles:
        writeFile(aFile, "Hi")
    for aFile in deleteFiles:
        assert os.path.isfile(aFile)

    # Open project and check that files that are not supposed to be
    # there have been removed
    assert theProject.openProject(nwOldProj)
    for aFile in deleteFiles:
        assert not os.path.isfile(aFile)

    assert not os.path.isdir(os.path.join(nwOldProj, "data_1", "stuff"))
    assert not os.path.isdir(os.path.join(nwOldProj, "data_1"))
    assert not os.path.isdir(os.path.join(nwOldProj, "data_7"))
    assert not os.path.isdir(os.path.join(nwOldProj, "data_8"))
    assert not os.path.isdir(os.path.join(nwOldProj, "data_9"))
    assert not os.path.isdir(os.path.join(nwOldProj, "data_a"))
    assert not os.path.isdir(os.path.join(nwOldProj, "data_f"))

    # Check stuff that has been moved
    assert os.path.isdir(os.path.join(nwOldProj, "junk"))
    assert os.path.isdir(os.path.join(nwOldProj, "junk", "stuff"))
    assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.nwd"))
    assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.txt"))

    # Check that files we want to keep are in the right place
    assert os.path.isdir(os.path.join(nwOldProj, "cache"))
    assert os.path.isdir(os.path.join(nwOldProj, "content"))
    assert os.path.isdir(os.path.join(nwOldProj, "meta"))

    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "f528d831f5b24.nwd"))
    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "88124a4292d8b.nwd"))
    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "91239bf2f8b69.nwd"))
    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "19752e7f9d8af.nwd"))
    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "a764d5acf5a21.nwd"))
    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "9058ae29f0dfd.nwd"))
    assert os.path.isfile(
        os.path.join(nwOldProj, "content", "7ff63b8afc4cd.nwd"))

    assert os.path.isfile(os.path.join(nwOldProj, "meta", "tagsIndex.json"))
    assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionInfo.log"))

    # Close the project
    theProject.closeProject()

    # Check that new files have been created
    assert os.path.isfile(os.path.join(nwOldProj, "meta", "guiOptions.json"))
    assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionStats.log"))
    assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt"))
Example #12
0
def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
    """Check that files in the content folder that are not tracked in
    the project XML file are handled correctly by the orphaned files
    function. It should also restore as much meta data as possible from
    the meta line at the top of the document file.
    """
    theProject = NWProject(mockGUI)

    assert theProject.openProject(nwLipsum)
    assert theProject.projTree["636b6aa9b697b"] is None
    assert theProject.closeProject()

    # First Item with Meta Data
    orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
    writeFile(orphPath, ("%%~name:[Recovered] Mars\n"
                         "%%~path:5eaea4e8cdee8/636b6aa9b697b\n"
                         "%%~kind:WORLD/NOTE\n"
                         "%%~invalid\n"
                         "\n"))

    # Second Item without Meta Data
    orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd")
    writeFile(orphPath, "\n")

    # Invalid File Name
    tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt")
    writeFile(tstPath, "\n")

    # Invalid File Name
    tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd")
    writeFile(tstPath, "\n")

    # Invalid File Name
    tstPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd")
    writeFile(tstPath, "\n")

    assert theProject.openProject(nwLipsum)
    assert theProject.projPath is not None
    assert theProject.projTree["636b6aa9b697bb"] is None
    assert theProject.projTree["abcdefghijklm"] is None

    # First Item with Meta Data
    oItem = theProject.projTree["636b6aa9b697b"]
    assert oItem is not None
    assert oItem.itemName == "[Recovered] Mars"
    assert oItem.itemHandle == "636b6aa9b697b"
    assert oItem.itemParent == "60bdf227455cc"
    assert oItem.itemClass == nwItemClass.WORLD
    assert oItem.itemType == nwItemType.FILE
    assert oItem.itemLayout == nwItemLayout.NOTE

    # Second Item without Meta Data
    oItem = theProject.projTree["736b6aa9b697b"]
    assert oItem is not None
    assert oItem.itemName == "Recovered File 1"
    assert oItem.itemHandle == "736b6aa9b697b"
    assert oItem.itemParent == "b3643d0f92e32"
    assert oItem.itemClass == nwItemClass.NOVEL
    assert oItem.itemType == nwItemType.FILE
    assert oItem.itemLayout == nwItemLayout.NOTE

    assert theProject.saveProject(nwLipsum)
    assert theProject.closeProject()

    # Finally, check that the orphaned files function returns
    # if no project is open and no path is set
    assert not theProject._scanProjectFolder()
Example #13
0
def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
    """Test adding and removing items from the project tree.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "information",
                        lambda *args: QMessageBox.Yes)
    monkeypatch.setattr(GuiMain, "editItem", lambda *args: None)

    nwGUI.theProject.projTree.setSeed(42)
    nwTree = nwGUI.treeView

    ##
    #  Add New Items
    ##

    # Try to add and move item with no project
    assert not nwTree.newTreeItem(nwItemType.FILE, None)
    assert not nwTree.moveTreeItem(1)

    # Open a project
    assert nwGUI.openProject(nwMinimal)

    # No location selected for new item
    nwTree.clearSelection()
    assert not nwTree.newTreeItem(nwItemType.FILE, None)
    assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
    assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)

    # No itemType set or ROOT, but no class
    assert not nwTree.newTreeItem(None, None)
    assert not nwTree.newTreeItem(nwItemType.ROOT, None)

    # Select a location
    chItem = nwTree._getTreeItem("a6d311a93600a")
    nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
    chItem.setExpanded(True)

    # Create new item with no class set (defaults to NOVEL)
    assert nwTree.newTreeItem(nwItemType.FILE, None)
    assert nwTree.newTreeItem(nwItemType.FOLDER, None)

    # Check that we have the correct tree order
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048",
        "71ee45a3c0db9"
    ]

    # Add roots
    assert not nwTree.newTreeItem(nwItemType.ROOT,
                                  nwItemClass.WORLD)  # Duplicate
    assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM)  # Valid

    # Change max depth and try to add a subfolder that is too deep
    monkeypatch.setattr("nw.constants.nwConst.MAX_DEPTH", 2)
    chItem = nwTree._getTreeItem("71ee45a3c0db9")
    nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
    assert not nwTree.newTreeItem(nwItemType.FOLDER, None)

    ##
    #  Move Items
    ##

    nwTree.setSelectedHandle("8c659a11cd429")

    # Shift focus and try to move item
    monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: False)
    assert not nwTree.moveTreeItem(1)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048",
        "71ee45a3c0db9"
    ]
    monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)

    # Move second item up twice (should give same result)
    nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048",
        "71ee45a3c0db9"
    ]
    nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048",
        "71ee45a3c0db9"
    ]

    # Move it back down four times (last two should be the same)
    nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048",
        "71ee45a3c0db9"
    ]
    nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "8c659a11cd429",
        "71ee45a3c0db9"
    ]
    nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9",
        "8c659a11cd429"
    ]
    nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9",
        "8c659a11cd429"
    ]

    # Move up twice, and undo
    nwTree._lastMove = {}
    nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
    nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
    nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
    assert nwTree.getTreeFromHandle("a6d311a93600a") == [
        "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9",
        "8c659a11cd429"
    ]

    # Move a root item (top level items are different) twice
    nwTree.flushTreeOrder()
    assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
    nwTree.setSelectedHandle("9d5247ab588e0")

    nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
    nwTree.flushTreeOrder()
    assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11

    nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
    nwTree.flushTreeOrder()
    assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11

    ##
    #  Delete and Trash
    ##

    # Add some content to the new file
    nwGUI.openDocument("73475cb40a568")
    nwGUI.docEditor.setText("# Hello World\n")
    nwGUI.saveDocument()
    nwGUI.saveProject()
    assert os.path.isfile(
        os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))

    # Delete the items we added earlier
    nwTree.clearSelection()
    assert not nwTree.emptyTrash()  # No folder yet
    assert not nwTree.deleteItem(None)
    assert not nwTree.deleteItem("1111111111111")
    assert nwTree.deleteItem("73475cb40a568")  # New File
    assert nwTree.deleteItem("71ee45a3c0db9")  # New Folder
    assert nwTree.deleteItem("811786ad1ae74")  # Custom Root
    assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
    assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
    assert "811786ad1ae74" not in nwGUI.theProject.projTree._treeOrder

    # The file is in trash, empty it
    assert os.path.isfile(
        os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
    assert nwTree.emptyTrash()
    assert not nwTree.emptyTrash()  # Already empty
    assert not os.path.isfile(
        os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
    assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder

    # Should not be allowed to add files and folders to Trash
    trashHandle = nwGUI.theProject.projTree.trashRoot()
    chItem = nwTree._getTreeItem(trashHandle)
    nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
    assert not nwTree.newTreeItem(nwItemType.FILE, None)
    assert not nwTree.newTreeItem(nwItemType.FOLDER, None)

    # Close the project
    nwGUI.closeProject()

    ##
    #  Orphaned Files
    ##

    # Add an orphaned file
    orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd")
    writeFile(orphFile, "# Hello World\n")

    # Open the project again
    nwGUI.openProject(nwMinimal)

    # Check that the orphaned file was found and added to the tree
    nwTree.flushTreeOrder()
    assert "1234567890abc" in nwGUI.theProject.projTree._treeOrder
    orItem = nwTree._getTreeItem("1234567890abc")
    assert orItem.text(nwTree.C_NAME) == "Recovered File 1"

    ##
    #  Unexpected Error Handling
    ##

    # Add an item with an invalid type
    assert not nwTree.newTreeItem(nwItemType.NO_TYPE, nwItemClass.NOVEL)
    assert "Failed to add new item" in caplog.messages[-1]

    # Add new file after one that has no parent handle
    chItem = nwTree._getTreeItem("44cb730c42048")
    nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
    nwTree.theProject.projTree["44cb730c42048"].itemParent = None
    assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
    nwTree.clearSelection()

    # Add a file with no parent, and fail to find a suitable parent item
    monkeypatch.setattr("nw.core.tree.NWTree.findRoot", lambda *args: None)

    assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
    assert not nwTree.newTreeItem(nwItemType.FOLDER, nwItemClass.NOVEL)

    # qtbot.stopForInteraction()
    nwGUI.closeProject()
Example #14
0
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
    """Test the split document tool.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
    monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text:
                        (text, True))

    # Create a new project
    buildTestProject(nwGUI, fncProj)

    # Handles for new objects
    hNovelRoot = "0000000000008"
    hChapterDir = "000000000000d"
    hToSplit = "0000000000010"
    hNewFolder = "0000000000021"
    hPartition = "0000000000022"
    hChapterOne = "0000000000023"
    hSceneOne = "0000000000024"
    hSceneTwo = "0000000000025"
    hSceneThree = "0000000000026"
    hSceneFour = "0000000000027"
    hSceneFive = "0000000000028"

    # Add Project Content
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hNovelRoot).setSelected(True)
    nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)

    assert nwGUI.saveProject() is True
    assert nwGUI.closeProject() is True

    tPartition = "# Nantucket"
    tChapterOne = "## Chapter One\n\n% Chapter one comment"
    tSceneOne = "### Scene One\n\nThere once was a man from Nantucket"
    tSceneTwo = "### Scene Two\n\nWho kept all his cash in a bucket."
    tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan,  \n\tRan away with a man"
    tSceneFour = "### Scene Four\n\nAnd as for the bucket, Nantucket."
    tSceneFive = "#### The End\n\nend"

    tToSplit = (f"{tPartition}\n\n{tChapterOne}\n\n"
                f"{tSceneOne}\n\n{tSceneTwo}\n\n"
                f"{tSceneThree}\n\n{tSceneFour}\n\n"
                f"{tSceneFive}\n\n")

    contentDir = os.path.join(fncProj, "content")
    writeFile(os.path.join(contentDir, hToSplit + ".nwd"), tToSplit)

    assert nwGUI.openProject(fncProj) is True

    # Open the Split tool
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hToSplit).setSelected(True)

    monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None)
    nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
    qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None,
                    timeout=1000)

    nwSplit = getGuiItem("GuiDocSplit")
    assert isinstance(nwSplit, GuiDocSplit)
    nwSplit.show()
    qtbot.wait(50)

    # Populate List
    # =============

    nwSplit.listBox.clear()
    assert nwSplit.listBox.count() == 0

    # No item selected
    nwSplit.sourceItem = None
    nwGUI.projView.projTree.clearSelection()
    assert nwSplit._populateList() is False
    assert nwSplit.listBox.count() == 0

    # Non-existing item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        nwSplit.sourceItem = None
        nwGUI.projView.projTree.clearSelection()
        nwGUI.projView.projTree._getTreeItem(hToSplit).setSelected(True)
        assert nwSplit._populateList() is False
        assert nwSplit.listBox.count() == 0

    # Select a non-file
    nwSplit.sourceItem = None
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
    assert nwSplit._populateList() is False
    assert nwSplit.listBox.count() == 0

    # Error when reading documents
    with monkeypatch.context() as mp:
        mp.setattr(NWDoc, "readDocument", lambda *a: None)
        nwSplit.sourceItem = hToSplit
        assert nwSplit._populateList() is False
        assert nwSplit.listBox.count() == 0

    # Read properly, and check split levels

    # Level 1
    nwSplit.splitLevel.setCurrentIndex(0)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 1

    # Level 2
    nwSplit.splitLevel.setCurrentIndex(1)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 2

    # Level 3
    nwSplit.splitLevel.setCurrentIndex(2)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 6

    # Level 4
    nwSplit.splitLevel.setCurrentIndex(3)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 7

    # Split Document
    # ==============

    # Test a proper split first
    with monkeypatch.context() as mp:
        mp.setattr(GuiDocSplit, "_doClose", lambda *a: None)
        assert nwSplit._doSplit() is True
        assert nwGUI.saveProject()

        assert readFile(os.path.join(
            contentDir, hPartition +
            ".nwd")) == ("%%%%~name: Nantucket\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hPartition, tPartition)

        assert readFile(os.path.join(
            contentDir, hChapterOne +
            ".nwd")) == ("%%%%~name: Chapter One\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hChapterOne, tChapterOne)

        assert readFile(os.path.join(
            contentDir, hSceneOne +
            ".nwd")) == ("%%%%~name: Scene One\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hSceneOne, tSceneOne)

        assert readFile(os.path.join(
            contentDir, hSceneTwo +
            ".nwd")) == ("%%%%~name: Scene Two\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hSceneTwo, tSceneTwo)

        assert readFile(os.path.join(
            contentDir, hSceneThree +
            ".nwd")) == ("%%%%~name: Scene Three\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hSceneThree, tSceneThree)

        assert readFile(os.path.join(
            contentDir, hSceneFour +
            ".nwd")) == ("%%%%~name: Scene Four\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hSceneFour, tSceneFour)

        assert readFile(os.path.join(
            contentDir, hSceneFive +
            ".nwd")) == ("%%%%~name: The End\n"
                         "%%%%~path: %s/%s\n"
                         "%%%%~kind: NOVEL/DOCUMENT\n"
                         "%s\n\n") % (hNewFolder, hSceneFive, tSceneFive)

    # OS error
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert nwSplit._doSplit() is False

    # Select to not split
    with monkeypatch.context() as mp:
        mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
        assert nwSplit._doSplit() is False

    # Clear the list
    nwSplit.listBox.clear()
    assert nwSplit._doSplit() is False

    # Can't find sourcv item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        assert nwSplit._doSplit() is False

    # No source item set
    nwSplit.sourceItem = None
    assert nwSplit._doSplit() is False

    # Close
    nwSplit._doClose()
Example #15
0
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
    """Test config intialisation.
    """
    tstConf = Config()

    confFile = os.path.join(tmpDir, "novelwriter.conf")
    testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
    compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")

    # Make sure we don't have any old conf file
    if os.path.isfile(confFile):
        os.unlink(confFile)

    # Let the config class figure out the path
    with monkeypatch.context() as mp:
        mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation",
                   lambda *a: fncDir)
        tstConf.verQtValue = 50600
        tstConf.initConfig()
        assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
        assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
        assert not os.path.isfile(confFile)
        tstConf.verQtValue = 50000
        tstConf.initConfig()
        assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
        assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
        assert not os.path.isfile(confFile)

    # Fail to make folders
    with monkeypatch.context() as mp:
        mp.setattr("os.mkdir", causeOSError)

        tstConfDir = os.path.join(fncDir, "test_conf")
        tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir)
        assert tstConf.confPath is None
        assert tstConf.dataPath == tmpDir
        assert not os.path.isfile(confFile)

        tstDataDir = os.path.join(fncDir, "test_data")
        tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir)
        assert tstConf.confPath == tmpDir
        assert tstConf.dataPath is None
        assert os.path.isfile(confFile)
        os.unlink(confFile)

    # Test load/save with no path
    tstConf.confPath = None
    assert tstConf.loadConfig() is False
    assert tstConf.saveConfig() is False

    # Run again and set the paths directly and correctly
    # This should create a config file as well
    with monkeypatch.context() as mp:
        mp.setattr("os.path.expanduser", lambda *a: "")
        tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
        assert tstConf.confPath == tmpDir
        assert tstConf.dataPath == tmpDir
        assert os.path.isfile(confFile)

        copyfile(confFile, testFile)
        assert cmpFiles(testFile,
                        compFile,
                        ignoreStart=("timestamp", "lastnotes", "guilang"))

    # Load and save with OSError
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)

        assert not tstConf.loadConfig()
        assert tstConf.hasError is True
        assert tstConf.errData != []
        assert tstConf.getErrData().startswith("Could not")
        assert tstConf.hasError is False
        assert tstConf.errData == []

        assert not tstConf.saveConfig()
        assert tstConf.hasError is True
        assert tstConf.errData != []
        assert tstConf.getErrData().startswith("Could not")
        assert tstConf.hasError is False
        assert tstConf.errData == []

    # Check handling of novelWriter as a package
    with monkeypatch.context() as mp:
        tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
        assert tstConf.confPath == tmpDir
        assert tstConf.dataPath == tmpDir
        appRoot = tstConf.appRoot

        mp.setattr("os.path.isfile", lambda *a: True)
        tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
        assert tstConf.confPath == tmpDir
        assert tstConf.dataPath == tmpDir
        assert tstConf.appRoot == os.path.dirname(appRoot)
        assert tstConf.appPath == os.path.dirname(appRoot)

    assert tstConf.loadConfig() is True
    assert tstConf.saveConfig() is True

    # Test Correcting Quote Settings
    origDbl = tstConf.fmtDoubleQuotes
    origSng = tstConf.fmtSingleQuotes
    orDoDbl = tstConf.doReplaceDQuote
    orDoSng = tstConf.doReplaceSQuote

    tstConf.fmtDoubleQuotes = ["\"", "\""]
    tstConf.fmtSingleQuotes = ["'", "'"]
    tstConf.doReplaceDQuote = True
    tstConf.doReplaceSQuote = True
    assert tstConf.saveConfig() is True

    assert tstConf.loadConfig() is True
    assert tstConf.doReplaceDQuote is False
    assert tstConf.doReplaceSQuote is False

    tstConf.fmtDoubleQuotes = origDbl
    tstConf.fmtSingleQuotes = origSng
    tstConf.doReplaceDQuote = orDoDbl
    tstConf.doReplaceSQuote = orDoSng
    assert tstConf.saveConfig() is True

    # Test Correcting icon theme
    origIcons = tstConf.guiIcons

    tstConf.guiIcons = "typicons_colour_dark"
    assert tstConf.saveConfig() is True
    assert tstConf.loadConfig() is True
    assert tstConf.guiIcons == "typicons_dark"

    tstConf.guiIcons = "typicons_grey_dark"
    assert tstConf.saveConfig() is True
    assert tstConf.loadConfig() is True
    assert tstConf.guiIcons == "typicons_dark"

    tstConf.guiIcons = "typicons_colour_light"
    assert tstConf.saveConfig() is True
    assert tstConf.loadConfig() is True
    assert tstConf.guiIcons == "typicons_light"

    tstConf.guiIcons = "typicons_grey_light"
    assert tstConf.saveConfig() is True
    assert tstConf.loadConfig() is True
    assert tstConf.guiIcons == "typicons_light"

    tstConf.guiIcons = origIcons
    assert tstConf.saveConfig()

    # Localisation
    # ============

    i18nDir = os.path.join(fncDir, "i18n")
    os.mkdir(i18nDir)
    os.mkdir(os.path.join(i18nDir, "stuff"))
    tstConf.nwLangPath = i18nDir

    copyfile(os.path.join(filesDir, "nw_en_GB.qm"),
             os.path.join(i18nDir, "nw_en_GB.qm"))
    writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "")
    writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "")

    tstApp = MockApp()
    tstConf.initLocalisation(tstApp)

    # Check Lists
    theList = tstConf.listLanguages(tstConf.LANG_NW)
    assert theList == [("en_GB", "British English")]
    theList = tstConf.listLanguages(tstConf.LANG_PROJ)
    assert theList == [("en_GB", "British English")]
    theList = tstConf.listLanguages(None)
    assert theList == []

    # Add Language
    copyfile(os.path.join(filesDir, "nw_en_GB.qm"),
             os.path.join(i18nDir, "nw_fr.qm"))
    writeFile(os.path.join(i18nDir, "nw_fr.ts"), "")

    theList = tstConf.listLanguages(tstConf.LANG_NW)
    assert theList == [("en_GB", "British English"), ("fr", "Français")]

    copyfile(confFile, testFile)
    assert cmpFiles(testFile,
                    compFile,
                    ignoreStart=("timestamp", "lastnotes", "guilang"))
Example #16
0
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
    """Test navigating the novel tree.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))

    buildTestProject(nwGUI, fncProj)

    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
    nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)

    writeFile(
        os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"),
        "# Jane Doe\n\n@tag: Jane\n\n"
    )
    writeFile(
        os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"), (
            "### Scene One\n\n"
            "@pov: Jane\n"
            "@focus: Jane\n\n"
            "% Synopsis: This is a scene."
        )
    )

    novelView = nwGUI.novelView
    novelTree = novelView.novelTree
    novelBar  = novelView.novelBar

    # Show/Hide Scrollbars
    # ====================

    nwGUI.mainConf.hideVScroll = True
    nwGUI.mainConf.hideHScroll = True
    novelView.initSettings()
    assert not novelTree.verticalScrollBar().isVisible()
    assert not novelTree.horizontalScrollBar().isVisible()

    nwGUI.mainConf.hideVScroll = False
    nwGUI.mainConf.hideHScroll = False
    novelView.initSettings()
    assert novelTree.verticalScrollBar().isEnabled()
    assert novelTree.horizontalScrollBar().isEnabled()

    # Populate Tree
    # =============

    nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView)
    nwGUI.rebuildIndex()
    novelTree._populateTree(rootHandle=None)
    assert novelTree.topLevelItemCount() == 3

    # Rebuild should preserve selection
    topItem = novelTree.topLevelItem(0)
    assert not topItem.isSelected()
    topItem.setSelected(True)
    assert novelTree.selectedItems()[0] == topItem
    assert novelView.getSelectedHandle() == ("000000000000c", 0)

    # Refresh using the slot for the butoom
    novelBar._refreshNovelTree()
    assert novelTree.topLevelItem(0).isSelected()

    # Open Items
    # ==========

    # Clear selection
    novelTree.clearSelection()
    scItem = novelTree.topLevelItem(2)
    scItem.setSelected(True)
    assert scItem.isSelected()

    # Clear selection with mouse
    vPort = novelTree.viewport()
    qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
    assert not scItem.isSelected()

    # Double-click item
    scItem.setSelected(True)
    assert scItem.isSelected()
    assert nwGUI.docEditor.docHandle() is None
    novelTree._treeDoubleClick(scItem, 0)
    assert nwGUI.docEditor.docHandle() == "000000000000f"

    # Open item with middle mouse button
    scItem.setSelected(True)
    assert scItem.isSelected()
    assert nwGUI.docViewer.docHandle() is None
    qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10)
    assert nwGUI.docViewer.docHandle() is None

    scRect = novelTree.visualItemRect(scItem)
    oldData = scItem.data(novelTree.C_TITLE, novelTree.D_HANDLE)
    scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, None)
    qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
    assert nwGUI.docViewer.docHandle() is None

    scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, oldData)
    qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
    assert nwGUI.docViewer.docHandle() == "000000000000f"

    # Last Column
    # ===========

    novelBar.setLastColType(NovelTreeColumn.HIDDEN)
    assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True
    assert novelTree.lastColType == NovelTreeColumn.HIDDEN
    assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "")

    novelBar.setLastColType(NovelTreeColumn.POV)
    assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
    assert novelTree.lastColType == NovelTreeColumn.POV
    assert novelTree._getLastColumnText("000000000000f", "T000001") == (
        "Jane", "Point of View: Jane"
    )

    novelBar.setLastColType(NovelTreeColumn.FOCUS)
    assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
    assert novelTree.lastColType == NovelTreeColumn.FOCUS
    assert novelTree._getLastColumnText("000000000000f", "T000001") == (
        "Jane", "Focus: Jane"
    )

    novelBar.setLastColType(NovelTreeColumn.PLOT)
    assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
    assert novelTree.lastColType == NovelTreeColumn.PLOT
    assert novelTree._getLastColumnText("000000000000f", "T000001") == (
        "", "Plot: "
    )

    novelTree._lastCol = None
    assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "")

    # Item Meta
    # =========

    ttText = ""

    def showText(pos, text):
        nonlocal ttText
        ttText = text

    mIndex = novelTree.model().index(2, novelTree.C_MORE)
    with monkeypatch.context() as mp:
        mp.setattr(QToolTip, "showText", showText)
        novelTree._treeItemClicked(mIndex)
        assert ttText == (
            "<p><b>Point of View</b>: Jane<br><b>Focus</b>: Jane</p>"
            "<p><b>Synopsis</b>: This is a scene.</p>"
        )

    # Other Checks
    # ============

    scItem = novelTree.topLevelItem(2)
    scItem.setSelected(True)
    assert scItem.isSelected()
    novelTree.focusOutEvent(QFocusEvent(QEvent.None_, Qt.MouseFocusReason))
    assert not scItem.isSelected()

    # Close
    # =====

    # qtbot.stop()
    nwGUI.closeProject()
def startSearch(status_passed):
    """
    Invokes the search on the file.
    
    Central bit of the core - examines the source file byte by byte and checks against the
    provided start sequences of files (provided by the signatures). Ones, a start sequence
    has been found, the further actions depend on the type of signature (in fact, how the end
    of the file is identified - by end sequence, file size info inside file or manual by
    additional module).
    
    The status object is constantly updated. The frequency of updating the status instance with
    progress within a source file depends on the value in the settings instance (
    ExecutionSettings.ExecutionSettings.output_frequency) In fact, this variable says how often
    a message shall be sent to the status object in total for the current source file.
    
    @param status_passed: Reference to the status instance for applying runtime information and
    gaining settings for the running.
    @type status_passed: ExecutionSettings.ExecutionStatus
    return: Active Signatures; Overall Counter
    rtype: C{List} of C{Signatures}; C{int}
    """
    global binfile, start, skipped, size, maxlength
    global status
    status= status_passed
    st = binfile.read(maxlength-1)

    dx = size / status.settings.output_frequency           # for user output only
    x = dx                                  # same here    
    
    file_pos = binfile.tell()
    status.startedOneSourceFile(size)
    
    while 1:
        c = ''
        if file_pos < status.file_end:
            c = binfile.read(1)
        file_pos = binfile.tell()
        if c!='':               # end of file
            st += c
    
        if file_pos-status.file_start >= x:
            status.updateFineshedForCurrent(file_pos-status.file_start)
            
            if status.settings.output_level == 0:
                pass  
            elif status.settings.output_level == 3 and size!=0:
                print "Pos: 0x%x - %d / %d KB (%d %%)" %(file_pos, (file_pos-status.file_start)  / 1024 , size / 1024, (file_pos-status.file_start)*100/size)
            elif status.settings.output_level == 2:
                print "%d %%" %((file_pos-status.file_start)*100/size)
            elif status.settings.output_level == 1:
                print '#' ,
            x += dx
            
        for sig in status.settings.signatures:
            if start[sig[signatures.name]] == -1:
                if sig[signatures.start_seq][0] == ord(st[0]):
                    if checkString(st, sig[signatures.start_seq]):
                        start_pos = file_pos-len(st)
                        if status.settings.output_level == 3:
                            print ('Found start at 0x%x for %s' %(start_pos, sig[signatures.description]))
                        if sig[signatures.filesize_type] == signatures.TYPE_FILE_SIZE:
                            offsets = sig[signatures.filesize_address_offsets]
                            ofs = 0
                            for i in offsets:
                                binfile.seek(start_pos + i)
                                val = ord(binfile.read(1))
                                ofs = ofs * 256 + val
                            end_pos = start_pos + ofs
                            correction = sig[signatures.filesize_info_correction]
                            end_pos = end_pos + correction
                            writeFile(sig[signatures.name],status.counterr[sig[signatures.name]]+status.settings.counterstart_global,
                                  sig[signatures.extension],binfile, start_pos, end_pos-1,
                                  status.settings.dest_folder, status.settings.output_level == 3, status)
                            status.counter[sig[signatures.name]] += 1
                            status.counterr[sig[signatures.name]] += 1
                            status.foundFile()
                            binfile.seek(file_pos)
                        elif sig[signatures.filesize_type] == signatures.TYPE_MANUAL:
                            function = sig[signatures.filesizemanual_functionname]
                            if status.settings.output_level == 3:
                                print ('-- Enter signature defined function for end address determination for this file')
                            end_address = function(binfile, start_pos, status.settings.output_level == 3)
                            if (end_address < start_pos):
                                if status.settings.output_level == 3:
                                    print ('-- No valid end address found - skip this file.')
                                continue
                            writeFile(sig[signatures.name],status.counterr[sig[signatures.name]]+status.settings.counterstart_global,
                                  sig[signatures.extension],binfile, start_pos, end_address,
                                  status.settings.dest_folder, status.settings.output_level == 3, status)
                            status.counter[sig[signatures.name]] += 1   
                            status.counterr[sig[signatures.name]] += 1   
                            status.foundFile()
                            binfile.seek(file_pos)
                        else:
                            start[sig[signatures.name]] = start_pos
            else:
                if file_pos < start[sig[signatures.name]] + len(sig[signatures.start_seq]):
                    continue
                if sig[signatures.end_seq][0] == ord(st[0]):
                    if tools.checkString(st, sig[signatures.end_seq]):
                        end_pos = file_pos-len(st)+len(sig[signatures.end_seq])-1
                        if skipped[sig[signatures.name]] < sig[signatures.skip_end_seqs]:
                            skipped[sig[signatures.name]] +=1
                            if status.settings.output_level == 3:
                                print ('Found end at 0x%x for %s - skipped' %(end_pos, sig[signatures.description]))
                            continue
                        if status.settings.output_level == 3:
                            print ('Found end at 0x%x for %s' %(end_pos, sig[signatures.description]))
                        writeFile(sig[signatures.name],status.counterr[sig[signatures.name]]+status.settings.counterstart_global,
                                sig[signatures.extension],binfile, start[sig[signatures.name]], end_pos,
                                status.settings.dest_folder, status.settings.output_level == 3, status)
                        start[sig[signatures.name]] = -1
                        status.counter[sig[signatures.name]] += 1
                        status.counterr[sig[signatures.name]] += 1
                        status.foundFile()
                        skipped[sig[signatures.name]] = 0
            
        if len(st)==1:
            break
        st = st[1:]
    
    status.finishedOneSourceFile()
    binfile.close()
    return status.settings.signatures, status.counterr
Example #18
0
def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
    """Test loading and saving from the OptionState class.
    """
    theProject = NWProject(mockGUI)
    theOpts = OptionState(theProject)

    # Write a test file
    optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE)
    writeFile(optFile, json.dumps({
        "GuiBuildNovel": {
            "winWidth": 1000,
            "winHeight": 700,
            "addNovel": True,
            "addNotes": False,
            "textFont": "Cantarell",
            "mockItem": None,
        },
        "MockGroup": {
            "mockItem": None,
        },
    }))

    # Load and save with no path set
    theProject.projMeta = None
    assert not theOpts.loadSettings()
    assert not theOpts.saveSettings()

    # Set path
    theProject.projMeta = tmpDir
    assert theProject.projMeta == tmpDir

    # Cause open() to fail
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert not theOpts.loadSettings()
        assert not theOpts.saveSettings()

    # Load proper
    assert theOpts.loadSettings()

    # Check that unwanted items have been removed
    assert theOpts._theState == {
        "GuiBuildNovel": {
            "winWidth": 1000,
            "winHeight": 700,
            "addNovel": True,
            "addNotes": False,
            "textFont": "Cantarell",
        },
    }

    # Save proper
    assert theOpts.saveSettings()

    # Load again to check we get the values back
    assert theOpts.loadSettings()
    assert theOpts._theState == {
        "GuiBuildNovel": {
            "winWidth": 1000,
            "winHeight": 700,
            "addNovel": True,
            "addNotes": False,
            "textFont": "Cantarell",
        },
    }
Example #19
0
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
    """Test config intialisation.
    """
    tstConf = Config()

    confFile = os.path.join(tmpDir, "novelwriter.conf")
    testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
    compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")

    # Make sure we don't have any old conf file
    if os.path.isfile(confFile):
        os.unlink(confFile)

    # Let the config class figure out the path
    with monkeypatch.context() as mp:
        mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir)
        tstConf.verQtValue = 50600
        tstConf.initConfig()
        assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
        assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
        assert not os.path.isfile(confFile)
        tstConf.verQtValue = 50000
        tstConf.initConfig()
        assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
        assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
        assert not os.path.isfile(confFile)

    # Fail to make folders
    with monkeypatch.context() as mp:
        mp.setattr("os.mkdir", causeOSError)

        tstConfDir = os.path.join(fncDir, "test_conf")
        tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir)
        assert tstConf.confPath is None
        assert tstConf.dataPath == tmpDir
        assert not os.path.isfile(confFile)

        tstDataDir = os.path.join(fncDir, "test_data")
        tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir)
        assert tstConf.confPath == tmpDir
        assert tstConf.dataPath is None
        assert os.path.isfile(confFile)
        os.unlink(confFile)

    # Test load/save with no path
    tstConf.confPath = None
    assert not tstConf.loadConfig()
    assert not tstConf.saveConfig()

    # Run again and set the paths directly and correctly
    # This should create a config file as well
    with monkeypatch.context() as mp:
        mp.setattr("os.path.expanduser", lambda *args: "")
        tstConf.spellTool = nwConst.SP_INTERNAL
        tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
        assert tstConf.confPath == tmpDir
        assert tstConf.dataPath == tmpDir
        assert os.path.isfile(confFile)

        copyfile(confFile, testFile)
        assert cmpFiles(testFile, compFile, [2, 9, 10])

    # Load and save with OSError
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)

        assert not tstConf.loadConfig()
        assert tstConf.hasError is True
        assert tstConf.errData != []
        assert tstConf.getErrData().startswith("Could not")
        assert tstConf.hasError is False
        assert tstConf.errData == []

        assert not tstConf.saveConfig()
        assert tstConf.hasError is True
        assert tstConf.errData != []
        assert tstConf.getErrData().startswith("Could not")
        assert tstConf.hasError is False
        assert tstConf.errData == []

    assert tstConf.loadConfig()
    assert tstConf.saveConfig()

    # Test Correcting Quote Settings
    origDbl = tstConf.fmtDoubleQuotes
    origSng = tstConf.fmtSingleQuotes
    orDoDbl = tstConf.doReplaceDQuote
    orDoSng = tstConf.doReplaceSQuote

    tstConf.fmtDoubleQuotes = ["\"", "\""]
    tstConf.fmtSingleQuotes = ["'", "'"]
    tstConf.doReplaceDQuote = True
    tstConf.doReplaceSQuote = True
    assert tstConf.saveConfig()

    assert tstConf.loadConfig()
    assert not tstConf.doReplaceDQuote
    assert not tstConf.doReplaceSQuote

    tstConf.fmtDoubleQuotes = origDbl
    tstConf.fmtSingleQuotes = origSng
    tstConf.doReplaceDQuote = orDoDbl
    tstConf.doReplaceSQuote = orDoSng
    assert tstConf.saveConfig()

    # Localisation
    i18nDir = os.path.join(fncDir, "i18n")
    os.mkdir(i18nDir)
    os.mkdir(os.path.join(i18nDir, "stuff"))
    tstConf.nwLangPath = i18nDir

    copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(fncDir, "nw_en_GB.qm"))
    writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "")
    writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "")

    tstApp = DummyApp()
    tstConf.initLocalisation(tstApp)
    theList = tstConf.listLanguages(tstConf.LANG_NW)
    assert theList == [("en_GB", "British English")]

    copyfile(confFile, testFile)
    assert cmpFiles(testFile, compFile, [2, 9, 10])
Example #20
0
def testCoreSpell_Enchant(monkeypatch, tmpDir):
    """Test the pyenchant spell checker
    """
    wList = os.path.join(tmpDir, "wordlist.txt")
    writeFile(wList, "a_word\nb_word\nc_word\n")

    # Block the enchant package (and trigger the default class)
    with monkeypatch.context() as mp:
        mp.setitem(sys.modules, "enchant", None)
        spChk = NWSpellEnchant()

        spChk.setLanguage("en", wList)
        assert spChk.setLanguage("", "") is None
        assert spChk.checkWord("") is True
        assert spChk.suggestWords("") == []
        assert spChk.listDictionaries() == []
        assert spChk.describeDict() == ("", "")

    # Break the enchant package, and check error handling
    spChk = NWSpellEnchant()
    spChk.theDict = None
    assert spChk.checkWord("word") is True
    assert spChk.suggestWords("word") == []
    assert spChk.addWord("word") is False

    # Load the proper enchant package (twice)
    spChk = NWSpellEnchant()
    spChk.setLanguage("en", wList)
    spChk.setLanguage("en", wList)

    # Add a word to the user's dictionary
    assert spChk._readProjectDictionary("stuff") is False
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert spChk._readProjectDictionary(wList) is False

    assert spChk._readProjectDictionary(None) is False
    assert spChk._readProjectDictionary(wList) is True
    assert spChk._projectDict == wList

    # Cannot write to file
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert spChk.addWord("d_word") is False

    assert readFile(wList) == "a_word\nb_word\nc_word\n"
    assert spChk.addWord("d_word") is True
    assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n"
    assert spChk.addWord("d_word") is False

    # Check words
    assert spChk.checkWord("a_word") is True
    assert spChk.checkWord("b_word") is True
    assert spChk.checkWord("c_word") is True
    assert spChk.checkWord("d_word") is True
    assert spChk.checkWord("e_word") is False

    spChk.addWord("d_word")
    assert spChk.checkWord("d_word") is True

    wSuggest = spChk.suggestWords("wrod")
    assert len(wSuggest) > 0
    assert "word" in wSuggest

    dList = spChk.listDictionaries()
    assert len(dList) > 0

    aTag, aName = spChk.describeDict()
    assert aTag == "en"
    assert aName != ""
Example #21
0
def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf):
    """Test the fallback simple spell checker
    """
    wList = os.path.join(tmpDir, "wordlist.txt")
    wDict = os.path.join(tmpDir, "en.dict")
    writeFile(wList, "a_word\nb_word\nc_word\n")
    writeFile(wDict, "# Comment\ne_word\nf_word\ng_word\n")

    spChk = NWSpellSimple()
    spChk.mainConf = tmpConf
    spChk.mainConf.dictPath = tmpDir

    # Load dictionary, but fail
    monkeypatch.setattr("builtins.open", causeOSError)
    spChk.setLanguage("en", wList)
    assert spChk.spellLanguage is None
    assert spChk.WORDS == spChk.projDict
    monkeypatch.undo()

    # Load dictionary properly
    spChk.setLanguage("en", wList)
    assert spChk.projDict == ["a_word", "b_word", "c_word"]
    assert spChk.WORDS == [
        "e_word", "f_word", "g_word", "a_word", "b_word", "c_word"
    ]

    # Check words
    assert spChk.checkWord("a_word")
    assert spChk.checkWord("b_word")
    assert spChk.checkWord("c_word")
    assert not spChk.checkWord("d_word")
    assert spChk.checkWord("e_word")
    assert spChk.checkWord("f_word")
    assert spChk.checkWord("g_word")

    # Add word
    spChk.addWord("d_word")
    assert spChk.checkWord("d_word")

    # Check spelling
    assert spChk.suggestWords(" \t") == []

    wSuggest = spChk.suggestWords("d_wrod")
    assert len(wSuggest) > 0
    assert "d_word" in wSuggest

    # Break the matching
    monkeypatch.setattr("difflib.get_close_matches",
                        lambda *args, **kwargs: [""])
    assert spChk.suggestWords("word") == []
    monkeypatch.undo()

    # Capitalisation
    wSuggest = spChk.suggestWords("D_wrod")
    assert len(wSuggest) > 0
    assert "D_word" in wSuggest

    # List dictionaries
    assert spChk.listDictionaries() == [("en",
                                         "English [%s]" % nwConst.SP_INTERNAL)]

    # Description
    aTag, aName = spChk.describeDict()
    assert aTag == "en"
    assert aName == nwConst.SP_INTERNAL
Example #22
0
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
    """Test the Insert menu.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)

    nwGUI.theProject.projTree.setSeed(42)
    assert nwGUI.newProject({"projPath": fncProj})

    assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
    assert nwGUI.openDocument("0e17daca5f3e1") is True
    nwGUI.docEditor.clear()

    # Test Faulty Inserts
    assert nwGUI.docEditor.insertText("hello world")
    assert nwGUI.docEditor.getText() == "hello world"
    nwGUI.docEditor.clear()

    assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT)
    assert nwGUI.docEditor.isEmpty()

    assert not nwGUI.docEditor.insertText(None)
    assert nwGUI.docEditor.isEmpty()

    # qtbot.stopForInteraction()

    # Check Menu Entries
    nwGUI.mainMenu.aInsENDash.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_ENDASH
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsEMDash.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_EMDASH
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsHorBar.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_HBAR
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsFigDash.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_FGDASH
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsQuoteLS.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[0]
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsQuoteRS.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[1]
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsQuoteLD.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[0]
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsQuoteRD.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[1]
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsMSApos.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsEllipsis.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_HELLIP
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsPrime.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_PRIME
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsDPrime.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_DPRIME
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsBullet.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_BULL
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsHyBull.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_HYBULL
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsFlower.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_FLOWER
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsPerMille.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_PERMIL
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsDegree.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_DEGREE
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsMinus.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_MINUS
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsTimes.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_TIMES
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsDivide.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_DIVIDE
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsNBSpace.activate(QAction.Trigger)
    if nwGUI.mainConf.verQtValue >= 50900:
        assert nwGUI.docEditor.getText() == nwUnicode.U_NBSP
    else:
        assert nwGUI.docEditor.getText() == " "
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsThinSpace.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == nwUnicode.U_THSP
    nwGUI.docEditor.clear()

    nwGUI.mainMenu.aInsThinNBSpace.activate(QAction.Trigger)
    if nwGUI.mainConf.verQtValue >= 50900:
        assert nwGUI.docEditor.getText() == nwUnicode.U_THNBSP
    else:
        assert nwGUI.docEditor.getText() == " "
    nwGUI.docEditor.clear()

    ##
    #  Insert Keywords
    ##

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.TAG_KEY][0].activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TAG_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.POV_KEY][0].activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.POV_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.FOCUS_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.FOCUS_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.CHAR_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CHAR_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.PLOT_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.PLOT_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.TIME_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TIME_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.WORLD_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.WORLD_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.OBJECT_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.OBJECT_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.ENTITY_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.ENTITY_KEY

    nwGUI.docEditor.setText("Stuff")
    nwGUI.mainMenu.mInsKWItems[nwKeyWords.CUSTOM_KEY][0].activate(
        QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CUSTOM_KEY

    # Faulty Keyword Inserts
    assert not nwGUI.docEditor.insertKeyWord("blabla")
    with monkeypatch.context() as mp:
        mp.setattr(QTextBlock, "isValid", lambda *a, **k: False)
        assert not nwGUI.docEditor.insertKeyWord(nwKeyWords.TAG_KEY)

    nwGUI.docEditor.clear()

    ##
    #  Insert Break or Space
    ##

    nwGUI.docEditor.setText("### Stuff\n")
    nwGUI.mainMenu.aInsNewPage.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == "[NEW PAGE]\n### Stuff\n"

    nwGUI.docEditor.setText("### Stuff\n")
    nwGUI.mainMenu.aInsVSpaceS.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == "[VSPACE]\n### Stuff\n"

    nwGUI.docEditor.setText("### Stuff\n")
    nwGUI.mainMenu.aInsVSpaceM.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == "[VSPACE:2]\n### Stuff\n"

    nwGUI.docEditor.clear()

    ##
    #  Insert text from file
    ##

    nwGUI.closeDocument()

    # First, with no path
    monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k:
                        ("", ""))
    assert not nwGUI.importDocument()

    # Then with a path, but an invalid one
    monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k:
                        (" ", ""))
    assert not nwGUI.importDocument()

    # Then a valid path, but bot a file that exists
    theFile = os.path.join(fncDir, "import.txt")
    monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k:
                        (theFile, ""))
    assert not nwGUI.importDocument()

    # Create the file and try again, but with no target document open
    writeFile(theFile, "Foo")
    assert not nwGUI.importDocument()

    # Open the document from before, and add some text to it
    nwGUI.openDocument("0e17daca5f3e1")
    nwGUI.docEditor.setText("Bar")
    assert nwGUI.docEditor.getText() == "Bar"

    # The document isn't empty, so the message box should pop
    monkeypatch.setattr(QMessageBox, "question",
                        lambda *a, **k: QMessageBox.No)
    assert not nwGUI.importDocument()
    assert nwGUI.docEditor.getText() == "Bar"

    # Finally, accept the replaced text, this time we use the menu entry to trigger it
    monkeypatch.setattr(QMessageBox, "question",
                        lambda *a, **k: QMessageBox.Yes)
    nwGUI.mainMenu.aImportFile.activate(QAction.Trigger)
    assert nwGUI.docEditor.getText() == "Foo"

    ##
    #  Reveal file location
    ##

    theMessage = ""

    def recordMsg(*args):
        nonlocal theMessage
        theMessage = args[3]
        return None

    assert not theMessage
    monkeypatch.setattr(QMessageBox, "information", recordMsg)
    nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)

    theBits = theMessage.split("<br>")
    assert len(theBits) == 2
    assert theBits[0] == "The currently open file is saved in:"
    assert theBits[1] == os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
    """Test loading and saving a document with the NWDoc class.
    """
    theProject = NWProject(mockGUI)
    assert theProject.openProject(nwMinimal) is True
    assert theProject.projPath == nwMinimal

    sHandle = "8c659a11cd429"

    # Read Document
    # =============

    # Not a valid handle
    theDoc = NWDoc(theProject, "stuff")
    assert bool(theDoc) is False
    assert theDoc.readDocument() is None

    # Non-existent handle
    theDoc = NWDoc(theProject, "0000000000000")
    assert theDoc.readDocument() is None
    assert theDoc._currHash is None

    # Cause open() to fail while loading
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        theDoc = NWDoc(theProject, sHandle)
        assert theDoc.readDocument() is None
        assert theDoc.getError() == "OSError: Mock OSError"

    # Load the text
    theDoc = NWDoc(theProject, sHandle)
    assert theDoc.readDocument() == "### New Scene\n\n"

    # Try to open a new (non-existent) file
    nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL)
    assert nHandle is not None
    xHandle = theProject.newFile("New File", nwItemClass.NOVEL, nHandle)
    theDoc = NWDoc(theProject, xHandle)
    assert bool(theDoc) is True
    assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
    assert theDoc.readDocument() == ""

    # Write Document
    # ==============

    # Set handle and save again
    theText = "### Test File\n\nText ...\n\n"
    theDoc = NWDoc(theProject, xHandle)
    assert theDoc.readDocument(xHandle) == ""
    assert theDoc.writeDocument(theText) is True

    # Save again to ensure temp file and previous file is handled
    assert theDoc.writeDocument(theText)

    # Check file content
    docPath = os.path.join(nwMinimal, "content", xHandle + ".nwd")
    assert readFile(docPath) == ("%%~name: New File\n"
                                 f"%%~path: a508bb932959c/{xHandle}\n"
                                 "%%~kind: NOVEL/DOCUMENT\n"
                                 "### Test File\n\n"
                                 "Text ...\n\n")

    # Alter the document on disk and save again
    writeFile(docPath, "blablabla")
    assert theDoc.writeDocument(theText) is False

    # Force the overwrite
    assert theDoc.writeDocument(theText, forceWrite=True) is True

    # Force no meta data
    theDoc._theItem = None
    assert theDoc.writeDocument(theText) is True
    assert readFile(docPath) == theText

    # Cause open() to fail while saving
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert theDoc.writeDocument(theText) is False
        assert theDoc.getError() == "OSError: Mock OSError"

    theDoc._docError = ""
    assert theDoc.getError() == ""

    # Cause os.replace() to fail while saving
    with monkeypatch.context() as mp:
        mp.setattr("os.replace", causeOSError)
        assert theDoc.writeDocument(theText) is False
        assert theDoc.getError() == "OSError: Mock OSError"

    theDoc._docError = ""
    assert theDoc.getError() == ""

    # Saving with no handle
    theDoc._docHandle = None
    assert theDoc.writeDocument(theText) is False

    # Delete Document
    # ===============

    # Delete the last document
    theDoc = NWDoc(theProject, "stuff")
    assert theDoc.deleteDocument() is False
    assert os.path.isfile(docPath)

    # Cause the delete to fail
    with monkeypatch.context() as mp:
        mp.setattr("os.unlink", causeOSError)
        theDoc = NWDoc(theProject, xHandle)
        assert theDoc.deleteDocument() is False
        assert theDoc.getError() == "OSError: Mock OSError"

    # Make the delete pass
    theDoc = NWDoc(theProject, xHandle)
    assert theDoc.deleteDocument() is True
    assert not os.path.isfile(docPath)
Example #24
0
def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
    """Test the functins that handle legacy data folders and structure
    with additional tests of failure handling.
    """
    theProject = NWProject(dummyGUI)
    theProject.setProjectPath(fncDir)

    # assert theProject.newProject({"projPath": fncDir})
    # assert theProject.saveProject()
    # assert theProject.closeProject()

    # Check behaviour of deprecated files function on OSError
    tstFile = os.path.join(fncDir, "ToC.json")
    writeFile(tstFile, "dummy")
    assert os.path.isfile(tstFile)

    monkeypatch.setattr("os.unlink", causeOSError)
    assert not theProject._deprecatedFiles()
    monkeypatch.undo()

    assert theProject._deprecatedFiles()
    assert not os.path.isfile(tstFile)

    # Check processing non-folders
    tstFile = os.path.join(fncDir, "data_0")
    writeFile(tstFile, "dummy")
    assert os.path.isfile(tstFile)

    errList = []
    errList = theProject._legacyDataFolder(tstFile, errList)
    assert len(errList) > 0

    # Move folder in data folder, shouldn't be there
    tstData = os.path.join(fncDir, "data_1")
    errItem = os.path.join(fncDir, "data_1", "stuff")
    os.mkdir(tstData)
    os.mkdir(errItem)
    assert os.path.isdir(tstData)
    assert os.path.isdir(errItem)

    # This causes a failure to create the 'junk' folder
    monkeypatch.setattr("os.mkdir", causeOSError)
    errList = []
    errList = theProject._legacyDataFolder(tstData, errList)
    assert len(errList) > 0
    monkeypatch.undo()

    # This causes a failure to move 'stuff' to 'junk'
    monkeypatch.setattr("os.rename", causeOSError)
    errList = []
    errList = theProject._legacyDataFolder(tstData, errList)
    assert len(errList) > 0
    monkeypatch.undo()

    # This should be successful
    errList = []
    errList = theProject._legacyDataFolder(tstData, errList)
    assert len(errList) == 0
    assert os.path.isdir(os.path.join(fncDir, "junk", "stuff"))

    # Check renaming/deleting of old document files
    tstData = os.path.join(fncDir, "data_2")
    tstDoc1m = os.path.join(tstData, "000000000001_main.nwd")
    tstDoc1b = os.path.join(tstData, "000000000001_main.bak")
    tstDoc2m = os.path.join(tstData, "000000000002_main.nwd")
    tstDoc2b = os.path.join(tstData, "000000000002_main.bak")
    tstDoc3m = os.path.join(tstData, "tooshort003_main.nwd")
    tstDoc3b = os.path.join(tstData, "tooshort003_main.bak")

    os.mkdir(tstData)
    writeFile(tstDoc1m, "dummy")
    writeFile(tstDoc1b, "dummy")
    writeFile(tstDoc2m, "dummy")
    writeFile(tstDoc2b, "dummy")
    writeFile(tstDoc3m, "dummy")
    writeFile(tstDoc3b, "dummy")

    # Make the above fail
    monkeypatch.setattr("os.rename", causeOSError)
    monkeypatch.setattr("os.unlink", causeOSError)
    errList = []
    errList = theProject._legacyDataFolder(tstData, errList)
    assert len(errList) > 0
    assert os.path.isfile(tstDoc1m)
    assert os.path.isfile(tstDoc1b)
    assert os.path.isfile(tstDoc2m)
    assert os.path.isfile(tstDoc2b)
    assert os.path.isfile(tstDoc3m)
    assert os.path.isfile(tstDoc3b)
    monkeypatch.undo()

    # And succeed ...
    errList = []
    errList = theProject._legacyDataFolder(tstData, errList)
    assert len(errList) == 0

    assert not os.path.isdir(tstData)
    assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd"))
    assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd"))
    assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.nwd"))
    assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.bak"))
Example #25
0
def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
    """Test opening a project.
    """
    theProject = NWProject(dummyGUI)

    # Rename the project file to check handling
    rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE)
    wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE + "_sdfghj")
    os.rename(rName, wName)
    assert theProject.openProject(nwMinimal) is False
    os.rename(wName, rName)

    # Fail on folder structure check
    monkeypatch.setattr("os.mkdir", causeOSError)
    assert theProject.openProject(nwMinimal) is False
    monkeypatch.undo()

    # Fail on lock file
    theProject.setProjectPath(nwMinimal)
    assert theProject._writeLockFile()
    assert theProject.openProject(nwMinimal) is False

    # Fail to read lockfile (which still opens the project)
    monkeypatch.setattr("builtins.open", causeOSError)
    assert theProject.openProject(nwMinimal) is True
    monkeypatch.undo()
    assert theProject.closeProject()

    # Force open with lockfile
    theProject.setProjectPath(nwMinimal)
    assert theProject._writeLockFile()
    assert theProject.openProject(nwMinimal, overrideLock=True) is True
    assert theProject.closeProject()

    # Make a junk XML file
    oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3] + "orig")
    bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3] + "bak")
    os.rename(rName, oName)
    writeFile(rName, "dummy")
    assert theProject.openProject(nwMinimal) is False

    # Also write a jun XML backup file
    writeFile(bName, "dummy")
    assert theProject.openProject(nwMinimal) is False

    # Wrong root item
    writeFile(rName, "<not_novelWriterXML></not_novelWriterXML>\n")
    assert theProject.openProject(nwMinimal) is False

    # Wrong file version
    writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n"
                      "<novelWriterXML "
                      "appVersion=\"1.0\" "
                      "hexVersion=\"0x01000000\" "
                      "fileVersion=\"1.0\" "
                      "timeStamp=\"2020-01-01 00:00:00\">\n"
                      "</novelWriterXML>\n"))
    dummyGUI.askResponse = False
    assert theProject.openProject(nwMinimal) is False
    dummyGUI.undo()

    # Future file version
    writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n"
                      "<novelWriterXML "
                      "appVersion=\"1.0\" "
                      "hexVersion=\"0x01000000\" "
                      "fileVersion=\"99.99\" "
                      "timeStamp=\"2020-01-01 00:00:00\">\n"
                      "</novelWriterXML>\n"))
    assert theProject.openProject(nwMinimal) is False

    # Larger hex version
    writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n"
                      "<novelWriterXML "
                      "appVersion=\"1.0\" "
                      "hexVersion=\"0xffffffff\" "
                      "fileVersion=\"1.2\" "
                      "timeStamp=\"2020-01-01 00:00:00\">\n"
                      "</novelWriterXML>\n"))
    dummyGUI.askResponse = False
    assert theProject.openProject(nwMinimal) is False
    dummyGUI.undo()

    # Test skipping XML entries
    writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n"
                      "<novelWriterXML "
                      "appVersion=\"1.0\" "
                      "hexVersion=\"0x01000000\" "
                      "fileVersion=\"1.2\" "
                      "timeStamp=\"2020-01-01 00:00:00\">\n"
                      "<project><stuff/></project>\n"
                      "<settings><stuff/></settings>\n"
                      "</novelWriterXML>\n"))
    assert theProject.openProject(nwMinimal) is True
    assert theProject.closeProject()

    # Test deprecated XML entries
    writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n"
                      "<novelWriterXML "
                      "appVersion=\"1.0\" "
                      "hexVersion=\"0x01000000\" "
                      "fileVersion=\"1.2\" "
                      "timeStamp=\"2020-01-01 00:00:00\">\n"
                      "<settings>\n"
                      "<autoReplace>\n"
                      "<A>B</A>\n"
                      "</autoReplace>\n"
                      "</settings>\n"
                      "</novelWriterXML>\n"))
    assert theProject.openProject(nwMinimal) is True
    assert theProject.autoReplace == {"A": "B"}
    assert theProject.closeProject()

    # Clean up XML files
    os.unlink(rName)
    os.unlink(bName)
    os.rename(oName, rName)

    # Add some legacy stuff that cannot be removed
    writeFile(os.path.join(nwMinimal, "junk"), "dummy")
    os.mkdir(os.path.join(nwMinimal, "data_0"))
    writeFile(os.path.join(nwMinimal, "data_0", "junk"), "dummy")
    dummyGUI.clear()
    assert theProject.openProject(nwMinimal) is True
    assert "data_0" in dummyGUI.lastAlert
    assert theProject.closeProject()
Example #26
0
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
    """Test navigating the novel tree.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)

    nwGUI.openProject(nwMinimal)
    nwGUI.theProject.projTree.setSeed(42)
    nwTree = nwGUI.novelView

    ##
    #  Show/Hide Scrollbars
    ##

    nwTree.mainConf.hideVScroll = True
    nwTree.mainConf.hideHScroll = True
    nwTree.initTree()
    assert not nwTree.verticalScrollBar().isVisible()
    assert not nwTree.horizontalScrollBar().isVisible()

    nwTree.mainConf.hideVScroll = False
    nwTree.mainConf.hideHScroll = False
    nwTree.initTree()
    assert nwTree.verticalScrollBar().isEnabled()
    assert nwTree.horizontalScrollBar().isEnabled()

    ##
    #  Populate Tree
    ##

    nwGUI.projTabs.setCurrentIndex(nwGUI.idxNovelView)
    nwGUI.rebuildIndex()
    nwTree._populateTree()
    assert nwTree.topLevelItemCount() == 1

    # Rebuild should preserve selection
    topItem = nwTree.topLevelItem(0)
    assert not topItem.isSelected()
    topItem.setSelected(True)
    assert nwTree.selectedItems()[0] == topItem
    assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0)

    nwTree.refreshTree()
    assert nwTree.topLevelItem(0).isSelected()

    ##
    #  Open Items
    ##

    # Clear selection
    nwTree.clearSelection()
    scItem = nwTree.topLevelItem(0).child(0).child(0)
    scItem.setSelected(True)
    assert scItem.isSelected()

    # Clear selection with mouse
    vPort = nwTree.viewport()
    qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
    assert not scItem.isSelected()

    # Double-click item
    scItem.setSelected(True)
    assert scItem.isSelected()
    assert nwGUI.docEditor.docHandle() is None
    nwTree._treeDoubleClick(scItem, 0)
    assert nwGUI.docEditor.docHandle() == "8c659a11cd429"

    # Open item with middle mouse button
    scItem.setSelected(True)
    assert scItem.isSelected()
    assert nwGUI.docViewer.docHandle() is None
    qtbot.mouseClick(vPort,
                     Qt.MiddleButton,
                     pos=vPort.rect().center(),
                     delay=10)
    assert nwGUI.docViewer.docHandle() is None

    scRect = nwTree.visualItemRect(scItem)
    oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole)
    scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", ""))
    qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
    assert nwGUI.docViewer.docHandle() is None

    scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData)
    qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
    assert nwGUI.docViewer.docHandle() == "8c659a11cd429"

    ##
    #  Populate Tree
    ##

    # Add weird titles to first file to check hnadling of non-standard
    # order of title levels.
    writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"),
              ("#### Section wo/Scene\n\n"
               "### Scene wo/Chapter\n\n"
               "## Chapter wo/Title\n\n"
               "# Title\n\n"
               "#### Section w/Title, wo/Scene\n\n"
               "### Scene w/Title, wo/Chapter\n\n"
               "## Chapter\n\n"
               "#### Section w/Chapter, wo/Scene\n\n"
               "### Scene\n\n"
               "#### Section\n\n"))
    nwGUI.rebuildIndex()
    nwTree._populateTree()
    assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene"
    assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter"
    assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title"
    assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title"

    tTitle = nwTree.topLevelItem(3)
    assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene"
    assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter"
    assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter"

    tChap = tTitle.child(2)
    assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene"
    assert tChap.child(1).text(nwTree.C_TITLE) == "Scene"

    tScene = tChap.child(1)
    assert tScene.child(0).text(nwTree.C_TITLE) == "Section"

    ##
    #  Close
    ##

    # qtbot.stopForInteraction()
    nwGUI.closeProject()
Example #27
0
def testBaseCommon_NWConfigParser(fncDir):
    """Test the NWConfigParser subclass.
    """
    tstConf = os.path.join(fncDir, "test.cfg")
    writeFile(tstConf, ("[main]\n"
                        "stropt = value\n"
                        "intopt1 = 42\n"
                        "intopt2 = 42.43\n"
                        "boolopt1 = true\n"
                        "boolopt2 = TRUE\n"
                        "boolopt3 = 1\n"
                        "boolopt4 = 0\n"
                        "list1 = a, b, c\n"
                        "list2 = 17, 18, 19\n"
                        "float1 = 4.2\n"))

    cfgParser = NWConfigParser()
    cfgParser.read(tstConf)

    # Readers
    # =======

    # Read String
    assert cfgParser.rdStr("main", "stropt", "stuff") == "value"
    assert cfgParser.rdStr("main", "boolopt1", "stuff") == "true"
    assert cfgParser.rdStr("main", "intopt1", "stuff") == "42"

    assert cfgParser.rdStr("nope", "stropt", "stuff") == "stuff"
    assert cfgParser.rdStr("main", "blabla", "stuff") == "stuff"

    # Read Boolean
    assert cfgParser.rdBool("main", "boolopt1", None) is True
    assert cfgParser.rdBool("main", "boolopt2", None) is True
    assert cfgParser.rdBool("main", "boolopt3", None) is True
    assert cfgParser.rdBool("main", "boolopt4", None) is False
    assert cfgParser.rdBool("main", "intopt1", None) is None

    assert cfgParser.rdBool("nope", "boolopt1", None) is None
    assert cfgParser.rdBool("main", "blabla", None) is None

    # Read Integer
    assert cfgParser.rdInt("main", "intopt1", 13) == 42
    assert cfgParser.rdInt("main", "intopt2", 13) == 13
    assert cfgParser.rdInt("main", "stropt", 13) == 13

    assert cfgParser.rdInt("nope", "intopt1", 13) == 13
    assert cfgParser.rdInt("main", "blabla", 13) == 13

    # Read Float
    assert cfgParser.rdFlt("main", "intopt1", 13.0) == 42.0
    assert cfgParser.rdFlt("main", "float1", 13.0) == 4.2
    assert cfgParser.rdInt("main", "stropt", 13.0) == 13.0

    assert cfgParser.rdInt("nope", "intopt1", 13.0) == 13.0
    assert cfgParser.rdInt("main", "blabla", 13.0) == 13.0

    # Read String List
    assert cfgParser.rdStrList("main", "list1", []) == []
    assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"]
    assert cfgParser.rdStrList("main", "list1", ["x", "y"]) == ["a", "b"]
    assert cfgParser.rdStrList("main", "list1",
                               ["x", "y", "z"]) == ["a", "b", "c"]
    assert cfgParser.rdStrList("main", "list1",
                               ["x", "y", "z", "w"]) == ["a", "b", "c", "w"]

    assert cfgParser.rdStrList("main", "stropt", ["x"]) == ["value"]
    assert cfgParser.rdStrList("main", "intopt1", ["x"]) == ["42"]

    assert cfgParser.rdStrList("nope", "list1", ["x"]) == ["x"]
    assert cfgParser.rdStrList("main", "blabla", ["x"]) == ["x"]

    # Read Integer List
    assert cfgParser.rdIntList("main", "list2", []) == []
    assert cfgParser.rdIntList("main", "list2", [1]) == [17]
    assert cfgParser.rdIntList("main", "list2", [1, 2]) == [17, 18]
    assert cfgParser.rdIntList("main", "list2", [1, 2, 3]) == [17, 18, 19]
    assert cfgParser.rdIntList("main", "list2",
                               [1, 2, 3, 4]) == [17, 18, 19, 4]

    assert cfgParser.rdIntList("main", "stropt", [1]) == [1]
    assert cfgParser.rdIntList("main", "boolopt1", [1]) == [1]

    assert cfgParser.rdIntList("nope", "list2", [1]) == [1]
    assert cfgParser.rdIntList("main", "blabla", [1]) == [1]

    # Internal
    # ========

    assert cfgParser._parseLine("main", "stropt", None, 999) is None
Example #28
0
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
    """Test the merge documents tool.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
    monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))

    # Create a new project
    buildTestProject(nwGUI, fncProj)

    # Handles for new objects
    hNovelRoot  = "0000000000008"
    hChapterDir = "000000000000d"
    hChapterOne = "000000000000e"
    hSceneOne   = "000000000000f"
    hSceneTwo   = "0000000000010"
    hSceneThree = "0000000000011"
    hSceneFour  = "0000000000012"
    hMergedDoc  = "0000000000023"

    # Add Project Content
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
    nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
    nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
    nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)

    assert nwGUI.saveProject() is True
    assert nwGUI.closeProject() is True

    tChapterOne = "## Chapter One\n\n% Chapter one comment\n"
    tSceneOne   = "### Scene One\n\nThere once was a man from Nantucket"
    tSceneTwo   = "### Scene Two\n\nWho kept all his cash in a bucket."
    tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan,  \n\tRan away with a man"
    tSceneFour  = "### Scene Four\n\nAnd as for the bucket, Nantucket."

    contentDir = os.path.join(fncProj, "content")
    writeFile(os.path.join(contentDir, hChapterOne+".nwd"), tChapterOne)
    writeFile(os.path.join(contentDir, hSceneOne+".nwd"), tSceneOne)
    writeFile(os.path.join(contentDir, hSceneTwo+".nwd"), tSceneTwo)
    writeFile(os.path.join(contentDir, hSceneThree+".nwd"), tSceneThree)
    writeFile(os.path.join(contentDir, hSceneFour+".nwd"), tSceneFour)

    assert nwGUI.openProject(fncProj) is True

    # Open the Merge tool
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)

    monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None)
    nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
    qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000)

    nwMerge = getGuiItem("GuiDocMerge")
    assert isinstance(nwMerge, GuiDocMerge)
    nwMerge.show()
    qtbot.wait(50)

    # Populate List
    # =============

    nwMerge.listBox.clear()
    assert nwMerge.listBox.count() == 0

    # No item selected
    nwGUI.projView.projTree.clearSelection()
    assert nwMerge._populateList() is False
    assert nwMerge.listBox.count() == 0

    # Non-existing item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        nwGUI.projView.projTree.clearSelection()
        nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
        assert nwMerge._populateList() is False
        assert nwMerge.listBox.count() == 0

    # Select a non-folder
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hChapterOne).setSelected(True)
    assert nwMerge._populateList() is False
    assert nwMerge.listBox.count() == 0

    # Select the chapter folder
    nwGUI.projView.projTree.clearSelection()
    nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
    assert nwMerge._populateList() is True
    assert nwMerge.listBox.count() == 5

    # Merge Documents
    # ===============

    # First, a successful merge
    with monkeypatch.context() as mp:
        mp.setattr(GuiDocMerge, "_doClose", lambda *a: None)
        assert nwMerge._doMerge() is True
        assert nwGUI.saveProject() is True
        mergedFile = os.path.join(contentDir, hMergedDoc+".nwd")
        assert os.path.isfile(mergedFile)
        assert readFile(mergedFile) == (
            "%%%%~name: New Chapter\n"
            "%%%%~path: %s/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
            "%s\n\n"
            "%s\n\n"
            "%s\n\n"
            "%s\n\n"
        ) % (
            hNovelRoot,
            hMergedDoc,
            tChapterOne.strip(),
            tSceneOne.strip(),
            tSceneTwo.strip(),
            tSceneThree.strip(),
            tSceneFour.strip(),
        )

    # OS error
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert nwMerge._doMerge() is False

    # Can't find the source item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        assert nwMerge._doMerge() is False

    # No source handle set
    nwMerge.sourceItem = None
    assert nwMerge._doMerge() is False

    # No documents to merge
    nwMerge.listBox.clear()
    assert nwMerge._doMerge() is False

    # Close up
    nwMerge._doClose()
Example #29
0
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj):
    """Test the merge documents tool.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)

    # Create a new project
    nwGUI.theProject.projTree.setSeed(42)
    assert nwGUI.newProject({"projPath": fncProj})

    # Handles for new objects
    hChapterDir = "31489056e0916"
    hChapterOne = "98010bd9270f9"
    hSceneOne   = "0e17daca5f3e1"
    hSceneTwo   = "1a6562590ef19"
    hSceneThree = "031b4af5197ec"
    hSceneFour  = "41cfc0d1f2d12"
    hMergedDoc  = "2858dcd1057d3"

    # Add Project Content
    monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted)
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
    nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
    nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
    nwGUI.treeView.newTreeItem(nwItemType.FILE, None)

    assert nwGUI.saveProject() is True
    assert nwGUI.closeProject() is True

    tChapterOne = "## Chapter One\n\n% Chapter one comment\n"
    tSceneOne   = "### Scene One\n\nThere once was a man from Nantucket"
    tSceneTwo   = "### Scene Two\n\nWho kept all his cash in a bucket."
    tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan,  \n\tRan away with a man"
    tSceneFour  = "### Scene Four\n\nAnd as for the bucket, Nantucket."

    contentDir = os.path.join(fncProj, "content")
    writeFile(os.path.join(contentDir, hChapterOne+".nwd"), tChapterOne)
    writeFile(os.path.join(contentDir, hSceneOne+".nwd"), tSceneOne)
    writeFile(os.path.join(contentDir, hSceneTwo+".nwd"), tSceneTwo)
    writeFile(os.path.join(contentDir, hSceneThree+".nwd"), tSceneThree)
    writeFile(os.path.join(contentDir, hSceneFour+".nwd"), tSceneFour)

    assert nwGUI.openProject(fncProj) is True

    # Open the Merge tool
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)

    monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None)
    nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
    qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000)

    nwMerge = getGuiItem("GuiDocMerge")
    assert isinstance(nwMerge, GuiDocMerge)
    nwMerge.show()
    qtbot.wait(50)

    # Populate List
    # =============

    nwMerge.listBox.clear()
    assert nwMerge.listBox.count() == 0

    # No item selected
    nwGUI.treeView.clearSelection()
    assert nwMerge._populateList() is False
    assert nwMerge.listBox.count() == 0

    # Non-existing item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        nwGUI.treeView.clearSelection()
        nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
        assert nwMerge._populateList() is False
        assert nwMerge.listBox.count() == 0

    # Select a non-folder
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hChapterOne).setSelected(True)
    assert nwMerge._populateList() is False
    assert nwMerge.listBox.count() == 0

    # Select the chapter folder
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
    assert nwMerge._populateList() is True
    assert nwMerge.listBox.count() == 5

    # Merge Documents
    # ===============

    # First, a successful merge
    with monkeypatch.context() as mp:
        mp.setattr(GuiDocMerge, "_doClose", lambda *a: None)
        assert nwMerge._doMerge() is True
        assert nwGUI.saveProject() is True
        mergedFile = os.path.join(contentDir, hMergedDoc+".nwd")
        assert os.path.isfile(mergedFile)
        assert readFile(mergedFile) == (
            "%%%%~name: New Chapter\n"
            "%%%%~path: 73475cb40a568/2858dcd1057d3\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
            "%s\n\n"
            "%s\n\n"
            "%s\n\n"
            "%s\n\n"
        ) % (
            tChapterOne.strip(),
            tSceneOne.strip(),
            tSceneTwo.strip(),
            tSceneThree.strip(),
            tSceneFour.strip(),
        )

    # OS error
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert nwMerge._doMerge() is False

    # Can't find the source item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        assert nwMerge._doMerge() is False

    # No source handle set
    nwMerge.sourceItem = None
    assert nwMerge._doMerge() is False

    # No documents to merge
    nwMerge.listBox.clear()
    assert nwMerge._doMerge() is False

    # Close up
    nwMerge._doClose()
Example #30
0
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
    """Test the full writing stats tool.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)

    # Create a project to work on
    assert nwGUI.newProject({"projPath": fncProj})
    qtbot.wait(100)
    assert nwGUI.saveProject()
    sessFile = os.path.join(fncProj, "meta", nwFiles.SESS_STATS)

    # Open the Writing Stats dialog
    nwGUI.mainConf.lastPath = ""
    nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
    qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None,
                    timeout=1000)

    sessLog = getGuiItem("GuiWritingStats")
    assert isinstance(sessLog, GuiWritingStats)
    qtbot.wait(stepDelay)

    # Test Loading
    # ============

    # No initial logfile
    assert not os.path.isfile(sessFile)
    assert not sessLog._loadLogFile()

    # Make a test log file
    writeFile(sessFile, (
        "# Offset 123\n"
        "# Start Time         End Time                Novel     Notes      Idle\n"
        "2020-01-01 21:00:00  2020-01-01 21:00:05         6         0\n"
        "2020-01-03 21:00:00  2020-01-03 21:00:15       125         0\n"
        "2020-01-03 21:30:00  2020-01-03 21:30:15       125         5\n"
        "2020-01-06 21:00:00  2020-01-06 21:00:10       125         5\n"))
    assert os.path.isfile(sessFile)
    assert sessLog._loadLogFile()
    assert sessLog.wordOffset == 123
    assert len(sessLog.logData) == 4

    # Make sure a faulty file can still be read
    writeFile(sessFile, (
        "# Offset abc123\n"
        "# Start Time         End Time                Novel     Notes      Idle\n"
        "2020-01-01 21:00:00  2020-01-01 21:00:05         6         0        50\n"
        "2020-01-03 21:00:00  2020-01-03 21:00:15       125         0\n"
        "2020-01-03 21:30:00  2020-01-03 21:30:15       125         5\n"
        "2020-01-06 21:00:00  2020-01-06 21:00:10       125\n"))
    assert sessLog._loadLogFile()
    assert sessLog.wordOffset == 0
    assert len(sessLog.logData) == 3

    # Test Exporting
    # ==============

    writeFile(sessFile, (
        "# Offset 1075\n"
        "# Start Time         End Time                Novel     Notes      Idle\n"
        "2021-01-31 19:00:00  2021-01-31 19:30:00       700       375         0\n"
        "2021-02-01 19:00:00  2021-02-01 19:30:00       700       375        10\n"
        "2021-02-01 20:00:00  2021-02-01 20:30:00       600       275        20\n"
        "2021-02-02 19:00:00  2021-02-02 19:30:00       750       425        30\n"
        "2021-02-02 20:00:00  2021-02-02 20:30:00       690       365        40\n"
        "2021-02-03 19:00:00  2021-02-03 19:30:00       680       355        50\n"
        "2021-02-04 19:00:00  2021-02-04 19:30:00       700       375        60\n"
        "2021-02-05 19:00:00  2021-02-05 19:30:00       500       175        70\n"
        "2021-02-06 19:00:00  2021-02-06 19:30:00       600       275        80\n"
        "2021-02-07 19:00:00  2021-02-07 19:30:00       600       275        90\n"
    ))
    sessLog.populateGUI()

    # Make the saving fail
    monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *a, **k:
                        ("", ""))
    assert not sessLog._saveData(sessLog.FMT_CSV)
    assert not sessLog._saveData(sessLog.FMT_JSON)
    assert not sessLog._saveData(None)

    # Make the save succeed
    monkeypatch.setattr("os.path.expanduser", lambda *a: fncDir)
    monkeypatch.setattr(QFileDialog, "getSaveFileName",
                        lambda ss, tt, pp, options: (pp, ""))

    sessLog.listBox.sortByColumn(sessLog.C_TIME, 0)

    assert sessLog.novelWords.text() == "{:n}".format(600)
    assert sessLog.notesWords.text() == "{:n}".format(275)
    assert sessLog.totalWords.text() == "{:n}".format(875)

    assert sessLog.listBox.topLevelItem(0).text(
        sessLog.C_COUNT) == "{:n}".format(1)
    assert sessLog.listBox.topLevelItem(1).text(
        sessLog.C_COUNT) == "{:n}".format(-200)
    assert sessLog.listBox.topLevelItem(2).text(
        sessLog.C_COUNT) == "{:n}".format(300)
    assert sessLog.listBox.topLevelItem(3).text(
        sessLog.C_COUNT) == "{:n}".format(-120)
    assert sessLog.listBox.topLevelItem(4).text(
        sessLog.C_COUNT) == "{:n}".format(-20)
    assert sessLog.listBox.topLevelItem(5).text(
        sessLog.C_COUNT) == "{:n}".format(40)
    assert sessLog.listBox.topLevelItem(6).text(
        sessLog.C_COUNT) == "{:n}".format(-400)
    assert sessLog.listBox.topLevelItem(7).text(
        sessLog.C_COUNT) == "{:n}".format(200)

    assert sessLog._saveData(sessLog.FMT_CSV)
    qtbot.wait(100)

    assert sessLog._saveData(sessLog.FMT_JSON)
    qtbot.wait(100)

    assert nwGUI.mainConf.lastPath == fncDir

    # Check the exported files
    jsonStats = os.path.join(fncDir, "sessionStats.json")
    with open(jsonStats, mode="r", encoding="utf-8") as inFile:
        jsonData = json.load(inFile)

    assert jsonData == [{
        "date": "2021-01-31 19:00:00",
        "length": 1800.0,
        "newWords": 1,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 0
    }, {
        "date": "2021-02-01 20:00:00",
        "length": 1800.0,
        "newWords": -200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 20
    }, {
        "date": "2021-02-02 19:00:00",
        "length": 1800.0,
        "newWords": 300,
        "novelWords": 750,
        "noteWords": 425,
        "idleTime": 30
    }, {
        "date": "2021-02-02 20:00:00",
        "length": 1800.0,
        "newWords": -120,
        "novelWords": 690,
        "noteWords": 365,
        "idleTime": 40
    }, {
        "date": "2021-02-03 19:00:00",
        "length": 1800.0,
        "newWords": -20,
        "novelWords": 680,
        "noteWords": 355,
        "idleTime": 50
    }, {
        "date": "2021-02-04 19:00:00",
        "length": 1800.0,
        "newWords": 40,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 60
    }, {
        "date": "2021-02-05 19:00:00",
        "length": 1800.0,
        "newWords": -400,
        "novelWords": 500,
        "noteWords": 175,
        "idleTime": 70
    }, {
        "date": "2021-02-06 19:00:00",
        "length": 1800.0,
        "newWords": 200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 80
    }]

    # Test Filters
    # ============

    # No Novel Files
    qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
    qtbot.wait(stepDelay)
    assert sessLog._saveData(sessLog.FMT_JSON)
    qtbot.wait(stepDelay)

    jsonStats = os.path.join(fncDir, "sessionStats.json")
    with open(jsonStats, mode="r", encoding="utf-8") as inFile:
        jsonData = json.loads(inFile.read())

    assert sessLog.listBox.topLevelItem(0).text(
        sessLog.C_COUNT) == "{:n}".format(1)
    assert sessLog.listBox.topLevelItem(1).text(
        sessLog.C_COUNT) == "{:n}".format(-100)
    assert sessLog.listBox.topLevelItem(2).text(
        sessLog.C_COUNT) == "{:n}".format(150)
    assert sessLog.listBox.topLevelItem(3).text(
        sessLog.C_COUNT) == "{:n}".format(-60)
    assert sessLog.listBox.topLevelItem(4).text(
        sessLog.C_COUNT) == "{:n}".format(-10)
    assert sessLog.listBox.topLevelItem(5).text(
        sessLog.C_COUNT) == "{:n}".format(20)
    assert sessLog.listBox.topLevelItem(6).text(
        sessLog.C_COUNT) == "{:n}".format(-200)
    assert sessLog.listBox.topLevelItem(7).text(
        sessLog.C_COUNT) == "{:n}".format(100)

    assert jsonData == [{
        "date": "2021-01-31 19:00:00",
        "length": 1800.0,
        "newWords": 1,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 0
    }, {
        "date": "2021-02-01 20:00:00",
        "length": 1800.0,
        "newWords": -100,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 20
    }, {
        "date": "2021-02-02 19:00:00",
        "length": 1800.0,
        "newWords": 150,
        "novelWords": 750,
        "noteWords": 425,
        "idleTime": 30
    }, {
        "date": "2021-02-02 20:00:00",
        "length": 1800.0,
        "newWords": -60,
        "novelWords": 690,
        "noteWords": 365,
        "idleTime": 40
    }, {
        "date": "2021-02-03 19:00:00",
        "length": 1800.0,
        "newWords": -10,
        "novelWords": 680,
        "noteWords": 355,
        "idleTime": 50
    }, {
        "date": "2021-02-04 19:00:00",
        "length": 1800.0,
        "newWords": 20,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 60
    }, {
        "date": "2021-02-05 19:00:00",
        "length": 1800.0,
        "newWords": -200,
        "novelWords": 500,
        "noteWords": 175,
        "idleTime": 70
    }, {
        "date": "2021-02-06 19:00:00",
        "length": 1800.0,
        "newWords": 100,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 80
    }]

    # No Note Files
    qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
    qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
    qtbot.wait(stepDelay)
    assert sessLog._saveData(sessLog.FMT_JSON)
    qtbot.wait(stepDelay)

    jsonStats = os.path.join(fncDir, "sessionStats.json")
    with open(jsonStats, mode="r", encoding="utf-8") as inFile:
        jsonData = json.load(inFile)

    assert sessLog.listBox.topLevelItem(0).text(
        sessLog.C_COUNT) == "{:n}".format(1)
    assert sessLog.listBox.topLevelItem(1).text(
        sessLog.C_COUNT) == "{:n}".format(-100)
    assert sessLog.listBox.topLevelItem(2).text(
        sessLog.C_COUNT) == "{:n}".format(150)
    assert sessLog.listBox.topLevelItem(3).text(
        sessLog.C_COUNT) == "{:n}".format(-60)
    assert sessLog.listBox.topLevelItem(4).text(
        sessLog.C_COUNT) == "{:n}".format(-10)
    assert sessLog.listBox.topLevelItem(5).text(
        sessLog.C_COUNT) == "{:n}".format(20)
    assert sessLog.listBox.topLevelItem(6).text(
        sessLog.C_COUNT) == "{:n}".format(-200)
    assert sessLog.listBox.topLevelItem(7).text(
        sessLog.C_COUNT) == "{:n}".format(100)

    assert jsonData == [{
        "date": "2021-01-31 19:00:00",
        "length": 1800.0,
        "newWords": 1,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 0
    }, {
        "date": "2021-02-01 20:00:00",
        "length": 1800.0,
        "newWords": -100,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 20
    }, {
        "date": "2021-02-02 19:00:00",
        "length": 1800.0,
        "newWords": 150,
        "novelWords": 750,
        "noteWords": 425,
        "idleTime": 30
    }, {
        "date": "2021-02-02 20:00:00",
        "length": 1800.0,
        "newWords": -60,
        "novelWords": 690,
        "noteWords": 365,
        "idleTime": 40
    }, {
        "date": "2021-02-03 19:00:00",
        "length": 1800.0,
        "newWords": -10,
        "novelWords": 680,
        "noteWords": 355,
        "idleTime": 50
    }, {
        "date": "2021-02-04 19:00:00",
        "length": 1800.0,
        "newWords": 20,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 60
    }, {
        "date": "2021-02-05 19:00:00",
        "length": 1800.0,
        "newWords": -200,
        "novelWords": 500,
        "noteWords": 175,
        "idleTime": 70
    }, {
        "date": "2021-02-06 19:00:00",
        "length": 1800.0,
        "newWords": 100,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 80
    }]

    # No Negative Entries
    qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
    qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
    qtbot.wait(stepDelay)
    assert sessLog._saveData(sessLog.FMT_JSON)
    qtbot.wait(stepDelay)

    # qtbot.stopForInteraction()

    jsonStats = os.path.join(fncDir, "sessionStats.json")
    with open(jsonStats, mode="r", encoding="utf-8") as inFile:
        jsonData = json.load(inFile)

    assert sessLog.listBox.topLevelItem(0).text(
        sessLog.C_COUNT) == "{:n}".format(1)
    assert sessLog.listBox.topLevelItem(1).text(
        sessLog.C_COUNT) == "{:n}".format(300)
    assert sessLog.listBox.topLevelItem(2).text(
        sessLog.C_COUNT) == "{:n}".format(40)
    assert sessLog.listBox.topLevelItem(3).text(
        sessLog.C_COUNT) == "{:n}".format(200)

    assert jsonData == [{
        "date": "2021-01-31 19:00:00",
        "length": 1800.0,
        "newWords": 1,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 0
    }, {
        "date": "2021-02-02 19:00:00",
        "length": 1800.0,
        "newWords": 300,
        "novelWords": 750,
        "noteWords": 425,
        "idleTime": 30
    }, {
        "date": "2021-02-04 19:00:00",
        "length": 1800.0,
        "newWords": 40,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 60
    }, {
        "date": "2021-02-06 19:00:00",
        "length": 1800.0,
        "newWords": 200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 80
    }]

    # Un-hide Zero Entries
    qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
    qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton)
    qtbot.wait(stepDelay)
    assert sessLog._saveData(sessLog.FMT_JSON)
    qtbot.wait(stepDelay)

    jsonStats = os.path.join(fncDir, "sessionStats.json")
    with open(jsonStats, mode="r", encoding="utf-8") as inFile:
        jsonData = json.load(inFile)

    assert sessLog.listBox.topLevelItem(0).text(
        sessLog.C_COUNT) == "{:n}".format(1)
    assert sessLog.listBox.topLevelItem(1).text(
        sessLog.C_COUNT) == "{:n}".format(0)
    assert sessLog.listBox.topLevelItem(2).text(
        sessLog.C_COUNT) == "{:n}".format(-200)
    assert sessLog.listBox.topLevelItem(3).text(
        sessLog.C_COUNT) == "{:n}".format(300)
    assert sessLog.listBox.topLevelItem(4).text(
        sessLog.C_COUNT) == "{:n}".format(-120)
    assert sessLog.listBox.topLevelItem(5).text(
        sessLog.C_COUNT) == "{:n}".format(-20)
    assert sessLog.listBox.topLevelItem(6).text(
        sessLog.C_COUNT) == "{:n}".format(40)
    assert sessLog.listBox.topLevelItem(7).text(
        sessLog.C_COUNT) == "{:n}".format(-400)
    assert sessLog.listBox.topLevelItem(8).text(
        sessLog.C_COUNT) == "{:n}".format(200)
    assert sessLog.listBox.topLevelItem(9).text(
        sessLog.C_COUNT) == "{:n}".format(0)

    assert jsonData == [{
        "date": "2021-01-31 19:00:00",
        "length": 1800.0,
        "newWords": 1,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 0
    }, {
        "date": "2021-02-01 19:00:00",
        "length": 1800.0,
        "newWords": 0,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 10
    }, {
        "date": "2021-02-01 20:00:00",
        "length": 1800.0,
        "newWords": -200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 20
    }, {
        "date": "2021-02-02 19:00:00",
        "length": 1800.0,
        "newWords": 300,
        "novelWords": 750,
        "noteWords": 425,
        "idleTime": 30
    }, {
        "date": "2021-02-02 20:00:00",
        "length": 1800.0,
        "newWords": -120,
        "novelWords": 690,
        "noteWords": 365,
        "idleTime": 40
    }, {
        "date": "2021-02-03 19:00:00",
        "length": 1800.0,
        "newWords": -20,
        "novelWords": 680,
        "noteWords": 355,
        "idleTime": 50
    }, {
        "date": "2021-02-04 19:00:00",
        "length": 1800.0,
        "newWords": 40,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 60
    }, {
        "date": "2021-02-05 19:00:00",
        "length": 1800.0,
        "newWords": -400,
        "novelWords": 500,
        "noteWords": 175,
        "idleTime": 70
    }, {
        "date": "2021-02-06 19:00:00",
        "length": 1800.0,
        "newWords": 200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 80
    }, {
        "date": "2021-02-07 19:00:00",
        "length": 1800.0,
        "newWords": 0,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 90
    }]

    # Group by Day
    qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
    qtbot.wait(stepDelay)
    assert sessLog._saveData(sessLog.FMT_JSON)
    qtbot.wait(stepDelay)

    jsonStats = os.path.join(fncDir, "sessionStats.json")
    with open(jsonStats, mode="r", encoding="utf-8") as inFile:
        jsonData = json.load(inFile)

    assert sessLog.listBox.topLevelItem(0).text(
        sessLog.C_COUNT) == "{:n}".format(1)
    assert sessLog.listBox.topLevelItem(1).text(
        sessLog.C_COUNT) == "{:n}".format(-200)
    assert sessLog.listBox.topLevelItem(2).text(
        sessLog.C_COUNT) == "{:n}".format(180)
    assert sessLog.listBox.topLevelItem(3).text(
        sessLog.C_COUNT) == "{:n}".format(-20)
    assert sessLog.listBox.topLevelItem(4).text(
        sessLog.C_COUNT) == "{:n}".format(40)
    assert sessLog.listBox.topLevelItem(5).text(
        sessLog.C_COUNT) == "{:n}".format(-400)
    assert sessLog.listBox.topLevelItem(6).text(
        sessLog.C_COUNT) == "{:n}".format(200)
    assert sessLog.listBox.topLevelItem(7).text(
        sessLog.C_COUNT) == "{:n}".format(0)

    assert jsonData == [{
        "date": "2021-01-31",
        "length": 1800.0,
        "newWords": 1,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 0
    }, {
        "date": "2021-02-01",
        "length": 3600.0,
        "newWords": -200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 30
    }, {
        "date": "2021-02-02",
        "length": 3600.0,
        "newWords": 180,
        "novelWords": 690,
        "noteWords": 365,
        "idleTime": 70
    }, {
        "date": "2021-02-03",
        "length": 1800.0,
        "newWords": -20,
        "novelWords": 680,
        "noteWords": 355,
        "idleTime": 50
    }, {
        "date": "2021-02-04",
        "length": 1800.0,
        "newWords": 40,
        "novelWords": 700,
        "noteWords": 375,
        "idleTime": 60
    }, {
        "date": "2021-02-05",
        "length": 1800.0,
        "newWords": -400,
        "novelWords": 500,
        "noteWords": 175,
        "idleTime": 70
    }, {
        "date": "2021-02-06",
        "length": 1800.0,
        "newWords": 200,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 80
    }, {
        "date": "2021-02-07",
        "length": 1800.0,
        "newWords": 0,
        "novelWords": 600,
        "noteWords": 275,
        "idleTime": 90
    }]

    # IOError
    # =======
    monkeypatch.setattr("builtins.open", causeOSError)
    assert not sessLog._loadLogFile()
    assert not sessLog._saveData(sessLog.FMT_CSV)

    # qtbot.stopForInteraction()

    sessLog._doClose()
    assert nwGUI.closeProject()
    qtbot.wait(stepDelay)
Example #31
0
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
    """Test the split document tool.
    """
    # Block message box
    monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
    monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)

    # Create a new project
    nwGUI.theProject.projTree.setSeed(42)
    assert nwGUI.newProject({"projPath": fncProj}) is True

    # Handles for new objects
    hNovelRoot  = "73475cb40a568"
    hChapterDir = "31489056e0916"
    hToSplit    = "1a6562590ef19"
    hPartition  = "41cfc0d1f2d12"
    hChapterOne = "2858dcd1057d3"
    hSceneOne   = "2fca346db6561"
    hSceneTwo   = "02d20bbd7e394"
    hSceneThree = "7688b6ef52555"
    hSceneFour  = "c837649cce43f"
    hSceneFive  = "6208ef0f7750c"

    # Add Project Content
    monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted)
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hNovelRoot).setSelected(True)
    nwGUI.treeView.newTreeItem(nwItemType.FILE, None)

    assert nwGUI.saveProject() is True
    assert nwGUI.closeProject() is True

    tPartition  = "# Nantucket"
    tChapterOne = "## Chapter One\n\n% Chapter one comment"
    tSceneOne   = "### Scene One\n\nThere once was a man from Nantucket"
    tSceneTwo   = "### Scene Two\n\nWho kept all his cash in a bucket."
    tSceneThree = "### Scene Three\n\n\tBut his daughter, named Nan,  \n\tRan away with a man"
    tSceneFour  = "### Scene Four\n\nAnd as for the bucket, Nantucket."
    tSceneFive  = "#### The End\n\nend"

    tToSplit = (
        f"{tPartition}\n\n{tChapterOne}\n\n"
        f"{tSceneOne}\n\n{tSceneTwo}\n\n"
        f"{tSceneThree}\n\n{tSceneFour}\n\n"
        f"{tSceneFive}\n\n"
    )

    contentDir = os.path.join(fncProj, "content")
    writeFile(os.path.join(contentDir, hToSplit+".nwd"), tToSplit)

    assert nwGUI.openProject(fncProj) is True

    # Open the Split tool
    nwGUI.switchFocus(nwWidget.TREE)
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hToSplit).setSelected(True)

    monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None)
    nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
    qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)

    nwSplit = getGuiItem("GuiDocSplit")
    assert isinstance(nwSplit, GuiDocSplit)
    nwSplit.show()
    qtbot.wait(50)

    # Populate List
    # =============

    nwSplit.listBox.clear()
    assert nwSplit.listBox.count() == 0

    # No item selected
    nwSplit.sourceItem = None
    nwGUI.treeView.clearSelection()
    assert nwSplit._populateList() is False
    assert nwSplit.listBox.count() == 0

    # Non-existing item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        nwSplit.sourceItem = None
        nwGUI.treeView.clearSelection()
        nwGUI.treeView._getTreeItem(hToSplit).setSelected(True)
        assert nwSplit._populateList() is False
        assert nwSplit.listBox.count() == 0

    # Select a non-file
    nwSplit.sourceItem = None
    nwGUI.treeView.clearSelection()
    nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
    assert nwSplit._populateList() is False
    assert nwSplit.listBox.count() == 0

    # Error when reading documents
    with monkeypatch.context() as mp:
        mp.setattr(NWDoc, "readDocument", lambda *a: None)
        nwSplit.sourceItem = hToSplit
        assert nwSplit._populateList() is False
        assert nwSplit.listBox.count() == 0

    # Read properly, and check split levels

    # Level 1
    nwSplit.splitLevel.setCurrentIndex(0)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 1

    # Level 2
    nwSplit.splitLevel.setCurrentIndex(1)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 2

    # Level 3
    nwSplit.splitLevel.setCurrentIndex(2)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 6

    # Level 4
    nwSplit.splitLevel.setCurrentIndex(3)
    nwSplit.sourceItem = hToSplit
    assert nwSplit._populateList() is True
    assert nwSplit.listBox.count() == 7

    # Split Document
    # ==============

    # Test a proper split first
    with monkeypatch.context() as mp:
        mp.setattr(GuiDocSplit, "_doClose", lambda *a: None)
        assert nwSplit._doSplit() is True
        assert nwGUI.saveProject()

        assert readFile(os.path.join(contentDir, hPartition+".nwd")) == (
            "%%%%~name: Nantucket\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hPartition, tPartition)

        assert readFile(os.path.join(contentDir, hChapterOne+".nwd")) == (
            "%%%%~name: Chapter One\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hChapterOne, tChapterOne)

        assert readFile(os.path.join(contentDir, hSceneOne+".nwd")) == (
            "%%%%~name: Scene One\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hSceneOne, tSceneOne)

        assert readFile(os.path.join(contentDir, hSceneTwo+".nwd")) == (
            "%%%%~name: Scene Two\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hSceneTwo, tSceneTwo)

        assert readFile(os.path.join(contentDir, hSceneThree+".nwd")) == (
            "%%%%~name: Scene Three\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hSceneThree, tSceneThree)

        assert readFile(os.path.join(contentDir, hSceneFour+".nwd")) == (
            "%%%%~name: Scene Four\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hSceneFour, tSceneFour)

        assert readFile(os.path.join(contentDir, hSceneFive+".nwd")) == (
            "%%%%~name: The End\n"
            "%%%%~path: 031b4af5197ec/%s\n"
            "%%%%~kind: NOVEL/DOCUMENT\n"
            "%s\n\n"
        ) % (hSceneFive, tSceneFive)

    # OS error
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert nwSplit._doSplit() is False

    # Select to not split
    with monkeypatch.context() as mp:
        mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
        assert nwSplit._doSplit() is False

    # Block folder creation by returning that the folder has a depth
    # of 50 items in the tree
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "getItemPath", lambda *a: [""]*50)
        assert nwSplit._doSplit() is False

    # Clear the list
    nwSplit.listBox.clear()
    assert nwSplit._doSplit() is False

    # Can't find sourcv item
    with monkeypatch.context() as mp:
        mp.setattr(NWTree, "__getitem__", lambda *a: None)
        assert nwSplit._doSplit() is False

    # No source item set
    nwSplit.sourceItem = None
    assert nwSplit._doSplit() is False

    # Close
    nwSplit._doClose()
Example #32
0
def makeGnuplotFile(inFile, outFile):
    inLines = [l.replace("\n", "") for l in readFile(inFile)]
    output = "\n".join(createGnuplotFile(inLines))
    print("--------------------------")
    print(output)
    writeFile(outFile, output)