diff --git a/novelwriter/common.py b/novelwriter/common.py index 08fd407b..f914a6d1 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -25,7 +25,6 @@ from __future__ import annotations import json import uuid -import hashlib import logging import unicodedata import xml.etree.ElementTree as ET @@ -488,25 +487,6 @@ def makeFileNameSafe(text: str) -> str: return "".join(c for c in text if c.isalnum() or c in allowed) -def sha256sum(path: str | Path) -> str | None: - """Make a shasum of a file using a buffer. - Based on: https://stackoverflow.com/a/44873382/5825851 - """ - digest = hashlib.sha256() - bData = bytearray(65536) - mData = memoryview(bData) - try: - with open(path, mode="rb", buffering=0) as inFile: - for n in iter(lambda: inFile.readinto(mData), 0): - digest.update(mData[:n]) - except Exception: - logger.error("Could not create sha256sum of: %s", path) - logException() - return None - - return digest.hexdigest() - - # =============================================================================================== # # Other Functions # =============================================================================================== # diff --git a/novelwriter/core/document.py b/novelwriter/core/document.py index a8a99f8b..1586ac46 100644 --- a/novelwriter/core/document.py +++ b/novelwriter/core/document.py @@ -23,15 +23,17 @@ along with this program. If not, see . """ from __future__ import annotations +import hashlib import logging +from time import time from typing import TYPE_CHECKING from pathlib import Path -from novelwriter.core.item import NWItem from novelwriter.enum import nwItemLayout, nwItemClass from novelwriter.error import formatException -from novelwriter.common import isHandle, sha256sum +from novelwriter.common import formatTimeStamp, isHandle +from novelwriter.core.item import NWItem if TYPE_CHECKING: # pragma: no cover from novelwriter.core.project import NWProject @@ -50,30 +52,38 @@ class NWDocument: def __init__(self, project: NWProject, tHandle: str | None) -> None: - self._project = project + self._project = project - # Internal Variables - self._theItem = None # The currently open item - self._docHandle = None # The handle of the currently open item - self._fileLoc = None # The file location of the currently open item - self._docMeta = {} # The meta data of the currently open item - self._docError = "" # The latest encountered IO error - self._prevHash = None # Previous sha256sum of the document file - self._currHash = None # Latest sha256sum of the document file + self._item = None # The currently open item + self._handle = None # The handle of the currently open item + self._fileLoc = None # The file location of the currently open item + self._docMeta = {} # The meta data of the currently open item + self._docError = "" # The latest encountered IO error + self._lastHash = "" # The last known SHA hash + self._hashError = False # Hash mismatch on last write attempt if isHandle(tHandle): - self._docHandle = tHandle + self._handle = tHandle - if self._docHandle is not None: - self._theItem = self._project.tree[tHandle] + if self._handle is not None: + self._item = self._project.tree[tHandle] return def __repr__(self) -> str: - return f"" + return f"" def __bool__(self) -> bool: - return self._docHandle is not None and bool(self._theItem) + return self._handle is not None and self._item is not None + + ## + # Properties + ## + + @property + def hashError(self) -> bool: + """Check if the file hash has changed outside of novelWriter.""" + return self._hashError ## # Class Methods @@ -81,7 +91,7 @@ class NWDocument: def fileExists(self) -> bool: """Check if the document file exists.""" - if self._docHandle is None: + if self._handle is None: return False contentPath = self._project.storage.contentPath @@ -89,7 +99,7 @@ class NWDocument: logger.error("No content path set") return False - return (contentPath / f"{self._docHandle}.nwd").is_file() + return (contentPath / f"{self._handle}.nwd").is_file() def readDocument(self, isOrphan: bool = False) -> str | None: """Read the document specified by the handle set in the @@ -98,11 +108,11 @@ class NWDocument: empty string. If something went wrong, return None. """ self._docError = "" - if not isinstance(self._docHandle, str): + if not isinstance(self._handle, str): logger.error("No document handle set") return None - if self._theItem is None and not isOrphan: + if self._item is None and not isOrphan: logger.error("Unknown novelWriter document") return None @@ -111,31 +121,30 @@ class NWDocument: logger.error("No content path set") return None - docFile = f"{self._docHandle}.nwd" + docFile = f"{self._handle}.nwd" logger.debug("Opening document: %s", docFile) docPath = contentPath / docFile self._fileLoc = docPath - theText = "" + text = "" self._docMeta = {} - self._prevHash = None + self._lastHash = "" if docPath.exists(): - self._prevHash = sha256sum(docPath) try: with open(docPath, mode="r", encoding="utf-8") as inFile: # Check the first <= 10 lines for metadata for i in range(10): - inLine = inFile.readline() - if inLine.startswith(r"%%~"): - self._parseMeta(inLine) + line = inFile.readline() + if line.startswith(r"%%~"): + self._parseMeta(line) else: - theText = inLine + text = line break # Load the rest of the file - theText += inFile.read() + text += inFile.read() except Exception as exc: self._docError = formatException(exc) @@ -143,19 +152,20 @@ class NWDocument: else: # The document file does not exist, so we assume it's a new - # document and initialise an empty text string. + # document and return an empty text string. logger.debug("The requested document does not exist") - return "" - return theText + self._lastHash = hashlib.sha1(text.encode()).hexdigest() - def writeDocument(self, docText: str, forceWrite: bool = False) -> bool: + return text + + def writeDocument(self, text: str, forceWrite: bool = False) -> bool: """Write the document specified by the handle attribute. Handle any IO errors in the process Returns True if successful, False if not. """ self._docError = "" - if not isinstance(self._docHandle, str): + if not isinstance(self._handle, str): logger.error("No document handle set") return False @@ -164,32 +174,45 @@ class NWDocument: logger.error("No content path set") return False - docFile = f"{self._docHandle}.nwd" + docFile = f"{self._handle}.nwd" logger.debug("Saving document: %s", docFile) docPath = contentPath / docFile docTemp = docPath.with_suffix(".tmp") - if self._prevHash is not None and not forceWrite: - self._currHash = sha256sum(docPath) - if self._currHash is not None and self._currHash != self._prevHash: - logger.error("File has been altered on disk since opened") - return False + # Re-read the document on disk to check if it has changed + prevHash = self._lastHash + self.readDocument() + if prevHash and self._lastHash != prevHash and not forceWrite: + logger.error("File has been altered on disk since opened") + self._hashError = True + return False + + currTime = formatTimeStamp(time()) + writeHash = hashlib.sha1(text.encode()).hexdigest() + createdDate = self._docMeta.get("created", "Unknown") + updatedDate = self._docMeta.get("updated", "Unknown") + if writeHash != self._lastHash: + updatedDate = currTime + if not docPath.is_file(): + createdDate = currTime + updatedDate = currTime # DocMeta Line - if self._theItem is None: - docMeta = "" - else: + docMeta = "" + if self._item: docMeta = ( - f"%%~name: {self._theItem.itemName}\n" - f"%%~path: {self._theItem.itemParent}/{self._theItem.itemHandle}\n" - f"%%~kind: {self._theItem.itemClass.name}/{self._theItem.itemLayout.name}\n" + f"%%~name: {self._item.itemName}\n" + f"%%~path: {self._item.itemParent}/{self._item.itemHandle}\n" + f"%%~kind: {self._item.itemClass.name}/{self._item.itemLayout.name}\n" + f"%%~hash: {writeHash}\n" + f"%%~date: {createdDate}/{updatedDate}\n" ) try: with open(docTemp, mode="w", encoding="utf-8") as outFile: outFile.write(docMeta) - outFile.write(docText) + outFile.write(text) except Exception as exc: self._docError = formatException(exc) return False @@ -202,8 +225,8 @@ class NWDocument: self._docError = formatException(exc) return False - self._prevHash = sha256sum(docPath) - self._currHash = self._prevHash + self._lastHash = writeHash + self._hashError = False return True @@ -212,7 +235,7 @@ class NWDocument: from the project data folder. """ self._docError = "" - if not isinstance(self._docHandle, str): + if not isinstance(self._handle, str): logger.error("No document handle set") return False @@ -221,7 +244,7 @@ class NWDocument: logger.error("No content path set") return False - docPath = contentPath / f"{self._docHandle}.nwd" + docPath = contentPath / f"{self._handle}.nwd" docTemp = docPath.with_suffix(".tmp") try: @@ -247,7 +270,7 @@ class NWDocument: def getCurrentItem(self) -> NWItem | None: """Return a pointer to the currently open NWItem.""" - return self._theItem + return self._item def getMeta(self) -> tuple[str, str | None, nwItemClass | None, nwItemLayout | None]: """Parse the document meta tag and return the name, parent, @@ -293,8 +316,18 @@ class NWDocument: if metaBits[1] in nwItemLayout.__members__: self._docMeta["layout"] = nwItemLayout[metaBits[1]] + elif metaLine.startswith("%%~hash:"): + self._docMeta["hash"] = metaLine[8:].strip() + + elif metaLine.startswith("%%~date:"): + metaVal = metaLine[8:].strip() + metaBits = metaVal.split("/") + if len(metaBits) == 2: + self._docMeta["created"] = metaBits[0].strip() + self._docMeta["updated"] = metaBits[1].strip() + else: - logger.debug("Ignoring meta data: '%s'", metaLine.strip()) + logger.debug("Unknown meta data: '%s'", metaLine.strip()) return diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index d4204a36..8c40f203 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -520,7 +520,7 @@ class GuiDocEditor(QTextEdit): self.saveCursorPosition() if not self._nwDocument.writeDocument(docText): saveOk = False - if self._nwDocument._currHash != self._nwDocument._prevHash: + if self._nwDocument.hashError: msgYes = SHARED.question(self.tr( "This document has been changed outside of novelWriter " "while it was open. Overwrite the file on disk?" diff --git a/sample/content/14298de4d9524.nwd b/sample/content/14298de4d9524.nwd index 732e2078..0f69520e 100644 --- a/sample/content/14298de4d9524.nwd +++ b/sample/content/14298de4d9524.nwd @@ -1,6 +1,8 @@ %%~name: John Smith %%~path: f7e2d9f330615/14298de4d9524 %%~kind: CHARACTER/NOTE +%%~hash: 259eff30f10e101cf93e27e8764e851d356d54b8 +%%~date: Unknown/2023-08-25 16:52:03 # John Smith @tag: John diff --git a/sample/content/53b69b83cdafc.nwd b/sample/content/53b69b83cdafc.nwd index 18f8cab8..9163e10f 100644 --- a/sample/content/53b69b83cdafc.nwd +++ b/sample/content/53b69b83cdafc.nwd @@ -1,6 +1,8 @@ %%~name: Title Page %%~path: 7031beac91f75/53b69b83cdafc %%~kind: NOVEL/DOCUMENT +%%~hash: c5dc35d18ecb074a9e41a1410d1bff8021cf0a5b +%%~date: Unknown/2023-08-25 16:51:52 #! My Novel >> **By Jane Smith** << diff --git a/sample/content/5eaea4e8cdee8.nwd b/sample/content/5eaea4e8cdee8.nwd index 0f8ecc26..18451297 100644 --- a/sample/content/5eaea4e8cdee8.nwd +++ b/sample/content/5eaea4e8cdee8.nwd @@ -1,6 +1,8 @@ %%~name: Mars %%~path: 15c4492bd5107/5eaea4e8cdee8 %%~kind: WORLD/NOTE +%%~hash: 0838371bc03e31f503fd12a8617d94ef36e5f7c7 +%%~date: Unknown/2023-08-25 16:52:07 # Mars @tag: Mars diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index a334e8ce..f726f89d 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -1,6 +1,8 @@ %%~name: Making a Scene %%~path: 6a2d6d5f4f401/636b6aa9b697b %%~kind: NOVEL/DOCUMENT +%%~hash: 053cc65631403c15ddc112849dc7fcae44eb9d63 +%%~date: Unknown/2023-08-25 16:56:11 ### Making a Scene @pov: Jane diff --git a/sample/content/6a2d6d5f4f401.nwd b/sample/content/6a2d6d5f4f401.nwd index 2a536e9f..d9a6bb24 100644 --- a/sample/content/6a2d6d5f4f401.nwd +++ b/sample/content/6a2d6d5f4f401.nwd @@ -1,6 +1,8 @@ %%~name: Chapter One %%~path: 7031beac91f75/6a2d6d5f4f401 %%~kind: NOVEL/DOCUMENT +%%~hash: 89687756c204742d17f1d37a0ba7fa3aadaf00f2 +%%~date: Unknown/2023-08-25 16:51:56 ## So it Begins @pov: Jane diff --git a/sample/content/88706ddc78b1b.nwd b/sample/content/88706ddc78b1b.nwd index ceaddd29..b454163a 100644 --- a/sample/content/88706ddc78b1b.nwd +++ b/sample/content/88706ddc78b1b.nwd @@ -1,6 +1,8 @@ %%~name: Chapter Two %%~path: 7031beac91f75/88706ddc78b1b %%~kind: NOVEL/DOCUMENT +%%~hash: ca68d1134e1b7870b7bf0fcbd8c0d67384970717 +%%~date: Unknown/2023-08-25 16:52:00 ## Where has John Gone? @pov: Jane diff --git a/sample/content/8a5deb88c0e97.nwd b/sample/content/8a5deb88c0e97.nwd index 31a99aeb..e3b70b6c 100644 --- a/sample/content/8a5deb88c0e97.nwd +++ b/sample/content/8a5deb88c0e97.nwd @@ -1,6 +1,8 @@ %%~name: Old File %%~path: ae9bf3c3ea159/8a5deb88c0e97 %%~kind: ARCHIVE/DOCUMENT +%%~hash: de9f8f39428e9d127504f47c554fc3e701b0b773 +%%~date: Unknown/2023-08-25 16:52:08 ### Discarded Scene If you have files you no longer want in your main project, you can move them to the “Archive” folder. This is equivalent to turning off the “Include when building project” switch, just that you also put the file away. diff --git a/sample/content/96b68994dfa3d.nwd b/sample/content/96b68994dfa3d.nwd index 05ae7770..9e81b1ce 100644 --- a/sample/content/96b68994dfa3d.nwd +++ b/sample/content/96b68994dfa3d.nwd @@ -1,6 +1,8 @@ %%~name: A Note on Structure %%~path: 7031beac91f75/96b68994dfa3d %%~kind: NOVEL/NOTE +%%~hash: f4f88fa2d824ed4e92223bd6744979cd3f9f45e7 +%%~date: Unknown/2023-08-25 16:51:59 # A Note on Structure This file is just a note. You can save notes anywhere you like in the project tree. Notes can be filtered out when you export the project. diff --git a/sample/content/974e400180a99.nwd b/sample/content/974e400180a99.nwd index 99919687..0175798b 100644 --- a/sample/content/974e400180a99.nwd +++ b/sample/content/974e400180a99.nwd @@ -1,6 +1,8 @@ %%~name: Page %%~path: 7031beac91f75/974e400180a99 %%~kind: NOVEL/DOCUMENT +%%~hash: 7547809e972205eb2a55dd988595e0aa1d01e139 +%%~date: Unknown/2023-08-25 16:51:54 [NEW PAGE] [VSPACE:2] diff --git a/sample/content/a520879ca0b45.nwd b/sample/content/a520879ca0b45.nwd index 75ba39c8..a8afd6ce 100644 --- a/sample/content/a520879ca0b45.nwd +++ b/sample/content/a520879ca0b45.nwd @@ -1,6 +1,8 @@ %%~name: Chapter One %%~path: e5e47ebf63b1c/a520879ca0b45 %%~kind: NOVEL/DOCUMENT +%%~hash: 2285256b970dbb3ccf833d9275cd5f8935b6aff1 +%%~date: Unknown/2023-08-25 16:52:02 ## Chapter One @pov: Jane diff --git a/sample/content/ae7339df26ded.nwd b/sample/content/ae7339df26ded.nwd index 8b53f816..4a72e54c 100644 --- a/sample/content/ae7339df26ded.nwd +++ b/sample/content/ae7339df26ded.nwd @@ -1,6 +1,8 @@ %%~name: We Found John! %%~path: 88706ddc78b1b/ae7339df26ded %%~kind: NOVEL/DOCUMENT +%%~hash: 1da9cf71a57d11c5d94b021a2c3e6a5b8df67f14 +%%~date: Unknown/2023-08-25 16:52:01 ### We Found John! @pov: John diff --git a/sample/content/b3e74dbc1f584.nwd b/sample/content/b3e74dbc1f584.nwd index bb88600b..e547822a 100644 --- a/sample/content/b3e74dbc1f584.nwd +++ b/sample/content/b3e74dbc1f584.nwd @@ -1,6 +1,8 @@ %%~name: Earth %%~path: 15c4492bd5107/b3e74dbc1f584 %%~kind: WORLD/NOTE +%%~hash: 87f520b4e80af8505af1b868ab25ec6d51b7ff48 +%%~date: Unknown/2023-08-25 16:52:05 # Earth @tag: Earth diff --git a/sample/content/b8136a5a774a0.nwd b/sample/content/b8136a5a774a0.nwd index 636c7227..573e476f 100644 --- a/sample/content/b8136a5a774a0.nwd +++ b/sample/content/b8136a5a774a0.nwd @@ -1,6 +1,8 @@ %%~name: Delete Me! %%~path: 98acd8c76c93a/b8136a5a774a0 %%~kind: TRASH/DOCUMENT +%%~hash: e1eabd80260e03595c8c0a1f82d813d0ae33c273 +%%~date: Unknown/2023-08-25 16:52:09 ### Delete Me! This scene is trash. \ No newline at end of file diff --git a/sample/content/ba8a28a246524.nwd b/sample/content/ba8a28a246524.nwd index c2acfb26..21f5401e 100644 --- a/sample/content/ba8a28a246524.nwd +++ b/sample/content/ba8a28a246524.nwd @@ -1,6 +1,8 @@ %%~name: Interlude %%~path: 7031beac91f75/ba8a28a246524 %%~kind: NOVEL/DOCUMENT +%%~hash: df8904011eda90c7b694bed725dc774bf631b348 +%%~date: Unknown/2023-08-25 16:51:58 ##! Interlude % Notice that this document has a title with a ‘!’ in it in addition to the two hash symbols. This means it is an unnumbered chapter. Unnumbered chapters can be treated separately from numbered chapters during export. Perfect for when you want to add an interlude, or for a prologue or epilogue. diff --git a/sample/content/bacb7059e3083.nwd b/sample/content/bacb7059e3083.nwd index b6be7a07..309b3d3c 100644 --- a/sample/content/bacb7059e3083.nwd +++ b/sample/content/bacb7059e3083.nwd @@ -1,6 +1,8 @@ %%~name: Title Page %%~path: e5e47ebf63b1c/bacb7059e3083 %%~kind: NOVEL/DOCUMENT +%%~hash: 16b55dd0f8dc6bbba266384930a87ec211d471df +%%~date: Unknown/2023-08-25 16:52:01 #! Sequel Novel >> **By Jane Doh** << diff --git a/sample/content/bb2c23b3c42cc.nwd b/sample/content/bb2c23b3c42cc.nwd index 9827b61e..28175909 100644 --- a/sample/content/bb2c23b3c42cc.nwd +++ b/sample/content/bb2c23b3c42cc.nwd @@ -1,6 +1,8 @@ %%~name: Jane Smith %%~path: f7e2d9f330615/bb2c23b3c42cc %%~kind: CHARACTER/NOTE +%%~hash: a69e4ca6ceede2536c8d364f88e6b62ef0ec36c3 +%%~date: Unknown/2023-08-25 16:52:04 # Jane Smith @tag: Jane diff --git a/sample/content/bc0cbd2a407f3.nwd b/sample/content/bc0cbd2a407f3.nwd index fd920d0d..00642bba 100644 --- a/sample/content/bc0cbd2a407f3.nwd +++ b/sample/content/bc0cbd2a407f3.nwd @@ -1,6 +1,8 @@ %%~name: Another Scene %%~path: 6a2d6d5f4f401/bc0cbd2a407f3 %%~kind: NOVEL/DOCUMENT +%%~hash: b3f9e4b097f7041c77489876577014c435a8be0c +%%~date: Unknown/2023-08-25 16:51:57 ### Another Scene @pov: John diff --git a/sample/content/edca4be2fcaf8.nwd b/sample/content/edca4be2fcaf8.nwd index 8b3efc78..d541a0bc 100644 --- a/sample/content/edca4be2fcaf8.nwd +++ b/sample/content/edca4be2fcaf8.nwd @@ -1,6 +1,8 @@ %%~name: Part One %%~path: 7031beac91f75/edca4be2fcaf8 %%~kind: NOVEL/DOCUMENT +%%~hash: 9dc53a9bcc457d18b775adf7be38ac89df3408ca +%%~date: Unknown/2023-08-25 16:51:54 # Part One >> In the beginning … << diff --git a/sample/content/f1471bef9f2ae.nwd b/sample/content/f1471bef9f2ae.nwd index afc65f03..8ed84dde 100644 --- a/sample/content/f1471bef9f2ae.nwd +++ b/sample/content/f1471bef9f2ae.nwd @@ -1,6 +1,8 @@ %%~name: Space %%~path: 15c4492bd5107/f1471bef9f2ae %%~kind: WORLD/NOTE +%%~hash: bc139a020239c9c0d936aa9d0bcc3644ae2f370c +%%~date: Unknown/2023-08-25 16:52:06 # Space @tag: Space diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index cb3d212e..3140df48 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Sample Project Jane Smith @@ -58,7 +58,7 @@ Chapter One - + Making a Scene diff --git a/tests/conftest.py b/tests/conftest.py index 4d6e6e72..1969567e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -226,8 +226,7 @@ def prjLipsum(): @pytest.fixture(scope="session") def ipsumText(): - """Return five paragraphs of Lorem Ipsum text. - """ + """Return five paragraphs of Lorem Ipsum text.""" thatIpsum = [( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc maximus justo non dictum co" "mmodo. Curabitur lacinia tempor orci vel luctus. Phasellus porta metus eu massa luctus, e" diff --git a/tests/lipsum/content/04468803b92e1.nwd b/tests/lipsum/content/04468803b92e1.nwd index 6d706890..f1735bb7 100644 --- a/tests/lipsum/content/04468803b92e1.nwd +++ b/tests/lipsum/content/04468803b92e1.nwd @@ -1,6 +1,8 @@ %%~name: Ancient Europe %%~path: 60bdf227455cc/04468803b92e1 %%~kind: WORLD/NOTE +%%~hash: b4318c2de40a1fc4055d18f8d02696b9cf171ea2 +%%~date: Unknown/Unknown # Ancient Europe @tag: Europe diff --git a/tests/lipsum/content/2426c6f0ca922.nwd b/tests/lipsum/content/2426c6f0ca922.nwd index ad926141..e6d5dd54 100644 --- a/tests/lipsum/content/2426c6f0ca922.nwd +++ b/tests/lipsum/content/2426c6f0ca922.nwd @@ -1,6 +1,8 @@ %%~name: Main %%~path: 6c6afb1247750/2426c6f0ca922 %%~kind: PLOT/NOTE +%%~hash: db3897d166e246acdb5e25e9bd98c5a40a699ed0 +%%~date: Unknown/Unknown # Main Plot @tag: Main diff --git a/tests/lipsum/content/441420a886d82.nwd b/tests/lipsum/content/441420a886d82.nwd index 15eeb3d8..05881160 100644 --- a/tests/lipsum/content/441420a886d82.nwd +++ b/tests/lipsum/content/441420a886d82.nwd @@ -1,6 +1,8 @@ %%~name: Chapter Two %%~path: 6bd935d2490cd/441420a886d82 %%~kind: NOVEL/DOCUMENT +%%~hash: fd6d46708faa1333f8f7ba0442fc5b35bf1e3f85 +%%~date: Unknown/Unknown ## Chapter Two @pov: Bod diff --git a/tests/lipsum/content/47666c91c7ccf.nwd b/tests/lipsum/content/47666c91c7ccf.nwd index 0d3692fb..e551a0e3 100644 --- a/tests/lipsum/content/47666c91c7ccf.nwd +++ b/tests/lipsum/content/47666c91c7ccf.nwd @@ -1,6 +1,8 @@ %%~name: Scene Five %%~path: 6bd935d2490cd/47666c91c7ccf %%~kind: NOVEL/DOCUMENT +%%~hash: d210c26966da6f9edea8726567abf4860b7bb9b7 +%%~date: Unknown/Unknown ### Scene Five @pov: Bod diff --git a/tests/lipsum/content/4c4f28287af27.nwd b/tests/lipsum/content/4c4f28287af27.nwd index 29d8c61a..e7071bb1 100644 --- a/tests/lipsum/content/4c4f28287af27.nwd +++ b/tests/lipsum/content/4c4f28287af27.nwd @@ -1,6 +1,8 @@ %%~name: Mr. Nobody %%~path: 67a8707f2f249/4c4f28287af27 %%~kind: CHARACTER/NOTE +%%~hash: 07a86c2001669de7d7a7545286c22b963e3ccf63 +%%~date: Unknown/Unknown # Nobody Owens @tag: Bod diff --git a/tests/lipsum/content/7a992350f3eb6.nwd b/tests/lipsum/content/7a992350f3eb6.nwd index b8de881c..f36d5e4d 100644 --- a/tests/lipsum/content/7a992350f3eb6.nwd +++ b/tests/lipsum/content/7a992350f3eb6.nwd @@ -1,6 +1,8 @@ %%~name: Lorem Ipsum %%~path: b3643d0f92e32/7a992350f3eb6 %%~kind: NOVEL/DOCUMENT +%%~hash: 8efda028000b70be0d7dbe9647b6026082ef05c9 +%%~date: Unknown/Unknown #! Lorem Ipsum >> **By lipsum.com** << diff --git a/tests/lipsum/content/846352075de7d.nwd b/tests/lipsum/content/846352075de7d.nwd index c181e0e9..6a06fe22 100644 --- a/tests/lipsum/content/846352075de7d.nwd +++ b/tests/lipsum/content/846352075de7d.nwd @@ -1,6 +1,8 @@ %%~name: Interlude %%~path: b3643d0f92e32/846352075de7d %%~kind: NOVEL/DOCUMENT +%%~hash: ac0e16c65142b9f1e0fa281bdc9b954e44026740 +%%~date: Unknown/Unknown ##! Why do we use it? % Exctracted from the lipsum.com website. diff --git a/tests/lipsum/content/88243afbe5ed8.nwd b/tests/lipsum/content/88243afbe5ed8.nwd index 63f376be..3f40135a 100644 --- a/tests/lipsum/content/88243afbe5ed8.nwd +++ b/tests/lipsum/content/88243afbe5ed8.nwd @@ -1,6 +1,8 @@ %%~name: Scene One %%~path: 45e6b01ca35c1/88243afbe5ed8 %%~kind: NOVEL/DOCUMENT +%%~hash: a09245a7a772bbe02850b5db109977e336cd9cc1 +%%~date: Unknown/Unknown ### Scene One @pov: Bod diff --git a/tests/lipsum/content/88d59a277361b.nwd b/tests/lipsum/content/88d59a277361b.nwd index af6b3a48..d057e90a 100644 --- a/tests/lipsum/content/88d59a277361b.nwd +++ b/tests/lipsum/content/88d59a277361b.nwd @@ -1,6 +1,8 @@ %%~name: Prologue %%~path: b3643d0f92e32/88d59a277361b %%~kind: NOVEL/DOCUMENT +%%~hash: 19a2aa95b07ce10eaea753c46569929439648c63 +%%~date: Unknown/Unknown ##! Prologue % Synopsis:Explanation from the lipsum.com website. diff --git a/tests/lipsum/content/8c58a65414c23.nwd b/tests/lipsum/content/8c58a65414c23.nwd index 15140e3b..388608dc 100644 --- a/tests/lipsum/content/8c58a65414c23.nwd +++ b/tests/lipsum/content/8c58a65414c23.nwd @@ -1,6 +1,8 @@ %%~name: Front Matter %%~path: b3643d0f92e32/8c58a65414c23 %%~kind: NOVEL/DOCUMENT +%%~hash: 5c3961cb7616ef2b378010f38a89ed268ef15d92 +%%~date: Unknown/Unknown [NEW PAGE] % Exctracted from the lipsum.com website. diff --git a/tests/lipsum/content/db7e733775d4d.nwd b/tests/lipsum/content/db7e733775d4d.nwd index 15678925..8fe1e52e 100644 --- a/tests/lipsum/content/db7e733775d4d.nwd +++ b/tests/lipsum/content/db7e733775d4d.nwd @@ -1,6 +1,8 @@ %%~name: Act One %%~path: b3643d0f92e32/db7e733775d4d %%~kind: NOVEL/DOCUMENT +%%~hash: d93cd4c96d49e4afd93c82cca29d413a35012108 +%%~date: Unknown/Unknown # Act One >> “Fusce maximus felis libero” << \ No newline at end of file diff --git a/tests/lipsum/content/eb103bc70c90c.nwd b/tests/lipsum/content/eb103bc70c90c.nwd index ec090623..bdfac437 100644 --- a/tests/lipsum/content/eb103bc70c90c.nwd +++ b/tests/lipsum/content/eb103bc70c90c.nwd @@ -1,6 +1,8 @@ %%~name: Scene Three %%~path: 6bd935d2490cd/eb103bc70c90c %%~kind: NOVEL/DOCUMENT +%%~hash: c4eda49e4fe81dc450d547eee0bdabe77fdaaa98 +%%~date: Unknown/Unknown ### Scene Three @pov: Bod diff --git a/tests/lipsum/content/f8c0562e50f1b.nwd b/tests/lipsum/content/f8c0562e50f1b.nwd index e61e52fe..73bedd5f 100644 --- a/tests/lipsum/content/f8c0562e50f1b.nwd +++ b/tests/lipsum/content/f8c0562e50f1b.nwd @@ -1,6 +1,8 @@ %%~name: Scene Four %%~path: 6bd935d2490cd/f8c0562e50f1b %%~kind: NOVEL/DOCUMENT +%%~hash: 9461a279b9fb6ef005ee4d432fcda77ff5bfbd42 +%%~date: Unknown/Unknown ### Scene Four @pov: Bod diff --git a/tests/lipsum/content/f96ec11c6a3da.nwd b/tests/lipsum/content/f96ec11c6a3da.nwd index e7fb3054..b6434395 100644 --- a/tests/lipsum/content/f96ec11c6a3da.nwd +++ b/tests/lipsum/content/f96ec11c6a3da.nwd @@ -1,6 +1,8 @@ %%~name: Scene Two %%~path: 45e6b01ca35c1/f96ec11c6a3da %%~kind: NOVEL/DOCUMENT +%%~hash: ebe3fbaa16d9d81bc1a139822e3bf39bb357866d +%%~date: Unknown/Unknown ### Scene Two @pov: Bod diff --git a/tests/lipsum/content/fb609cd8319dc.nwd b/tests/lipsum/content/fb609cd8319dc.nwd index 394d4bbc..1ae2272f 100644 --- a/tests/lipsum/content/fb609cd8319dc.nwd +++ b/tests/lipsum/content/fb609cd8319dc.nwd @@ -1,6 +1,8 @@ %%~name: Chapter One %%~path: 45e6b01ca35c1/fb609cd8319dc %%~kind: NOVEL/DOCUMENT +%%~hash: 5dabeaa7a58238a6ad99ce73176b1bdfad1a71b7 +%%~date: Unknown/Unknown ## Chapter One @pov: Bod diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index d789716c..1c643caf 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Lorem Ipsum Lorem Ipsum lipsum.com diff --git a/tests/reference/coreDocTools_DocMerger_0000000000010.nwd b/tests/reference/coreDocTools_DocMerger_0000000000010.nwd index eb13ead2..fbeb598d 100644 --- a/tests/reference/coreDocTools_DocMerger_0000000000010.nwd +++ b/tests/reference/coreDocTools_DocMerger_0000000000010.nwd @@ -1,6 +1,8 @@ %%~name: Chapter 1 %%~path: 0000000000008/0000000000010 %%~kind: NOVEL/DOCUMENT +%%~hash: 25af8567c8a3b3b49a27dcda2999ed1f8811955a +%%~date: 2023-08-25 18:32:30/2023-08-25 18:32:30 ## Chapter 1 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc maximus justo non dictum commodo. Curabitur lacinia tempor orci vel luctus. Phasellus porta metus eu massa luctus, eget euismod risus rhoncus. Vestibulum sed arcu nisi. Maecenas pretium facilisis velit, vel semper lacus aliquam sit amet. Vestibulum vulputate neque ligula, rhoncus blandit turpis consequat id. Mauris sagittis vehicula imperdiet. Duis sed nunc pretium, ornare purus vel, sodales augue. Maecenas a suscipit risus. Quisque volutpat justo eleifend est ullamcorper fermentum. Donec ullamcorper et tortor a laoreet. Nam id risus nisi. Vivamus non imperdiet erat, sit amet imperdiet felis. Mauris vitae neque et est aliquam scelerisque non non ipsum. diff --git a/tests/reference/coreDocTools_DocMerger_0000000000014.nwd b/tests/reference/coreDocTools_DocMerger_0000000000014.nwd index 199454dc..b33c1905 100644 --- a/tests/reference/coreDocTools_DocMerger_0000000000014.nwd +++ b/tests/reference/coreDocTools_DocMerger_0000000000014.nwd @@ -1,6 +1,8 @@ %%~name: All of Chapter 1 %%~path: 0000000000008/0000000000014 %%~kind: NOVEL/DOCUMENT +%%~hash: 9e632e7e29860572c685da8501b92bf99825ac3b +%%~date: 2023-08-25 18:29:39/2023-08-25 18:29:39 % Merge Novel Chapter: Chapter 1 [New] ## Chapter 1 diff --git a/tests/reference/guiEditor_Main_Final_000000000000f.nwd b/tests/reference/guiEditor_Main_Final_000000000000f.nwd index b45a9dca..840d78eb 100644 --- a/tests/reference/guiEditor_Main_Final_000000000000f.nwd +++ b/tests/reference/guiEditor_Main_Final_000000000000f.nwd @@ -1,6 +1,8 @@ %%~name: New Scene %%~path: 000000000000d/000000000000f %%~kind: NOVEL/DOCUMENT +%%~hash: fd5dc2f0c9767cb124b1bf2300d7a33b7780045e +%%~date: 2023-08-25 18:08:01/2023-08-25 18:08:04 # Novel ## Chapter diff --git a/tests/reference/guiEditor_Main_Final_0000000000010.nwd b/tests/reference/guiEditor_Main_Final_0000000000010.nwd index bc255b88..eb6ce774 100644 --- a/tests/reference/guiEditor_Main_Final_0000000000010.nwd +++ b/tests/reference/guiEditor_Main_Final_0000000000010.nwd @@ -1,6 +1,8 @@ %%~name: New Note %%~path: 000000000000a/0000000000010 %%~kind: CHARACTER/NOTE +%%~hash: 9fae6dfdd3d1c0822d3a3cf90c0142e65ad8e557 +%%~date: 2023-08-25 18:14:24/2023-08-25 18:14:24 # Jane Doe @tag: Jane diff --git a/tests/reference/guiEditor_Main_Final_0000000000011.nwd b/tests/reference/guiEditor_Main_Final_0000000000011.nwd index 99705061..ed7002ab 100644 --- a/tests/reference/guiEditor_Main_Final_0000000000011.nwd +++ b/tests/reference/guiEditor_Main_Final_0000000000011.nwd @@ -1,6 +1,8 @@ %%~name: New Note %%~path: 0000000000009/0000000000011 %%~kind: PLOT/NOTE +%%~hash: 8ff26f8a18ad6390c2ce725c441ab0c792a125cf +%%~date: 2023-08-25 18:15:35/2023-08-25 18:15:35 # Main Plot @tag: MainPlot diff --git a/tests/reference/guiEditor_Main_Final_0000000000012.nwd b/tests/reference/guiEditor_Main_Final_0000000000012.nwd index ea19dae5..2c624eca 100644 --- a/tests/reference/guiEditor_Main_Final_0000000000012.nwd +++ b/tests/reference/guiEditor_Main_Final_0000000000012.nwd @@ -1,6 +1,8 @@ %%~name: New Note %%~path: 000000000000b/0000000000012 %%~kind: WORLD/NOTE +%%~hash: 3f5c3c6c3ba1c27c30b8ac9e59c222fb9a1bd775 +%%~date: 2023-08-25 18:17:45/2023-08-25 18:17:45 # Main Location @tag: Home diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index 278ce145..8ac8f357 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -21,7 +21,6 @@ along with this program. If not, see . import time import pytest -import hashlib from pathlib import Path from xml.etree import ElementTree as ET @@ -35,8 +34,8 @@ from novelwriter.common import ( checkString, checkStringNone, checkUuid, formatInt, formatTime, formatTimeStamp, fuzzyTime, getGuiItem, hexToInt, isHandle, isItemClass, isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax, - numberToRoman, NWConfigParser, readTextFile, sha256sum, simplified, - transferCase, xmlIndent, yesNo + numberToRoman, NWConfigParser, readTextFile, simplified, transferCase, + xmlIndent, yesNo ) @@ -622,45 +621,6 @@ def testBaseCommon_makeFileNameSafe(): # END Test testBaseCommon_makeFileNameSafe -@pytest.mark.base -def testBaseCommon_sha256sum(monkeypatch, fncPath, ipsumText): - """Test the sha256sum function.""" - longText = 50*(" ".join(ipsumText) + " ") - shortText = "This is a short file" - noneText = "" - - assert len(longText) == 175650 - - longFile = fncPath / "long_file.txt" - shortFile = fncPath / "short_file.txt" - noneFile = fncPath / "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 - -# END Test testBaseCommon_sha256sum - - @pytest.mark.base def testBaseCommon_getGuiItem(nwGUI): """Check the GUI item function.""" diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py index c5c90023..a3f4e11c 100644 --- a/tests/test_core/test_core_coretools.py +++ b/tests/test_core/test_core_coretools.py @@ -26,8 +26,8 @@ from shutil import copyfile from pathlib import Path from zipfile import ZipFile +from tools import C, NWD_IGNORE, buildTestProject, cmpFiles, XML_IGNORE from mocked import causeOSError -from tools import C, buildTestProject, cmpFiles, XML_IGNORE from novelwriter import CONFIG from novelwriter.constants import nwItemClass @@ -46,19 +46,19 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip # ===================== hChapter1 = theProject.newFile("Chapter 1", C.hNovelRoot) - hSceneOne11 = theProject.newFile("Scene 1.1", hChapter1) - hSceneOne12 = theProject.newFile("Scene 1.2", hChapter1) - hSceneOne13 = theProject.newFile("Scene 1.3", hChapter1) + hSceneOne11 = theProject.newFile("Scene 1.1", hChapter1) # type: ignore + hSceneOne12 = theProject.newFile("Scene 1.2", hChapter1) # type: ignore + hSceneOne13 = theProject.newFile("Scene 1.3", hChapter1) # type: ignore docText1 = "\n\n".join(ipsumText[0:2]) + "\n\n" docText2 = "\n\n".join(ipsumText[1:3]) + "\n\n" docText3 = "\n\n".join(ipsumText[2:4]) + "\n\n" docText4 = "\n\n".join(ipsumText[3:5]) + "\n\n" - theProject.writeNewFile(hChapter1, 2, True, docText1) - theProject.writeNewFile(hSceneOne11, 3, True, docText2) - theProject.writeNewFile(hSceneOne12, 3, True, docText3) - theProject.writeNewFile(hSceneOne13, 3, True, docText4) + theProject.writeNewFile(hChapter1, 2, True, docText1) # type: ignore + theProject.writeNewFile(hSceneOne11, 3, True, docText2) # type: ignore + theProject.writeNewFile(hSceneOne12, 3, True, docText3) # type: ignore + theProject.writeNewFile(hSceneOne13, 3, True, docText4) # type: ignore # Basic Checks # ============ @@ -81,12 +81,12 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip testFile = tstPaths.outDir / "coreDocTools_DocMerger_0000000000014.nwd" compFile = tstPaths.refDir / "coreDocTools_DocMerger_0000000000014.nwd" - assert docMerger.newTargetDoc(hChapter1, "All of Chapter 1") == "0000000000014" + assert docMerger.newTargetDoc(hChapter1, "All of Chapter 1") == "0000000000014" # type: ignore - assert docMerger.appendText(hChapter1, True, "Merge") is True - assert docMerger.appendText(hSceneOne11, True, "Merge") is True - assert docMerger.appendText(hSceneOne12, True, "Merge") is True - assert docMerger.appendText(hSceneOne13, True, "Merge") is True + assert docMerger.appendText(hChapter1, True, "Merge") is True # type: ignore + assert docMerger.appendText(hSceneOne11, True, "Merge") is True # type: ignore + assert docMerger.appendText(hSceneOne12, True, "Merge") is True # type: ignore + assert docMerger.appendText(hSceneOne13, True, "Merge") is True # type: ignore # Block writing and check error handling with monkeypatch.context() as mp: @@ -98,7 +98,7 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip # Write properly, and compare assert docMerger.writeTargetDoc() is True copyfile(saveFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE) # Merge into Existing # =================== @@ -107,15 +107,15 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip testFile = tstPaths.outDir / "coreDocTools_DocMerger_0000000000010.nwd" compFile = tstPaths.refDir / "coreDocTools_DocMerger_0000000000010.nwd" - docMerger.setTargetDoc(hChapter1) + docMerger.setTargetDoc(hChapter1) # type: ignore - assert docMerger.appendText(hSceneOne11, True, "Merge") is True - assert docMerger.appendText(hSceneOne12, True, "Merge") is True - assert docMerger.appendText(hSceneOne13, True, "Merge") is True + assert docMerger.appendText(hSceneOne11, True, "Merge") is True # type: ignore + assert docMerger.appendText(hSceneOne12, True, "Merge") is True # type: ignore + assert docMerger.appendText(hSceneOne13, True, "Merge") is True # type: ignore assert docMerger.writeTargetDoc() is True copyfile(saveFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE) # Just for debugging docMerger.writeTargetDoc() @@ -163,11 +163,11 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText) docText = "\n\n".join(docData) docRaw = docText.splitlines() assert theProject.storage.getDocument(hSplitDoc).writeDocument(docText) is True - theProject.tree[hSplitDoc].setStatus(C.sFinished) - theProject.tree[hSplitDoc].setImport(C.iMain) + theProject.tree[hSplitDoc].setStatus(C.sFinished) # type: ignore + theProject.tree[hSplitDoc].setImport(C.iMain) # type: ignore - docSplitter = DocSplitter(theProject, hSplitDoc) - assert docSplitter._srcItem.isFileType() + docSplitter = DocSplitter(theProject, hSplitDoc) # type: ignore + assert docSplitter._srcItem.isFileType() # type: ignore assert docSplitter.getError() == "" # Run the split algorithm @@ -247,8 +247,8 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText) # Check that status and importance has been preserved for rHandle in resDocHandle: - assert theProject.tree[rHandle].itemStatus == C.sFinished - assert theProject.tree[rHandle].itemImport == C.iMain + assert theProject.tree[rHandle].itemStatus == C.sFinished # type: ignore + assert theProject.tree[rHandle].itemImport == C.iMain # type: ignore # Check handling of improper initialisation docSplitter = DocSplitter(theProject, C.hInvalid) @@ -416,7 +416,7 @@ def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd): assert projBuild.buildProject({}) is False # Wrong type should also fail - assert projBuild.buildProject("stuff") is False + assert projBuild.buildProject("stuff") is False # type: ignore # Try again with a proper path assert projBuild.buildProject({"projPath": fncPath}) is True @@ -508,7 +508,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockRnd): @pytest.mark.core -def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths): +def testCoreTools_NewSample(monkeypatch, mockGUI, fncPath, tstPaths): """Check that we can create a new project can be created from the provided sample project via a zip file. """ diff --git a/tests/test_core/test_core_document.py b/tests/test_core/test_core_document.py index 8cafa69a..e7488668 100644 --- a/tests/test_core/test_core_document.py +++ b/tests/test_core/test_core_document.py @@ -21,8 +21,8 @@ along with this program. If not, see . import pytest +from tools import C, MOCK_TIME, buildTestProject, readFile, writeFile from mocked import causeOSError -from tools import C, buildTestProject, readFile, writeFile from novelwriter.enum import nwItemClass, nwItemLayout from novelwriter.core.project import NWProject @@ -32,6 +32,8 @@ from novelwriter.core.document import NWDocument @pytest.mark.core def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): """Test loading and saving a document with the NWDocument class.""" + monkeypatch.setattr("novelwriter.core.document.time", lambda: MOCK_TIME) + theProject = NWProject() mockRnd.reset() buildTestProject(theProject, fncPath) @@ -48,7 +50,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): # Non-existent handle theDoc = NWDocument(theProject, C.hInvalid) assert theDoc.readDocument() is None - assert theDoc._currHash is None + assert theDoc._lastHash == "" assert theDoc.fileExists() is False # No content path @@ -90,7 +92,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): # Set handle and save theText = "### Test File\n\nText ...\n\n" theDoc = NWDocument(theProject, xHandle) - assert theDoc.readDocument(xHandle) == "" + assert theDoc.readDocument(xHandle) == "" # type: ignore assert theDoc.writeDocument(theText) is True # Save again to ensure temp file and previous file is handled @@ -102,6 +104,8 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): "%%~name: New File\n" f"%%~path: {C.hNovelRoot}/{xHandle}\n" "%%~kind: NOVEL/DOCUMENT\n" + "%%~hash: b288c3ab03181027d9a16d7fd2291262f5de9ac8\n" + "%%~date: 2019-05-10 18:52:00/2019-05-10 18:52:00\n" "### Test File\n\n" "Text ...\n\n" ) @@ -114,7 +118,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): assert theDoc.writeDocument(theText, forceWrite=True) is True # Force no meta data - theDoc._theItem = None + theDoc._item = None assert theDoc.writeDocument(theText) is True assert readFile(docPath) == theText @@ -137,7 +141,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): assert theDoc.getError() == "" # Saving with no handle - theDoc._docHandle = None + theDoc._handle = None assert theDoc.writeDocument(theText) is False # Delete Document @@ -170,9 +174,10 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): @pytest.mark.core -def testCoreDocument_Methods(mockGUI, fncPath, mockRnd): - """Test other methods of the NWDocument class. - """ +def testCoreDocument_Methods(monkeypatch, mockGUI, fncPath, mockRnd): + """Test other methods of the NWDocument class.""" + monkeypatch.setattr("novelwriter.core.document.time", lambda: MOCK_TIME) + theProject = NWProject() mockRnd.reset() buildTestProject(theProject, fncPath) @@ -187,7 +192,7 @@ def testCoreDocument_Methods(mockGUI, fncPath, mockRnd): # Check the item assert theDoc.getCurrentItem() is not None - assert theDoc.getCurrentItem().itemHandle == C.hSceneDoc + assert theDoc.getCurrentItem().itemHandle == C.hSceneDoc # type: ignore # Check the meta theName, theParent, theClass, theLayout = theDoc.getMeta() @@ -202,6 +207,8 @@ def testCoreDocument_Methods(mockGUI, fncPath, mockRnd): "%%~name: New Scene\n" f"%%~path: {C.hChapterDir}/{C.hSceneDoc}\n" "%%~kind: NOVEL/DOCUMENT\n" + "%%~hash: dd350c602de803554b2a7c17f191ae25dea1df63\n" + "%%~date: 2019-05-10 18:52:00/2019-05-10 18:52:00\n" "%%~ stuff\n" "### Test File\n\n" "Text ...\n\n" diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index e3025931..0716214e 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -24,7 +24,7 @@ import pytest from shutil import copyfile from tools import ( - C, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile + C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile ) from PyQt5.QtCore import Qt @@ -529,25 +529,25 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): testFile = tstPaths.outDir / "guiEditor_Main_Final_000000000000f.nwd" compFile = tstPaths.refDir / "guiEditor_Main_Final_000000000000f.nwd" copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE) projFile = projPath / "content" / "0000000000010.nwd" testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000010.nwd" compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000010.nwd" copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE) projFile = projPath / "content" / "0000000000011.nwd" testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000011.nwd" compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000011.nwd" copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE) projFile = projPath / "content" / "0000000000012.nwd" testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000012.nwd" compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000012.nwd" copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE) # qtbot.stop() diff --git a/tests/tools.py b/tests/tools.py index 6230d1a2..1eb4e11c 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -23,11 +23,14 @@ from __future__ import annotations import shutil from pathlib import Path +from datetime import datetime from PyQt5.QtWidgets import qApp XML_IGNORE = (" bool: - """Compare two files, but optionally ignore lines given by a list. - """ + """Compare two files, with optional line ignore.""" if ignoreLines is None: ignoreLines = []