From ca818bc4971fbd41ffe7342b1ded3c0f99262813 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 12:36:17 +0200 Subject: [PATCH 01/16] Fix detecting slashes in spell check hihglighting --- nw/gui/dochighlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index d743caa7..23902db1 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -198,7 +198,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Build a QRegExp for spell checker # Include additional characters that the highlighter should # consider to be word separators - wordSep = r"_\+" + wordSep = r"_\+/" wordSep += nwUnicode.U_ENDASH wordSep += nwUnicode.U_EMDASH self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b") From 27c36f38808ddb1565c7e555ea7188fa8d14e615 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 13:19:25 +0200 Subject: [PATCH 02/16] Added the modifier apostrophe characters to the Unicode list and doc actions --- nw/constants/constants.py | 4 ++++ nw/constants/enum.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 7c25fa46..e810dabf 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -233,6 +233,8 @@ class nwUnicode: U_ENDASH = "\u2013" # Short dash U_EMDASH = "\u2014" # Long dash U_HELLIP = "\u2026" # Ellipsis + U_MAPOSS = "\u02bc" # Modifier letter single apostrophe + U_MAPOSD = "\u02ee" # Modifier letter double apostrophe ## Spaces and Lines U_NBSP = "\u00a0" # Non-breaking space @@ -283,6 +285,8 @@ class nwUnicode: H_ENDASH = "–" H_EMDASH = "—" H_HELLIP = "…" + H_MAPOSS = "ʼ" + H_MAPOSD = "ˮ" ## Spaces H_NBSP = " " diff --git a/nw/constants/enum.py b/nw/constants/enum.py index 49d76f5e..81200a63 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -112,6 +112,8 @@ class nwDocInsert(Enum): QUOTE_RS = 9 QUOTE_LD = 10 QUOTE_RD = 11 + MODAPOS_S = 12 + MODAPOS_D = 13 # END Enum nwDocInsert From 377c9a3d410f381870987dd9d64f8c71cb61ee63 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 13:20:01 +0200 Subject: [PATCH 03/16] Added insert menu entries and insert action code --- nw/gui/doceditor.py | 4 ++++ nw/gui/mainmenu.py | 18 ++++++++++++++++++ nw/guimain.py | 2 ++ 3 files changed, 24 insertions(+) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index d10a4d6e..dc25b909 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -612,6 +612,10 @@ class GuiDocEditor(QTextEdit): theText = nwUnicode.U_EMDASH elif theInsert == nwDocInsert.ELLIPSIS: theText = nwUnicode.U_HELLIP + elif theInsert == nwDocInsert.MODAPOS_S: + theText = nwUnicode.U_MAPOSS + elif theInsert == nwDocInsert.MODAPOS_D: + theText = nwUnicode.U_MAPOSD elif theInsert == nwDocInsert.QUOTE_LS: theText = self.typSQOpen elif theInsert == nwDocInsert.QUOTE_RS: diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index c462c748..4aa7e4e9 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -554,6 +554,24 @@ class GuiMainMenu(QMenuBar): # Insert > Separator self.insertMenu.addSeparator() + # Insert > Modifier Single Apostrophe + self.aInsMSApos = QAction("Modifier Single Apostrophe", self) + self.aInsMSApos.setStatusTip("Insert unicode modifier letter single apostrophe") + self.aInsMSApos.setShortcut("Ctrl+K, '") + self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_S)) + self.insertMenu.addAction(self.aInsMSApos) + + # Insert > Modifier Double Apostrophe + self.aInsMDApos = QAction("Modifier Double Apostrophe", self) + self.aInsMDApos.setStatusTip("Insert unicode modifier letter double apostrophe") + self.aInsMDApos.setShortcut("Ctrl+K, \"") + self.aInsMDApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_D)) + self.insertMenu.addAction(self.aInsMDApos) + + # Insert > Separator + self.insertMenu.addSeparator() + + # Insert > Hard Line Break # Insert > Hard Line Break self.aInsHardBreak = QAction("Hard Line Break", self) self.aInsHardBreak.setStatusTip("Insert a hard line break") diff --git a/nw/guimain.py b/nw/guimain.py index 9267a49d..48ea5c73 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -1039,6 +1039,8 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aInsQuoteRS) self.addAction(self.mainMenu.aInsQuoteLD) self.addAction(self.mainMenu.aInsQuoteRD) + self.addAction(self.mainMenu.aInsMSApos) + self.addAction(self.mainMenu.aInsMDApos) self.addAction(self.mainMenu.aInsHardBreak) self.addAction(self.mainMenu.aInsNBSpace) self.addAction(self.mainMenu.aInsThinSpace) From 995685241a1b400ab9370bd81aa736d9ddfefbf8 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 13:20:19 +0200 Subject: [PATCH 04/16] The html converter should strip these out again --- nw/core/tohtml.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index dadf55bd..6053082a 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -54,6 +54,8 @@ class ToHtml(Tokenizer): nwUnicode.U_NBSP : nwUnicode.H_NBSP, nwUnicode.U_THNSP : nwUnicode.H_THNSP, nwUnicode.U_THNBSP : nwUnicode.H_THNBSP, + nwUnicode.U_MAPOSS : nwUnicode.H_RSQUO, + nwUnicode.U_MAPOSD : nwUnicode.H_RDQUO, } self.revDict = {} self.reReplace = [] From 21d68eb1dc015dd000e2c7aaa6ad96420bb494c4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 13:20:39 +0200 Subject: [PATCH 05/16] Added the new insert menu entries to the corresponding test --- tests/test_gui.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_gui.py b/tests/test_gui.py index f7104308..318c9f1a 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -1049,6 +1049,14 @@ def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp): 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.aInsMDApos.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSD + nwGUI.docEditor.clear() + nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == " \n" nwGUI.docEditor.clear() From bc1c54b643e8311e061952feada9fd067db3b0a4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 14:06:57 +0200 Subject: [PATCH 06/16] Updated documentation --- docs/source/index.rst | 1 + docs/source/interface.rst | 27 ++++++----- docs/source/typography.rst | 93 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 docs/source/typography.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 8ac56990..fe485433 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -60,6 +60,7 @@ already familiar with how to run Python applications on your platform. introduction started interface + typography .. toctree:: diff --git a/docs/source/interface.rst b/docs/source/interface.rst index 48b27691..9191a5d8 100644 --- a/docs/source/interface.rst +++ b/docs/source/interface.rst @@ -1,8 +1,8 @@ .. _a_ui: -*************** +************** User Interface -*************** +************** The user interface is kept as simple as possible to avoid distractions when writing. This page lists all the main GUI elements, and explains what they do. @@ -341,11 +341,12 @@ Most features are available as keyboard shortcuts. These are as follows: ":kbd:`Ctrl`:kbd:`B`", "Format selected text, or word under cursor, with strong emphasis (bold)." ":kbd:`Ctrl`:kbd:`C`", "Copy selected text to clipboard." ":kbd:`Ctrl`:kbd:`D`", "Wrap selected text, or word under cursor, in double quotes." - ":kbd:`Ctrl`:kbd:`E`", "If in the project tree, edit a document or folder settings. (Same as :kbd:`F2`)" + ":kbd:`Ctrl`:kbd:`E`", "If in the project tree, edit a document or folder settings. (Same as :kbd:`F2`.)" ":kbd:`Ctrl`:kbd:`F`", "Open the search bar and search for the selected word, if any is selected." - ":kbd:`Ctrl`:kbd:`G`", "Find next occurrence of search word in current document. (Same as :kbd:`F3`)" - ":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`)" + ":kbd:`Ctrl`:kbd:`G`", "Find next occurrence of search word in current document. (Same as :kbd:`F3`.)" + ":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`.)" ":kbd:`Ctrl`:kbd:`I`", "Format selected text, or word under cursor, with emphasis (italic)." + ":kbd:`Ctrl`:kbd:`K`", "Activate the insert commands. The commands are listed in :ref:`a_ui_shortcuts_ins`." ":kbd:`Ctrl`:kbd:`N`", "Create new document." ":kbd:`Ctrl`:kbd:`O`", "Open selected document." ":kbd:`Ctrl`:kbd:`Q`", "Exit novelWriter." @@ -365,7 +366,7 @@ Most features are available as keyboard shortcuts. These are as follows: ":kbd:`Ctrl`:kbd:`Shift`:kbd:`1`", "Replace occurrence of search word in current document, and search for next occurrence." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`A`", "Select all text in current paragraph." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`D`", "Wrap selected text, or word under cursor, in single quotes." - ":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document. (Same as :kbd:`Shift`:kbd:`F3`)" + ":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document. (Same as :kbd:`Shift`:kbd:`F3`.)" ":kbd:`Ctrl`:kbd:`Shift`:kbd:`I`", "Import text to the current document from a text file." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`N`", "Create new folder." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`O`", "Open a project." @@ -386,7 +387,7 @@ Most features are available as keyboard shortcuts. These are as follows: ":kbd:`F10`", "Re-build the project outline." ":kbd:`F11`", "Activate full screen mode." ":kbd:`Shift`:kbd:`F1`", "Open the online documentation in the system default browser." - ":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document. (Same as :kbd:`Ctrl`:kbd:`Shift`:kbd:`G`)" + ":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document. (Same as :kbd:`Ctrl`:kbd:`Shift`:kbd:`G`.)" ":kbd:`Return`", "If in the project tree, open a document for editing." .. note:: @@ -409,11 +410,13 @@ combination for the inserted character or punctuation. ":kbd:`Ctrl`:kbd:`K`, :kbd:`-`", "Insert a short dash (en dash)." ":kbd:`Ctrl`:kbd:`K`, :kbd:`_`", "Insert a long dash (em dash)." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert ellipsis." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`1`", "Insert left single quote." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`2`", "Insert right single quote." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`3`", "Insert left double quote." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`4`", "Insert right double quote." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert an ellipsis." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`1`", "Insert a left single quote." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`2`", "Insert a right single quote." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`3`", "Insert a left double quote." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`4`", "Insert a right double quote." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`'`", "Insert a modifier single apostrophe." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`""`", "Insert a modifier double apostrophe." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Return`", "Insert a hard line break." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Space`", "Insert a non-breaking space." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Shift`:kbd:`Space`", "Insert a thin space." diff --git a/docs/source/typography.rst b/docs/source/typography.rst new file mode 100644 index 00000000..debddb50 --- /dev/null +++ b/docs/source/typography.rst @@ -0,0 +1,93 @@ +.. _a_typ: + +******************* +Typographical Notes +******************* + +novelWriter has some support for typographical symbols that are not usually easily available in many +text editors. This includes for instance the proper unicode quotation marks, dashes, ellipsis, thin +spaces, etc. All these symbols are available from the :guilabel:`Insert` menu, and via keyboard +shortcuts. See :ref:`a_ui_shortcuts_ins`. + +This chapter provides some additional information on how novelWriter handles these symbols. + + +.. _a_typ_notes: + +Special Notes on Symbols +======================== + +Some additional notes on these symbols. + + +Dashes and Ellipsis +------------------- + +With the auto-replace feature enabled (see :ref:`a_ui_edit_auto`), multiple hyphens are converted +automatically to short and long dashes, and three dots to ellipsis. The last auto-replace can always +be reverted with the undo command :kbd:`Ctrl`:kbd:`Z`, reverting the text to what you typed before +the automatic replacement occurred. + + +Single and Double Quotes +------------------------ + +All the different quotation marks listed on the `Quotation Mark`_ Wikipedia page are available, and +can be selected as auto-replaced symbols for straight single and double quote key strokes. The +settings can be found in the :guilabel:`Preferences`. + +Ordinarily, text wrapped in quotes are highlighted by the editor. This is meant as a convenience for +highlighting dialogue between characters. This feature can be disabled in the +:guilabel:`Preferences` if this feature isn't wanted. + +The editor distinguishes between text wrapped in straight quotes and with the user-selected double +quote symbols. This is to help the writer recognise which parts of the text are not using the chosen +quote symbols. Two convenience functions in the :guilabel:`Format` menu can be used to re-format a +selected section of text with the correct quote symbols. + +.. _Quotation Mark: https://en.wikipedia.org/wiki/Quotation_mark + + +Modifier Letter Apostrophes +--------------------------- + +The auto-replace feature will consider any right-facing single straight quote as a quote symbol, +even if it's intended as an apostrophe. This also includes the syntax highlighter, which may decide +to use an apostrophe as the closing symbol of a single quoted string. + +Alternative apostrophes, both single and double, are available. They are special Unicode characters +that are not categorised as punctuation, but modifiers. They are usually renderred the same way as +right single and double quotation marks, but not always, depending on the font. + +There is a Wikipedia article for both the single_ and double_ version of this symbol, explaining +what they're for. + +You can use these symbols if you want, and especially if you have an apostrophe within a single +quoted piece of text. + +.. note:: + On export with the :guilabel:`Build Novel Project` tool, these apostrophes will be replaced + automatically with the corresponding right hand quote symbols. In that respect, it doesn't matter + if you mix them. + +.. _single: https://en.wikipedia.org/wiki/Modifier_letter_apostrophe +.. _double: https://en.wikipedia.org/wiki/Modifier_letter_double_apostrophe + + +Special Space Symbols +--------------------- + +A few variations of the regular space character is supported. The correct typographical way to +separate a number from its unit is with a `thin space`_. It is usually 2/3 the width of a regular +space. For numbers and units, this should in addition be a non-breaking space, that is, the text +wrapping should not add a line break on this particular space. + +A regular space can also be made into a non-breaking space. + +All non-breaking spaces are highlighted with a differently coloured packground. The colour will +depend on the selected colour theme. + +The thin and non-breaking spaces are converted to their corresponding HTML codes on export to HTML +format. For plain text, they are exported as regular spaces. + +.. _thin space: https://en.wikipedia.org/wiki/Thin_space From 6c367a4b58b2a3b329a844852628cb6363fea078 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 14:10:22 +0200 Subject: [PATCH 07/16] Added prefixes to documentation files --- docs/source/index.rst | 18 +++++++++--------- .../{interface.rst => int_interface.rst} | 0 .../{introduction.rst => int_introduction.rst} | 0 docs/source/{started.rst => int_started.rst} | 0 .../{typography.rst => int_typography.rst} | 0 .../{technical.rst => tech_technical.rst} | 0 docs/source/{export.rst => write_export.rst} | 0 docs/source/{notes.rst => write_notes.rst} | 0 .../{projects.rst => write_projects.rst} | 0 .../{structure.rst => write_structure.rst} | 0 10 files changed, 9 insertions(+), 9 deletions(-) rename docs/source/{interface.rst => int_interface.rst} (100%) rename docs/source/{introduction.rst => int_introduction.rst} (100%) rename docs/source/{started.rst => int_started.rst} (100%) rename docs/source/{typography.rst => int_typography.rst} (100%) rename docs/source/{technical.rst => tech_technical.rst} (100%) rename docs/source/{export.rst => write_export.rst} (100%) rename docs/source/{notes.rst => write_notes.rst} (100%) rename docs/source/{projects.rst => write_projects.rst} (100%) rename docs/source/{structure.rst => write_structure.rst} (100%) diff --git a/docs/source/index.rst b/docs/source/index.rst index fe485433..03e8cf98 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -57,27 +57,27 @@ already familiar with how to run Python applications on your platform. :maxdepth: 2 :caption: First Steps - introduction - started - interface - typography + int_introduction + int_started + int_interface + int_typography .. toctree:: :maxdepth: 2 :caption: Writing Novels - projects - structure - notes - export + write_projects + write_structure + write_notes + write_export .. toctree:: :maxdepth: 2 :caption: Under the Hood - technical + tech_technical Indices and Tables diff --git a/docs/source/interface.rst b/docs/source/int_interface.rst similarity index 100% rename from docs/source/interface.rst rename to docs/source/int_interface.rst diff --git a/docs/source/introduction.rst b/docs/source/int_introduction.rst similarity index 100% rename from docs/source/introduction.rst rename to docs/source/int_introduction.rst diff --git a/docs/source/started.rst b/docs/source/int_started.rst similarity index 100% rename from docs/source/started.rst rename to docs/source/int_started.rst diff --git a/docs/source/typography.rst b/docs/source/int_typography.rst similarity index 100% rename from docs/source/typography.rst rename to docs/source/int_typography.rst diff --git a/docs/source/technical.rst b/docs/source/tech_technical.rst similarity index 100% rename from docs/source/technical.rst rename to docs/source/tech_technical.rst diff --git a/docs/source/export.rst b/docs/source/write_export.rst similarity index 100% rename from docs/source/export.rst rename to docs/source/write_export.rst diff --git a/docs/source/notes.rst b/docs/source/write_notes.rst similarity index 100% rename from docs/source/notes.rst rename to docs/source/write_notes.rst diff --git a/docs/source/projects.rst b/docs/source/write_projects.rst similarity index 100% rename from docs/source/projects.rst rename to docs/source/write_projects.rst diff --git a/docs/source/structure.rst b/docs/source/write_structure.rst similarity index 100% rename from docs/source/structure.rst rename to docs/source/write_structure.rst From 42fa913be571b053c9690dc06ad3ca2a9131c969 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Aug 2020 14:17:18 +0200 Subject: [PATCH 08/16] Shorter menu entries --- nw/gui/mainmenu.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 4aa7e4e9..949a8c77 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -554,15 +554,15 @@ class GuiMainMenu(QMenuBar): # Insert > Separator self.insertMenu.addSeparator() - # Insert > Modifier Single Apostrophe - self.aInsMSApos = QAction("Modifier Single Apostrophe", self) + # Insert > Alt. Single Apostrophe + self.aInsMSApos = QAction("Alt. Single Apostrophe", self) self.aInsMSApos.setStatusTip("Insert unicode modifier letter single apostrophe") self.aInsMSApos.setShortcut("Ctrl+K, '") self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_S)) self.insertMenu.addAction(self.aInsMSApos) - # Insert > Modifier Double Apostrophe - self.aInsMDApos = QAction("Modifier Double Apostrophe", self) + # Insert > Alt. Double Apostrophe + self.aInsMDApos = QAction("Alt. Double Apostrophe", self) self.aInsMDApos.setStatusTip("Insert unicode modifier letter double apostrophe") self.aInsMDApos.setShortcut("Ctrl+K, \"") self.aInsMDApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_D)) @@ -571,7 +571,6 @@ class GuiMainMenu(QMenuBar): # Insert > Separator self.insertMenu.addSeparator() - # Insert > Hard Line Break # Insert > Hard Line Break self.aInsHardBreak = QAction("Hard Line Break", self) self.aInsHardBreak.setStatusTip("Insert a hard line break") From 5f033a98bfeb662eb53418c6e557754a6d9e9976 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 23 Aug 2020 20:55:30 +0200 Subject: [PATCH 09/16] Remove the alternative double apostrophe again --- nw/constants/constants.py | 2 -- nw/constants/enum.py | 1 - nw/core/tohtml.py | 1 - nw/gui/doceditor.py | 2 -- nw/gui/mainmenu.py | 11 ++--------- nw/guimain.py | 1 - 6 files changed, 2 insertions(+), 16 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index e810dabf..3bfc9125 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -234,7 +234,6 @@ class nwUnicode: U_EMDASH = "\u2014" # Long dash U_HELLIP = "\u2026" # Ellipsis U_MAPOSS = "\u02bc" # Modifier letter single apostrophe - U_MAPOSD = "\u02ee" # Modifier letter double apostrophe ## Spaces and Lines U_NBSP = "\u00a0" # Non-breaking space @@ -286,7 +285,6 @@ class nwUnicode: H_EMDASH = "—" H_HELLIP = "…" H_MAPOSS = "ʼ" - H_MAPOSD = "ˮ" ## Spaces H_NBSP = " " diff --git a/nw/constants/enum.py b/nw/constants/enum.py index 81200a63..d7c3bde2 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -113,7 +113,6 @@ class nwDocInsert(Enum): QUOTE_LD = 10 QUOTE_RD = 11 MODAPOS_S = 12 - MODAPOS_D = 13 # END Enum nwDocInsert diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 6053082a..cb8cfa1a 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -55,7 +55,6 @@ class ToHtml(Tokenizer): nwUnicode.U_THNSP : nwUnicode.H_THNSP, nwUnicode.U_THNBSP : nwUnicode.H_THNBSP, nwUnicode.U_MAPOSS : nwUnicode.H_RSQUO, - nwUnicode.U_MAPOSD : nwUnicode.H_RDQUO, } self.revDict = {} self.reReplace = [] diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index dc25b909..ca860561 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -614,8 +614,6 @@ class GuiDocEditor(QTextEdit): theText = nwUnicode.U_HELLIP elif theInsert == nwDocInsert.MODAPOS_S: theText = nwUnicode.U_MAPOSS - elif theInsert == nwDocInsert.MODAPOS_D: - theText = nwUnicode.U_MAPOSD elif theInsert == nwDocInsert.QUOTE_LS: theText = self.typSQOpen elif theInsert == nwDocInsert.QUOTE_RS: diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 949a8c77..b904ffd9 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -554,20 +554,13 @@ class GuiMainMenu(QMenuBar): # Insert > Separator self.insertMenu.addSeparator() - # Insert > Alt. Single Apostrophe - self.aInsMSApos = QAction("Alt. Single Apostrophe", self) + # Insert > Alternative Apostrophe + self.aInsMSApos = QAction("Alternative Apostrophe", self) self.aInsMSApos.setStatusTip("Insert unicode modifier letter single apostrophe") self.aInsMSApos.setShortcut("Ctrl+K, '") self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_S)) self.insertMenu.addAction(self.aInsMSApos) - # Insert > Alt. Double Apostrophe - self.aInsMDApos = QAction("Alt. Double Apostrophe", self) - self.aInsMDApos.setStatusTip("Insert unicode modifier letter double apostrophe") - self.aInsMDApos.setShortcut("Ctrl+K, \"") - self.aInsMDApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_D)) - self.insertMenu.addAction(self.aInsMDApos) - # Insert > Separator self.insertMenu.addSeparator() diff --git a/nw/guimain.py b/nw/guimain.py index 48ea5c73..66bcc7cb 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -1040,7 +1040,6 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aInsQuoteLD) self.addAction(self.mainMenu.aInsQuoteRD) self.addAction(self.mainMenu.aInsMSApos) - self.addAction(self.mainMenu.aInsMDApos) self.addAction(self.mainMenu.aInsHardBreak) self.addAction(self.mainMenu.aInsNBSpace) self.addAction(self.mainMenu.aInsThinSpace) From f1f7697df2e15655d4f2526fcc5107fdecd599c0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 23 Aug 2020 20:55:54 +0200 Subject: [PATCH 10/16] Clean up docs and test --- docs/source/int_interface.rst | 3 +-- docs/source/int_typography.rst | 24 +++++++++--------------- tests/test_gui.py | 4 ---- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/docs/source/int_interface.rst b/docs/source/int_interface.rst index 9191a5d8..3c4b772a 100644 --- a/docs/source/int_interface.rst +++ b/docs/source/int_interface.rst @@ -415,8 +415,7 @@ combination for the inserted character or punctuation. ":kbd:`Ctrl`:kbd:`K`, :kbd:`2`", "Insert a right single quote." ":kbd:`Ctrl`:kbd:`K`, :kbd:`3`", "Insert a left double quote." ":kbd:`Ctrl`:kbd:`K`, :kbd:`4`", "Insert a right double quote." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`'`", "Insert a modifier single apostrophe." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`""`", "Insert a modifier double apostrophe." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`'`", "Insert a modifier apostrophe." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Return`", "Insert a hard line break." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Space`", "Insert a non-breaking space." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Shift`:kbd:`Space`", "Insert a thin space." diff --git a/docs/source/int_typography.rst b/docs/source/int_typography.rst index debddb50..1903ba15 100644 --- a/docs/source/int_typography.rst +++ b/docs/source/int_typography.rst @@ -52,26 +52,20 @@ Modifier Letter Apostrophes --------------------------- The auto-replace feature will consider any right-facing single straight quote as a quote symbol, -even if it's intended as an apostrophe. This also includes the syntax highlighter, which may decide -to use an apostrophe as the closing symbol of a single quoted string. +even if it's intended as an apostrophe. This also includes the syntax highlighter, which may assume +the first following apostrophe is the closing symbol of a single quoted region of text. -Alternative apostrophes, both single and double, are available. They are special Unicode characters -that are not categorised as punctuation, but modifiers. They are usually renderred the same way as -right single and double quotation marks, but not always, depending on the font. - -There is a Wikipedia article for both the single_ and double_ version of this symbol, explaining -what they're for. - -You can use these symbols if you want, and especially if you have an apostrophe within a single -quoted piece of text. +To get around this, an alternative apostrophe is available. It is a special Unicode character that +is not categorised as punctuation, but as a modifier. It is usually renderred the same way as right +single quotation marks, but not always, depending on the font. There is a Wikipedia article for the +`Modifier letter apostrophe`_ with more details. .. note:: On export with the :guilabel:`Build Novel Project` tool, these apostrophes will be replaced - automatically with the corresponding right hand quote symbols. In that respect, it doesn't matter - if you mix them. + automatically with the corresponding right hand quote symbols as is generally recommended. + Therefore it doesn't really matter if you only use them to correct highlighting. -.. _single: https://en.wikipedia.org/wiki/Modifier_letter_apostrophe -.. _double: https://en.wikipedia.org/wiki/Modifier_letter_double_apostrophe +.. _Modifier letter apostrophe: https://en.wikipedia.org/wiki/Modifier_letter_apostrophe Special Space Symbols diff --git a/tests/test_gui.py b/tests/test_gui.py index 318c9f1a..22f82c61 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -1053,10 +1053,6 @@ def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp): assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS nwGUI.docEditor.clear() - nwGUI.mainMenu.aInsMDApos.activate(QAction.Trigger) - assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSD - nwGUI.docEditor.clear() - nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == " \n" nwGUI.docEditor.clear() From 9567e3dc4a68351bc7ecd3f9876520d979093d9c Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 23 Aug 2020 20:56:15 +0200 Subject: [PATCH 11/16] Improve regex matches for quote strings --- nw/gui/dochighlight.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 23902db1..a374c9a6 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -143,17 +143,17 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Quoted Strings if self.mainConf.highlightQuotes: self.hRules.append(( - "{:s}(.+?){:s}".format('"', '"'), { + "\\B{:s}(.*?){:s}\\B".format('"', '"'), { 0 : self.hStyles["dialogue1"], } )) self.hRules.append(( - "{:s}(.+?){:s}".format(*self.mainConf.fmtDoubleQuotes), { + "\\B{:s}(.*?){:s}\\B".format(*self.mainConf.fmtDoubleQuotes), { 0 : self.hStyles["dialogue2"], } )) self.hRules.append(( - "{:s}(.+?){:s}".format(*self.mainConf.fmtSingleQuotes), { + "\\B{:s}(.*?){:s}\\B".format(*self.mainConf.fmtSingleQuotes), { 0 : self.hStyles["dialogue3"], } )) From 8340c0665d3bc4a0ba44f2ac5907ebfb60dd5368 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Aug 2020 12:31:38 +0200 Subject: [PATCH 12/16] Made project tests independent --- tests/conftest.py | 57 +++++--- tests/minimal/ToC.json | 17 +++ tests/minimal/ToC.txt | 10 ++ tests/minimal/content/8c659a11cd429.nwd | 3 + tests/minimal/content/a35baf2e93843.nwd | 4 + tests/minimal/content/f5ab3e30151e1.nwd | 3 + tests/minimal/meta/guiOptions.json | 1 + tests/minimal/meta/sessionStats.log | 2 + tests/minimal/meta/tagsIndex.json | 91 +++++++++++++ tests/minimal/nwProject.nwx | 115 ++++++++++++++++ tests/reference/proj/3_nwProject.nwx | 38 +----- tests/test_project.py | 171 ++++++++++++++---------- 12 files changed, 390 insertions(+), 122 deletions(-) create mode 100644 tests/minimal/ToC.json create mode 100644 tests/minimal/ToC.txt create mode 100644 tests/minimal/content/8c659a11cd429.nwd create mode 100644 tests/minimal/content/a35baf2e93843.nwd create mode 100644 tests/minimal/content/f5ab3e30151e1.nwd create mode 100644 tests/minimal/meta/guiOptions.json create mode 100644 tests/minimal/meta/sessionStats.log create mode 100644 tests/minimal/meta/tagsIndex.json create mode 100644 tests/minimal/nwProject.nwx diff --git a/tests/conftest.py b/tests/conftest.py index 7423d849..d4b10213 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,10 +5,14 @@ import sys import pytest import shutil + from os import path, mkdir +from nwdummy import DummyMain sys.path.insert(1, path.abspath(path.join(path.dirname(__file__), path.pardir))) +from nw.config import Config # noqa: E402 + @pytest.fixture(scope="session") def nwTemp(): testDir = path.dirname(__file__) @@ -19,6 +23,24 @@ def nwTemp(): mkdir(tempDir) return tempDir +@pytest.fixture(scope="session") +def nwRef(): + testDir = path.dirname(__file__) + refDir = path.join(testDir, "reference") + return refDir + +@pytest.fixture(scope="session") +def nwConf(nwRef, nwTemp): + theConf = Config() + theConf.initConfig(nwRef, nwTemp) + return theConf + +@pytest.fixture(scope="session") +def nwDummy(nwRef, nwTemp, nwConf): + theDummy = DummyMain() + theDummy.mainConf = nwConf + return theDummy + @pytest.fixture(scope="session") def nwTempProj(nwTemp): projDir = path.join(nwTemp, "proj") @@ -40,20 +62,6 @@ def nwTempBuild(nwTemp): mkdir(buildDir) return buildDir -@pytest.fixture(scope="session") -def nwTempCustom(nwTemp): - customDir = path.join(nwTemp, "custom") - if not path.isdir(customDir): - mkdir(customDir) - return customDir - -@pytest.fixture(scope="session") -def nwTempSample(nwTemp): - sampleDir = path.join(nwTemp, "sample") - if not path.isdir(sampleDir): - mkdir(sampleDir) - return sampleDir - @pytest.fixture(scope="function") def nwFuncTemp(nwTemp): funcDir = path.join(nwTemp, "ftemp") @@ -66,11 +74,24 @@ def nwFuncTemp(nwTemp): shutil.rmtree(funcDir) return -@pytest.fixture(scope="session") -def nwRef(): +@pytest.fixture(scope="function") +def nwMinimal(nwTemp): testDir = path.dirname(__file__) - refDir = path.join(testDir, "reference") - return refDir + minimalStore = path.join(testDir, "minimal") + minimalDir = path.join(nwTemp, "minimal") + if path.isdir(minimalDir): + shutil.rmtree(minimalDir) + shutil.copytree(minimalStore, minimalDir) + cacheDir = path.join(minimalDir, "cache") + if path.isdir(cacheDir): + shutil.rmtree(cacheDir) + metaDir = path.join(minimalDir, "meta") + if path.isdir(metaDir): + shutil.rmtree(metaDir) + yield minimalDir + if path.isdir(minimalDir): + shutil.rmtree(minimalDir) + return @pytest.fixture(scope="session") def nwLipsum(): diff --git a/tests/minimal/ToC.json b/tests/minimal/ToC.json new file mode 100644 index 00000000..5881c392 --- /dev/null +++ b/tests/minimal/ToC.json @@ -0,0 +1,17 @@ +[ + [ + "content/8c659a11cd429.nwd", + "NOVEL", + "New Scene" + ], + [ + "content/a35baf2e93843.nwd", + "NOVEL", + "Title Page" + ], + [ + "content/f5ab3e30151e1.nwd", + "NOVEL", + "New Chapter" + ] +] \ No newline at end of file diff --git a/tests/minimal/ToC.txt b/tests/minimal/ToC.txt new file mode 100644 index 00000000..f5b94b72 --- /dev/null +++ b/tests/minimal/ToC.txt @@ -0,0 +1,10 @@ + + Table of Contents +=================== + + File Name Class Document Label +-------------------------------------------------------------------------------- + content/8c659a11cd429.nwd NOVEL New Scene + content/a35baf2e93843.nwd NOVEL Title Page + content/f5ab3e30151e1.nwd NOVEL New Chapter + diff --git a/tests/minimal/content/8c659a11cd429.nwd b/tests/minimal/content/8c659a11cd429.nwd new file mode 100644 index 00000000..5ecf5c59 --- /dev/null +++ b/tests/minimal/content/8c659a11cd429.nwd @@ -0,0 +1,3 @@ +%%~ 8c659a11cd429:a6d311a93600a:a508bb932959c:NOVEL:SCENE:New Scene +### New Scene + diff --git a/tests/minimal/content/a35baf2e93843.nwd b/tests/minimal/content/a35baf2e93843.nwd new file mode 100644 index 00000000..a7745e3b --- /dev/null +++ b/tests/minimal/content/a35baf2e93843.nwd @@ -0,0 +1,4 @@ +%%~ a35baf2e93843:a508bb932959c:NOVEL:TITLE:Title Page +# Minimal + +By Jane Doe, John Doh diff --git a/tests/minimal/content/f5ab3e30151e1.nwd b/tests/minimal/content/f5ab3e30151e1.nwd new file mode 100644 index 00000000..ba1c9faa --- /dev/null +++ b/tests/minimal/content/f5ab3e30151e1.nwd @@ -0,0 +1,3 @@ +%%~ f5ab3e30151e1:a6d311a93600a:a508bb932959c:NOVEL:CHAPTER:New Chapter +## New Chapter + diff --git a/tests/minimal/meta/guiOptions.json b/tests/minimal/meta/guiOptions.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/minimal/meta/guiOptions.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/minimal/meta/sessionStats.log b/tests/minimal/meta/sessionStats.log new file mode 100644 index 00000000..38cbde3d --- /dev/null +++ b/tests/minimal/meta/sessionStats.log @@ -0,0 +1,2 @@ +# Start Time End Time Novel Notes +2020-08-28 11:36:36 2020-08-28 11:36:48 10 0 diff --git a/tests/minimal/meta/tagsIndex.json b/tests/minimal/meta/tagsIndex.json new file mode 100644 index 00000000..ec600a1c --- /dev/null +++ b/tests/minimal/meta/tagsIndex.json @@ -0,0 +1,91 @@ +{ + "tagIndex": {}, + "refIndex": { + "a35baf2e93843": { + "T000000": { + "tags": [], + "updated": 1598607396 + }, + "T000001": { + "tags": [], + "updated": 1598607396 + } + }, + "f5ab3e30151e1": { + "T000000": { + "tags": [], + "updated": 1598607396 + }, + "T000001": { + "tags": [], + "updated": 1598607396 + } + }, + "8c659a11cd429": { + "T000000": { + "tags": [], + "updated": 1598607396 + }, + "T000001": { + "tags": [], + "updated": 1598607396 + } + } + }, + "novelIndex": { + "a35baf2e93843": { + "T000001": { + "level": "H1", + "title": "Minimal", + "layout": "TITLE", + "synopsis": "", + "cCount": 7, + "wCount": 1, + "pCount": 0, + "updated": 1598607396 + } + }, + "f5ab3e30151e1": { + "T000001": { + "level": "H2", + "title": "New Chapter", + "layout": "CHAPTER", + "synopsis": "", + "cCount": 11, + "wCount": 2, + "pCount": 0, + "updated": 1598607396 + } + }, + "8c659a11cd429": { + "T000001": { + "level": "H3", + "title": "New Scene", + "layout": "SCENE", + "synopsis": "", + "cCount": 9, + "wCount": 2, + "pCount": 0, + "updated": 1598607396 + } + } + }, + "noteIndex": {}, + "textCounts": { + "a35baf2e93843": [ + 28, + 6, + 1 + ], + "f5ab3e30151e1": [ + 11, + 2, + 0 + ], + "8c659a11cd429": [ + 9, + 2, + 0 + ] + } +} \ No newline at end of file diff --git a/tests/minimal/nwProject.nwx b/tests/minimal/nwProject.nwx new file mode 100644 index 00000000..af99a8ce --- /dev/null +++ b/tests/minimal/nwProject.nwx @@ -0,0 +1,115 @@ + + + + Test Minimal + Minimal + Jane Doe + John Doh + 1 + 1 + 8 + + + True + False + True + None + None + 10 + 10 + 0 + + + %title% + Chapter %ch%: %title% + %title% + * * * +
+
+ + New + Note + Draft + Finished + + + New + Minor + Major + Main + +
+ + + Novel + ROOT + NOVEL + New + False + + + Title Page + FILE + NOVEL + New + True + TITLE + 28 + 6 + 1 + 0 + + + New Chapter + FOLDER + NOVEL + New + False + + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 11 + 2 + 0 + 0 + + + New Scene + FILE + NOVEL + New + True + SCENE + 9 + 2 + 0 + 0 + + + Plot + ROOT + PLOT + New + False + + + Characters + ROOT + CHARACTER + New + False + + + World + ROOT + WORLD + New + False + + +
diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/proj/3_nwProject.nwx index 0c396343..c2be9007 100644 --- a/tests/reference/proj/3_nwProject.nwx +++ b/tests/reference/proj/3_nwProject.nwx @@ -1,9 +1,9 @@ - + New Project - 5 + 2 1 0 @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -109,35 +109,7 @@ 0 0 - - Timeline - ROOT - TIMELINE - New - False - - - Object - ROOT - OBJECT - New - False - - - Custom1 - ROOT - CUSTOM - New - False - - - Custom2 - ROOT - CUSTOM - New - False - - + Hello FILE NOVEL @@ -149,7 +121,7 @@ 0 0 - + Jane FILE CHARACTER diff --git a/tests/test_project.py b/tests/test_project.py index 005bac2b..4e90a8c4 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -4,63 +4,66 @@ import pytest from os import path +from shutil import copyfile from nwtools import cmpFiles -from nwdummy import DummyMain -from nw.config import Config from nw.core.project import NWProject from nw.core.index import NWIndex from nw.constants import nwItemClass -theConf = Config() -theMain = DummyMain() -theMain.mainConf = theConf - -theProject = NWProject(theMain) -theProject.projTree.setSeed(42) - @pytest.mark.project -def testProjectNewMinimal(nwTempProj, nwRef, nwTemp): - projFile = path.join(nwTempProj, "nwProject.nwx") +def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy): + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempProj, "1_nwProject.nwx") refFile = path.join(nwRef, "proj", "1_nwProject.nwx") - assert theConf.initConfig(nwRef, nwTemp) - assert theProject.newProject({"projPath": nwTempProj}) - assert theProject.setProjectPath(nwTempProj) + + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + + assert theProject.newProject({"projPath": nwFuncTemp}) + assert theProject.setProjectPath(nwFuncTemp) assert theProject.saveProject() assert theProject.closeProject() - assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) -@pytest.mark.project -def testProjectOpen(nwTempProj): - projFile = path.join(nwTempProj, "nwProject.nwx") + # Check the new project + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + + # Open again assert theProject.openProject(projFile) -@pytest.mark.project -def testProjectSave(nwTempProj, nwRef): - projFile = path.join(nwTempProj, "nwProject.nwx") - refFile = path.join(nwRef, "proj", "1_nwProject.nwx") + # Save and close assert theProject.saveProject() assert theProject.closeProject() - assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) assert not theProject.projChanged -@pytest.mark.project -def testProjectOpenTwice(nwTempProj, nwRef): - projFile = path.join(nwTempProj, "nwProject.nwx") - refFile = path.join(nwRef, "proj", "1_nwProject.nwx") + # Open a second time assert theProject.openProject(projFile) assert not theProject.openProject(projFile) assert theProject.openProject(projFile, overrideLock=True) assert theProject.saveProject() assert theProject.closeProject() - assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewRoot(nwTempProj, nwRef): - projFile = path.join(nwTempProj, "nwProject.nwx") +def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy): + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempProj, "2_nwProject.nwx") refFile = path.join(nwRef, "proj", "2_nwProject.nwx") + + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + + assert theProject.newProject({"projPath": nwFuncTemp}) + assert theProject.setProjectPath(nwFuncTemp) + assert theProject.saveProject() + assert theProject.closeProject() assert theProject.openProject(projFile) + assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None)) assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None)) assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None)) @@ -69,31 +72,48 @@ def testProjectNewRoot(nwTempProj, nwRef): 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 assert theProject.saveProject() assert theProject.closeProject() - assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) + + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) assert not theProject.projChanged @pytest.mark.project -def testProjectNewFile(nwTempProj, nwRef): - projFile = path.join(nwTempProj, "nwProject.nwx") +def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy): + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempProj, "3_nwProject.nwx") refFile = path.join(nwRef, "proj", "3_nwProject.nwx") + + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + + assert theProject.newProject({"projPath": nwFuncTemp}) + assert theProject.setProjectPath(nwFuncTemp) + assert theProject.saveProject() + assert theProject.closeProject() assert theProject.openProject(projFile) - assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "73475cb40a568"), str) + + assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str) assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) assert theProject.projChanged assert theProject.saveProject() assert theProject.closeProject() - assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) + + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) assert not theProject.projChanged @pytest.mark.project -def testIndexScanThis(nwTempProj): - projFile = path.join(nwTempProj, "nwProject.nwx") - assert theProject.openProject(projFile) +def testIndexScanThis(nwMinimal, nwDummy): - theIndex = NWIndex(theProject, theMain) + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + assert theProject.openProject(nwMinimal) + + theIndex = NWIndex(theProject, nwDummy) isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") assert not isValid @@ -135,15 +155,17 @@ def testIndexScanThis(nwTempProj): assert theProject.closeProject() @pytest.mark.project -def testIndexCheckThese(nwTempProj): - projFile = path.join(nwTempProj, "nwProject.nwx") - assert theProject.openProject(projFile) +def testIndexCheckThese(nwMinimal, nwDummy): - theIndex = NWIndex(theProject, theMain) - nHandle = "0e17daca5f3e1" - nItem = theProject.projTree[nHandle] - cHandle = "02d20bbd7e394" - cItem = theProject.projTree[cHandle] + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + assert theProject.openProject(nwMinimal) + + theIndex = NWIndex(theProject, nwDummy) + nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") + cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") + nItem = theProject.projTree[nHandle] + cItem = theProject.projTree[cHandle] assert theIndex.scanText(cHandle, ( "# Jane Smith\n" @@ -153,7 +175,7 @@ def testIndexCheckThese(nwTempProj): "# Hello World!\n" "@pov: Jane" )) - assert str(theIndex.tagIndex) == "{'Jane': [2, '02d20bbd7e394', 'CHARACTER', 'T000001']}" + assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]" @@ -168,13 +190,15 @@ def testIndexCheckThese(nwTempProj): assert theProject.closeProject() @pytest.mark.project -def testIndexMeta(nwTempProj): - projFile = path.join(nwTempProj, "nwProject.nwx") - assert theProject.openProject(projFile) +def testIndexMeta(nwMinimal, nwDummy): - theIndex = NWIndex(theProject, theMain) - nHandle = "0e17daca5f3e1" - cHandle = "02d20bbd7e394" + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + assert theProject.openProject(nwMinimal) + + theIndex = NWIndex(theProject, nwDummy) + nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") + cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") assert theIndex.scanText(cHandle, ( "# Jane Smith\n" @@ -191,11 +215,11 @@ def testIndexMeta(nwTempProj): "\n" "Well, not really.\n" )) - assert str(theIndex.tagIndex) == "{'Jane': [2, '02d20bbd7e394', 'CHARACTER', 'T000001']}" + assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" # The novel structure should contain the pointer to the novel file header - assert str(theIndex.getNovelStructure()) == "['0e17daca5f3e1:T000001']" + assert str(theIndex.getNovelStructure()) == "['%s:T000001']" % nHandle # The novel file should have the correct counts cC, wC, pC = theIndex.getCounts(nHandle) @@ -210,21 +234,22 @@ def testIndexMeta(nwTempProj): # The character file should have a record of the reference from the novel file theRefs = theIndex.getBackReferenceList(cHandle) - assert str(theRefs) == "{'0e17daca5f3e1': 'T000001'}" + assert str(theRefs) == "{'%s': 'T000001'}" % nHandle assert theProject.closeProject() -# The two following tests must be at the end as they mess up the config object -# and the handle seed. They go into their own folders, but use the same project -# object as the test above. - @pytest.mark.project -def testProjectNewCustom(nwTempCustom, nwRef, nwTemp): +def testProjectNewCustom(nwFuncTemp, nwTempProj, nwRef, nwDummy): + + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempProj, "4_nwProject.nwx") + refFile = path.join(nwRef, "proj", "4_nwProject.nwx") + projData = { "projName": "Test Custom", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": nwTempCustom, + "projPath": nwFuncTemp, "popSample": False, "popMinimal": False, "popCustom": True, @@ -240,29 +265,33 @@ def testProjectNewCustom(nwTempCustom, nwRef, nwTemp): "numScenes": 3, "chFolders": True, } - theProject.mainConf = theConf + theProject = NWProject(nwDummy) theProject.projTree.setSeed(42) + assert theProject.newProject(projData) assert theProject.saveProject() assert theProject.closeProject() - projFile = path.join(nwTempCustom, "nwProject.nwx") - refFile = path.join(nwRef, "proj", "4_nwProject.nwx") - assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) + + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewSample(nwTempSample, nwLipsum, nwRef, nwTemp): +def testProjectNewSample(nwFuncTemp, nwRef, nwConf, nwDummy): projData = { "projName": "Test Sample", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": nwTempSample, + "projPath": nwFuncTemp, "popSample": True, "popMinimal": False, "popCustom": False, } - theProject.mainConf = theConf + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + theProject.mainConf = nwConf + assert theProject.newProject(projData) - assert theProject.openProject(nwTempSample) + assert theProject.openProject(nwFuncTemp) assert theProject.projName == "Sample Project" assert theProject.saveProject() assert theProject.closeProject() From b065e4d52b5beb09ddebb5ec808608a77d4c2736 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Aug 2020 12:56:03 +0200 Subject: [PATCH 13/16] Decoupled config tests --- tests/conftest.py | 7 ++ tests/test_config.py | 176 +++++++++++++++++++++---------------------- 2 files changed, 94 insertions(+), 89 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d4b10213..c3128fc4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,13 @@ def nwConf(nwRef, nwTemp): theConf.initConfig(nwRef, nwTemp) return theConf +@pytest.fixture(scope="session") +def tmpConf(nwRef, nwTemp): + theConf = Config() + theConf.initConfig(nwTemp, nwTemp) + theConf.setLastPath("") + return theConf + @pytest.fixture(scope="session") def nwDummy(nwRef, nwTemp, nwConf): theDummy = DummyMain() diff --git a/tests/test_config.py b/tests/test_config.py index cfff5d5d..b58e9697 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,112 +5,110 @@ import pytest from nwtools import cmpFiles from os import path -from nw.config import Config - -theConf = Config() @pytest.mark.core -def testConfigInit(nwTemp, nwRef): - tmpConf = path.join(nwTemp, "novelwriter.conf") +def testConfigCore(tmpConf, nwTemp, nwRef): refConf = path.join(nwRef, "novelwriter.conf") - assert theConf.initConfig(nwTemp, nwTemp) - assert theConf.setLastPath("") - assert theConf.saveConfig() - assert cmpFiles(tmpConf, refConf, [2]) - assert not theConf.confChanged + testConf = path.join(tmpConf.confPath, "novelwriter.conf") + + assert tmpConf.confPath == nwTemp + assert tmpConf.saveConfig() + assert cmpFiles(testConf, refConf, [2]) + assert not tmpConf.confChanged + + assert tmpConf.loadConfig() + assert not tmpConf.confChanged @pytest.mark.core -def testConfigSave(nwTemp, nwRef): - tmpConf = path.join(nwTemp, "novelwriter.conf") +def testConfigSetConfPath(tmpConf, nwTemp): + assert tmpConf.setConfPath(None) + assert not tmpConf.setConfPath(path.join("somewhere", "over", "the", "rainbow")) + assert tmpConf.setConfPath(path.join(nwTemp, "novelwriter.conf")) + assert tmpConf.confPath == nwTemp + assert tmpConf.confFile == "novelwriter.conf" + assert not tmpConf.confChanged + +@pytest.mark.core +def testConfigSetDataPath(tmpConf, nwTemp): + assert tmpConf.setDataPath(None) + assert not tmpConf.setDataPath(path.join("somewhere", "over", "the", "rainbow")) + assert tmpConf.setDataPath(nwTemp) + assert tmpConf.dataPath == nwTemp + assert not tmpConf.confChanged + +@pytest.mark.core +def testConfigSetWinSize(tmpConf, nwTemp, nwRef): refConf = path.join(nwRef, "novelwriter.conf") - assert theConf.confPath == nwTemp - assert theConf.saveConfig() - assert cmpFiles(tmpConf, refConf, [2]) - assert not theConf.confChanged + testConf = path.join(tmpConf.confPath, "novelwriter.conf") + + assert tmpConf.confPath == nwTemp + assert tmpConf.setWinSize(1105, 655) + assert not tmpConf.confChanged + assert tmpConf.setWinSize(70, 70) + assert tmpConf.confChanged + assert tmpConf.setWinSize(1100, 650) + assert tmpConf.saveConfig() + + assert cmpFiles(testConf, refConf, [2]) + assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetConfPath(nwTemp): - assert theConf.setConfPath(None) - assert not theConf.setConfPath(path.join("somewhere", "over", "the", "rainbow")) - assert theConf.setConfPath(path.join(nwTemp, "novelwriter.conf")) - assert theConf.confPath == nwTemp - assert theConf.confFile == "novelwriter.conf" - assert not theConf.confChanged - -@pytest.mark.core -def testConfigSetDataPath(nwTemp): - assert theConf.setDataPath(None) - assert not theConf.setDataPath(path.join("somewhere", "over", "the", "rainbow")) - assert theConf.setDataPath(nwTemp) - assert theConf.dataPath == nwTemp - assert not theConf.confChanged - -@pytest.mark.core -def testConfigLoad(): - assert theConf.loadConfig() - assert not theConf.confChanged - -@pytest.mark.core -def testConfigSetWinSize(nwTemp, nwRef): - tmpConf = path.join(nwTemp, "novelwriter.conf") +def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef): refConf = path.join(nwRef, "novelwriter.conf") - assert theConf.setWinSize(1105, 655) - assert not theConf.confChanged - assert theConf.setWinSize(70, 70) - assert theConf.confChanged - assert theConf.setWinSize(1100, 650) - assert theConf.saveConfig() - assert cmpFiles(tmpConf, refConf, [2]) - assert not theConf.confChanged + testConf = path.join(tmpConf.confPath, "novelwriter.conf") + + assert tmpConf.confPath == nwTemp + assert tmpConf.setTreeColWidths([0, 0, 0]) + assert tmpConf.confChanged + assert tmpConf.setTreeColWidths([120, 30, 50]) + assert tmpConf.setProjColWidths([140, 55, 140]) + assert tmpConf.saveConfig() + + assert cmpFiles(testConf, refConf, [2]) + assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetTreeColWidths(nwTemp, nwRef): - tmpConf = path.join(nwTemp, "novelwriter.conf") +def testConfigSetPanePos(tmpConf, nwTemp, nwRef): refConf = path.join(nwRef, "novelwriter.conf") - assert theConf.setTreeColWidths([0, 0, 0]) - assert theConf.confChanged - assert theConf.setTreeColWidths([120, 30, 50]) - assert theConf.setProjColWidths([140, 55, 140]) - assert theConf.saveConfig() - assert cmpFiles(tmpConf, refConf, [2]) - assert not theConf.confChanged + testConf = path.join(tmpConf.confPath, "novelwriter.conf") + + assert tmpConf.confPath == nwTemp + assert tmpConf.setMainPanePos([0, 0]) + assert tmpConf.confChanged + assert tmpConf.setMainPanePos([300, 800]) + assert tmpConf.setDocPanePos([400, 400]) + assert tmpConf.setViewPanePos([500, 150]) + assert tmpConf.setOutlinePanePos([500, 150]) + assert tmpConf.saveConfig() + + assert cmpFiles(testConf, refConf, [2]) + assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetPanePos(nwTemp, nwRef): - tmpConf = path.join(nwTemp, "novelwriter.conf") +def testConfigFlags(tmpConf, nwTemp, nwRef): refConf = path.join(nwRef, "novelwriter.conf") - assert theConf.setMainPanePos([0, 0]) - assert theConf.confChanged - assert theConf.setMainPanePos([300, 800]) - assert theConf.setDocPanePos([400, 400]) - assert theConf.setViewPanePos([500, 150]) - assert theConf.setOutlinePanePos([500, 150]) - assert theConf.saveConfig() - assert cmpFiles(tmpConf, refConf, [2]) - assert not theConf.confChanged + testConf = path.join(tmpConf.confPath, "novelwriter.conf") + + assert tmpConf.confPath == nwTemp + assert not tmpConf.setShowRefPanel(False) + assert tmpConf.setShowRefPanel(True) + assert tmpConf.confChanged + assert tmpConf.saveConfig() + + assert cmpFiles(testConf, refConf, [2]) + assert not tmpConf.confChanged @pytest.mark.core -def testConfigFlags(nwTemp, nwRef): - tmpConf = path.join(nwTemp, "novelwriter.conf") - refConf = path.join(nwRef, "novelwriter.conf") - assert not theConf.setShowRefPanel(False) - assert theConf.setShowRefPanel(True) - assert theConf.confChanged - assert theConf.saveConfig() - assert cmpFiles(tmpConf, refConf, [2]) - assert not theConf.confChanged - -@pytest.mark.core -def testConfigErrors(nwTemp): +def testConfigErrors(tmpConf): nonPath = path.join("somewhere", "over", "the", "rainbow") - assert theConf.initConfig(nonPath, nonPath) - assert theConf.hasError - assert not theConf.loadConfig() - assert not theConf.saveConfig() - assert not theConf.loadRecentCache() - assert len(theConf.getErrData()) > 0 + assert tmpConf.initConfig(nonPath, nonPath) + assert tmpConf.hasError + assert not tmpConf.loadConfig() + assert not tmpConf.saveConfig() + assert not tmpConf.loadRecentCache() + assert len(tmpConf.getErrData()) > 0 @pytest.mark.core -def testConfigInternals(): - assert theConf._checkNone(None) is None - assert theConf._checkNone("None") is None +def testConfigInternals(tmpConf): + assert tmpConf._checkNone(None) is None + assert tmpConf._checkNone("None") is None From 3869d8670c530e5d9effe92ee2304dea2c8e67c9 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Aug 2020 13:16:22 +0200 Subject: [PATCH 14/16] Decouple item tests --- tests/test_item.py | 76 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/tests/test_item.py b/tests/test_item.py index ca78d28e..54cb02d4 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -11,16 +11,19 @@ from nw.config import Config from nw.core.project import NWProject, NWItem from nw.constants import nwItemClass, nwItemType, nwItemLayout -theConf = Config() -theMain = DummyMain() -theMain.mainConf = theConf +# theConf = Config() +# theMain = DummyMain() +# theMain.mainConf = theConf -theProject = NWProject(theMain) -theItem = NWItem(theProject) -nwXML = etree.Element("novelWriterXML") +# theProject = NWProject(theMain) +# theItem = NWItem(theProject) +# nwXML = etree.Element("novelWriterXML") @pytest.mark.project -def testItemSettersSimple(): +def testItemSettersSimple(nwDummy): + + theProject = NWProject(nwDummy) + theItem = NWItem(theProject) # Name theItem.setName("A Name") @@ -113,7 +116,10 @@ def testItemSettersSimple(): assert theItem.cursorPos == 1 @pytest.mark.project -def testItemClassSetter(): +def testItemClassSetter(nwDummy): + + theProject = NWProject(nwDummy) + theItem = NWItem(theProject) # Class theItem.setClass(None) @@ -138,13 +144,18 @@ def testItemClassSetter(): assert theItem.itemClass == nwItemClass.ENTITY theItem.setClass("CUSTOM") assert theItem.itemClass == nwItemClass.CUSTOM + theItem.setClass("ARCHIVE") + assert theItem.itemClass == nwItemClass.ARCHIVE theItem.setClass("TRASH") assert theItem.itemClass == nwItemClass.TRASH @pytest.mark.project -def testItemTypeSetter(): +def testItemTypeSetter(nwDummy): - # Class + theProject = NWProject(nwDummy) + theItem = NWItem(theProject) + + # Type theItem.setType(None) assert theItem.itemType == nwItemType.NO_TYPE theItem.setType("NONSENSE") @@ -161,9 +172,12 @@ def testItemTypeSetter(): assert theItem.itemType == nwItemType.TRASH @pytest.mark.project -def testItemLayoutSetter(): +def testItemLayoutSetter(nwDummy): - # Class + theProject = NWProject(nwDummy) + theItem = NWItem(theProject) + + # Layout theItem.setLayout(None) assert theItem.itemLayout == nwItemLayout.NO_LAYOUT theItem.setLayout("NONSENSE") @@ -188,7 +202,25 @@ def testItemLayoutSetter(): assert theItem.itemLayout == nwItemLayout.NOTE @pytest.mark.project -def testItemXMLPackUnpack(): +def testItemXMLPackUnpack(nwDummy): + + theProject = NWProject(nwDummy) + theItem = NWItem(theProject) + nwXML = etree.Element("novelWriterXML") + + theItem.setHandle("0123456789abc") + theItem.setParent("0123456789abc") + theItem.setOrder(1) + theItem.setName("A Name") + theItem.setClass("NOVEL") + theItem.setType("FILE") + theItem.setStatus("Main") + theItem.setLayout("NOTE") + theItem.setExpanded(True) + theItem.setParaCount(3) + theItem.setWordCount(5) + theItem.setCharCount(7) + theItem.setCursorPos(11) # Pack xContent = etree.SubElement(nwXML, "content") @@ -196,9 +228,9 @@ def testItemXMLPackUnpack(): assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == ( b"" b"" - b"A NameTRASHTRASH" - b"MainTrue" - b"" + b"A NameFILENOVELNew" + b"TrueNOTE7" + b"5311" b"" ) @@ -208,10 +240,10 @@ def testItemXMLPackUnpack(): assert theItem.parHandle == "0123456789abc" assert theItem.itemOrder == 1 assert theItem.isExpanded - assert theItem.charCount == 1 - assert theItem.wordCount == 1 - assert theItem.paraCount == 1 - assert theItem.cursorPos == 1 - assert theItem.itemClass == nwItemClass.TRASH - assert theItem.itemType == nwItemType.TRASH + assert theItem.paraCount == 3 + assert theItem.wordCount == 5 + assert theItem.charCount == 7 + assert theItem.cursorPos == 11 + assert theItem.itemClass == nwItemClass.NOVEL + assert theItem.itemType == nwItemType.FILE assert theItem.itemLayout == nwItemLayout.NOTE From 64cd356c341e2b452043653dd114bd002c925882 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Aug 2020 16:10:47 +0200 Subject: [PATCH 15/16] Decoupled gui tests --- tests/conftest.py | 12 +- tests/lipsum/ToC.json | 2 +- tests/lipsum/ToC.txt | 2 +- tests/lipsum/nwProject.nwx | 14 +- tests/test_gui.py | 364 ++++++++++++++++++++++++------------- 5 files changed, 258 insertions(+), 136 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c3128fc4..021c888b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -100,12 +100,11 @@ def nwMinimal(nwTemp): shutil.rmtree(minimalDir) return -@pytest.fixture(scope="session") -def nwLipsum(): +@pytest.fixture(scope="function") +def nwLipsum(nwTemp): testDir = path.dirname(__file__) - tempDir = path.join(testDir, "temp") lipsumStore = path.join(testDir, "lipsum") - lipsumDir = path.join(tempDir, "lipsum") + lipsumDir = path.join(nwTemp, "lipsum") if path.isdir(lipsumDir): shutil.rmtree(lipsumDir) shutil.copytree(lipsumStore, lipsumDir) @@ -115,4 +114,7 @@ def nwLipsum(): metaDir = path.join(lipsumDir, "meta") if path.isdir(metaDir): shutil.rmtree(metaDir) - return lipsumDir + yield lipsumDir + if path.isdir(lipsumDir): + shutil.rmtree(lipsumDir) + return diff --git a/tests/lipsum/ToC.json b/tests/lipsum/ToC.json index 7540d6c9..4d22a21c 100644 --- a/tests/lipsum/ToC.json +++ b/tests/lipsum/ToC.json @@ -27,7 +27,7 @@ [ "content/7a992350f3eb6.nwd", "NOVEL", - "Lorem Ipusm" + "Lorem Ipsum" ], [ "content/846352075de7d.nwd", diff --git a/tests/lipsum/ToC.txt b/tests/lipsum/ToC.txt index 86098bcd..a499c6f8 100644 --- a/tests/lipsum/ToC.txt +++ b/tests/lipsum/ToC.txt @@ -9,7 +9,7 @@ content/441420a886d82.nwd NOVEL Chapter Two content/47666c91c7ccf.nwd NOVEL Scene Five content/4c4f28287af27.nwd CHARACTER Mr. Nobody - content/7a992350f3eb6.nwd NOVEL Lorem Ipusm + content/7a992350f3eb6.nwd NOVEL Lorem Ipsum content/846352075de7d.nwd NOVEL Interlude content/88243afbe5ed8.nwd NOVEL Scene One content/88d59a277361b.nwd NOVEL Prologue diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index d6a58a7a..be2463a4 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -1,23 +1,25 @@ - + Lorem Ipsum Lorem Ipsum lipsum.com - 7 + 8 21 - 1459 + 1472 False False True - 04468803b92e1 + fb609cd8319dc None 3847 + 3109 + 738 - Replace Text 1 - Replace Text 2 + Replace Text 1 + Replace Text 2 %title% diff --git a/tests/test_gui.py b/tests/test_gui.py index 22f82c61..5e02b21c 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -5,6 +5,7 @@ import nw import pytest import json +from shutil import copyfile from nwtools import cmpFiles from os import path @@ -24,8 +25,9 @@ keyDelay = 2 stepDelay = 20 @pytest.mark.gui -def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testMainWindows(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp): + + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -33,7 +35,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, close project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwTempGUI}, True) + assert nwGUI.newProject({"projPath": nwFuncTemp}, True) assert nwGUI.saveProject() assert nwGUI.closeProject() @@ -50,14 +52,17 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): assert not nwGUI.theProject.spellCheck # Check the files - projFile = path.join(nwTempGUI, "nwProject.nwx") - assert cmpFiles(projFile, path.join(nwRef, "gui", "0_nwProject.nwx"), [2, 6, 7, 8]) + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempGUI, "0_nwProject.nwx") + refFile = path.join(nwRef, "gui", "0_nwProject.nwx") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) qtbot.wait(stepDelay) # qtbot.stopForInteraction() # Re-open project - assert nwGUI.openProject(nwTempGUI) + assert nwGUI.openProject(nwFuncTemp) qtbot.wait(stepDelay) # Check that we loaded the data @@ -65,8 +70,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): assert len(nwGUI.theProject.projTree._treeOrder) == 8 assert len(nwGUI.theProject.projTree._treeRoots) == 4 assert nwGUI.theProject.projTree.trashRoot() is None - assert nwGUI.theProject.projPath == nwTempGUI - assert nwGUI.theProject.projMeta == path.join(nwTempGUI, "meta") + assert nwGUI.theProject.projPath == nwFuncTemp + assert nwGUI.theProject.projMeta == path.join(nwFuncTemp, "meta") assert nwGUI.theProject.projFile == "nwProject.nwx" assert nwGUI.theProject.projName == "New Project" assert nwGUI.theProject.bookTitle == "" @@ -266,23 +271,42 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): assert nwGUI.saveProject() # Check the files - refFile = path.join(nwTempGUI, "nwProject.nwx") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2, 6, 7, 8]) - refFile = path.join(nwTempGUI, "content", "031b4af5197ec.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_031b4af5197ec.nwd")) - refFile = path.join(nwTempGUI, "content", "1a6562590ef19.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_1a6562590ef19.nwd")) - refFile = path.join(nwTempGUI, "content", "0e17daca5f3e1.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd")) - refFile = path.join(nwTempGUI, "content", "41cfc0d1f2d12.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_41cfc0d1f2d12.nwd")) + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempGUI, "1_nwProject.nwx") + refFile = path.join(nwRef, "gui", "1_nwProject.nwx") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + + projFile = path.join(nwFuncTemp, "content", "031b4af5197ec.nwd") + testFile = path.join(nwTempGUI, "1_031b4af5197ec.nwd") + refFile = path.join(nwRef, "gui", "1_031b4af5197ec.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwFuncTemp, "content", "1a6562590ef19.nwd") + testFile = path.join(nwTempGUI, "1_1a6562590ef19.nwd") + refFile = path.join(nwRef, "gui", "1_1a6562590ef19.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd") + testFile = path.join(nwTempGUI, "1_0e17daca5f3e1.nwd") + refFile = path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwFuncTemp, "content", "41cfc0d1f2d12.nwd") + testFile = path.join(nwTempGUI, "1_41cfc0d1f2d12.nwd") + refFile = path.join(nwRef, "gui", "1_41cfc0d1f2d12.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) nwGUI.closeMain() # qtbot.stopForInteraction() @pytest.mark.gui -def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp): + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -290,8 +314,8 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, open project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwTempGUI}, True) - nwGUI.mainConf.backupPath = nwTempGUI + assert nwGUI.newProject({"projPath": nwFuncTemp}, True) + nwGUI.mainConf.backupPath = nwFuncTemp projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject) projEdit.show() @@ -369,15 +393,18 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.wait(stepDelay) # Check the files - projFile = path.join(nwTempGUI, "nwProject.nwx") - assert cmpFiles(projFile, path.join(nwRef, "gui", "2_nwProject.nwx"), [2, 8, 9, 10]) + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempGUI, "2_nwProject.nwx") + refFile = path.join(nwRef, "gui", "2_nwProject.nwx") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) # qtbot.stopForInteraction() nwGUI.closeMain() @pytest.mark.gui -def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp): + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -385,7 +412,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, open project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwTempGUI}, True) + assert nwGUI.newProject({"projPath": nwFuncTemp}, True) assert nwGUI.openDocument("0e17daca5f3e1") itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1") @@ -425,24 +452,70 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.wait(stepDelay) # Check the files - projFile = path.join(nwTempGUI, "nwProject.nwx") - assert cmpFiles(projFile, path.join(nwRef, "gui", "3_nwProject.nwx"), [2, 6, 7, 8]) + projFile = path.join(nwFuncTemp, "nwProject.nwx") + testFile = path.join(nwTempGUI, "3_nwProject.nwx") + refFile = path.join(nwRef, "gui", "3_nwProject.nwx") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) nwGUI.closeMain() # qtbot.stopForInteraction() @pytest.mark.gui -def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp): + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) qtbot.wait(stepDelay) - assert nwGUI.openProject(nwTempGUI) + # Create new, save, close project + nwGUI.theProject.projTree.setSeed(42) + assert nwGUI.newProject({"projPath": nwFuncTemp}, True) + assert nwGUI.saveProject() + assert nwGUI.closeProject() qtbot.wait(stepDelay) - nwGUI.mainConf.lastPath = nwTempGUI + assert nwGUI.openProject(nwFuncTemp) + qtbot.wait(stepDelay) + + # Add some text to the scene file + assert nwGUI.openDocument("0e17daca5f3e1") + assert nwGUI.docEditor.insertText( + "# Scene One\n\n" + "It was the best of times, it was the worst of times, it was the age of wisdom, it was " + "the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it " + "was the season of Light, it was the season of Darkness, it was the spring of hope, it " + "was the winter of despair, we had everything before us, we had nothing before us, we " + "were all going direct to Heaven, we were all going direct the other way – in short, the " + "period was so far like the present period, that some of its noisiest authorities " + "insisted on its being received, for good or for evil, in the superlative degree of " + "comparison only.\n\n" + ) + assert nwGUI.saveDocument() + + # Add a note file with some text + nwGUI.setFocus(1) + nwGUI.treeView.clearSelection() + nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True) + nwGUI.treeView.newTreeItem(nwItemType.FILE, None) + assert nwGUI.openSelectedItem() + assert nwGUI.docEditor.insertText( + "# Jane Doe\n\n" + "All about Jane.\n\n" + ) + assert nwGUI.saveDocument() + qtbot.wait(500) # Ensures that the session length is > 0 + + assert nwGUI.saveProject() + assert nwGUI.closeProject() + qtbot.wait(stepDelay) + + # Open again, and check the stats + assert nwGUI.openProject(nwFuncTemp) + qtbot.wait(stepDelay) + + nwGUI.mainConf.lastPath = nwFuncTemp sessLog = GuiWritingStats(nwGUI, nwGUI.theProject) sessLog.show() qtbot.wait(stepDelay) @@ -452,15 +525,15 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = path.join(nwTempGUI, "sessionStats.json") + jsonStats = path.join(nwFuncTemp, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) - assert len(jsonData) == 3 - assert jsonData[1]["length"] > 0 - assert jsonData[1]["newWords"] == 84 - assert jsonData[1]["novelWords"] == 63 - assert jsonData[1]["noteWords"] == 27 + assert len(jsonData) == 2 + assert jsonData[1]["length"] >= 0 + assert jsonData[1]["newWords"] == 126 + assert jsonData[1]["novelWords"] == 127 + assert jsonData[1]["noteWords"] == 5 # No Novel Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) @@ -468,15 +541,15 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = path.join(nwTempGUI, "sessionStats.json") + jsonStats = path.join(nwFuncTemp, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) - assert len(jsonData) == 2 - assert jsonData[0]["length"] > 0 - assert jsonData[0]["newWords"] == 27 - assert jsonData[0]["novelWords"] == 63 - assert jsonData[0]["noteWords"] == 27 + assert len(jsonData) == 1 + assert jsonData[0]["length"] >= 0 + assert jsonData[0]["newWords"] == 5 + assert jsonData[0]["novelWords"] == 127 + assert jsonData[0]["noteWords"] == 5 # No Note Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) @@ -485,15 +558,15 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = path.join(nwTempGUI, "sessionStats.json") + jsonStats = path.join(nwFuncTemp, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) - assert len(jsonData) == 3 - assert jsonData[1]["length"] > 0 - assert jsonData[1]["newWords"] == 57 - assert jsonData[1]["novelWords"] == 63 - assert jsonData[1]["noteWords"] == 27 + assert len(jsonData) == 2 + assert jsonData[1]["length"] >= 0 + assert jsonData[1]["newWords"] == 121 + assert jsonData[1]["novelWords"] == 127 + assert jsonData[1]["noteWords"] == 5 # No Negative Entries qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton) @@ -502,7 +575,7 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = path.join(nwTempGUI, "sessionStats.json") + jsonStats = path.join(nwFuncTemp, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) @@ -515,11 +588,11 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = path.join(nwTempGUI, "sessionStats.json") + jsonStats = path.join(nwFuncTemp, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) - assert len(jsonData) == 4 + assert len(jsonData) == 2 # Group by Day qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton) @@ -527,7 +600,7 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = path.join(nwTempGUI, "sessionStats.json") + jsonStats = path.join(nwFuncTemp, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) @@ -541,8 +614,8 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testAboutBox(qtbot, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testAboutBox(qtbot, nwFuncTemp, nwTemp): + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -553,14 +626,13 @@ def testAboutBox(qtbot, nwTempGUI, nwRef, nwTemp): assert msgAbout.pageLicense.document().characterCount() > 100 # qtbot.stopForInteraction() - msgAbout._doClose() nwGUI.closeMain() @pytest.mark.gui def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempBuild, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -568,7 +640,7 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp): assert nwGUI.openProject(nwLipsum) - nwGUI.mainConf.lastPath = nwTempBuild + nwGUI.mainConf.lastPath = nwLipsum nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject) @@ -578,10 +650,17 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp): assert nwBuild._saveDocument(nwBuild.FMT_NWD) assert nwBuild._saveDocument(nwBuild.FMT_HTM) - refFile = path.join(nwTempBuild, "Lorem Ipsum.nwd") - assert cmpFiles(refFile, path.join(nwRef, "build", "1_LoremIpsum.nwd"), []) - refFile = path.join(nwTempBuild, "Lorem Ipsum.htm") - assert cmpFiles(refFile, path.join(nwRef, "build", "1_LoremIpsum.htm"), []) + projFile = path.join(nwLipsum, "Lorem Ipsum.nwd") + testFile = path.join(nwTempBuild, "1_LoremIpsum.nwd") + refFile = path.join(nwRef, "build", "1_LoremIpsum.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "Lorem Ipsum.htm") + testFile = path.join(nwTempBuild, "1_LoremIpsum.htm") + refFile = path.join(nwRef, "build", "1_LoremIpsum.htm") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) # Change Title Formats and Flip Switches nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") @@ -610,10 +689,17 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp): assert nwBuild._saveDocument(nwBuild.FMT_NWD) assert nwBuild._saveDocument(nwBuild.FMT_HTM) - refFile = path.join(nwTempBuild, "Lorem Ipsum.nwd") - assert cmpFiles(refFile, path.join(nwRef, "build", "2_LoremIpsum.nwd"), []) - refFile = path.join(nwTempBuild, "Lorem Ipsum.htm") - assert cmpFiles(refFile, path.join(nwRef, "build", "2_LoremIpsum.htm"), []) + projFile = path.join(nwLipsum, "Lorem Ipsum.nwd") + testFile = path.join(nwTempBuild, "2_LoremIpsum.nwd") + refFile = path.join(nwRef, "build", "2_LoremIpsum.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "Lorem Ipsum.htm") + testFile = path.join(nwTempBuild, "2_LoremIpsum.htm") + refFile = path.join(nwRef, "build", "2_LoremIpsum.htm") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) # Putline Mode nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") @@ -637,19 +723,26 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp): assert nwBuild._saveDocument(nwBuild.FMT_NWD) assert nwBuild._saveDocument(nwBuild.FMT_HTM) - refFile = path.join(nwTempBuild, "Lorem Ipsum.nwd") - assert cmpFiles(refFile, path.join(nwRef, "build", "3_LoremIpsum.nwd"), []) - refFile = path.join(nwTempBuild, "Lorem Ipsum.htm") - assert cmpFiles(refFile, path.join(nwRef, "build", "3_LoremIpsum.htm"), []) + projFile = path.join(nwLipsum, "Lorem Ipsum.nwd") + testFile = path.join(nwTempBuild, "3_LoremIpsum.nwd") + refFile = path.join(nwRef, "build", "3_LoremIpsum.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "Lorem Ipsum.htm") + testFile = path.join(nwTempBuild, "3_LoremIpsum.htm") + refFile = path.join(nwRef, "build", "3_LoremIpsum.htm") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) # qtbot.stopForInteraction() nwBuild._doClose() nwGUI.closeMain() @pytest.mark.gui -def testMergeTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): +def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -670,24 +763,11 @@ def testMergeTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): assert nwGUI.theProject.projTree["73475cb40a568"] is not None - refFile = path.join(nwLipsum, "content", "73475cb40a568.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "4_73475cb40a568.nwd")) - - # qtbot.stopForInteraction() - nwGUI.closeMain() - -@pytest.mark.gui -def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): - - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - qtbot.wait(stepDelay) - - nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.openProject(nwLipsum) - qtbot.wait(stepDelay) + projFile = path.join(nwLipsum, "content", "73475cb40a568.nwd") + testFile = path.join(nwTempGUI, "4_73475cb40a568.nwd") + refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) # Split By Chapter assert nwGUI.treeView.setSelectedHandle("73475cb40a568") @@ -701,8 +781,11 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): assert nwGUI.theProject.projTree["71ee45a3c0db9"] is not None # This should give us back the file as it was before - refFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "4_73475cb40a568.nwd"), [1]) + projFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd") + testFile = path.join(nwTempGUI, "4_71ee45a3c0db9.nwd") + refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [1]) # Split By Scene assert nwGUI.treeView.setSelectedHandle("73475cb40a568") @@ -718,12 +801,23 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): assert nwGUI.theProject.projTree["31489056e0916"] is not None assert nwGUI.theProject.projTree["98010bd9270f9"] is not None - refFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")) - refFile = path.join(nwLipsum, "content", "31489056e0916.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_31489056e0916.nwd")) - refFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_98010bd9270f9.nwd")) + projFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd") + testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd") + refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "content", "31489056e0916.nwd") + testFile = path.join(nwTempGUI, "5_31489056e0916.nwd") + refFile = path.join(nwRef, "gui", "5_31489056e0916.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd") + testFile = path.join(nwTempGUI, "5_98010bd9270f9.nwd") + refFile = path.join(nwRef, "gui", "5_98010bd9270f9.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) # Split By Section assert nwGUI.treeView.setSelectedHandle("73475cb40a568") @@ -741,22 +835,41 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None assert nwGUI.theProject.projTree["2fca346db6561"] is not None - refFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd"), [1]) - refFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_031b4af5197ec.nwd")) - refFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd")) - refFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_2858dcd1057d3.nwd")) - refFile = path.join(nwLipsum, "content", "2fca346db6561.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "5_2fca346db6561.nwd")) + projFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd") + testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd") + refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile, [1]) + + projFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd") + testFile = path.join(nwTempGUI, "5_031b4af5197ec.nwd") + refFile = path.join(nwRef, "gui", "5_031b4af5197ec.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd") + testFile = path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd") + refFile = path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd") + testFile = path.join(nwTempGUI, "5_2858dcd1057d3.nwd") + refFile = path.join(nwRef, "gui", "5_2858dcd1057d3.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + + projFile = path.join(nwLipsum, "content", "2fca346db6561.nwd") + testFile = path.join(nwTempGUI, "5_2fca346db6561.nwd") + refFile = path.join(nwRef, "gui", "5_2fca346db6561.nwd") + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) # qtbot.stopForInteraction() nwGUI.closeMain() @pytest.mark.gui -def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): +def testNewProjectWizard(qtbot, nwLipsum, nwTemp): from PyQt5.QtWidgets import QWizard from nw.gui.projwizard import ( @@ -764,7 +877,7 @@ def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): ProjWizardCustomPage, ProjWizardFinalPage ) - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -885,9 +998,9 @@ def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): +def testDocAction(qtbot, nwLipsum, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1001,8 +1114,8 @@ def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testInsertMenu(qtbot, nwFuncTemp, nwTemp): + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1079,24 +1192,29 @@ def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testLoadProject(qtbot, nwTempGUI, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) +def testLoadProject(qtbot, nwMinimal, nwTemp): + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) qtbot.wait(stepDelay) + assert nwGUI.openProject(nwMinimal) + assert nwGUI.closeProject() + nwLoad = GuiProjectLoad(nwGUI) nwLoad.show() recentCount = nwLoad.listBox.topLevelItemCount() - assert recentCount > 1 + assert recentCount > 0 - selItem = nwLoad.listBox.topLevelItem(1) + selItem = nwLoad.listBox.topLevelItem(0) selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole) + assert isinstance(selItem, QTreeWidgetItem) nwLoad.selPath.setText("") nwLoad.listBox.setCurrentItem(selItem) + nwLoad._doSelectRecent() assert nwLoad.selPath.text() == selPath qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton) @@ -1125,16 +1243,16 @@ def testLoadProject(qtbot, nwTempGUI, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testOutline(qtbot, nwTempBuild, nwLipsum, nwTemp): +def testOutline(qtbot, nwLipsum, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTempBuild, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) qtbot.wait(stepDelay) assert nwGUI.openProject(nwLipsum) - nwGUI.mainConf.lastPath = nwTempBuild + nwGUI.mainConf.lastPath = nwLipsum nwGUI.rebuildIndex() nwGUI.tabWidget.setCurrentIndex(nwGUI.idxTabProj) From 7e830c9aab059574ef743e4418f036325a043c6a Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Aug 2020 16:20:31 +0200 Subject: [PATCH 16/16] Cleanup unneeded stuff --- tests/test_item.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/test_item.py b/tests/test_item.py index 54cb02d4..a3280c14 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -5,20 +5,10 @@ import pytest from lxml import etree -from nwdummy import DummyMain -from nw.config import Config from nw.core.project import NWProject, NWItem from nw.constants import nwItemClass, nwItemType, nwItemLayout -# theConf = Config() -# theMain = DummyMain() -# theMain.mainConf = theConf - -# theProject = NWProject(theMain) -# theItem = NWItem(theProject) -# nwXML = etree.Element("novelWriterXML") - @pytest.mark.project def testItemSettersSimple(nwDummy):