예제 #1
0
def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI):
    """Check that new files can be added to the project.
    """
    projFile = os.path.join(fncDir, "nwProject.nwx")
    testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
    compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")

    theProject = NWProject(mockGUI)
    theProject.projTree.setSeed(42)

    assert theProject.newProject({"projPath": fncDir}) is True
    assert theProject.setProjectPath(fncDir) is True
    assert theProject.saveProject() is True
    assert theProject.closeProject() is True
    assert theProject.openProject(projFile) is True

    assert isinstance(
        theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str)
    assert isinstance(
        theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"),
        str)
    assert theProject.projChanged
    assert theProject.saveProject() is True
    assert theProject.closeProject() is True

    copyfile(projFile, testFile)
    assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
    assert theProject.projChanged is False
예제 #2
0
def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
    """Check that new root folders can be added to the project.
    """
    projFile = os.path.join(fncDir, "nwProject.nwx")
    testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx")
    compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx")

    theProject = NWProject(mockGUI)
    theProject.projTree.setSeed(42)

    assert theProject.newProject({"projPath": fncDir}) is True
    assert theProject.setProjectPath(fncDir) is True
    assert theProject.saveProject() is True
    assert theProject.closeProject() is True
    assert theProject.openProject(projFile) is True

    assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL),
                      type(None))
    assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None))
    assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER),
                      type(None))
    assert isinstance(theProject.newRoot("World", nwItemClass.WORLD),
                      type(None))
    assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE),
                      str)
    assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str)
    assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str)
    assert isinstance(theProject.newRoot("Custom2", nwItemClass.CUSTOM), str)

    assert theProject.projChanged is True
    assert theProject.saveProject() is True
    assert theProject.closeProject() is True

    copyfile(projFile, testFile)
    assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
    assert theProject.projChanged is False
