269 lines
9.4 KiB
Python
269 lines
9.4 KiB
Python
"""
|
|
Step 13 — Markdown in Description Fields
|
|
- test_markdown_hint_on_exercise_form: Hint text visible next to description label on exercise form
|
|
- test_markdown_hint_on_combo_form: Hint text visible next to description label on combo form
|
|
- test_markdown_hint_on_session_form: Hint text visible next to description label on session form
|
|
- test_markdown_rendered_in_exercise_detail: Bold, italic and list render as HTML in exercise detail
|
|
- test_markdown_rendered_in_combo_detail: Bold text renders as HTML in combo detail
|
|
- test_markdown_rendered_in_session_detail: Bold text renders as HTML in session detail
|
|
- test_description_absent_from_exercise_list: Description is not shown on exercise list cards
|
|
- test_description_absent_from_combo_list: Description is not shown on combo list cards
|
|
- test_description_absent_from_session_list: Description is not shown on session list cards
|
|
"""
|
|
from playwright.sync_api import Page, expect
|
|
|
|
HINT_TEXT = "Basic Markdown supported"
|
|
|
|
|
|
def _wait_for_db(page: Page, app_url: str):
|
|
"""Navigate and wait for the database to be ready."""
|
|
page.goto(app_url)
|
|
page.wait_for_function(
|
|
"document.documentElement.getAttribute('data-db-ready') === 'true'",
|
|
timeout=15000,
|
|
)
|
|
|
|
|
|
def _clean_all(page: Page):
|
|
"""Remove all exercises, combos, and sessions via the repositories."""
|
|
page.evaluate(
|
|
"""async () => {
|
|
for (const e of await window.__repos.exercises.getAll())
|
|
await window.__repos.exercises.delete(e.id);
|
|
for (const c of await window.__repos.combos.getAll())
|
|
await window.__repos.combos.delete(c.id);
|
|
for (const s of await window.__repos.sessions.getAll())
|
|
await window.__repos.sessions.delete(s.id);
|
|
}"""
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Hint tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_markdown_hint_on_exercise_form(page: Page, app_url: str):
|
|
"""Hint 'Basic Markdown supported' appears next to the description label on the exercise form."""
|
|
_wait_for_db(page, app_url)
|
|
page.click('a[href="#/exercises"]')
|
|
page.wait_for_timeout(500)
|
|
page.click('[data-testid="exercise-new-btn"]')
|
|
page.wait_for_selector('[data-testid="exercise-description"]')
|
|
|
|
hint = page.locator('[data-testid="exercise-description"]').locator(
|
|
'xpath=../label .field-hint'
|
|
)
|
|
# Locate via the form-group label
|
|
hint = page.locator('label[for="exercise-description"] .field-hint')
|
|
expect(hint).to_have_text(HINT_TEXT)
|
|
|
|
|
|
def test_markdown_hint_on_combo_form(page: Page, app_url: str):
|
|
"""Hint 'Basic Markdown supported' appears next to the description label on the combo form."""
|
|
_wait_for_db(page, app_url)
|
|
page.click('a[href="#/combos"]')
|
|
page.wait_for_timeout(500)
|
|
page.click('[data-testid="combo-new-btn"]')
|
|
page.wait_for_selector('[data-testid="combo-description"]')
|
|
|
|
hint = page.locator('label[for="combo-description"] .field-hint')
|
|
expect(hint).to_have_text(HINT_TEXT)
|
|
|
|
|
|
def test_markdown_hint_on_session_form(page: Page, app_url: str):
|
|
"""Hint 'Basic Markdown supported' appears next to the description label on the session form."""
|
|
_wait_for_db(page, app_url)
|
|
page.click('a[href="#/sessions"]')
|
|
page.wait_for_timeout(500)
|
|
page.click('[data-testid="session-new-btn"]')
|
|
page.wait_for_selector('[data-testid="session-description"]')
|
|
|
|
hint = page.locator('label[for="session-description"] .field-hint')
|
|
expect(hint).to_have_text(HINT_TEXT)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Rendering tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_markdown_rendered_in_exercise_detail(page: Page, app_url: str):
|
|
"""Bold, italic, and list items in a description render as HTML elements in the exercise detail view."""
|
|
_wait_for_db(page, app_url)
|
|
_clean_all(page)
|
|
|
|
exercise_id = page.evaluate(
|
|
"""async () => {
|
|
const uuid = await window.__repos.settings.get('user_uuid');
|
|
return window.__repos.exercises.create({
|
|
title: 'MD Exercise',
|
|
description: '**bold** and *italic*\\n- item one',
|
|
created_by: uuid,
|
|
});
|
|
}"""
|
|
)
|
|
|
|
page.goto(f"{app_url}/#/exercises/{exercise_id}")
|
|
page.wait_for_selector('[data-testid="exercise-detail-description"]')
|
|
|
|
desc = page.locator('[data-testid="exercise-detail-description"]')
|
|
expect(desc.locator('strong')).to_have_text('bold')
|
|
expect(desc.locator('em')).to_have_text('italic')
|
|
expect(desc.locator('li').first).to_have_text('item one')
|
|
|
|
_clean_all(page)
|
|
|
|
|
|
def test_markdown_rendered_in_combo_detail(page: Page, app_url: str):
|
|
"""Bold text in a combo description renders as a <strong> element in the detail view."""
|
|
_wait_for_db(page, app_url)
|
|
_clean_all(page)
|
|
|
|
combo_id = page.evaluate(
|
|
"""async () => {
|
|
const uuid = await window.__repos.settings.get('user_uuid');
|
|
return window.__repos.combos.create({
|
|
title: 'MD Combo',
|
|
description: '**important**',
|
|
type: 'NONE',
|
|
exercises: [],
|
|
timer_config: {},
|
|
created_by: uuid,
|
|
});
|
|
}"""
|
|
)
|
|
|
|
page.goto(f"{app_url}/#/combos/{combo_id}")
|
|
page.wait_for_selector('[data-testid="combo-detail-description"]')
|
|
|
|
desc = page.locator('[data-testid="combo-detail-description"]')
|
|
expect(desc.locator('strong')).to_have_text('important')
|
|
|
|
_clean_all(page)
|
|
|
|
|
|
def test_markdown_rendered_in_session_detail(page: Page, app_url: str):
|
|
"""Bold text in a session description renders as a <strong> element in the detail view."""
|
|
_wait_for_db(page, app_url)
|
|
_clean_all(page)
|
|
|
|
session_id = page.evaluate(
|
|
"""async () => {
|
|
const uuid = await window.__repos.settings.get('user_uuid');
|
|
return window.__repos.sessions.create({
|
|
title: 'MD Session',
|
|
description: '**focus**',
|
|
items: [],
|
|
created_by: uuid,
|
|
});
|
|
}"""
|
|
)
|
|
|
|
page.goto(f"{app_url}/#/sessions/{session_id}")
|
|
page.wait_for_selector('[data-testid="session-detail-description"]')
|
|
|
|
desc = page.locator('[data-testid="session-detail-description"]')
|
|
expect(desc.locator('strong')).to_have_text('focus')
|
|
|
|
_clean_all(page)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# List view — description must not appear
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_description_absent_from_exercise_list(page: Page, app_url: str):
|
|
"""Exercise list cards do not display the description text."""
|
|
_wait_for_db(page, app_url)
|
|
_clean_all(page)
|
|
|
|
page.evaluate(
|
|
"""async () => {
|
|
const uuid = await window.__repos.settings.get('user_uuid');
|
|
await window.__repos.exercises.create({
|
|
title: 'No Desc Exercise',
|
|
description: 'should not be visible in list',
|
|
created_by: uuid,
|
|
});
|
|
}"""
|
|
)
|
|
|
|
page.click('a[href="#/exercises"]')
|
|
page.wait_for_timeout(500)
|
|
page.reload()
|
|
page.wait_for_function(
|
|
"document.documentElement.getAttribute('data-db-ready') === 'true'",
|
|
timeout=15000,
|
|
)
|
|
page.click('a[href="#/exercises"]')
|
|
page.wait_for_timeout(500)
|
|
|
|
expect(page.get_by_text('should not be visible in list')).not_to_be_visible()
|
|
|
|
_clean_all(page)
|
|
|
|
|
|
def test_description_absent_from_combo_list(page: Page, app_url: str):
|
|
"""Combo list cards do not display the description text."""
|
|
_wait_for_db(page, app_url)
|
|
_clean_all(page)
|
|
|
|
page.evaluate(
|
|
"""async () => {
|
|
const uuid = await window.__repos.settings.get('user_uuid');
|
|
await window.__repos.combos.create({
|
|
title: 'No Desc Combo',
|
|
description: 'combo desc not in list',
|
|
type: 'NONE',
|
|
exercises: [],
|
|
timer_config: {},
|
|
created_by: uuid,
|
|
});
|
|
}"""
|
|
)
|
|
|
|
page.click('a[href="#/combos"]')
|
|
page.wait_for_timeout(500)
|
|
page.reload()
|
|
page.wait_for_function(
|
|
"document.documentElement.getAttribute('data-db-ready') === 'true'",
|
|
timeout=15000,
|
|
)
|
|
page.click('a[href="#/combos"]')
|
|
page.wait_for_timeout(500)
|
|
|
|
expect(page.get_by_text('combo desc not in list')).not_to_be_visible()
|
|
|
|
_clean_all(page)
|
|
|
|
|
|
def test_description_absent_from_session_list(page: Page, app_url: str):
|
|
"""Session list cards do not display the description text."""
|
|
_wait_for_db(page, app_url)
|
|
_clean_all(page)
|
|
|
|
page.evaluate(
|
|
"""async () => {
|
|
const uuid = await window.__repos.settings.get('user_uuid');
|
|
await window.__repos.sessions.create({
|
|
title: 'No Desc Session',
|
|
description: 'session desc not in list',
|
|
items: [],
|
|
created_by: uuid,
|
|
});
|
|
}"""
|
|
)
|
|
|
|
page.click('a[href="#/sessions"]')
|
|
page.wait_for_timeout(500)
|
|
page.reload()
|
|
page.wait_for_function(
|
|
"document.documentElement.getAttribute('data-db-ready') === 'true'",
|
|
timeout=15000,
|
|
)
|
|
page.click('a[href="#/sessions"]')
|
|
page.wait_for_timeout(500)
|
|
|
|
expect(page.get_by_text('session desc not in list')).not_to_be_visible()
|
|
|
|
_clean_all(page)
|