Skip to content

Commit 0b0df9a

Browse files
committed
test(keys): lifecycle, placement, hover and export
33 tests. The Python half covers the lifecycle and validation; the render half covers what only the browser can answer. Notable cases, each pinned to a decision rather than to current output: * a bare RGBA key draws NO card — alpha 0 stays transparent, which is the whole reason a triangle or a disc can sit on an image * keys pin to the AXES BOX, not the letterboxed picture. The panel is made much wider than its square image so the two differ, and the key must track imgX. This is the scale bar's rule and the test says so. * hover_only stays dark until the pointer arrives, and a plain key is unaffected by hovering * three export regressions: a hover_only key must appear in an export taken with the pointer away; cold and hovered exports must be byte-identical; and a LONE hover_only key must still export — that last one is the display:none / 0x0-rect trap, which no other key is around to mask. Adds a shared `mount_page` fixture to tests/conftest.py. `interact_page` calls renderFn directly and exposes no mount handle, so it can drive a pointer but cannot export or reach the panels map; these tests need both at once. test_export_png.py and test_texture.py predate it and keep their own local copies.
1 parent 64541b4 commit 0b0df9a

2 files changed

Lines changed: 419 additions & 0 deletions

File tree

anyplotlib/tests/conftest.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,3 +646,74 @@ def _run_bench(page, panel_id, *, n_warmup=3, n_samples=15,
646646
finally:
647647
page.set_default_timeout(30_000) # restore Playwright default
648648

649+
650+
651+
# ---------------------------------------------------------------------------
652+
# Mount-API page fixture
653+
# ---------------------------------------------------------------------------
654+
# `interact_page` above calls renderFn({model, el}) directly, which is right for
655+
# driving widgets but exposes no mount handle — so no exportPNG and no panels
656+
# map. Tests that need both a live pointer AND the public mount API use this.
657+
# test_embed/test_export_png.py and test_plot3d/test_texture.py each predate it
658+
# and keep their own local copies; new tests should use this one.
659+
660+
_MOUNT_PAGE_HTML = """<!DOCTYPE html>
661+
<html><head><meta charset="utf-8"/>
662+
<style>html,body{margin:0;padding:0;}</style></head>
663+
<body><div id="host"></div>
664+
<script type="module">
665+
const STATE = __STATE__;
666+
const esmSource = __ESM__;
667+
const blobUrl = URL.createObjectURL(new Blob([esmSource], {type: "text/javascript"}));
668+
import(blobUrl).then(mod => {
669+
window._handle = mod.mount(document.getElementById("host"), STATE, {});
670+
window._aplReady = true;
671+
}).catch(err => { document.body.textContent = "mount error: " + err; });
672+
</script></body></html>
673+
"""
674+
675+
676+
@pytest.fixture
677+
def mount_page(_pw_browser):
678+
"""Open a figure through the public ``mount()`` API; return the live Page.
679+
680+
The page exposes ``window._handle`` — ``exportPNG(opts)`` and ``api.panels``
681+
— and accepts ordinary Playwright mouse events, so one fixture covers
682+
"render it, point at it, export it".
683+
"""
684+
import json as _json
685+
import pathlib as _pathlib
686+
import tempfile as _tempfile
687+
688+
from anyplotlib.embed import esm_path, figure_state
689+
690+
pages, paths = [], []
691+
692+
def _open(fig):
693+
html = (_MOUNT_PAGE_HTML
694+
.replace("__STATE__", _json.dumps(figure_state(fig)))
695+
.replace("__ESM__",
696+
_json.dumps(esm_path().read_text(encoding="utf-8"))))
697+
with _tempfile.NamedTemporaryFile(suffix=".html", mode="w",
698+
encoding="utf-8", delete=False) as fh:
699+
fh.write(html)
700+
tmp = _pathlib.Path(fh.name)
701+
paths.append(tmp)
702+
page = _pw_browser.new_page()
703+
pages.append(page)
704+
page.goto(tmp.as_uri())
705+
page.wait_for_function("() => window._aplReady === true", timeout=15_000)
706+
page.evaluate(
707+
"() => new Promise(r => requestAnimationFrame("
708+
"() => requestAnimationFrame(r)))")
709+
return page
710+
711+
yield _open
712+
713+
for p in pages:
714+
try:
715+
p.close()
716+
except Exception:
717+
pass
718+
for f in paths:
719+
f.unlink(missing_ok=True)

0 commit comments

Comments
 (0)