""" 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 . """ # noqa 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.enum import nwComment 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 # 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.setCommentType(nwComment.SYNOPSIS, True) html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

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

\n" ) html.setCommentType(nwComment.SHORT, 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.setCommentType(nwComment.PLAIN, True) html._text = "% A comment ...\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

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

\n" ) @pytest.mark.core def testFmtToHtml_ConvertMeta(mockGUI): """Test meta data formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) html.initDocument() html._isNovel = True html._isFirst = True # Keywords Disabled html.setKeywords(False) html._text = "@char: Bod, Jane\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == "" # Keywords Enabled html.setKeywords(True) html._text = "@char: Bod, Jane\n" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

" "Characters: " "Bod, " "Jane

\n" ) # Tags w/Colour html.setStyles(True) 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" ) # Tags wo/Colour html.setStyles(False) 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 w/Colour html.setStyles(True) 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" ) # Multiple Keywords wo/Colour html.setStyles(False) 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_Alignment(mockGUI): """Test paragraph alignment in the ToHtml class.""" project = NWProject() html = ToHtml(project) html.initDocument() # Left html._text = "This is text <<\nspanning multiple\nlines" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

This is text
spanning multiple
lines

\n" ) # Right html._text = ">> This is text\nspanning multiple\nlines" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

This is text
spanning multiple
lines

\n" ) # Centre html._text = ">> This is text <<\nspanning multiple\nlines" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

This is text
spanning multiple
lines

\n" ) # Left before Right html._text = ">> This is text\nspanning multiple <<\nlines" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

This is text
spanning multiple
lines

\n" ) # Right before Centre html._text = ">> This is text <<\n>> spanning multiple\nlines" html.tokenizeText() html.doConvert() assert html._pages[-1] == ( "

This is text
spanning multiple
lines

\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.HEAD1, 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) html.setStyles(True) # Align Left html._blocks = [ (BlockTyp.PART, tMeta, "A Title", [], BlockFmt.LEFT), ] html.doConvert() assert html._pages[-1] == ( "

A Title

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

A Title

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

A Title

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

A Title

\n" ) # Page Break # ========== # Page Break Always html._blocks = [ (BlockTyp.PART, 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.setCommentType(nwComment.PLAIN, 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 # =========== hStyle = html.getStyleSheet() # HTML w/Styles html.setStyles(True) htmlDocCSS = ( "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "{body}\n" "\n" "\n" ).format( style="\n".join(hStyle), body="".join(resText).rstrip() ) saveFile = fncPath / "outFile.htm" html.saveDocument(saveFile) assert saveFile.read_text(encoding="utf-8") == htmlDocCSS # HTML wo/Styles html.setStyles(False) htmlDocCSS = ( "\n" "\n" "\n" "\n" "\n" "\n" "\n" "{body}\n" "\n" "\n" ).format( body="".join(resText).rstrip() ) saveFile = fncPath / "outFile.htm" html.saveDocument(saveFile) assert saveFile.read_text(encoding="utf-8") == htmlDocCSS # JSON + HTML w/Styles html.setStyles(True) 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() == []