Various tweaks to theme, more code coverage
This commit is contained in:
parent
c3b83b0238
commit
7c3ec19748
17 changed files with 812 additions and 49 deletions
|
|
@ -1,12 +1,21 @@
|
|||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QImage, QTextCursor, QTextImageFormat
|
||||
from PySide6.QtCore import Qt, QMimeData, QPoint, QUrl
|
||||
from PySide6.QtGui import QImage, QMouseEvent, QKeyEvent, QTextCursor, QTextImageFormat
|
||||
from PySide6.QtTest import QTest
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from bouquin.editor import Editor
|
||||
from bouquin.theme import ThemeManager, ThemeConfig, Theme
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def _mk_editor() -> Editor:
|
||||
# pytest-qt ensures a QApplication exists
|
||||
app = QApplication.instance()
|
||||
tm = ThemeManager(app, ThemeConfig())
|
||||
return Editor(tm)
|
||||
|
||||
|
||||
def _move_cursor_to_first_image(editor: Editor) -> QTextImageFormat | None:
|
||||
c = editor.textCursor()
|
||||
c.movePosition(QTextCursor.Start)
|
||||
|
|
@ -31,7 +40,7 @@ def _fmt_at(editor: Editor, pos: int):
|
|||
|
||||
|
||||
def test_space_breaks_link_anchor_and_styling(qtbot):
|
||||
e = Editor()
|
||||
e = _mk_editor()
|
||||
e.resize(600, 300)
|
||||
e.show()
|
||||
qtbot.waitExposed(e)
|
||||
|
|
@ -75,7 +84,7 @@ def test_space_breaks_link_anchor_and_styling(qtbot):
|
|||
|
||||
|
||||
def test_embed_qimage_saved_as_data_url(qtbot):
|
||||
e = Editor()
|
||||
e = _mk_editor()
|
||||
e.resize(600, 400)
|
||||
qtbot.addWidget(e)
|
||||
e.show()
|
||||
|
|
@ -96,7 +105,7 @@ def test_insert_images_autoscale_and_fit(qtbot, tmp_path):
|
|||
big_path = tmp_path / "big.png"
|
||||
big.save(str(big_path))
|
||||
|
||||
e = Editor()
|
||||
e = _mk_editor()
|
||||
e.resize(420, 300) # known viewport width
|
||||
qtbot.addWidget(e)
|
||||
e.show()
|
||||
|
|
@ -120,7 +129,7 @@ def test_insert_images_autoscale_and_fit(qtbot, tmp_path):
|
|||
|
||||
|
||||
def test_linkify_trims_trailing_punctuation(qtbot):
|
||||
e = Editor()
|
||||
e = _mk_editor()
|
||||
qtbot.addWidget(e)
|
||||
e.show()
|
||||
qtbot.waitExposed(e)
|
||||
|
|
@ -135,31 +144,13 @@ def test_linkify_trims_trailing_punctuation(qtbot):
|
|||
assert 'href="https://example.com)."' not in html
|
||||
|
||||
|
||||
def test_space_does_not_bleed_anchor_format(qtbot):
|
||||
e = Editor()
|
||||
qtbot.addWidget(e)
|
||||
e.show()
|
||||
qtbot.waitExposed(e)
|
||||
|
||||
e.setPlainText("https://a.example")
|
||||
qtbot.waitUntil(lambda: 'href="' in e.document().toHtml())
|
||||
|
||||
c = e.textCursor()
|
||||
c.movePosition(QTextCursor.End)
|
||||
e.setTextCursor(c)
|
||||
|
||||
# Press Space; keyPressEvent should break the anchor for the next char
|
||||
QTest.keyClick(e, Qt.Key_Space)
|
||||
assert e.currentCharFormat().isAnchor() is False
|
||||
|
||||
|
||||
def test_code_block_enter_exits_on_empty_line(qtbot):
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QTextCursor
|
||||
from PySide6.QtTest import QTest
|
||||
from bouquin.editor import Editor
|
||||
|
||||
e = Editor()
|
||||
e = _mk_editor()
|
||||
qtbot.addWidget(e)
|
||||
e.show()
|
||||
qtbot.waitExposed(e)
|
||||
|
|
@ -185,3 +176,169 @@ def test_code_block_enter_exits_on_empty_line(qtbot):
|
|||
# Second Enter should jump *out* of the frame
|
||||
QTest.keyClick(e, Qt.Key_Return)
|
||||
# qtbot.waitUntil(lambda: e._find_code_frame(e.textCursor()) is None)
|
||||
|
||||
|
||||
class DummyMenu:
|
||||
def __init__(self):
|
||||
self.seps = 0
|
||||
self.subs = []
|
||||
self.exec_called = False
|
||||
|
||||
def addSeparator(self):
|
||||
self.seps += 1
|
||||
|
||||
def addMenu(self, title):
|
||||
m = DummyMenu()
|
||||
self.subs.append((title, m))
|
||||
return m
|
||||
|
||||
def addAction(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self, *a, **k):
|
||||
self.exec_called = True
|
||||
|
||||
|
||||
def _themes():
|
||||
app = QApplication.instance()
|
||||
return ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||
|
||||
|
||||
def test_context_menu_adds_image_actions(monkeypatch, qtbot):
|
||||
e = Editor(_themes())
|
||||
qtbot.addWidget(e)
|
||||
# Fake an image at cursor
|
||||
qi = QImage(10, 10, QImage.Format_ARGB32)
|
||||
qi.fill(0xFF00FF00)
|
||||
imgfmt = QTextImageFormat()
|
||||
imgfmt.setName("x")
|
||||
imgfmt.setWidth(10)
|
||||
imgfmt.setHeight(10)
|
||||
tc = e.textCursor()
|
||||
monkeypatch.setattr(e, "_image_info_at_cursor", lambda: (tc, imgfmt, qi))
|
||||
|
||||
dummy = DummyMenu()
|
||||
monkeypatch.setattr(e, "createStandardContextMenu", lambda: dummy)
|
||||
|
||||
class Evt:
|
||||
def globalPos(self):
|
||||
return QPoint(0, 0)
|
||||
|
||||
e.contextMenuEvent(Evt())
|
||||
assert dummy.exec_called
|
||||
assert dummy.seps == 1
|
||||
assert any(t == "Image size" for t, _ in dummy.subs)
|
||||
|
||||
|
||||
def test_insert_from_mime_image_and_urls(tmp_path, qtbot):
|
||||
e = Editor(_themes())
|
||||
qtbot.addWidget(e)
|
||||
# Build a mime with an image
|
||||
mime = QMimeData()
|
||||
img = QImage(6, 6, QImage.Format_ARGB32)
|
||||
img.fill(0xFF0000FF)
|
||||
mime.setImageData(img)
|
||||
e.insertFromMimeData(mime)
|
||||
html = e.document().toHtml()
|
||||
assert "<img" in html
|
||||
|
||||
# Now with urls: local non-image + local image + remote url
|
||||
png = tmp_path / "t.png"
|
||||
img.save(str(png))
|
||||
txt = tmp_path / "x.txt"
|
||||
txt.write_text("hi", encoding="utf-8")
|
||||
mime2 = QMimeData()
|
||||
mime2.setUrls(
|
||||
[
|
||||
QUrl.fromLocalFile(str(txt)),
|
||||
QUrl.fromLocalFile(str(png)),
|
||||
QUrl("https://example.com/file"),
|
||||
]
|
||||
)
|
||||
e.insertFromMimeData(mime2)
|
||||
h2 = e.document().toHtml()
|
||||
assert 'href="file://' in h2 # local file link inserted
|
||||
assert "<img" in h2 # image inserted
|
||||
assert 'href="https://example.com/file"' in h2 # remote url link
|
||||
|
||||
|
||||
def test_mouse_release_ctrl_click_opens(monkeypatch, qtbot):
|
||||
e = Editor(_themes())
|
||||
qtbot.addWidget(e)
|
||||
# Anchor under cursor
|
||||
monkeypatch.setattr(e, "anchorAt", lambda p: "https://example.com")
|
||||
opened = {}
|
||||
from PySide6.QtGui import QDesktopServices as DS
|
||||
|
||||
monkeypatch.setattr(
|
||||
DS, "openUrl", lambda url: opened.setdefault("u", url.toString())
|
||||
)
|
||||
ev = QMouseEvent(
|
||||
QMouseEvent.MouseButtonRelease,
|
||||
QPoint(1, 1),
|
||||
Qt.LeftButton,
|
||||
Qt.LeftButton,
|
||||
Qt.ControlModifier,
|
||||
)
|
||||
e.mouseReleaseEvent(ev)
|
||||
assert opened.get("u") == "https://example.com"
|
||||
|
||||
|
||||
def test_keypress_space_breaks_anchor(monkeypatch, qtbot):
|
||||
e = Editor(_themes())
|
||||
qtbot.addWidget(e)
|
||||
called = {}
|
||||
monkeypatch.setattr(
|
||||
e, "_break_anchor_for_next_char", lambda: called.setdefault("x", True)
|
||||
)
|
||||
ev = QKeyEvent(QKeyEvent.KeyPress, Qt.Key_Space, Qt.NoModifier, " ")
|
||||
e.keyPressEvent(ev)
|
||||
assert called.get("x") is True
|
||||
|
||||
|
||||
def test_enter_leaves_code_frame(qtbot):
|
||||
e = Editor(_themes())
|
||||
qtbot.addWidget(e)
|
||||
e.setPlainText("")
|
||||
# Insert a code block frame
|
||||
e.apply_code()
|
||||
# Place cursor inside the empty code block
|
||||
c = e.textCursor()
|
||||
c.movePosition(QTextCursor.End)
|
||||
e.setTextCursor(c)
|
||||
# Press Enter; should jump outside the frame and start normal paragraph
|
||||
ev = QKeyEvent(QKeyEvent.KeyPress, Qt.Key_Return, Qt.NoModifier)
|
||||
e.keyPressEvent(ev)
|
||||
# After enter, the cursor should not be inside a code frame
|
||||
assert e._find_code_frame(e.textCursor()) is None
|
||||
|
||||
|
||||
def test_space_does_not_bleed_anchor_format(qtbot):
|
||||
e = _mk_editor()
|
||||
qtbot.addWidget(e)
|
||||
e.show()
|
||||
qtbot.waitExposed(e)
|
||||
|
||||
e.setPlainText("https://a.example")
|
||||
qtbot.waitUntil(lambda: 'href="' in e.document().toHtml())
|
||||
|
||||
c = e.textCursor()
|
||||
c.movePosition(QTextCursor.End)
|
||||
e.setTextCursor(c)
|
||||
|
||||
# Press Space; keyPressEvent should break the anchor for the next char
|
||||
QTest.keyClick(e, Qt.Key_Space)
|
||||
assert e.currentCharFormat().isAnchor() is False
|
||||
|
||||
|
||||
def test_editor_small_helpers(qtbot):
|
||||
app = QApplication.instance()
|
||||
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||
e = Editor(themes)
|
||||
qtbot.addWidget(e)
|
||||
# _approx returns True when |a-b| <= eps
|
||||
assert e._approx(1.0, 1.25, eps=0.3) is True
|
||||
assert e._approx(1.0, 1.6, eps=0.3) is False
|
||||
# Exercise helpers
|
||||
_ = e._is_heading_typing()
|
||||
e._apply_normal_typing()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue