#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.13" # dependencies = ["pytest", "playwright"] # /// """ Browser tests for markdown-to-rich-text.html: drive a real Chromium, then read back what landed on the clipboard. uv run tests/test_markdown_to_rich_text.py # self-contained uv run --with playwright playwright install chromium # once, to get the browser The page loads marked and DOMPurify from jsdelivr, so these need network access. """ import sys from pathlib import Path import pytest playwright_api = pytest.importorskip("playwright.sync_api") PAGE = (Path(__file__).resolve().parent.parent / "markdown-to-rich-text.html").as_uri() SAMPLE = """# Title ## Section Some **bold**, *italic* and `inline code`, a [web link](https://example.com) and a [repo link](src/app.ts#L42). - [x] done - [ ] to do | Name | Value | | --- | :---: | | a | 1 | ```python print("hi") ``` > quoted """ READ_CLIPBOARD = """async () => { const [item] = await navigator.clipboard.read(); const get = async t => item.types.includes(t) ? (await item.getType(t)).text() : null; return { html: await get("text/html"), text: await get("text/plain") }; }""" @pytest.fixture(scope="module") def browser(): with playwright_api.sync_playwright() as p: try: b = p.chromium.launch() except Exception as e: pytest.skip(f"Chromium not installed: {e}") yield b b.close() @pytest.fixture def page(browser): ctx = browser.new_context(permissions=["clipboard-read", "clipboard-write"]) pg = ctx.new_page() pg.goto(PAGE) # module scripts, CDN imports included, run before the load event goto waits for yield pg ctx.close() def copied(page, markdown): page.fill("#md", markdown) page.click("#copy") page.wait_for_selector("#status:has-text('Copied')") return page.evaluate(READ_CLIPBOARD) def test_copy_puts_formatted_html_and_markdown_on_the_clipboard(page): clip = copied(page, SAMPLE) html = clip["html"] assert clip["text"].replace("\r\n", "\n") == SAMPLE assert "

Title

" in html and "

Section

" in html # unstyled, so they map onto the destination's Heading styles assert "bold" in html and "italic" in html assert 'href="https://example.com/"' in html # Chromium normalises URLs when reading the clipboard back def test_outlook_friendly_rewrites(page): html = copied(page, SAMPLE)["html"] assert "src/app.ts" not in html and "repo link" in html # relative links keep only their text assert " navigator.clipboard.writeText(t)", "# Pasted\n\n- one\n- two\n") page.focus("#md") page.keyboard.press("Control+V") page.wait_for_selector("#status:has-text('Copied')") assert page.input_value("#md") == "# Pasted\n\n- one\n- two\n" html = page.evaluate(READ_CLIPBOARD)["html"] assert "

Pasted

" in html and ">one" in html def test_paste_edits_normally_when_auto_copy_is_off(page): page.uncheck("#auto") page.fill("#md", "start ") page.evaluate("t => navigator.clipboard.writeText(t)", "**more**") page.focus("#md") page.keyboard.press("End") page.keyboard.press("Control+V") page.wait_for_function("document.querySelector('#md').value.includes('more')") assert page.input_value("#md") == "start **more**" assert page.inner_html("#preview").strip() == '

start more

' def test_single_newlines_are_line_breaks(page): html = copied(page, "**Traveller:** me\n**Purpose:** a workshop\n")["html"] assert "Traveller: me
Purpose: a workshop" in html def test_joining_single_newlines_into_one_paragraph(page): page.check("#join") page.uncheck("#gap") page.select_option("#size", "") page.fill("#md", "One sentence.\nAnother sentence.\n\nNew paragraph.\n\n- item\n continued\n\nHard \nbreak\n\n```\nkeep\nthese\n```\n") html = page.inner_html("#preview") assert "

One sentence.\nAnother sentence.

" in html and "

New paragraph.

" in html assert page.inner_text("#preview p:first-child") == "One sentence. Another sentence." # the newline renders as a space assert "item\ncontinued" in html and "Hard
" in html and "keep\nthese" in html page.reload() assert page.is_checked("#join") # the choice is remembered page.uncheck("#join") assert "One sentence.
" in copied(page, "One sentence.\nAnother sentence.\n")["html"] # and off means line breaks again def test_blank_line_between_paragraphs(page): page.select_option("#size", "") html = copied(page, "One\n\nTwo\n\nThree\n\n- item\n\nFour\n")["html"] assert html.count("\u00a0

") == 2 or html.count(" 

") == 2 # between One/Two and Two/Three, not around the list assert page.locator("#preview p").count() == 6 and page.inner_text("#preview p:nth-child(2)").strip() == "" assert page.evaluate("getComputedStyle(document.querySelector('#preview p')).marginBottom") == "0px" page.uncheck("#gap") assert page.locator("#preview p").count() == 4 assert page.evaluate("getComputedStyle(document.querySelector('#preview p')).marginBottom") != "0px" page.reload() assert not page.is_checked("#gap") # the choice is remembered def test_text_size_applies_to_body_text_but_not_headings(page): html = copied(page, "# Head\n\nbody\n\n- item\n")["html"] assert "

Head

" in html assert '

body

' in html and '
  • item
  • ' in html page.select_option("#size", "") # leave it to the destination html = copied(page, "# Head\n\nbody\n")["html"] assert "

    body

    " in html page.reload() assert page.input_value("#size") == "" # the choice is remembered if __name__ == "__main__": sys.exit(pytest.main([__file__, "-v"]))