Files
rustpad/tests/mention-navigation-browser.py
T
2026-10-12 12:19:33 +02:00

180 lines
8.7 KiB
Python

#!/usr/bin/env python3
"""Browser integration test for the mentioned-users bar and cyclic jumps."""
from __future__ import annotations
import base64
from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[1]
HTML = '''<!doctype html><html lang="pl"><head><meta charset="utf-8">
<style>__STYLES__</style>
<style>
body { padding: 20px; }
.workspace { width: min(970px, calc(100vw - 40px)); height: 400px; grid-template-columns: 1fr 1fr; }
.editor-column { display: flex; flex-direction: column; height: 100%; border: 1px solid #aaa; }
.preview-column { display: grid; min-width: 0; min-height: 0; }
.editor-shell { flex: 1 1 auto; min-height: 0; }
#editor { box-sizing: border-box; width: 100%; height: 100%; padding: 18px; line-height: 22px; }
#preview { box-sizing: border-box; width: 100%; height: 100%; overflow: auto; border: 1px solid #aaa; padding: 18px; }
</style>
</head><body class="pad-page">
<div id="editor-workspace" class="workspace view-split">
<div class="editor-column">
<div class="column-label">Editor</div>
<div class="participant-badges"><span class="participant-badge">Autor</span></div>
<div id="mentioned-users" class="mentioned-users" role="group" hidden>
<span class="mentioned-users__label">Wspomniani</span>
<div id="mentioned-users-list" class="mentioned-users__list"></div>
</div>
<div class="editor-shell"><textarea id="editor" wrap="off"></textarea></div>
</div>
<div class="preview-column">
<div id="preview-label" class="column-label">Markdown preview</div>
<div id="mentioned-users-preview" class="mentioned-users mentioned-users--preview" role="group" hidden>
<span class="mentioned-users__label">Wspomniani</span>
<div id="mentioned-users-preview-list" class="mentioned-users__list"></div>
</div>
<article class="markdown-body preview" id="preview"></article>
</div>
</div>
<script type="module">
import {createMentionNavigator} from '__NAVIGATION_URL__';
import {setMarkdownMentions,renderMarkdown} from '__MARKDOWN_URL__';
const editor = document.querySelector('#editor');
const preview = document.querySelector('#preview');
let view = 'split', members = [];
const navigator = createMentionNavigator({
container: document.querySelector('#mentioned-users'),
list: document.querySelector('#mentioned-users-list'),
previewContainer: document.querySelector('#mentioned-users-preview'),
previewList: document.querySelector('#mentioned-users-preview-list'),
editor, preview, getView: () => view, onEditorScroll: () => {},
});
window.runCase = (source, users) => {
editor.value = source; members = users;
navigator.setUsers(members);
setMarkdownMentions(members);
preview.innerHTML = renderMarkdown(source);
navigator.render({markdown:true});
};
window.setView = value => {
view=value;
document.querySelector('#editor-workspace').className = `workspace view-${value}`;
};
window.setUsers = value => {
navigator.setUsers(value);
setMarkdownMentions(value);
preview.innerHTML = renderMarkdown(editor.value);
navigator.render({markdown:true});
};
window.ready = true;
</script></body></html>'''
def as_data_url(source):
return 'data:text/javascript;base64,' + base64.b64encode(source.encode('utf-8')).decode('ascii')
def create_fixture():
autocomplete = (ROOT / 'static/js/mention-autocomplete.js').read_text()
autocomplete = autocomplete.replace('import { t } from "@rustpad/i18n";',
'const t = (key, values, fallback) => fallback;')
navigation = (ROOT / 'static/js/mention-navigation.js').read_text()
navigation = navigation.replace('import { t } from "@rustpad/i18n";',
'const t = (key, values, fallback) => fallback;')
navigation = navigation.replace('"@rustpad/mention-autocomplete"',
f'"{as_data_url(autocomplete)}"')
markdown = (ROOT / 'static/js/markdown.js').read_text()
markdown = '\n'.join(line for line in markdown.split('\n') if not line.startswith('import '))
markdown = ('const EMOJI_SHORTCODES = {}; '
'const t = (key, values, fallback) => fallback; '
'const parseImageAlias = () => null;\n') + markdown
styles = (ROOT / 'static/css/styles.css').read_text()
return (HTML.replace('__STYLES__', styles)
.replace('__NAVIGATION_URL__', as_data_url(navigation))
.replace('__MARKDOWN_URL__', as_data_url(markdown)))
def main():
fixture = create_fixture()
with sync_playwright() as playwright:
browser = playwright.chromium.launch(executable_path='/usr/bin/chromium', args=['--no-sandbox'])
try:
page = browser.new_page(viewport={'width': 1020, 'height': 680})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.set_content(fixture)
page.wait_for_function('window.ready === true')
lines = [
'Na początku @Dominik i @Dominik, do tego @Dora.',
'`@Dominik` [@Dominik](https://example.com/@Dora)',
'```sh',
'@Dominik',
'```',
*[f'Wypełnienie linii {i}' for i in range(1, 18)],
'<details>',
'<summary>Szczegóły</summary>',
'Wewnątrz @Dominik',
'</details>',
*[f'Treść linii {i}' for i in range(18, 38)],
'Na końcu @Dominik.',
]
content = '\n'.join(lines)
users = [{'nickname': 'Dominik', 'color': '#123456'}, {'nickname':'Dora', 'color':'#654321', 'email':'private@example.org'}]
page.evaluate('([text,users]) => window.runCase(text,users)', [content, users])
bar = page.locator('#mentioned-users')
assert bar.is_visible()
badges = page.locator('#mentioned-users-list .mentioned-users__badge')
preview_badges = page.locator('#mentioned-users-preview-list .mentioned-users__badge')
assert page.locator('#mentioned-users-preview').is_hidden()
assert badges.count() == 2
assert preview_badges.count() == 2
assert badges.all_text_contents() == ['@Dominik4', '@Dora1']
assert 'private' not in bar.inner_text()
button = badges.first
# The two code/link occurrences are intentionally skipped.
source_starts = [content.index('@Dominik'),content.index('@Dominik',content.index('@Dominik')+1),content.index('@Dominik',content.index('Wewnątrz')), content.rindex('@Dominik')]
for step, start in enumerate(source_starts, 1):
button.click()
assert page.evaluate('document.querySelector("#editor").selectionStart') == start, step
assert page.evaluate('document.querySelector("#editor").selectionEnd') == start + len('@Dominik')
assert page.locator('.markdown-mention.is-mention-target').count() == 1
assert button.locator('.mentioned-users__count').inner_text() == f'{step}/4'
assert page.locator('details.markdown-details').evaluate('element => element.open') is True
button.click()
assert page.evaluate('document.querySelector("#editor").selectionStart') == source_starts[0]
assert button.locator('.mentioned-users__count').inner_text() == '1/4'
dora = badges.nth(1)
dora.click()
assert page.evaluate('document.querySelector("#editor").selectionStart') == content.index('@Dora')
assert dora.locator('.mentioned-users__count').inner_text() == '1/1'
page.evaluate('window.setView("preview")')
assert bar.is_hidden()
assert page.locator('#mentioned-users-preview').is_visible()
assert page.locator('#preview').is_visible()
original_selection = page.evaluate('document.querySelector("#editor").selectionStart')
preview_badges.first.click()
assert page.evaluate('document.querySelector("#editor").selectionStart') == original_selection
assert page.locator('.markdown-mention.is-mention-target').count() == 1
page.evaluate('window.setView("edit")')
assert bar.is_visible()
assert page.locator('#mentioned-users-preview').is_hidden()
button.click()
assert page.evaluate('document.querySelector("#editor").selectionStart') == source_starts[2]
page.evaluate('window.setUsers([])')
assert bar.is_hidden()
assert page.locator('#mentioned-users-preview').is_hidden()
assert badges.count() == 0
assert preview_badges.count() == 0
assert not errors, errors
print('PASS: Chromium mentions list, access, cyclic navigation, source selection, details, view switching')
finally:
browser.close()
if __name__ == '__main__':
main()