"""
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] == (
"\n"
)
html.setSynopsis(True)
html._text = "%short: A short description ...\n"
html.tokenizeText()
html.doConvert()
assert html._pages[-1] == (
"\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] == (
"\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"
"\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] == (
"\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() == []