diff --git a/make.ps1 b/make.ps1 index fb9866b..c0a0122 100644 --- a/make.ps1 +++ b/make.ps1 @@ -28,7 +28,7 @@ switch ($command) { Activate-Venv -venv $venv python -m pip install -r requirements-win.txt python -m pip install -e . - # python -m pre_commit install + python -m pre_commit install break } "compile" { diff --git a/src/pretty_jupyter/__init__.py b/src/pretty_jupyter/__init__.py index 70641a0..8ec9c39 100644 --- a/src/pretty_jupyter/__init__.py +++ b/src/pretty_jupyter/__init__.py @@ -1,7 +1,13 @@ from pretty_jupyter.magics import JinjaMagics # these imports are here for conf.json to work -from pretty_jupyter.preprocessors import TokenPreprocessor, TokenCleaningPreprocessor, NbMetadataPreprocessor, HtmlNbMetadataPreprocessor +from pretty_jupyter.preprocessors import ( + HtmlNbMetadataPreprocessor, + NbMetadataPreprocessor, + TokenCleaningPreprocessor, + TokenPreprocessor, +) + def load_ipython_extension(ipython): # The `ipython` argument is the currently active `InteractiveShell` diff --git a/src/pretty_jupyter/__main__.py b/src/pretty_jupyter/__main__.py index ef6d15f..ae45561 100644 --- a/src/pretty_jupyter/__main__.py +++ b/src/pretty_jupyter/__main__.py @@ -1,4 +1,4 @@ from pretty_jupyter.console import cli if __name__ == "__main__": - cli() \ No newline at end of file + cli() diff --git a/src/pretty_jupyter/console.py b/src/pretty_jupyter/console.py index 4f5461f..8a241cb 100644 --- a/src/pretty_jupyter/console.py +++ b/src/pretty_jupyter/console.py @@ -1,17 +1,20 @@ import os import shutil import sys +from pathlib import Path + import click -from traitlets.config import Config -from nbconvert.exporters import HTMLExporter, PDFExporter, LatexExporter import pkg_resources -from pathlib import Path +from nbconvert.exporters import HTMLExporter, LatexExporter, PDFExporter +from traitlets.config import Config @click.command() @click.argument("out_path", type=click.Path()) def quickstart(out_path): - in_path = pkg_resources.resource_filename("pretty_jupyter", os.path.join("quickstart", "empty.ipynb")) + in_path = pkg_resources.resource_filename( + "pretty_jupyter", os.path.join("quickstart", "empty.ipynb") + ) with open(in_path, "r") as file_r, open(out_path, "w") as file_w: in_text = file_r.read() @@ -31,15 +34,13 @@ def nbconvert_dev(input, to, out, include_input): if out is None: out = os.path.join(os.path.dirname(input), f"{Path(input).stem}.{to}") - template_map = { - "html": "pj", - "pdf": "pj-pdf", - "latex": "pj-pdf" - } + template_map = {"html": "pj", "pdf": "pj-pdf", "latex": "pj-pdf"} - config = Config() + config = Config() config.TemplateExporter.template_name = template_map[to] - config.TemplateExporter.extra_template_basedirs = [pkg_resources.resource_filename("pretty_jupyter", "templates")] + config.TemplateExporter.extra_template_basedirs = [ + pkg_resources.resource_filename("pretty_jupyter", "templates") + ] config.TemplateExporter.exclude_input = not include_input if to == "html": @@ -54,7 +55,7 @@ def nbconvert_dev(input, to, out, include_input): with open(input, "r", encoding="utf-8") as file: res = exporter.from_file(file) output_data = res[0] - + # open file as bytes if the output data are bytes, otherwise opne it as a string file = open(out, "wb") if isinstance(output_data, bytes) else open(out, "w", encoding="utf-8") try: @@ -71,8 +72,12 @@ def install_dev(): src_templates = ["pj", "pj-pdf", "pj-legacy"] for src_template in src_templates: - src_folder = os.path.join(pkg_resources.resource_filename("pretty_jupyter", "templates"), src_template) - target_folder = os.path.join(sys.prefix, f"share/jupyter/nbconvert/templates/{src_template}") + src_folder = os.path.join( + pkg_resources.resource_filename("pretty_jupyter", "templates"), src_template + ) + target_folder = os.path.join( + sys.prefix, f"share/jupyter/nbconvert/templates/{src_template}" + ) # for backward compatibility, otherwise copytree has dirs_exist_ok param if os.path.exists(target_folder): @@ -80,7 +85,6 @@ def install_dev(): shutil.copytree(src_folder, target_folder) - @click.version_option() @click.group() def cli(): diff --git a/src/pretty_jupyter/constants.py b/src/pretty_jupyter/constants.py index 039379e..ff9ed4a 100644 --- a/src/pretty_jupyter/constants.py +++ b/src/pretty_jupyter/constants.py @@ -62,11 +62,11 @@ "spacelab", "superhero", "united", - "yeti" + "yeti", ] """ List of all available local themes that can be specified in the `theme` html metadata. """ DEPRECATED_METADATA_MSG_FORMAT = "Specifying attributes '{attributes}' in this position to notebook metadata is deprecated since {version}. Please consider reading changes in this version.\n" -METADATA_ERROR_FORMAT = "An error occured when validating cell metadata. Error attributes in the metadata were the following:\n{error}" \ No newline at end of file +METADATA_ERROR_FORMAT = "An error occured when validating cell metadata. Error attributes in the metadata were the following:\n{error}" diff --git a/src/pretty_jupyter/helpers/__init__.py b/src/pretty_jupyter/helpers/__init__.py index 876c037..ce744bd 100644 --- a/src/pretty_jupyter/helpers/__init__.py +++ b/src/pretty_jupyter/helpers/__init__.py @@ -1,6 +1,3 @@ from pretty_jupyter.helpers.matplotlib import matplotlib_fig_to_html, matplotlib_fig_to_markdown -__all__ = [ - matplotlib_fig_to_markdown.__name__, - matplotlib_fig_to_html.__name__ -] \ No newline at end of file +__all__ = [matplotlib_fig_to_markdown.__name__, matplotlib_fig_to_html.__name__] diff --git a/src/pretty_jupyter/helpers/matplotlib.py b/src/pretty_jupyter/helpers/matplotlib.py index 724cdf0..1b066bc 100644 --- a/src/pretty_jupyter/helpers/matplotlib.py +++ b/src/pretty_jupyter/helpers/matplotlib.py @@ -1,19 +1,10 @@ -from io import BytesIO import base64 +from io import BytesIO -_MARKDOWN_SUPPORTED_FMT_MAP = { - "png": "png", - "jpeg": "jpeg", - "jpg": "jpeg" -} +_MARKDOWN_SUPPORTED_FMT_MAP = {"png": "png", "jpeg": "jpeg", "jpg": "jpeg"} _MARKDOWN_IMG_FORMAT = r"![image](data:image/{format};base64,{encoded})" -_HTML_SUPPORTED_FMT_MAP = { - "svg": "svg+xml", - "png": "png", - "jpeg": "jpeg", - "jpg": "jpeg" -} +_HTML_SUPPORTED_FMT_MAP = {"svg": "svg+xml", "png": "png", "jpeg": "jpeg", "jpg": "jpeg"} _HTML_IMG_FORMAT = r"" @@ -30,7 +21,15 @@ def matplotlib_fig_to_markdown(fig, fmt="png", autoclose: bool = True, bbox_inch Returns: str: Markdown string embedded representation of the figure. """ - return convert_mpl(fig, fmt=fmt, output_fmt_string=_MARKDOWN_IMG_FORMAT, supported_fmt_map=_MARKDOWN_SUPPORTED_FMT_MAP, autoclose=autoclose, bbox_inches=bbox_inches) + return convert_mpl( + fig, + fmt=fmt, + output_fmt_string=_MARKDOWN_IMG_FORMAT, + supported_fmt_map=_MARKDOWN_SUPPORTED_FMT_MAP, + autoclose=autoclose, + bbox_inches=bbox_inches, + ) + def matplotlib_fig_to_html(fig, fmt="png", autoclose: bool = True, bbox_inches: str = "tight"): """ @@ -47,7 +46,15 @@ def matplotlib_fig_to_html(fig, fmt="png", autoclose: bool = True, bbox_inches: """ return convert_mpl(fig, fmt, _HTML_IMG_FORMAT, _HTML_SUPPORTED_FMT_MAP, autoclose, bbox_inches) -def convert_mpl(fig, fmt, output_fmt_string, supported_fmt_map, autoclose: bool = True, bbox_inches: str = "tight"): + +def convert_mpl( + fig, + fmt, + output_fmt_string, + supported_fmt_map, + autoclose: bool = True, + bbox_inches: str = "tight", +): """ Converts matplotlib figure into embedded inline string. @@ -68,9 +75,12 @@ def convert_mpl(fig, fmt, output_fmt_string, supported_fmt_map, autoclose: bool tmpfile = BytesIO() fig.savefig(tmpfile, format=fmt, bbox_inches=bbox_inches) - markdown = output_fmt_string.format(format=supported_fmt_map[fmt], encoded=base64.b64encode(tmpfile.getvalue()).decode('utf-8')) + markdown = output_fmt_string.format( + format=supported_fmt_map[fmt], encoded=base64.b64encode(tmpfile.getvalue()).decode("utf-8") + ) if autoclose: import matplotlib.pyplot as plt + plt.close() - return markdown \ No newline at end of file + return markdown diff --git a/src/pretty_jupyter/magics.py b/src/pretty_jupyter/magics.py index 40813d7..fc8fd63 100644 --- a/src/pretty_jupyter/magics.py +++ b/src/pretty_jupyter/magics.py @@ -1,6 +1,6 @@ -from IPython import display -from IPython.core.magic import Magics, magics_class, cell_magic import jinja2 +from IPython import display +from IPython.core.magic import Magics, cell_magic, magics_class from pretty_jupyter.tokens import convert_markdown_tokens_to_html @@ -9,18 +9,20 @@ class JinjaMagics(Magics): def __init__(self, shell): super().__init__(shell) - + # create a jinja2 environment to use for rendering # this can be modified for desired effects (ie: using different variable syntax) - self.env = jinja2.Environment(loader=jinja2.FileSystemLoader('.')) + self.env = jinja2.Environment(loader=jinja2.FileSystemLoader(".")) # possible output types - self.display_functions = dict(html=display.HTML, - latex=display.Latex, - json=display.JSON, - pretty=display.Pretty, - display=display.display, - markdown=display.Markdown) + self.display_functions = dict( + html=display.HTML, + latex=display.Latex, + json=display.JSON, + pretty=display.Pretty, + display=display.display, + markdown=display.Markdown, + ) @cell_magic def jinja(self, line, cell): @@ -32,9 +34,14 @@ def jinja(self, line, cell): # render the cell with jinja (substitutes variables,...) tmp = self.env.from_string(cell) - rend = tmp.render(dict((k,v) for (k,v) in self.shell.user_ns.items() - if not k.startswith('_') and k not in self.shell.user_ns_hidden)) - + rend = tmp.render( + dict( + (k, v) + for (k, v) in self.shell.user_ns.items() + if not k.startswith("_") and k not in self.shell.user_ns_hidden + ) + ) + # convert tokens to html if display_fn_name == "markdown": rend = convert_markdown_tokens_to_html(rend) @@ -48,6 +55,7 @@ def jmd(self, line, cell): raise ValueError(r"%%jmd does not accept any arguments.") return self.jinja(line="markdown", cell=cell) + def is_jinja_cell(input_str: str) -> bool: """ Checks whether the input is input of a jinja cell. @@ -64,9 +72,6 @@ def is_jinja_cell(input_str: str) -> bool: return False first_line = lines[0] - fns = [ - lambda l: l.startswith("%%jinja"), - lambda l: l.startswith("%%jmd") - ] + fns = [lambda l: l.startswith("%%jinja"), lambda l: l.startswith("%%jmd")] - return any(fn(first_line) for fn in fns) \ No newline at end of file + return any(fn(first_line) for fn in fns) diff --git a/src/pretty_jupyter/preprocessors/__init__.py b/src/pretty_jupyter/preprocessors/__init__.py index efeb18a..d5a9ded 100644 --- a/src/pretty_jupyter/preprocessors/__init__.py +++ b/src/pretty_jupyter/preprocessors/__init__.py @@ -1,9 +1,15 @@ -from pretty_jupyter.preprocessors._token_preprocessor import TokenPreprocessor, TokenCleaningPreprocessor -from pretty_jupyter.preprocessors._metadata_preprocessor import NbMetadataPreprocessor, HtmlNbMetadataPreprocessor +from pretty_jupyter.preprocessors._metadata_preprocessor import ( + HtmlNbMetadataPreprocessor, + NbMetadataPreprocessor, +) +from pretty_jupyter.preprocessors._token_preprocessor import ( + TokenCleaningPreprocessor, + TokenPreprocessor, +) __all__ = [ "TokenPreprocessor", "TokenCleaningPreprocessor", "NbMetadatapreprocessor", - "HtmlNbMetadataPreprocessor" -] \ No newline at end of file + "HtmlNbMetadataPreprocessor", +] diff --git a/src/pretty_jupyter/preprocessors/_metadata_preprocessor.py b/src/pretty_jupyter/preprocessors/_metadata_preprocessor.py index e0302cf..7a417a8 100644 --- a/src/pretty_jupyter/preprocessors/_metadata_preprocessor.py +++ b/src/pretty_jupyter/preprocessors/_metadata_preprocessor.py @@ -1,22 +1,26 @@ import copy -import os import warnings -from pretty_jupyter.constants import DEPRECATED_METADATA_MSG_FORMAT, CONFIG_DIR, AVAILABLE_THEMES, METADATA_ERROR_FORMAT from datetime import date, datetime -from nbconvert.preprocessors import Preprocessor -from traitlets import Dict -from pretty_jupyter.utils import merge_dict from pathlib import Path -from cerberus import Validator -import yaml -import nbconvert -from packaging import version -import pkg_resources import jinja2 +import nbconvert +import pkg_resources +import yaml +from cerberus import Validator +from nbconvert.preprocessors import Preprocessor +from packaging import version +from traitlets import Dict -from pretty_jupyter.tokens import read_code_metadata_token, read_markdown_metadata_token +from pretty_jupyter.constants import ( + AVAILABLE_THEMES, + CONFIG_DIR, + DEPRECATED_METADATA_MSG_FORMAT, + METADATA_ERROR_FORMAT, +) from pretty_jupyter.magics import is_jinja_cell +from pretty_jupyter.tokens import read_code_metadata_token, read_markdown_metadata_token +from pretty_jupyter.utils import merge_dict _DEPRECATED_ATTRIBUTES = ["title", "theme", "toc", "code_folding"] _DEPRECATED_ATTRIBUTES_VERSION = "2.0.0" @@ -46,7 +50,7 @@ def __init__(self, **kw): super().__init__(**kw) - self.env = jinja2.Environment(loader=jinja2.FileSystemLoader('.')) + self.env = jinja2.Environment(loader=jinja2.FileSystemLoader(".")) with open(self.nb_spec_path) as file: nb_spec = yaml.safe_load(file.read()) @@ -64,7 +68,12 @@ def preprocess(self, nb, resources): # deprecation warnings to help users fix their error deprecated_attrs = [a for a in _DEPRECATED_ATTRIBUTES if a in nb.metadata] if len(deprecated_attrs) > 0: - warnings.warn(DEPRECATED_METADATA_MSG_FORMAT.format(attributes=", ".join(deprecated_attrs), version=_DEPRECATED_ATTRIBUTES_VERSION), category=DeprecationWarning) + warnings.warn( + DEPRECATED_METADATA_MSG_FORMAT.format( + attributes=", ".join(deprecated_attrs), version=_DEPRECATED_ATTRIBUTES_VERSION + ), + category=DeprecationWarning, + ) # temporarily store nb metadata to resources to be accessible in cell resources["__pj_metadata"] = nb.metadata.get("pj_metadata", {}) @@ -87,7 +96,7 @@ def preprocess_cell(self, cell, resources, index): cell, resources = self._preprocess_markdown_cell(cell, resources, index) if cell.cell_type == "code": cell, resources = self._preprocess_code_cell(cell, resources, index) - + return cell, resources def _synchronize_notebook_metadata(self, cell, resources): @@ -97,12 +106,17 @@ def _synchronize_notebook_metadata(self, cell, resources): try: src_metadata = yaml.safe_load(cell.source) except Exception as exc: - raise ValueError("An error happend when trying to parse first cell of the notebook with type raw.", exc) + raise ValueError( + "An error happend when trying to parse first cell of the notebook with type raw.", + exc, + ) remove_cell_input(cell) if len(nb_metadata) > 0 and len(src_metadata) > 0: - warnings.warn("Notebook-level metadata are defined both in the source and in the notebook's metadata. Please remove one of them.") + warnings.warn( + "Notebook-level metadata are defined both in the source and in the notebook's metadata. Please remove one of them." + ) metadata = src_metadata if len(src_metadata) > 0 else nb_metadata @@ -121,8 +135,12 @@ def _synchronize_notebook_metadata(self, cell, resources): # run metadata through jinja templating metadata_copy = copy.deepcopy(metadata) - for m_key, m_val in filter(lambda x: x[1] is not None and isinstance(x[1], str), metadata_copy.items()): - metadata[m_key] = self.env.from_string(m_val).render(datetime=datetime, date=date, pj_metadata=metadata_copy) + for m_key, m_val in filter( + lambda x: x[1] is not None and isinstance(x[1], str), metadata_copy.items() + ): + metadata[m_key] = self.env.from_string(m_val).render( + datetime=datetime, date=date, pj_metadata=metadata_copy + ) resources["pj_metadata"] = metadata @@ -179,7 +197,7 @@ def _preprocess_code_cell(self, cell, resources, index): for i, output in reversed(list(enumerate(cell.outputs))): if not is_output_enabled(cell, resources, output): cell.outputs.pop(i) - + return cell, resources @@ -208,8 +226,11 @@ def is_output_enabled(cell, resources, output): def is_stdout(output): return output.output_type == "stream" and output.name == "stdout" + def is_error(output): - return output.output_type == "error" or (output.output_type == "stream" and output.name == "stderr") + return output.output_type == "error" or ( + output.output_type == "stream" and output.name == "stderr" + ) # PRIORITY # cell > notebook-level, stdout > output (similarly stderr) @@ -235,6 +256,7 @@ def is_error(output): return is_enabled + def is_input_enabled(cell, resources): cell_metadata = cell.metadata["pj_metadata"] nb_metadata = resources["pj_metadata"]["output"]["general"] @@ -258,4 +280,4 @@ def get_code_folding_value(cell, resources): if "input_fold" in cell_metadata: code_folding = cell_metadata["input_fold"] value = f"fold-{code_folding}" - return value \ No newline at end of file + return value diff --git a/src/pretty_jupyter/preprocessors/_token_preprocessor.py b/src/pretty_jupyter/preprocessors/_token_preprocessor.py index b87893f..4ef14a7 100644 --- a/src/pretty_jupyter/preprocessors/_token_preprocessor.py +++ b/src/pretty_jupyter/preprocessors/_token_preprocessor.py @@ -6,7 +6,9 @@ import re + from nbconvert.preprocessors import Preprocessor + from pretty_jupyter.constants import HTML_TOKEN_REGEX, TOKEN_SEP from pretty_jupyter.tokens import convert_markdown_tokens_to_html @@ -33,6 +35,7 @@ class TokenPreprocessor(Preprocessor): ``` """ + def preprocess(self, nb, resources): resources["token_sep"] = TOKEN_SEP @@ -50,6 +53,7 @@ class TokenCleaningPreprocessor(Preprocessor): """ formats = ["text/markdown", "text/html"] + def preprocess_cell(self, cell, resources, index): if cell.cell_type == "code": for i, output in enumerate(cell.outputs): diff --git a/src/pretty_jupyter/testing.py b/src/pretty_jupyter/testing.py index 04237a7..c242d90 100644 --- a/src/pretty_jupyter/testing.py +++ b/src/pretty_jupyter/testing.py @@ -13,4 +13,4 @@ def _get_y_location(driver): def is_visible(element, driver, epsilon_px=10): - return abs(element.location["y"] - _get_y_location(driver)) < epsilon_px \ No newline at end of file + return abs(element.location["y"] - _get_y_location(driver)) < epsilon_px diff --git a/src/pretty_jupyter/tokens.py b/src/pretty_jupyter/tokens.py index 3cd2793..ff955f2 100644 --- a/src/pretty_jupyter/tokens.py +++ b/src/pretty_jupyter/tokens.py @@ -1,6 +1,14 @@ -import yaml import re -from pretty_jupyter.constants import CODE_METADATA_TOKEN_REGEX, MARKDOWN_METADATA_TOKEN_REGEX, MARKDOWN_TOKEN_REGEX, HTML_TOKEN_FORMAT, TOKEN_SEP + +import yaml + +from pretty_jupyter.constants import ( + CODE_METADATA_TOKEN_REGEX, + HTML_TOKEN_FORMAT, + MARKDOWN_METADATA_TOKEN_REGEX, + MARKDOWN_TOKEN_REGEX, + TOKEN_SEP, +) def convert_markdown_tokens_to_html(input_str: str) -> str: @@ -30,7 +38,7 @@ def convert_markdown_tokens_to_html(input_str: str) -> str: html = HTML_TOKEN_FORMAT.format(tokens=token_str) # replace the md tokens by html tokens - markdown = line[result.span()[0]:result.span()[1]] + markdown = line[result.span()[0] : result.span()[1]] line = line.replace(markdown, html) all_lines.append(line) @@ -38,6 +46,7 @@ def convert_markdown_tokens_to_html(input_str: str) -> str: output = "\n".join(all_lines) return output + def read_code_metadata_token(input_line: str) -> dict: # parse token result = re.search(CODE_METADATA_TOKEN_REGEX, input_line) @@ -50,6 +59,7 @@ def read_code_metadata_token(input_line: str) -> dict: return yaml.safe_load(result.groups()[0]) + def read_markdown_metadata_token(input_line: str): result = re.search(MARKDOWN_METADATA_TOKEN_REGEX, input_line) @@ -59,4 +69,4 @@ def read_markdown_metadata_token(input_line: str): if len(result.groups()) == 0: return None - return yaml.safe_load(result.groups()[0]) \ No newline at end of file + return yaml.safe_load(result.groups()[0]) diff --git a/src/pretty_jupyter/utils.py b/src/pretty_jupyter/utils.py index 4518d55..b2cad00 100644 --- a/src/pretty_jupyter/utils.py +++ b/src/pretty_jupyter/utils.py @@ -16,6 +16,7 @@ def merge_dict(main_dict: dict, other_dict: dict): return other_dict + def _update_dict(dict_, override_dict): for key, val in override_dict.items(): if isinstance(val, dict): diff --git a/tests/test_magics/test_magics.py b/tests/test_magics/test_magics.py index 066b996..815d974 100644 --- a/tests/test_magics/test_magics.py +++ b/tests/test_magics/test_magics.py @@ -1,22 +1,20 @@ from unittest.mock import MagicMock, PropertyMock + from pretty_jupyter.magics import JinjaMagics + def test_jinja(): shell_mock = MagicMock(name="shell", spec=["user_ns", "user_ns_hidden"]) - type(shell_mock).user_ns = PropertyMock( - name="user_ns", - return_value={"a": 15} - ) + type(shell_mock).user_ns = PropertyMock(name="user_ns", return_value={"a": 15}) jinja_magics = JinjaMagics(shell_mock) with open("tests/test_magics/fixture/input_jmarkdown.md") as file: input_str = file.read() actual_str = jinja_magics.jinja( - line=input_str.splitlines()[0][7:], - cell="\n".join(input_str.splitlines()[1:]) - ).data + line=input_str.splitlines()[0][7:], cell="\n".join(input_str.splitlines()[1:]) + ).data with open("tests/test_magics/fixture/expected_jmarkdown.md") as file: expected_str = file.read() - assert actual_str == expected_str, "Expected string is different than the actual." \ No newline at end of file + assert actual_str == expected_str, "Expected string is different than the actual." diff --git a/tests/test_notebooks/conftest.py b/tests/test_notebooks/conftest.py index 9a403d4..dd5e210 100644 --- a/tests/test_notebooks/conftest.py +++ b/tests/test_notebooks/conftest.py @@ -1,12 +1,13 @@ import os +from pathlib import Path + +import pkg_resources import pytest +import selenium.webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.core.utils import ChromeType -import selenium.webdriver -import pkg_resources -from pathlib import Path @pytest.fixture() @@ -21,7 +22,7 @@ def driver(): "--ignore-certificate-errors", "--disable-extensions", "--no-sandbox", - "--disable-dev-shm-usage" + "--disable-dev-shm-usage", ] for option in options: chrome_options.add_argument(option) @@ -32,18 +33,22 @@ def driver(): driver.close() + @pytest.fixture def fixture_dir(): return "tests/test_notebooks/fixture" + @pytest.fixture def templates_path(): return str(Path(pkg_resources.resource_filename("pretty_jupyter", "templates")).as_posix()) + @pytest.fixture def input_path(): raise NotImplementedError() + @pytest.fixture def out_path(tmpdir, input_path): path = str((Path(tmpdir) / f"{Path(input_path).stem}.html").absolute()) @@ -53,6 +58,7 @@ def out_path(tmpdir, input_path): if os.path.exists(path): os.remove(path) + @pytest.fixture def page_url(out_path): - return os.path.normpath(f"file:/{out_path}") \ No newline at end of file + return os.path.normpath(f"file:/{out_path}") diff --git a/tests/test_notebooks/test_basic.py b/tests/test_notebooks/test_basic.py index 00a91bf..992d89a 100644 --- a/tests/test_notebooks/test_basic.py +++ b/tests/test_notebooks/test_basic.py @@ -1,10 +1,12 @@ -from click.testing import CliRunner -import pytest -from pretty_jupyter.console import cli import os -from selenium.webdriver.common.by import By import time +import pytest +from click.testing import CliRunner +from selenium.webdriver.common.by import By + +from pretty_jupyter.console import cli + @pytest.fixture def input_path(fixture_dir): @@ -15,7 +17,7 @@ def input_path(fixture_dir): def test_basic(input_path, out_path, page_url, driver): runner = CliRunner() result = runner.invoke(cli, ["nbconvert-dev", input_path, "--out", out_path, "--to", "html"]) - + assert result.exit_code == 0 assert os.path.exists(out_path), "The expected file does not exist." @@ -38,42 +40,65 @@ def test_basic(input_path, out_path, page_url, driver): ########## assert main_content.find_elements(By.XPATH, h1_xpath)[1].text == "Chapter 1: Tabs" - tab_section = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[1] + tab_section = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[1] # select tabs - list_tabs = tab_section.find_elements(By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li") + list_tabs = tab_section.find_elements( + By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li" + ) assert len(list_tabs) == 3, "There should be 3 tabs." assert list_tabs[1].text == "Tab 2", "The name of the second tab should be 'Tab 2'." - - tab_contents = tab_section.find_elements(By.XPATH, """div[contains(@class, 'tab-content')] - /div[contains(@class, 'section') and contains(@class, 'level2') and contains(@class, 'tab-pane')]""") + tab_contents = tab_section.find_elements( + By.XPATH, + """div[contains(@class, 'tab-content')] + /div[contains(@class, 'section') and contains(@class, 'level2') and contains(@class, 'tab-pane')]""", + ) assert len(tab_contents) == 3, "There should be 3 tab contents for the tabs." - assert "active" in tab_contents[0].get_attribute("class"), "First tab should be active at the beginning." + assert "active" in tab_contents[0].get_attribute( + "class" + ), "First tab should be active at the beginning." # click on the second tab list_tabs[1].click() - assert "active" in tab_contents[1].get_attribute("class"), "Second tab should be active after clicking on it." + assert "active" in tab_contents[1].get_attribute( + "class" + ), "Second tab should be active after clicking on it." # check that the maths rendered properly # math needs time to render time.sleep(2) - assert len(tab_contents[1].find_elements(By.XPATH, ".//span[contains(@class, 'MJXc-display')]")) > 0, "Math didn't render." + assert ( + len(tab_contents[1].find_elements(By.XPATH, ".//span[contains(@class, 'MJXc-display')]")) + > 0 + ), "Math didn't render." # check that the maths rendered properly - math_text = tab_contents[1].find_elements(By.XPATH, ".//script[contains(@type, 'math/tex')]")[1].get_attribute("innerHTML") + math_text = ( + tab_contents[1] + .find_elements(By.XPATH, ".//script[contains(@type, 'math/tex')]")[1] + .get_attribute("innerHTML") + ) assert math_text == r"a \cdot a^2 = \frac{a^5}{a^2} = a^3 = 125" - + ################ # CODE FOLDING # ################ - jmd_section = driver.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[2] + jmd_section = driver.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[2] - code_div = jmd_section.find_elements(By.XPATH, "div[contains(@class, 'py-code-collapse') and contains(@class, 'collapse')]")[0] - assert len(code_div.find_elements(By.XPATH, "div[contains(@class, 'pj-input')]//pre/span")) > 0, "Code highlighting does not work." + code_div = jmd_section.find_elements( + By.XPATH, "div[contains(@class, 'py-code-collapse') and contains(@class, 'collapse')]" + )[0] + assert ( + len(code_div.find_elements(By.XPATH, "div[contains(@class, 'pj-input')]//pre/span")) > 0 + ), "Code highlighting does not work." ##################### # TABLE OF CONTENTS # @@ -104,10 +129,16 @@ def get_y_location(driver): tab_section_title = tab_section.find_element(By.XPATH, "h1") epsilon_px = 10 - assert abs(jmd_title.location["y"] - get_y_location(driver)) < epsilon_px, "Chapter 2: Jinja Markdown must be visible after clicking on it in TOC." - assert tab_section_title.location["y"] < get_y_location(driver), "Chapter 1: Tabs cannot be visible after clicking on chapter 2." + assert ( + abs(jmd_title.location["y"] - get_y_location(driver)) < epsilon_px + ), "Chapter 2: Jinja Markdown must be visible after clicking on it in TOC." + assert tab_section_title.location["y"] < get_y_location( + driver + ), "Chapter 1: Tabs cannot be visible after clicking on chapter 2." # go to Chapter 1 toc_first_level[1].find_element(By.XPATH, "li").click() time.sleep(2) - assert abs(tab_section_title.location["y"] - get_y_location(driver)) < epsilon_px, "Chapter 1: Tabs must be visible after clicking on chapter 1." \ No newline at end of file + assert ( + abs(tab_section_title.location["y"] - get_y_location(driver)) < epsilon_px + ), "Chapter 1: Tabs must be visible after clicking on chapter 1." diff --git a/tests/test_notebooks/test_cell_metadata.py b/tests/test_notebooks/test_cell_metadata.py index 629c74c..4d9819e 100644 --- a/tests/test_notebooks/test_cell_metadata.py +++ b/tests/test_notebooks/test_cell_metadata.py @@ -1,10 +1,10 @@ -import time -import pytest import os -from selenium.webdriver.common.by import By -from pathlib import Path -import sys import subprocess +import sys +from pathlib import Path + +import pytest +from selenium.webdriver.common.by import By @pytest.fixture @@ -16,23 +16,30 @@ def input_path(fixture_dir): def test_cell_metadata(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --ExecutePreprocessor.allow_errors=True --output-dir=\"{out_dir}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --ExecutePreprocessor.allow_errors=True --output-dir="{out_dir}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) main_content = driver.find_element(By.XPATH, "//*[@id = 'main-content']") - code_folding_inputs = main_content.find_elements(By.XPATH, "//*[contains(@class, 'py-code-collapse')]") + code_folding_inputs = main_content.find_elements( + By.XPATH, "//*[contains(@class, 'py-code-collapse')]" + ) assert len(code_folding_inputs) == 4, "There must be 4 code folding input cells." - assert "input: true, input_fold: show" in code_folding_inputs[0].get_attribute("innerHTML"), "First jinja markdown cell should have input: true and input_fold: show." - jmd_button = code_folding_inputs[0].find_element(By.XPATH, "preceding-sibling::div[@class = 'row'][1]//button/span") + assert "input: true, input_fold: show" in code_folding_inputs[0].get_attribute( + "innerHTML" + ), "First jinja markdown cell should have input: true and input_fold: show." + jmd_button = code_folding_inputs[0].find_element( + By.XPATH, "preceding-sibling::div[@class = 'row'][1]//button/span" + ) assert jmd_button.get_attribute("innerHTML") == "Hide", "The first jmd button should be shown." error_output = main_content.find_elements(By.XPATH, "//pre[contains(@class, 'bg-danger')]") - assert len(error_output) == 1, "The generated page should have one error output right in the end." - - - - - + assert ( + len(error_output) == 1 + ), "The generated page should have one error output right in the end." diff --git a/tests/test_notebooks/test_lang.py b/tests/test_notebooks/test_lang.py index cf16859..3dcd352 100644 --- a/tests/test_notebooks/test_lang.py +++ b/tests/test_notebooks/test_lang.py @@ -1,10 +1,11 @@ +import os +import subprocess +import sys import time +from pathlib import Path + import pytest -import os from selenium.webdriver.common.by import By -from pathlib import Path -import sys -import subprocess @pytest.fixture @@ -16,7 +17,11 @@ def input_path(fixture_dir): def test_lang(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir=\"{out_dir}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir="{out_dir}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) @@ -27,21 +32,31 @@ def test_lang(templates_path, input_path, out_path, page_url, driver): assert header.find_element(By.XPATH, "h1").get_attribute("innerHTML") == "Čeština" header = main_content.find_element(By.XPATH, "//div[@id = 'Hlavička-s-českými-znaky-O_O-1']") - assert header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Hlavička s českými znaky" + assert ( + header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Hlavička s českými znaky" + ) header = main_content.find_element(By.XPATH, "//div[@id = 'Hlavička-s-českými-znaky-O_O-2']") - assert header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Hlavička s českými znaky" + assert ( + header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Hlavička s českými znaky" + ) # note: 23 is encoded hash character - tab = main_content.find_element(By.XPATH, "//a[@href = '#Český-Tab-s-divnými-znaky--_23-O_O-2']/..") - tab_sec = main_content.find_element(By.XPATH, "//div[@id = 'Český-Tab-s-divnými-znaky--_23-O_O-2']") + tab = main_content.find_element( + By.XPATH, "//a[@href = '#Český-Tab-s-divnými-znaky--_23-O_O-2']/.." + ) + tab_sec = main_content.find_element( + By.XPATH, "//div[@id = 'Český-Tab-s-divnými-znaky--_23-O_O-2']" + ) assert "active" not in main_content.get_attribute("class") tab.click() time.sleep(0.5) assert "active" in tab_sec.get_attribute("class") header = main_content.find_element(By.XPATH, "//div[@id = 'české-id']") - assert header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Unikátní česká hlavička" + assert ( + header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Unikátní česká hlavička" + ) # RUSSIAN header = main_content.find_element(By.XPATH, "//div[@id = 'ру́сский-язы́к']") @@ -62,5 +77,3 @@ def test_lang(templates_path, input_path, out_path, page_url, driver): header = main_content.find_element(By.XPATH, "//div[@id = 'Российский-ID']") assert header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Уникальный заголовок" - - diff --git a/tests/test_notebooks/test_tabset.py b/tests/test_notebooks/test_tabset.py index 4766df1..d8a3672 100644 --- a/tests/test_notebooks/test_tabset.py +++ b/tests/test_notebooks/test_tabset.py @@ -1,10 +1,11 @@ +import os +import subprocess +import sys import time +from pathlib import Path + import pytest -import os from selenium.webdriver.common.by import By -from pathlib import Path -import sys -import subprocess from pretty_jupyter.testing import is_visible @@ -18,7 +19,11 @@ def input_path(fixture_dir): def test_tabset(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir=\"{out_dir}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir="{out_dir}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) @@ -27,11 +32,18 @@ def test_tabset(templates_path, input_path, out_path, page_url, driver): ################ # BASIC TABSET # ################ - first_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[0] - - list_tabs = first_chapter.find_elements(By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li") - tab_contents = first_chapter.find_elements(By.XPATH, """div[contains(@class, 'tab-content')] - /div[contains(@class, 'section') and contains(@class, 'level2') and contains(@class, 'tab-pane')]""") + first_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[0] + + list_tabs = first_chapter.find_elements( + By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li" + ) + tab_contents = first_chapter.find_elements( + By.XPATH, + """div[contains(@class, 'tab-content')] + /div[contains(@class, 'section') and contains(@class, 'level2') and contains(@class, 'tab-pane')]""", + ) assert len(list_tabs) == 2, "There should be 2 tabs." assert list_tabs[1].text == "Tab 2", "The name of the second tab should be 'Tab 2'." @@ -43,31 +55,46 @@ def test_tabset(templates_path, input_path, out_path, page_url, driver): # click on the first tab list_tabs[0].click() time.sleep(0.5) - assert "active" in list_tabs[0].get_attribute("class") and "active" not in list_tabs[1].get_attribute("class"), "Now tab 2 should be active" + assert "active" in list_tabs[0].get_attribute("class") and "active" not in list_tabs[ + 1 + ].get_attribute("class"), "Now tab 2 should be active" assert tab_contents[0].is_displayed(), "Tab 1 should be displayed right now." assert not tab_contents[1].is_displayed(), "Tab 2 shouldn't be displayed right now." - ################# # NESTED TABSET # ################# - second_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[1] - - tab1_list = second_chapter.find_elements(By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li") - tab1_contents = second_chapter.find_elements(By.XPATH, """div[contains(@class, 'tab-content')] - /div[contains(@class, 'section') and contains(@class, 'level2') and contains(@class, 'tab-pane')]""") + second_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[1] + + tab1_list = second_chapter.find_elements( + By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li" + ) + tab1_contents = second_chapter.find_elements( + By.XPATH, + """div[contains(@class, 'tab-content')] + /div[contains(@class, 'section') and contains(@class, 'level2') and contains(@class, 'tab-pane')]""", + ) assert len(tab1_list) == 2, "The first level of nested tabset has two options." assert tab1_contents[0].is_displayed(), "First tab on the second level is currently shown." - assert not tab1_contents[1].is_displayed(), "Second tab on the first level is currently not shown." - + assert not tab1_contents[ + 1 + ].is_displayed(), "Second tab on the first level is currently not shown." + tab1_list[1].click() time.sleep(0.5) assert tab1_contents[1].is_displayed(), "Second tab should be shown." - tab2_list = tab1_contents[1].find_elements(By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li") - tab2_contents = tab1_contents[1].find_elements(By.XPATH, """div[contains(@class, 'tab-content')] - /div[contains(@class, 'section') and contains(@class, 'level3') and contains(@class, 'tab-pane')]""") + tab2_list = tab1_contents[1].find_elements( + By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li" + ) + tab2_contents = tab1_contents[1].find_elements( + By.XPATH, + """div[contains(@class, 'tab-content')] + /div[contains(@class, 'section') and contains(@class, 'level3') and contains(@class, 'tab-pane')]""", + ) assert len(tab2_contents) == 3 and len(tab2_list) == 3 assert tab2_contents[2].is_displayed(), "Tab 23 should be displayed initially." @@ -75,23 +102,33 @@ def test_tabset(templates_path, input_path, out_path, page_url, driver): time.sleep(0.5) assert tab2_contents[0].is_displayed(), "Tab 21 should be displayed now." - ################ # CONTINUATION # ################ - third_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[2] + third_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[2] # two tabsets are there with an invisible title # the third is section after the tabset - tabsets = third_chapter.find_elements(By.XPATH, "div[contains(@class, 'section') and contains(@class, 'level2')]") + tabsets = third_chapter.find_elements( + By.XPATH, "div[contains(@class, 'section') and contains(@class, 'level2')]" + ) continuation1 = third_chapter.find_element(By.XPATH, "//span[@id='continuation1']") continuation2 = third_chapter.find_element(By.XPATH, "//span[@id='continuation2']") - assert len(tabsets) == 3, "There should be 3 sections, where the first two are tabsets and the last is section after it." + assert ( + len(tabsets) == 3 + ), "There should be 3 sections, where the first two are tabsets and the last is section after it." tabset1 = tabsets[0] - list_tabs = tabset1.find_elements(By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li") - tab_contents = tabset1.find_elements(By.XPATH, """div[contains(@class, 'tab-content')] - /div[contains(@class, 'section') and contains(@class, 'level3') and contains(@class, 'tab-pane')]""") + list_tabs = tabset1.find_elements( + By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li" + ) + tab_contents = tabset1.find_elements( + By.XPATH, + """div[contains(@class, 'tab-content')] + /div[contains(@class, 'section') and contains(@class, 'level3') and contains(@class, 'tab-pane')]""", + ) assert tab_contents[0].is_displayed(), "Tab 1 should be displayed at the beginning." assert not tab_contents[1].is_displayed(), "Tab 2 should not be displayed at the beginning." assert continuation1.is_displayed(), "Text between should be visible all the time." @@ -107,19 +144,20 @@ def test_tabset(templates_path, input_path, out_path, page_url, driver): NO_TABSET_METADATA = "{ output: { html: { tabset: false }} }" + def test_no_tabset(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir=\"{out_dir}\" --HtmlNbMetadataPreprocessor.pj_metadata=\"{NO_TABSET_METADATA}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir="{out_dir}" --HtmlNbMetadataPreprocessor.pj_metadata="{NO_TABSET_METADATA}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) - all_tabs = driver.find_elements(By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li") + all_tabs = driver.find_elements( + By.XPATH, "ul[contains(@class, 'nav') and contains(@class, 'nav-pills')]/li" + ) assert len(all_tabs) == 0, "Tabset is turned off but the tabs have been generated." - - - - - - \ No newline at end of file diff --git a/tests/test_notebooks/test_toc.py b/tests/test_notebooks/test_toc.py index 1d327af..f49b38b 100644 --- a/tests/test_notebooks/test_toc.py +++ b/tests/test_notebooks/test_toc.py @@ -1,12 +1,13 @@ +import os +import subprocess +import sys import time +from pathlib import Path + import pytest -import os from selenium.webdriver.common.by import By -from pathlib import Path -import sys -import subprocess -from pretty_jupyter.constants import TOKEN_SEP +from pretty_jupyter.constants import TOKEN_SEP from pretty_jupyter.testing import is_visible @@ -19,7 +20,11 @@ def input_path(fixture_dir): def test_toc(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir=\"{out_dir}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir="{out_dir}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) @@ -34,54 +39,88 @@ def test_toc(templates_path, input_path, out_path, page_url, driver): # check that first section is visible toc_first_level[0].find_element(By.XPATH, "li").click() time.sleep(2) - first_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[0] + first_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[0] first_header = first_chapter.find_element(By.XPATH, "h1") - assert is_visible(element=first_header, driver=driver), "First chapter should be visible at the beginning." + assert is_visible( + element=first_header, driver=driver + ), "First chapter should be visible at the beginning." # go to second chapter and check that it is visible and the first section is not (should be scrolled away) toc_first_level[1].find_element(By.XPATH, "li").click() time.sleep(2) toc_first_level[1].find_element(By.XPATH, "li").click() time.sleep(2) - second_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]")[1] + second_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]" + )[1] second_header = second_chapter.find_element(By.XPATH, "h1") - assert is_visible(element=second_header, driver=driver), "Second chapter should be visible after scrolling to it." - assert not is_visible(element=first_header, driver=driver), "First chapter shouldn't be visible because we scrolled to the second one." + assert is_visible( + element=second_header, driver=driver + ), "Second chapter should be visible after scrolling to it." + assert not is_visible( + element=first_header, driver=driver + ), "First chapter shouldn't be visible because we scrolled to the second one." # YAML config is one-line because windows cmd probably doesn't support multiline NOT_GENERATED_METADATA = "{ output: { html: { toc: false } } }" + def test_toc_not_generated(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs=\"{templates_path}\" --execute --output-dir=\"{out_dir}\" --HtmlNbMetadataPreprocessor.pj_metadata=\"{NOT_GENERATED_METADATA}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs="{templates_path}" --execute --output-dir="{out_dir}" --HtmlNbMetadataPreprocessor.pj_metadata="{NOT_GENERATED_METADATA}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) toc = driver.find_elements(By.XPATH, "//div[@id='TOC']") assert len(toc) == 0, "TOC shouldnt've been generated" + NUMBER_SECTIONS_METADATA = "{ output: { html: { number_sections: true }} }" + def test_toc_number_sections(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs=\"{templates_path}\" --execute --output-dir=\"{out_dir}\" --HtmlNbMetadataPreprocessor.pj_metadata=\"{NUMBER_SECTIONS_METADATA}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs="{templates_path}" --execute --output-dir="{out_dir}" --HtmlNbMetadataPreprocessor.pj_metadata="{NUMBER_SECTIONS_METADATA}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) main_content = driver.find_element(By.XPATH, "//*[@id = 'main-content']") - second_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]/h1")[1] + second_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]/h1" + )[1] assert second_chapter.get_attribute("innerHTML") == "2. Level 1" - third_chapter = main_content.find_elements(By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]/h1")[2] - assert third_chapter.get_attribute("innerHTML") == "Level 1", "The third Level 1 has been marked as unnumbered." + third_chapter = main_content.find_elements( + By.XPATH, "//div[contains(@class, 'section') and contains(@class, 'level1')]/h1" + )[2] + assert ( + third_chapter.get_attribute("innerHTML") == "Level 1" + ), "The third Level 1 has been marked as unnumbered." # test that Level 3s are numbered and they follow the proper naming convention - assert main_content.find_element(By.XPATH, f"//*[@id = 'Level-2-O_O-1']/h2").get_attribute("innerHTML") == "1.1. Level 2" - assert main_content.find_element(By.XPATH, f"//*[@id = 'Level-2-O_O-2']/h2").get_attribute("innerHTML") == "2.1. Level 2" - - - + assert ( + main_content.find_element(By.XPATH, f"//*[@id = 'Level-2-O_O-1']/h2").get_attribute( + "innerHTML" + ) + == "1.1. Level 2" + ) + assert ( + main_content.find_element(By.XPATH, f"//*[@id = 'Level-2-O_O-2']/h2").get_attribute( + "innerHTML" + ) + == "2.1. Level 2" + ) diff --git a/tests/test_notebooks/test_tokens.py b/tests/test_notebooks/test_tokens.py index bcb7292..3982900 100644 --- a/tests/test_notebooks/test_tokens.py +++ b/tests/test_notebooks/test_tokens.py @@ -1,11 +1,12 @@ -import pytest import os -from selenium.webdriver.common.by import By -from pathlib import Path -import sys import subprocess +import sys +from pathlib import Path + import nbconvert +import pytest from packaging import version +from selenium.webdriver.common.by import By @pytest.fixture @@ -17,7 +18,11 @@ def input_path(fixture_dir): def test_tokens(templates_path, input_path, out_path, page_url, driver): out_dir = os.path.dirname(out_path) python_path = sys.executable - retval = subprocess.run(f"{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir=\"{out_dir}\"", check=True, shell=True) + retval = subprocess.run( + f'{python_path} -m jupyter nbconvert --to html --template pj {input_path} --TemplateExporter.extra_template_basedirs={templates_path} --execute --output-dir="{out_dir}"', + check=True, + shell=True, + ) assert retval.returncode == 0, "jupyter nbconvert command ended up with a failure" driver.get(page_url) @@ -35,7 +40,9 @@ def test_tokens(templates_path, input_path, out_path, page_url, driver): assert "red" in header.get_attribute("class") header = main_content.find_element(By.XPATH, "//div[@id = 'header-whitespaces-id']") - assert header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Header with whitespaces" + assert ( + header.find_element(By.XPATH, "h2").get_attribute("innerHTML") == "Header with whitespaces" + ) assert "grey" in header.get_attribute("class") ################## @@ -50,16 +57,14 @@ def test_tokens(templates_path, input_path, out_path, page_url, driver): # PANDAS TABLE # ################ table = main_content.find_element(By.XPATH, "//table[@id = 'ptable-id']") - assert "red" in table.get_attribute("class") and "pj-table-ignore" in table.get_attribute("class") + assert "red" in table.get_attribute("class") and "pj-table-ignore" in table.get_attribute( + "class" + ) ################# # HTML ELEMENTS # ################# paragraph = main_content.find_element(By.XPATH, "//p[@id = 'paragraph-id']") - assert "white-letter" in paragraph.get_attribute("class") and "red" in paragraph.get_attribute("class") - - - - - - + assert "white-letter" in paragraph.get_attribute("class") and "red" in paragraph.get_attribute( + "class" + ) diff --git a/tests/test_preprocessors/test_metadata_preprocessor/conftest.py b/tests/test_preprocessors/test_metadata_preprocessor/conftest.py index 6199193..8de5500 100644 --- a/tests/test_preprocessors/test_metadata_preprocessor/conftest.py +++ b/tests/test_preprocessors/test_metadata_preprocessor/conftest.py @@ -1,8 +1,10 @@ from pathlib import Path from unittest.mock import MagicMock, PropertyMock + import pytest import yaml + @pytest.fixture def fixture_dir(): return "tests/test_preprocessors/test_metadata_preprocessor/fixture" @@ -18,13 +20,12 @@ def raw_cell(fixture_dir): return cell + @pytest.fixture def jmd_cell(fixture_dir): cell = MagicMock(name="jmd_cell") cell.metadata = {} - cell.metadata["pj_metadata"] = { - "input_fold": "disable" - } + cell.metadata["pj_metadata"] = {"input_fold": "disable"} with open(Path(fixture_dir) / "jmd_cell.md") as file: type(cell).source = PropertyMock(return_value=file.read()) type(cell).cell_type = PropertyMock(return_value="code") @@ -33,13 +34,12 @@ def jmd_cell(fixture_dir): output = MagicMock(name="output") output.metadata = {} type(output).output_type = PropertyMock(return_value="execute_result") - type(output).data = { - "text/markdown": [] - } + type(output).data = {"text/markdown": []} type(cell).outputs = PropertyMock(return_value=[output]) return cell + @pytest.fixture def code_cell(fixture_dir): cell = MagicMock(name="code_cell") @@ -85,11 +85,13 @@ def code_cell(fixture_dir): return cell + @pytest.fixture def nb_defaults_path(fixture_dir): return Path(fixture_dir) / "default_nb.yaml" + @pytest.fixture def nb_defaults(nb_defaults_path): with open(nb_defaults_path) as file: - return yaml.safe_load(nb_defaults) \ No newline at end of file + return yaml.safe_load(nb_defaults) diff --git a/tests/test_preprocessors/test_metadata_preprocessor/fixture/code_cell.py b/tests/test_preprocessors/test_metadata_preprocessor/fixture/code_cell.py index 6fa07bf..b3f389e 100644 --- a/tests/test_preprocessors/test_metadata_preprocessor/fixture/code_cell.py +++ b/tests/test_preprocessors/test_metadata_preprocessor/fixture/code_cell.py @@ -1,4 +1,4 @@ # -.-|m { input: true, input_fold: hide, output_error: false } a = 5 b = 3 -c = a + b \ No newline at end of file +c = a + b diff --git a/tests/test_preprocessors/test_metadata_preprocessor/test_metadata_preprocessor.py b/tests/test_preprocessors/test_metadata_preprocessor/test_metadata_preprocessor.py index 9467a9e..1207c6c 100644 --- a/tests/test_preprocessors/test_metadata_preprocessor/test_metadata_preprocessor.py +++ b/tests/test_preprocessors/test_metadata_preprocessor/test_metadata_preprocessor.py @@ -1,17 +1,20 @@ from pathlib import Path -import yaml -import pytest from unittest.mock import MagicMock -from pretty_jupyter.preprocessors import NbMetadataPreprocessor, HtmlNbMetadataPreprocessor + import nbconvert +import pytest +import yaml from packaging import version +from pretty_jupyter.preprocessors import HtmlNbMetadataPreprocessor, NbMetadataPreprocessor + @pytest.fixture def expected_raw_cell(fixture_dir): with open(Path(fixture_dir) / "expected_test_raw.yaml") as file: return yaml.safe_load(file.read()) + def test_preprocess_raw_cell(raw_cell, nb_defaults_path, expected_raw_cell): NbMetadataPreprocessor.nb_defaults_path = nb_defaults_path override_config = "{ output: { general: { input: false } } }" @@ -41,7 +44,12 @@ def test_preprocess_jmd_cell(jmd_cell, nb_defaults_path): with pytest.warns(UserWarning): cell, resources = preprocessor.preprocess_cell(jmd_cell, resources, index=3) - assert cell.metadata["pj_metadata"] == {'input': False, 'output': False, 'input_fold': 'fold-show', 'output_error': True} + assert cell.metadata["pj_metadata"] == { + "input": False, + "output": False, + "input_fold": "fold-show", + "output_error": True, + } if version.parse(nbconvert.__version__) >= version.parse("7.0.0"): assert cell.metadata["transient"] == {"remove_source": True} else: @@ -60,7 +68,3 @@ def test_preprocess_code_cell(code_cell, nb_defaults_path): assert cell.transient is None assert len(cell.outputs) == 1 assert cell.outputs[0].output_type == "execute_result" - - - - diff --git a/tests/test_tokens/conftest.py b/tests/test_tokens/conftest.py index 6df54c7..0bbcaea 100644 --- a/tests/test_tokens/conftest.py +++ b/tests/test_tokens/conftest.py @@ -6,7 +6,8 @@ def input_str(): with open("tests/test_tokens/fixture/input.md", "r") as file: return file.read() + @pytest.fixture def expected_str(): with open("tests/test_tokens/fixture/expected.md", "r") as file: - return file.read() \ No newline at end of file + return file.read() diff --git a/tests/test_tokens/test_tokens.py b/tests/test_tokens/test_tokens.py index cd9e3fe..231968a 100644 --- a/tests/test_tokens/test_tokens.py +++ b/tests/test_tokens/test_tokens.py @@ -1,6 +1,7 @@ from pretty_jupyter.tokens import convert_markdown_tokens_to_html + def test_convert_markdown_tokens_to_html(input_str, expected_str): actual_str = convert_markdown_tokens_to_html(input_str) - assert actual_str == expected_str, "Expected string is different than the actual." \ No newline at end of file + assert actual_str == expected_str, "Expected string is different than the actual."