""" 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 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 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)