"""A link that acts looks like a button, and is one element. style.md used to say to wrap such a link around a `", "secondary", "Settings"), ("range bar", '

Build a chart

{}
', "", "← Charts"), ("page actions", '

Charts

' '
{}' '
', "secondary", "View charts"), ("page primary", '

Charts

{}
', "primary", "+ Add a chart"), ("commit bar", '
{}
', "secondary", "Cancel"), ("table tools", '

Holdings {}

', "secondary small", "Manage holdings"), ("section heading", '

Ledger {}

', "secondary", "Record a trade"), ("table row", '
ACME{}
', "secondary small", "Edit"), ("calendar", '

October 2026

' '
{}
', "", "←"), ("upcoming", '
{}
', "secondary", "Record buy"), ("empty portfolio", '
{}
", "secondary", "Import from your broker"), ] DRAWN = ["background-color", "background-image", "color", "border-top-width", "border-top-style", "border-top-color", "border-radius", "padding-top", "padding-right", "padding-bottom", "padding-left", "font-size", "font-weight", "font-family", "line-height", "letter-spacing", "text-decoration-line", "text-align", "box-shadow", "cursor", "vertical-align", "filter"] PROBE = """ """ def _page() -> str: cases = [] for i, (_name, around, classes, label) in enumerate(CASES): drawn = {"button": f'', "link": f'
{label}'} cases += [f'
{around.format(markup)}
' for kind, markup in drawn.items()] style = (STATIC / "style.css").read_text(encoding="utf-8") return ("" + "".join(cases) + PROBE.replace("DRAWN", json.dumps(DRAWN)) + "") def _drawn(tmp_path, page: str, width: int) -> dict: sys.path.insert(0, str(ROOT)) from tools.browser import find_chrome chrome = find_chrome() if chrome is None: pytest.skip("no headless Chrome on this machine") target = tmp_path / "links.html" target.write_text(page, encoding="utf-8") dom = subprocess.run( [chrome, "--headless", "--disable-gpu", "--dump-dom", "--no-sandbox", f"--window-size={width},2000", "--virtual-time-budget=4000", target.as_uri()], capture_output=True, text=True, timeout=90).stdout found = re.search(r'
(.*?)
', dom, re.S) assert found, "the probe did not finish" return json.loads(html.unescape(found.group(1))) @pytest.mark.visual @pytest.mark.parametrize("width", [1400, 412]) @pytest.mark.parametrize("theme", ["light", "dark"]) def test_a_link_drawn_as_a_button_looks_like_a_button(tmp_path, width, theme): drawn = _drawn(tmp_path, _page().replace("", f'', 1), width) differences = { name: {key: (drawn[f"button-{i}"][key], drawn[f"link-{i}"][key]) for key in drawn[f"button-{i}"] if drawn[f"button-{i}"][key] != drawn[f"link-{i}"][key]} for i, (name, *_rest) in enumerate(CASES)} assert {name: diff for name, diff in differences.items() if diff} == {} @pytest.mark.visual @pytest.mark.parametrize("width", [1400, 412]) def test_the_account_menus_two_actions_are_one_width(tmp_path, width): """Settings is a link and Log out a form's button. In a flex row the link's padding grew along with it, and it came out wider.""" style = (STATIC / "style.css").read_text(encoding="utf-8") page = ("' + "") settings, log_out = _drawn(tmp_path, page, width) assert settings == log_out