""" novelWriter – ToHtml Class Tester ================================= This file is a part of novelWriter Copyright 2018–2024, Veronica Berglyd Olsen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from __future__ import annotations import json import pytest from novelwriter import CONFIG from novelwriter.core.project import NWProject from novelwriter.formats.tohtml import ToHtml @pytest.mark.core def testFmtToHtml_ConvertHeaders(mockGUI): """Test header formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) # Novel Files Headers # =================== html._isNovel = True html._isFirst = True # Header 1 html._text = "# Partition\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Partition

\n" ) # Header 2 html._text = "## Chapter Title\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Chapter Title

\n" ) # Header 3 html._text = "### Scene Title\n" html.tokenizeText() html.doConvert() assert html.result == "

Scene Title

\n" # Header 4 html._text = "#### Section Title\n" html.tokenizeText() html.doConvert() assert html.result == "

Section Title

\n" # Title html._text = "#! Title\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Title

\n" ) # Unnumbered html._text = "##! Prologue\n" html.tokenizeText() html.doConvert() assert html.result == "

Prologue

\n" # Note Files Headers # ================== html._isNovel = False html._isFirst = True html._handle = "0000000000000" html.setLinkHeadings(True) # Header 1 html._text = "# Heading One\n" html.tokenizeText() html.doConvert() assert html.result == "

Heading One

\n" # Header 2 html._text = "## Heading Two\n" html.tokenizeText() html.doConvert() assert html.result == "

Heading Two

\n" # Header 3 html._text = "### Heading Three\n" html.tokenizeText() html.doConvert() assert html.result == "

Heading Three

\n" # Header 4 html._text = "#### Heading Four\n" html.tokenizeText() html.doConvert() assert html.result == "

Heading Four

\n" # Title html._text = "#! Heading One\n" html.tokenizeText() html.doConvert() assert html.result == ( "

" "Heading One

\n" ) # Unnumbered html._text = "##! Heading Two\n" html.tokenizeText() html.doConvert() assert html.result == "

Heading Two

\n" @pytest.mark.core def testFmtToHtml_ConvertParagraphs(mockGUI): """Test paragraph formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) html._isNovel = True html._isFirst = True # Paragraphs # ========== # Text html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Some nested bold and italic and " "strikethrough text here

\n" ) # Shortcodes html._text = ( "Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], " "super[sup]script[/sup], sub[sub]script[/sub] here\n" ) html.tokenizeText() html.doConvert() assert html.result == ( "

Some bold, italic, strike, " "underline, mark, " "superscript, subscript here

\n" ) # Text w/Hard Break html._text = "Line one\nLine two\nLine three\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Line one
Line two
Line three

\n" ) # Synopsis, Short html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() assert html.result == "" html.setSynopsis(True) html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Synopsis: The synopsis ...

\n" ) html.setSynopsis(True) html._text = "%short: A short description ...\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Short Description: A short description ...

\n" ) # Comment html._text = "% A comment ...\n" html.tokenizeText() html.doConvert() assert html.result == "" html.setComments(True) html._text = "% A comment ...\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Comment: A comment ...

\n" ) # Keywords html._text = "@char: Bod, Jane\n" html.tokenizeText() html.doConvert() assert html.result == "" html.setKeywords(True) html._text = "@char: Bod, Jane\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Characters: " "Bod, " "Jane

\n" ) # Tags html._text = "@tag: Bod\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Tag: " "Bod

\n" ) html._text = "@tag: Bod | Nobody Owens\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Tag: " "Bod | Nobody Owens

\n" ) # Multiple Keywords html._isFirst = False html.setKeywords(True) html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Chapter

\n" "

" "Point of View: " "Bod

\n" "

" "Plot: " "Main

\n" "

" "Locations: " "Europe

\n" ) # Dialogue html.setDialogueHighlight(True) html._text = "## Chapter\n\nThis text \u201chas dialogue\u201d in it.\n\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Chapter

\n" "

This text \u201chas dialogue\u201d in it.

\n" ) # Alt. Dialogue CONFIG.altDialogOpen = "::" CONFIG.altDialogClose = "::" html.setDialogueHighlight(True) html._text = "## Chapter\n\nThis text ::has alt dialogue:: in it.\n\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Chapter

\n" "

This text ::has alt dialogue:: in it.

\n" ) # Footnotes # ========= html._text = ( "Text with one[footnote:fa] or two[footnote:fb] footnotes.\n\n" "%footnote.fa: Footnote text A.\n\n" ) html.tokenizeText() html.doConvert() assert html.result == ( "

Text with one1 " "or twoERR footnotes.

