Skip to content

Commit 23aacc9

Browse files
authored
fix: Add test for pasting markdown and admin editor initialization (#153)
1 parent 42832b5 commit 23aacc9

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

tests/integration/test_admin_editor.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,38 @@ def test_stacked_inline_admin_add_row(live_server, page, pizza, superuser):
5151
sleep(0.1) # Let browser work
5252

5353
assert stacked_group.locator(".cms-editor-inline-wrapper.textarea.fixed").count() == n + 1
54+
55+
56+
@pytest.mark.django_db
57+
@pytest.mark.skipif(not DJANGO_CMS4, reason="Integration tests only work on Django CMS 4")
58+
def test_stacked_inline_empty_form_not_initialized(live_server, page, pizza, superuser):
59+
"""Regression test for #123: RTE must not initialize on the empty-form template
60+
in stacked inlines, which would cause a duplicate editor after clicking add row."""
61+
login(live_server, page, superuser)
62+
63+
endpoint = reverse("admin:test_app_pizza_change", args=(pizza.pk,))
64+
page.goto(f"{live_server.url}{endpoint}")
65+
66+
stacked_group = page.locator("#sauce_set-group")
67+
68+
# The empty-form template must not have an initialized editor
69+
empty_form = stacked_group.locator(".inline-related.empty-form")
70+
assert empty_form.count() == 1, "Empty form template should exist"
71+
assert empty_form.locator(".cms-editor-inline-wrapper.textarea.fixed").count() == 0, (
72+
"Empty form template must not have an initialized editor"
73+
)
74+
75+
# Count editors before adding a row
76+
n = stacked_group.locator(".cms-editor-inline-wrapper.textarea.fixed").count()
77+
78+
# Add a row
79+
stacked_group.locator(".add-row a").click()
80+
sleep(0.1)
81+
82+
# Exactly one new editor should appear (not two as reported in #123)
83+
assert stacked_group.locator(".cms-editor-inline-wrapper.textarea.fixed").count() == n + 1
84+
85+
# The empty-form template must still not have an initialized editor
86+
assert empty_form.locator(".cms-editor-inline-wrapper.textarea.fixed").count() == 0, (
87+
"Empty form template must still not have an initialized editor after add row"
88+
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
3+
try:
4+
from cms.utils.urlutils import admin_reverse
5+
except ModuleNotFoundError:
6+
7+
def admin_reverse(viewname, args=None, kwargs=None, current_app=None):
8+
from django.urls import reverse
9+
10+
return reverse(f"admin:{viewname}", args, kwargs, current_app)
11+
12+
13+
from playwright.sync_api import expect
14+
15+
from tests.fixtures import DJANGO_CMS4
16+
from tests.integration.utils import login
17+
18+
19+
def clear_tiptap(page, tiptap):
20+
"""Clear all content in the tiptap editor."""
21+
tiptap.click()
22+
# Use tiptap's commands API to clear all content reliably
23+
page.evaluate(
24+
"""() => {
25+
const el = document.querySelector('.ProseMirror.tiptap');
26+
// Access the tiptap editor instance attached to the element
27+
if (el.editor) {
28+
el.editor.commands.clearContent();
29+
} else {
30+
el.innerHTML = '';
31+
}
32+
}"""
33+
)
34+
35+
36+
def paste_text(page, text):
37+
"""Paste plain text into the focused tiptap editor via a synthetic clipboard event."""
38+
page.evaluate(
39+
"""(text) => {
40+
const clipboardData = new DataTransfer();
41+
clipboardData.setData('text/plain', text);
42+
const event = new ClipboardEvent('paste', {
43+
bubbles: true,
44+
cancelable: true,
45+
clipboardData: clipboardData,
46+
});
47+
document.querySelector('.ProseMirror.tiptap').dispatchEvent(event);
48+
}""",
49+
text,
50+
)
51+
52+
53+
@pytest.mark.django_db
54+
@pytest.mark.skipif(not DJANGO_CMS4, reason="Integration tests only work on Django CMS 4")
55+
def test_paste_markdown_converts_to_html(live_server, page, text_plugin, superuser):
56+
"""Test that pasting markdown text into the tiptap editor converts it to HTML."""
57+
login(live_server, page, superuser)
58+
59+
page.goto(f"{live_server.url}{admin_reverse('cms_placeholder_edit_plugin', args=(text_plugin.pk,))}")
60+
61+
tiptap = page.locator(".ProseMirror.tiptap")
62+
expect(tiptap).to_be_visible()
63+
64+
clear_tiptap(page, tiptap)
65+
paste_text(page, "## Hello World\n\nThis is **bold** and *italic* text.")
66+
67+
# Scope assertions to editor content only (exclude toolbar/plugin-selector elements)
68+
content = tiptap.locator("> h2")
69+
expect(content).to_have_text("Hello World")
70+
71+
expect(tiptap.locator("> p strong")).to_have_text("bold")
72+
expect(tiptap.locator("> p em")).to_have_text("italic")
73+
74+
75+
@pytest.mark.django_db
76+
@pytest.mark.skipif(not DJANGO_CMS4, reason="Integration tests only work on Django CMS 4")
77+
def test_paste_plain_text_not_converted(live_server, page, text_plugin, superuser):
78+
"""Test that pasting plain text without markdown patterns is not converted."""
79+
login(live_server, page, superuser)
80+
81+
page.goto(f"{live_server.url}{admin_reverse('cms_placeholder_edit_plugin', args=(text_plugin.pk,))}")
82+
83+
tiptap = page.locator(".ProseMirror.tiptap")
84+
expect(tiptap).to_be_visible()
85+
86+
clear_tiptap(page, tiptap)
87+
88+
plain_text = "Just some regular text without any formatting."
89+
paste_text(page, plain_text)
90+
91+
# Verify text was pasted as plain text (no markdown conversion)
92+
expect(tiptap.locator("> p").first).to_have_text(plain_text)
93+
assert tiptap.locator("> h1, > h2, > h3").count() == 0
94+
assert tiptap.locator("> p strong, > p em").count() == 0

0 commit comments

Comments
 (0)