Skip to content

Commit

Permalink
Add the MkDocs-Test framework (with pytest)
Browse files Browse the repository at this point in the history
  - added tests for the simple, material and superfences test cases
  • Loading branch information
Laurent Franceschetti committed Nov 2, 2024
1 parent 97db6aa commit 18b0c2e
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ venv.bak/

# mkdocs documentation
site/
__test__/

# mypy
.mypy_cache/
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from setuptools import setup, find_packages


VERSION = '1.1.2'
VERSION = '1.2.0'

# required if you want to run tests
# pip install 'mkdocs-mermaid2-plugin[test]'
TEST_REQUIRE = ['mkdocs-material']
TEST_REQUIRE = ['mkdocs-material', 'mkdocs-macros-test', 'requests-html',
'packaging']


def readme():
Expand Down
Empty file added test/__init__.py
Empty file.
137 changes: 137 additions & 0 deletions test/fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
Specific for MkDocs Projects
(C) Laurent Franceschetti 2024
"""

import re
import requests

from super_collections import SuperDict
from mkdocs_test import DocProject, MkDocsPage
from packaging import version


URL_PATTERN = r'https://[^\s"]+'
VERSION_PATTERN = r"@(\d+\.\d+\.\d+)"

def extract_url(s:str) -> str|None:
"Extract the first url from a string"
match = re.search(URL_PATTERN, s)
if match:
url = match.group(0)
return url
else:
return None


def get_last_version(mermaid_url:str) -> version.Version:
"Get the last version from a mermaid url"
response = requests.get(mermaid_url)
version_no = response.url.split('@')[1].split('/')[0]
return version.parse(version_no)



def assert_string_contains(txt:str, items:list) -> bool:
"""
Find items in a string.
All items must be present (AND); however, if one item is an
iterable-non-string, then each subitem will an OR.
['foo', 'bar', ('baz', 'barbaz')] -> 'foo' AND 'bar' AND ('baz' OR 'barbaz')
"""
for item in items:
if isinstance(item, str):
assert item in txt, f"'{item}' not found in:\n{txt}!"
else:
assert any(subitem in txt for subitem in item), f"None of {item} found in:\n{txt}!"



class Mermaid2Page(MkDocsPage):
"Specific for Mermaid2"

LIB_VERSION_CHANGE = version.parse('10')


@property
def mermaid_script(self) -> str:
"""
Get the call to the mermaid javascript library
(in the two versions, pre- and post- 10.0).
Performs checks and initializes the js_version property.
For testing purposes, this function contains INVARIANTS
(principles that should remain the same in time, and
across different configurations).
"""
try:
self._mermaid_script
except AttributeError:
mermaid_script = self.find('script', type="module")
if mermaid_script:
# Version >= 10
script_txt = mermaid_script.string
FIND_TEXT = ["import mermaid", "esm.min.mjs",
('mermaid.initialize', 'mermaidConfig')]
assert_string_contains(script_txt, FIND_TEXT)
# Get the version number from the string; if not, from the Mermaid url:
mermaid_url = extract_url(script_txt)
assert mermaid_url, "No URL found for mermaid"
assert 'mermaid' in mermaid_url, f"Error in url: {mermaid_url}"
self._js_version = get_last_version(mermaid_url)
return script_txt
else:
# Version < 10
# Find the script calling the library
mermaid_script = self.find('script', src=lambda x: x and 'mermaid' in x)
assert mermaid_script, "Couldn't find the < 10 Mermaid library!"
src = mermaid_script.get('src')
match = re.search(VERSION_PATTERN, src)
# If < 10, it demands that the script version be explicit (x.y.z)
if match:
version_str = match.group(1)
version = version.Version(version_str)
self._js_version = version
else:
raise ValueError("No version number with < 10 Mermaid library")
return mermaid_script.string



@property
def js_version(self) -> version.Version:
"""
Get the version of the javascript library, while performing checks.
"""
try:
return self._js_version
except AttributeError:
# Initialize
self.mermaid_script
return self._js_version


class Mermaid2DocProject(DocProject):
"Specific for MkDocs-Macros"

@property
def plugin_version(self) -> version.Version|None:
"Get the Mermaid2 javascript library version from the plugin"
plugin = self.get_plugin('mermaid2')
try:
return version.Version(plugin.version)
except AttributeError:
pass


@property
def pages(self) -> dict[Mermaid2Page]:
"List of pages"
pages = super().pages
return {key: Mermaid2Page(value) for key, value in pages.items()}



Empty file added test/material/__init__.py
Empty file.
1 change: 1 addition & 0 deletions test/material/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ nav:

plugins:
- search
- test
- mermaid2:
version: '10.1.0'

82 changes: 82 additions & 0 deletions test/material/test_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Testing the project
Material theme, otherwise normal
(C) Laurent Franceschetti 2024
"""


