Testing your renderer#

A renderer is a function from source code to output, which makes it easy to test: render a known calculation and compare the result.

Use the full pipeline#

Test through HandCalcs, with an explicit namespace so the test does not depend on running the code:

from handcalcs import HandCalcs
from my_renderers import UnicodeRenderer


def render(source, **values):
    return HandCalcs(UnicodeRenderer())(source, values)


def test_multiplication_uses_centred_dot():
    out = render("b = a * 3", a=2, b=6)
    assert out.strip() == "b = a · 3 = 2 · 3 = 6"


def test_subscripts():
    assert render("x_1 = 2", x_1=2).strip() == "x₁ = 2"

Passing the namespace also lets you test values that are awkward to produce by running code, such as large or negative numbers, or NoValue (leave the name out).

Isolate the configuration#

The global configuration seeds every render, and a developer’s saved config would change your results. Point HANDCALCS_CONFIG at a file that does not exist and reset the in-memory config around each test:

import pytest
from handcalcs import config


@pytest.fixture(autouse=True)
def isolated_config(tmp_path, monkeypatch):
    monkeypatch.setenv("HANDCALCS_CONFIG", str(tmp_path / "config.json"))
    config.reset_config()
    yield
    config.reset_config()

handcalcs’ own tests do the same (see tests/test_jupyter.py for the pattern).

Golden files#

For a renderer with a lot of layout, compare against stored output (“golden” files) rather than writing every expected string by hand. Render the built-in demo, which exercises headings, prose, grids, long equations and nested conditionals:

import pathlib
from handcalcs import HandCalcs

GOLDEN = pathlib.Path(__file__).parent / "golden" / "demo.rst"


def test_demo_matches_golden():
    out = HandCalcs(RstRenderer()).demo()
    assert out == GOLDEN.read_text()

When the output changes on purpose, regenerate the file, read the diff, and commit it.

Validate the format#

Where a validator exists, use it: render, then parse the output with the format’s own tools so a structural mistake fails the test.

Format

Check with

HTML

html.parser, or lxml.html with strict parsing

reST

docutils.core.publish_string(..., settings_overrides={"halt_level": 2})

LaTeX

compile with latexmk in CI (slow; keep it to a few documents)

JSON

json.loads(json.dumps(result)) and a schema

Test rules on their own#

A rule is a plain function, so you can call it with a node and a context:

from handcalcs.parsing.nodes import Name
from handcalcs.renderers.base import BaseRenderContext, RenderContext


def test_subscript_rule():
    ctx = BaseRenderContext(RenderContext(), RenderContext.sparse())
    node = unicode_subscripts(None, Name(identifier="x_1"), ctx)
    assert node.identifier == "x₁"

Next: Publishing your renderer.