""" novelWriter – ToHtml Class Tester ================================= This file is a part of novelWriter Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors 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.constants import nwHeadFmt from novelwriter.core.project import NWProject from novelwriter.formats.shared import BlockFmt, BlockTyp 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) html.initDocument() # Novel Files Headers # =================== html._isNovel = True html._isFirst = True # Header 1 html._text = "# Title\n" html.setPartitionFormat(f"Part{nwHeadFmt.BR}{nwHeadFmt.TITLE}") html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Part
Title

\n" ) # Header 2 html._text = "## Title\n" html.setChapterFormat(f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}") html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Chapter 1
Title

\n" ) # Header 3 html._text = "### Title\n" html.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}") html.tokenizeText() html.doConvert() assert html._pages[-1] == "

Scene 1
Title

\n" # Header 4 html._text = "#### Title\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "

Title

\n" # Title html._text = "#! Title\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Title

\n" ) # Unnumbered html._text = "##! Prologue\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "

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._pages[-1] == "

Heading One

\n" # Header 2 html._text = "## Heading Two\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "

Heading Two

\n" # Header 3 html._text = "### Heading Three\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "

Heading Three

\n" # Header 4 html._text = "#### Heading Four\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "

Heading Four

\n" # Title html._text = "#! Heading One\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

" "Heading One

\n" ) # Unnumbered html._text = "##! Heading Two\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "

Heading Two

\n" @pytest.mark.core def testFmtToHtml_ConvertParagraphs(mockGUI): """Test paragraph formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) html.initDocument() 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._pages[-1] == ( "

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._pages[-1] == ( "

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._pages[-1] == ( "

Line one
Line two
Line three

\n" ) # Synopsis, Short html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "" html.setSynopsis(True) html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

" "Synopsis: " "The synopsis ..." "

\n" ) html.setSynopsis(True) html._text = "%short: A short description ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

" "Short Description: " "A short description ..." "

\n" ) # Comment html._text = "% A comment ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "" html.setComments(True) html._text = "% A comment ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

" "Comment: " "A comment ..." "

\n" ) # Keywords html._text = "@char: Bod, Jane\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "" html.setKeywords(True) html._text = "@char: Bod, Jane\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

" "Characters: " "Bod, " "Jane

\n" ) # Tags html._text = "@tag: Bod\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Tag: " "Bod

\n" ) html._text = "@tag: Bod | Nobody Owens\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

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._pages[-1] == ( "

Chapter

\n" "

" "Point of View: " "Bod

\n" "

" "Plot: " "Main

\n" "

" "Locations: " "Europe

\n" ) @pytest.mark.core def testFmtToHtml_Dialog(mockGUI): """Test paragraph formats in the ToHtml class.""" CONFIG.altDialogOpen = "::" CONFIG.altDialogClose = "::" project = NWProject() html = ToHtml(project) html.initDocument() html.setDialogHighlight(True) html._isNovel = True html._isFirst = True # Dialog html.setDialogHighlight(True) html._text = "## Chapter\n\nThis text \u201chas dialogue\u201d in it.\n\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Chapter

\n" "

This text “has dialogue” in it.

\n" ) # Alt Dialog html._text = "## Chapter\n\nThis text ::has alt dialogue:: in it.\n\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Chapter

\n" "

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

\n" ) @pytest.mark.core def testFmtToHtml_Footnotes(mockGUI): """Test paragraph formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) html.initDocument() html._isNovel = True html._isFirst = True 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._pages[-1] == ( "

Text with one1 " "or twoERR footnotes.

\n" ) html.closeDocument() assert html._pages[-2] == ( "

Text with one1 " "or twoERR footnotes.

\n" ) assert html._pages[-1] == ( "

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.initDocument() 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._pages[-1] == ( "

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._pages[-1] == ( "

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._pages[-1] == ( "

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.initDocument() html._isNovel = True html._handle = "0000000000000" html.setLinkHeadings(True) tMeta = "0000000000000:T0001" # Special Titles # ============== # Title html._blocks = [ (BlockTyp.TITLE, tMeta, "A Title", [], BlockFmt.PBB | BlockFmt.CENTRE), ] html.doConvert() assert html._pages[-1] == ( "

" "A Title

\n" ) # Unnumbered html._blocks = [ (BlockTyp.HEAD2, tMeta, "Prologue", [], BlockFmt.PBB), ] html.doConvert() assert html._pages[-1] == ( "

" "Prologue

\n" ) # Separators # ========== # Separator html._blocks = [ (BlockTyp.SEP, tMeta, "* * *", [], BlockFmt.CENTRE), ] html.doConvert() assert html._pages[-1] == "

* * *

\n" # Skip html._blocks = [ (BlockTyp.SKIP, tMeta, "", [], BlockFmt.NONE), ] html.doConvert() assert html._pages[-1] == "

 

\n" # Alignment # ========= html.setLinkHeadings(False) # Align Left html.setStyles(False) html._blocks = [ (BlockTyp.HEAD1, tMeta, "A Title", [], BlockFmt.LEFT), ] html.doConvert() assert html._pages[-1] == ( "

A Title

\n" ) html.setStyles(True) # Align Left html._blocks = [ (BlockTyp.HEAD1, tMeta, "A Title", [], BlockFmt.LEFT), ] html.doConvert() assert html._pages[-1] == ( "

A Title

\n" ) # Align Right html._blocks = [ (BlockTyp.HEAD1, tMeta, "A Title", [], BlockFmt.RIGHT), ] html.doConvert() assert html._pages[-1] == ( "

A Title

\n" ) # Align Centre html._blocks = [ (BlockTyp.HEAD1, tMeta, "A Title", [], BlockFmt.CENTRE), ] html.doConvert() assert html._pages[-1] == ( "

A Title

\n" ) # Align Justify html._blocks = [ (BlockTyp.HEAD1, tMeta, "A Title", [], BlockFmt.JUSTIFY), ] html.doConvert() assert html._pages[-1] == ( "

A Title

\n" ) # Page Break # ========== # Page Break Always html._blocks = [ (BlockTyp.HEAD1, tMeta, "A Title", [], BlockFmt.PBB | BlockFmt.PBA), ] html.doConvert() assert html._pages[-1] == ( "

A Title

\n" ) # Indent # ====== # Indent Left html._blocks = [ (BlockTyp.TEXT, tMeta, "Some text ...", [], BlockFmt.IND_L), ] html.doConvert() assert html._pages[-1] == ( "

Some text ...

\n" ) # Indent Right html._blocks = [ (BlockTyp.TEXT, tMeta, "Some text ...", [], BlockFmt.IND_R), ] html.doConvert() assert html._pages[-1] == ( "

Some text ...

\n" ) # Text Indent html._blocks = [ (BlockTyp.TEXT, tMeta, "Some text ...", [], BlockFmt.IND_T), ] html.doConvert() assert html._pages[-1] == ( "

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.initDocument() 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._pages[-1] == ( "

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._pages[-1] == ( "

Text with some <bold text> in it.

\n" ) html._text = "Let's > be > _difficult **shall** > we_?\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

Let's > be > difficult shall > we?

\n" ) html._text = "Test > text _<**bold**>_ and more.\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

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._pages[-1] == ( "

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

\n" ) html._isFirst = False html._text = "## Heading <1>\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

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._pages[-1] == ( "

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.initDocument() 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._pages == 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.saveDocument(saveFile) assert saveFile.read_text(encoding="utf-8") == htmlDoc # JSON + HTML saveFile = fncPath / "outFile.json" html.saveDocument(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.initDocument() # 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._pages[-1] == ( "

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._pages[-1] == ( "

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.setStyles(False) assert html.getStyleSheet() == []