예제 #3
0
def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
    """Test other project class methods and functions.
    """
    theProject = NWProject(mockGUI)
    theProject.projTree.setSeed(42)
    assert theProject.openProject(nwMinimal)
    assert theProject.projPath == nwMinimal

    # Setting project path
    assert theProject.setProjectPath(None)
    assert theProject.projPath is None
    assert theProject.setProjectPath("")
    assert theProject.projPath is None
    assert theProject.setProjectPath("~")
    assert theProject.projPath == os.path.expanduser("~")

    # Create a new folder and populate it
    projPath = os.path.join(nwMinimal, "mock1")
    assert theProject.setProjectPath(projPath, newProject=True)

    # Make os.mkdir fail
    monkeypatch.setattr("os.mkdir", causeOSError)
    projPath = os.path.join(nwMinimal, "mock2")
    assert not theProject.setProjectPath(projPath, newProject=True)

    # Set back
    assert theProject.setProjectPath(nwMinimal)

    # Project Name
    assert theProject.setProjectName("  A Name ")
    assert theProject.projName == "A Name"

    # Project Title
    assert theProject.setBookTitle("  A Title ")
    assert theProject.bookTitle == "A Title"

    # Project Authors
    # Check that the list is cleaned up and that it can be extracted as
    # a properly formatted string, depending on number of names
    assert not theProject.setBookAuthors([])
    assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
    assert theProject.bookAuthors == ["Jane Doe", "John Doh"]

    assert theProject.setBookAuthors("")
    assert theProject.getAuthors() == ""

    assert theProject.setBookAuthors("Jane Doe")
    assert theProject.getAuthors() == "Jane Doe"

    assert theProject.setBookAuthors("Jane Doe\nJohn Doh")
    assert theProject.getAuthors() == "Jane Doe and John Doh"

    assert theProject.setBookAuthors("Jane Doe\nJohn Doh\nBod Owens")
    assert theProject.getAuthors() == "Jane Doe, John Doh and Bod Owens"

    # Edit Time
    theProject.editTime = 1234
    theProject.projOpened = 1600000000
    with monkeypatch.context() as mp:
        mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
        assert theProject.getCurrentEditTime() == 6834

    # Trash folder
    # Should create on first call, and just returned on later calls
    assert theProject.projTree["73475cb40a568"] is None
    assert theProject.trashFolder() == "73475cb40a568"
    assert theProject.trashFolder() == "73475cb40a568"

    # Project backup
    assert theProject.doBackup is True
    assert theProject.setProjBackup(False)
    assert theProject.doBackup is False

    assert not theProject.setProjBackup(True)
    theProject.mainConf.backupPath = tmpDir
    assert theProject.setProjBackup(True)

    assert theProject.setProjectName("")
    assert not theProject.setProjBackup(True)
    assert theProject.setProjectName("A Name")
    assert theProject.setProjBackup(True)

    # Spell check
    theProject.projChanged = False
    assert theProject.setSpellCheck(True)
    assert not theProject.setSpellCheck(False)
    assert theProject.projChanged

    # Spell language
    theProject.projChanged = False
    assert theProject.setSpellLang(None)
    assert theProject.projSpell is None
    assert theProject.setSpellLang("None")
    assert theProject.projSpell is None
    assert theProject.setSpellLang("en_GB")
    assert theProject.projSpell == "en_GB"
    assert theProject.projChanged

    # Project Language
    theProject.projChanged = False
    theProject.projLang = "en"
    assert theProject.setProjectLang(None) is True
    assert theProject.projLang is None
    assert theProject.setProjectLang("en_GB") is True
    assert theProject.projLang == "en_GB"

    # Automatic outline update
    theProject.projChanged = False
    assert theProject.setAutoOutline(True)
    assert not theProject.setAutoOutline(False)
    assert theProject.projChanged

    # Last edited
    theProject.projChanged = False
    assert theProject.setLastEdited("0123456789abc")
    assert theProject.lastEdited == "0123456789abc"
    assert theProject.projChanged

    # Last viewed
    theProject.projChanged = False
    assert theProject.setLastViewed("0123456789abc")
    assert theProject.lastViewed == "0123456789abc"
    assert theProject.projChanged

    # Autoreplace
    theProject.projChanged = False
    assert theProject.setAutoReplace({"A": "B", "C": "D"})
    assert theProject.autoReplace == {"A": "B", "C": "D"}
    assert theProject.projChanged

    # Change project tree order
    oldOrder = [
        "a508bb932959c",
        "a35baf2e93843",
        "a6d311a93600a",
        "f5ab3e30151e1",
        "8c659a11cd429",
        "7695ce551d265",
        "afb3043c7b2b3",
        "9d5247ab588e0",
        "73475cb40a568",
    ]
    newOrder = [
        "f5ab3e30151e1",
        "8c659a11cd429",
        "7695ce551d265",
        "a508bb932959c",
        "a35baf2e93843",
        "a6d311a93600a",
        "afb3043c7b2b3",
        "9d5247ab588e0",
    ]
    assert theProject.projTree.handles() == oldOrder
    assert theProject.setTreeOrder(newOrder)
    assert theProject.projTree.handles() == newOrder
    assert theProject.setTreeOrder(oldOrder)
    assert theProject.projTree.handles() == oldOrder

    # Change status
    theProject.projTree["a35baf2e93843"].setStatus("Finished")
    theProject.projTree["a6d311a93600a"].setStatus("Draft")
    theProject.projTree["f5ab3e30151e1"].setStatus("Note")
    theProject.projTree["8c659a11cd429"].setStatus("Finished")
    newList = [
        ("New", 1, 1, 1, "New"),
        ("Draft", 2, 2, 2, "Note"),  # These are swapped
        ("Note", 3, 3, 3, "Draft"),  # These are swapped
        ("Edited", 4, 4, 4, "Finished"),  # Renamed
        ("Finished", 5, 5, 5, None),  # New, with reused name
    ]
    assert theProject.setStatusColours(newList)
    assert theProject.statusItems._theLabels == [
        "New", "Draft", "Note", "Edited", "Finished"
    ]
    assert theProject.statusItems._theColours == [(1, 1, 1), (2, 2, 2),
                                                  (3, 3, 3), (4, 4, 4),
                                                  (5, 5, 5)]
    assert theProject.projTree[
        "a35baf2e93843"].itemStatus == "Edited"  # Renamed
    assert theProject.projTree["a6d311a93600a"].itemStatus == "Note"  # Swapped
    assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft"  # Swapped
    assert theProject.projTree[
        "8c659a11cd429"].itemStatus == "Edited"  # Renamed

    # Change importance
    fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER,
                                 "afb3043c7b2b3")
    theProject.projTree[fHandle].setImport("Main")
    newList = [
        ("New", 1, 1, 1, "New"),
        ("Minor", 2, 2, 2, "Minor"),
        ("Major", 3, 3, 3, "Major"),
        ("Min", 4, 4, 4, "Main"),
        ("Max", 5, 5, 5, None),
    ]
    assert theProject.setImportColours(newList)
    assert theProject.importItems._theLabels == [
        "New", "Minor", "Major", "Min", "Max"
    ]
    assert theProject.importItems._theColours == [(1, 1, 1), (2, 2, 2),
                                                  (3, 3, 3), (4, 4, 4),
                                                  (5, 5, 5)]
    assert theProject.projTree[fHandle].itemImport == "Min"

    # Check status counts
    assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0]
    assert theProject.importItems._theCounts == [0, 0, 0, 0, 0]
    theProject.countStatus()
    assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
    assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]

    # Session stats
    theProject.currWCount = 200
    theProject.lastWCount = 100
    with monkeypatch.context() as mp:
        mp.setattr("os.path.isdir", lambda *a, **k: False)
        assert not theProject._appendSessionStats(idleTime=0)

    # Block open
    with monkeypatch.context() as mp:
        mp.setattr("builtins.open", causeOSError)
        assert not theProject._appendSessionStats(idleTime=0)

    # Write entry
    assert theProject.projMeta == os.path.join(nwMinimal, "meta")
    statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)

    theProject.projOpened = 1600002000
    theProject.currNovelWC = 200
    theProject.currNotesWC = 100

    with monkeypatch.context() as mp:
        mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
        assert theProject._appendSessionStats(idleTime=99)

    assert readFile(statsFile) == (
        "# Offset 100\n"
        "# Start Time         End Time                Novel     Notes      Idle\n"
        "%s  %s       200       100        99\n") % (
            formatTimeStamp(1600002000), formatTimeStamp(1600005600))

    # Pack XML Value
    xElem = etree.Element("element")
    theProject._packProjectValue(xElem, "A", "B", allowNone=False)
    assert etree.tostring(xElem, pretty_print=False,
                          encoding="utf-8") == (b"<element><A>B</A></element>")

    xElem = etree.Element("element")
    theProject._packProjectValue(xElem, "A", "", allowNone=False)
    assert etree.tostring(xElem, pretty_print=False,
                          encoding="utf-8") == (b"<element/>")

    # Pack XML Key/Value
    xElem = etree.Element("element")
    theProject._packProjectKeyValue(xElem, "item", {"A": "B", "C": "D"})
    assert etree.tostring(xElem, pretty_print=False,
                          encoding="utf-8") == (b"<element>"
                                                b"<item>"
                                                b"<entry key=\"A\">B</entry>"
                                                b"<entry key=\"C\">D</entry>"
                                                b"</item>"
                                                b"</element>")