\n" ) html.appendFootnotes() assert html.result == ( "

Text with one1 " "or twoERR footnotes.

\n" "

Footnotes

\n" "
    \n" "
  1. Footnote text A.

  2. \n" "
\n" ) @pytest.mark.core def testFmtToHtml_CloseTags(mockGUI): """Test automatic closing of HTML tags for shortcodes.""" project = NWProject() html = ToHtml(project) html._isNovel = True html._isFirst = True # Unclosed Shortcodes html._text = "Text [b][i][s][u][m][sup][sub]text text text.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Text " "text text text.

\n" ) # Double Shortcodes html._text = "Text [b][i][s][u][m][sup][sub]text [b][i][s][u][m][sup][sub]text text.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Text " "text text text.

\n" ) # Redundant Close Shortcodes html._text = "Text text [/b][/i][/s][/u][/m][/sup][/sub]text text.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Text text text text.

\n" ) @pytest.mark.core def testFmtToHtml_ConvertDirect(mockGUI): """Test the converter directly using the ToHtml class.""" project = NWProject() html = ToHtml(project) html._isNovel = True html._handle = "0000000000000" html.setLinkHeadings(True) # Special Titles # ============== # Title html._tokens = [ (html.T_TITLE, 1, "A Title", [], html.A_PBB | html.A_CENTRE), ] html.doConvert() assert html.result == ( "

" "A Title

\n" ) # Unnumbered html._tokens = [ (html.T_HEAD2, 1, "Prologue", [], html.A_PBB), ] html.doConvert() assert html.result == ( "

" "Prologue

\n" ) # Separators # ========== # Separator html._tokens = [ (html.T_SEP, 1, "* * *", [], html.A_CENTRE), ] html.doConvert() assert html.result == "

* * *

\n" # Skip html._tokens = [ (html.T_SKIP, 1, "", [], html.A_NONE), ] html.doConvert() assert html.result == "

 

\n" # Alignment # ========= html.setLinkHeadings(False) # Align Left html.setStyles(False) html._tokens = [ (html.T_HEAD1, 1, "A Title", [], html.A_LEFT), ] html.doConvert() assert html.result == ( "

A Title

\n" ) html.setStyles(True) # Align Left html._tokens = [ (html.T_HEAD1, 1, "A Title", [], html.A_LEFT), ] html.doConvert() assert html.result == ( "

A Title

\n" ) # Align Right html._tokens = [ (html.T_HEAD1, 1, "A Title", [], html.A_RIGHT), ] html.doConvert() assert html.result == ( "

A Title

\n" ) # Align Centre html._tokens = [ (html.T_HEAD1, 1, "A Title", [], html.A_CENTRE), ] html.doConvert() assert html.result == ( "

A Title

\n" ) # Align Justify html._tokens = [ (html.T_HEAD1, 1, "A Title", [], html.A_JUSTIFY), ] html.doConvert() assert html.result == ( "

A Title

\n" ) # Page Break # ========== # Page Break Always html._tokens = [ (html.T_HEAD1, 1, "A Title", [], html.A_PBB | html.A_PBA), ] html.doConvert() assert html.result == ( "

A Title

\n" ) # Indent # ====== # Indent Left html._tokens = [ (html.T_TEXT, 1, "Some text ...", [], html.A_IND_L), ] html.doConvert() assert html.result == ( "

Some text ...

\n" ) # Indent Right html._tokens = [ (html.T_TEXT, 1, "Some text ...", [], html.A_IND_R), ] html.doConvert() assert html.result == ( "

Some text ...

\n" ) # Text Indent html._tokens = [ (html.T_TEXT, 1, "Some text ...", [], html.A_IND_T), ] html.doConvert() assert html.result == ( "

Some text ...

\n" ) @pytest.mark.core def testFmtToHtml_SpecialCases(mockGUI): """Test some special cases that have caused errors in the past.""" project = NWProject() html = ToHtml(project) html._isNovel = True # Greater/Lesser than symbols # =========================== html._text = "Text with > and < with some **bold text** in it.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Text with > and < with some bold text in it.

\n" ) html._text = "Text with some <**bold text**> in it.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Text with some <bold text> in it.

\n" ) html._text = "Let's > be > _difficult **shall** > we_?\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Let's > be > difficult shall > we?

\n" ) html._text = "Test > text _<**bold**>_ and more.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Test > text <bold> and more.

\n" ) # Test for issue #950 # =================== # See: https://github.com/vkbo/novelWriter/issues/950 html.setComments(True) html._text = "% Test > text _<**bold**>_ and more.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

" "Comment: Test > text <bold> and more." "

\n" ) html._isFirst = False html._text = "## Heading <1>\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Heading <1>

\n" ) # Test for issue #1412 # ==================== # See: https://github.com/vkbo/novelWriter/issues/1412 html._text = "Test text \\**_bold_** and more.\n" html.tokenizeText() html.doConvert() assert html.result == ( "

Test text **bold** and more.

\n" ) @pytest.mark.core def testFmtToHtml_Save(mockGUI, fncPath): """Test the save method of the ToHtml class.""" project = NWProject() html = ToHtml(project) html._isNovel = True # Build Project # ============= docText = [ "# My Novel\n**By Jane Doh**\n", "## Chapter 1\n\nThe text of chapter one.\n", "### Scene 1\n\nThe text of scene one.\n", "#### A Section\n\nMore text in scene one.\n", "## Chapter 2\n\nThe text of chapter two.\n", "### Scene 2\n\nThe text of scene two.\n", "#### A Section\n\n\tMore text in scene two.\n", ] resText = [( "

My Novel

\n" "

By Jane Doh

\n" ), ( "

Chapter 1

\n" "

The text of chapter one.

\n" ), ( "

Scene 1

\n" "

The text of scene one.

\n" ), ( "

A Section

\n" "

More text in scene one.

\n" ), ( "

Chapter 2

\n" "

The text of chapter two.

\n" ), ( "

Scene 2

\n" "

The text of scene two.

\n" ), ( "

A Section

\n" "

\tMore text in scene two.

\n" )] for i in range(len(docText)): html._text = docText[i] html.doPreProcessing() html.tokenizeText() html.doConvert() assert html.result == resText[i] assert html.fullHTML == resText html.replaceTabs(nSpaces=2, spaceChar=" ") resText[6] = "

A Section

\n

  More text in scene two.

\n" # Check Files # =========== # HTML hStyle = html.getStyleSheet() htmlDoc = ( "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "
\n" "{bodyText:s}\n" "
\n" "\n" "\n" ).format( htmlStyle="\n".join(hStyle), bodyText="".join(resText).rstrip() ) saveFile = fncPath / "outFile.htm" html.saveHtml5(saveFile) assert saveFile.read_text(encoding="utf-8") == htmlDoc # JSON + HTML saveFile = fncPath / "outFile.json" html.saveHtmlJson(saveFile) data = json.loads(saveFile.read_text(encoding="utf-8")) assert data["meta"]["projectName"] == "" assert data["meta"]["novelAuthor"] == "" assert data["meta"]["buildTime"] > 0 assert data["meta"]["buildTimeStr"] != "" assert data["text"]["css"] == hStyle assert len(data["text"]["html"]) == len(resText) @pytest.mark.core def testFmtToHtml_Methods(mockGUI): """Test all the other methods of the ToHtml class.""" project = NWProject() html = ToHtml(project) html.setKeepMarkdown(True) # Auto-Replace, keep Unicode docText = "Text with & short–dash, long—dash …\n" html._text = docText html.setReplaceUnicode(False) html.doPreProcessing() html.tokenizeText() html.doConvert() assert html.result == ( "

Text with <brackets> & short–dash, long—dash …

\n" ) # Auto-Replace, replace Unicode docText = "Text with & short–dash, long—dash …\n" html._text = docText html.setReplaceUnicode(True) html.doPreProcessing() html.tokenizeText() html.doConvert() assert html.result == ( "

Text with <brackets> & short–dash, long—dash …

\n" ) # Result Size assert html.getFullResultSize() == 147 # CSS # === assert len(html.getStyleSheet()) > 1 assert "p {text-align: left;" in " ".join(html.getStyleSheet()) assert "p {text-align: justify;" not in " ".join(html.getStyleSheet()) html.setJustify(True) assert "p {text-align: left;" not in " ".join(html.getStyleSheet()) assert "p {text-align: justify;" in " ".join(html.getStyleSheet()) html.setStyles(False) assert html.getStyleSheet() == [] @pytest.mark.core def testFmtToHtml_Format(mockGUI): """Test all the formatters for the ToHtml class.""" project = NWProject() html = ToHtml(project) # Export Mode # =========== assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) assert html._formatSynopsis("short text", False) == ( "

Short Description: short text

\n" ) assert html._formatComments("comment text") == ( "

Comment: comment text

\n" ) assert html._formatKeywords("") == ("", "") assert html._formatKeywords("tag: Jane") == ( "tag", "Tag: Jane" ) assert html._formatKeywords("char: Bod, Jane") == ( "char", "Characters: " "Bod, " "Jane" )