import pytest

from mkdocs_test import DocProject

from test.fixture import Mermaid2DocProject




def test_pages():
"Test this project"



# ----------------
# First page
# ----------------
project = Mermaid2DocProject(".")
build_result = project.build(strict=False)
# did not fail
return_code = project.build_result.returncode
assert not return_code, f"Build returned with {return_code} {build_result.args})"



# ----------------
# First page
# ----------------
page = project.get_page('index')



# find the diagrams; they are divs
diagrams = page.find_all('div', class_='mermaid')
assert len(diagrams) == 2
assert diagrams[0].string.startswith('graph TD')
assert diagrams[1].string.startswith('gitGraph')

# use the fixture:
version = page.js_version
assert version == project.plugin_version
print("Version:", version)
assert version
assert version > page.LIB_VERSION_CHANGE

# find the piece of Python code
my_code = page.find('code', class_='language-python')
assert 'page.read()' in my_code.string


# ----------------
# Second page
# ----------------
page = project.get_page('second')

# find the diagrams; they are divs:
diagrams = page.find_all('div', class_='mermaid')
assert len(diagrams) == 3
wrong_diagram = diagrams[0].string # this one is wrong
assert wrong_diagram.startswith('graph FG')
assert "A[Client]" in wrong_diagram
# the other two are correct:
assert diagrams[1].string.startswith('graph TD')
assert diagrams[2].string.startswith('graph TD')

# check that the second page has same version as first
assert page.js_version == version






Empty file added test/simple/__init__.py
Empty file.
1 change: 1 addition & 0 deletions test/simple/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ nav:

plugins:
- search
- test
- mermaid2


69 changes: 69 additions & 0 deletions test/simple/test_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Testing the project
(C) Laurent Franceschetti 2024
"""


import pytest

from mkdocs_test import DocProject


from test.fixture import Mermaid2DocProject




def test_pages():
"Test this project"

FIND_TEXT = ["import mermaid", "mermaid.initialize", "esm.min.mjs"]

# ----------------
# First page
# ----------------
project = Mermaid2DocProject(".")
build_result = project.build(strict=False)
# did not fail
return_code = project.build_result.returncode
assert not return_code, f"Build returned with {return_code} {build_result.args})"

# ----------------
# First page
# ----------------
page = project.get_page('index')

# find the diagrams; they are divs
diagrams = page.find_all('div', class_='mermaid')
assert len(diagrams) == 2

# find the mermaid script
mermaid_script = page.find('script', type="module")
for text in FIND_TEXT:
assert text in mermaid_script.string, f"'{text}' not found!"

# use the fixture:
version = page.js_version
print("Version:", version)
assert version
assert version > page.LIB_VERSION_CHANGE



# ----------------
# Second page
# ----------------
# there is intentionally an error (`foo` does not exist)
page = project.get_page('second')
diagrams = page.find_all('div', class_='mermaid')
assert len(diagrams) == 3

# with open('output_file.html', 'w') as f:
# f.write(page.html)






Empty file added test/superfences/__init__.py
Empty file.
1 change: 1 addition & 0 deletions test/superfences/docs/second.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ Click on the arrow, to reveal the diagram,
graph TD
A[Client] --> B[Load Balancer]
```

This is additional text.
9 changes: 3 additions & 6 deletions test/superfences/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
site_name: Mermaid test (SuperFences)
site_name: Mermaid test (SuperFences) + custom loader
site_description: Test for mermaid
docs_dir: docs # indispensable or readthedocs will fail
theme:
name: material
language: en
font: "Open Sans"


nav:
Expand All @@ -13,6 +12,7 @@ nav:

plugins:
- search
- test
- mermaid2:
custom_loader: true
arguments:
Expand All @@ -28,7 +28,4 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:mermaid2.fence_mermaid_custom
# format: !!python/name:pymdownx.superfences.fence_div_format
extra_javascript:
# - https://unpkg.com/mermaid@8.6.4/dist/mermaid.min.js
- js/loader.js
# format: !!python/name:pymdownx.superfences.fence_div_format
Loading

0 comments on commit 18b0c2e

Please sign in to comment.