예제 #4
0
def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
    """Test opening a project.
    """
    theProject = NWProject(mockGUI)

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

    # Fail on folder structure check
    with monkeypatch.context() as mp:
        mp.setattr("os.mkdir", causeOSError)
        assert theProject.openProject(nwMinimal) is False

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

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

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

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

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

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

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

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

    # Update file version
    writeFile(rName, ("<?xml version='1.0' encoding='utf-8'?>\n"
                      "<novelWriterXML "
                      "appVersion=\"1.0\" "
                      "hexVersion=\"0xffffffff\" "
                      "fileVersion=\"1.2\" "
                      "timeStamp=\"2020-01-01 00:00:00\">\n"
                      "</novelWriterXML>\n"))
    mockGUI.askResponse = False
    assert theProject.openProject(nwMinimal) is False
    assert mockGUI.lastQuestion[0] == "File Version"
    mockGUI.undo()

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

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

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

    # Add some legacy stuff that cannot be removed
    writeFile(os.path.join(nwMinimal, "junk"), "stuff")
    os.mkdir(os.path.join(nwMinimal, "data_0"))
    writeFile(os.path.join(nwMinimal, "data_0", "junk"), "stuff")
    mockGUI.clear()
    assert theProject.openProject(nwMinimal) is True
    assert "data_0" in mockGUI.lastAlert
    assert theProject.closeProject()
예제 #5
0
def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
    """Test the functins that handle legacy data folders and structure
    with additional tests of failure handling.
    """
    theProject = NWProject(mockGUI)
    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, "stuff")
    assert os.path.isfile(tstFile)

    with monkeypatch.context() as mp:
        mp.setattr("os.unlink", causeOSError)
        assert not theProject._deprecatedFiles()

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

    # Check processing non-folders
    tstFile = os.path.join(fncDir, "data_0")
    writeFile(tstFile, "stuff")
    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
    with monkeypatch.context() as mp:
        mp.setattr("os.mkdir", causeOSError)
        errList = []
        errList = theProject._legacyDataFolder(tstData, errList)
        assert len(errList) > 0

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

    # 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, "stuff")
    writeFile(tstDoc1b, "stuff")
    writeFile(tstDoc2m, "stuff")
    writeFile(tstDoc2b, "stuff")
    writeFile(tstDoc3m, "stuff")
    writeFile(tstDoc3b, "stuff")

    # Make the above fail
    with monkeypatch.context() as mp:
        mp.setattr("os.rename", causeOSError)
        mp.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)

    # 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"))