Merge pull request #19 from o3LL/refactor/tests-read-the-whole-cascade

refactor(tests): read the whole cascade instead of style.css alone
This commit is contained in:
Alexandre Teixeira
2026-09-30 19:27:58 +01:00
committed by GitHub
72 changed files with 273 additions and 116 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -7,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_agent_thread_chevron_uses_css_shape_in_live_and_history_renderers():
live = (ROOT / "static/js/chat.js").read_text()
history = (ROOT / "static/js/chatRenderer.js").read_text()
css = (ROOT / "static/style.css").read_text()
css = app_css()
for src in (live, history):
assert 'class="agent-thread-chevron" aria-hidden="true"></span>' in src
+2 -3
View File
@@ -14,11 +14,10 @@ file identifies the breakpoint it belongs to.
import re
from pathlib import Path
from tests.helpers.stylesheets import app_css
CSS = (Path(__file__).resolve().parents[1] / "static" / "style.css").read_text(
encoding="utf-8"
)
CSS = app_css()
THREAD = r"^[ \t]*\.agent-thread[ \t]*\{"
RAIL = r"^[ \t]*\.agent-thread::before[ \t]*\{"
@@ -1,10 +1,11 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
CHAT = (ROOT / "static/js/chat.js").read_text()
SESSIONS = (ROOT / "static/js/sessions.js").read_text()
CSS = (ROOT / "static/style.css").read_text()
CSS = app_css()
def test_queued_prompts_are_persisted_per_session_and_restored_on_return():
+3 -2
View File
@@ -4,11 +4,12 @@ import subprocess
from pathlib import Path
import pytest
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
CALENDAR_JS = ROOT / "static" / "js" / "calendar.js"
STYLE_CSS = ROOT / "static" / "style.css"
STYLE_CSS_TEXT = app_css()
UTILS_JS = ROOT / "static" / "js" / "calendar" / "utils.js"
pytestmark = pytest.mark.skipif(not shutil.which("node"), reason="node binary not on PATH")
@@ -65,7 +66,7 @@ def test_calendar_readable_text_color_keeps_light_text_for_dark_colors():
def test_calendar_event_surfaces_use_computed_foreground_variable():
calendar_js = CALENDAR_JS.read_text(encoding="utf-8")
style_css = STYLE_CSS.read_text(encoding="utf-8")
style_css = STYLE_CSS_TEXT
utils_js = UTILS_JS.read_text(encoding="utf-8")
assert "_calReadableTextColor" in utils_js
+2 -1
View File
@@ -1,8 +1,9 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
LIBRARY = (ROOT / "static/js/documentLibrary.js").read_text(encoding="utf-8")
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -7,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_user_mode_pill_is_rendered_and_live_updated():
renderer = (ROOT / "static/js/chatRenderer.js").read_text(encoding="utf-8")
chat = (ROOT / "static/js/chat.js").read_text(encoding="utf-8")
styles = (ROOT / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
routes = (ROOT / "routes/chat_routes.py").read_text(encoding="utf-8")
helpers = (ROOT / "routes/chat_helpers.py").read_text(encoding="utf-8")
+2 -1
View File
@@ -12,6 +12,7 @@ import subprocess
import pytest
from src import chatgpt_subscription, llm_core
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).parents[1]
@@ -101,7 +102,7 @@ def test_model_picker_source_invariants():
def test_composer_reasoning_effort_ui_markup():
"""Verify static/index.html and static/style.css include reasoning effort controls."""
html = (ROOT / "static/index.html").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
# HTML elements
assert 'id="reasoning-effort-wrap"' in html
assert 'id="reasoning-effort-btn"' in html
+3 -2
View File
@@ -6,11 +6,12 @@ import subprocess
from pathlib import Path
import pytest
from tests.helpers.stylesheets import app_css
_REPO = Path(__file__).resolve().parent.parent
_MODULE = _REPO / "static" / "js" / "chatgptSubscriptionUsage.js"
_ADMIN = (_REPO / "static" / "js" / "admin.js").read_text(encoding="utf-8")
_STYLE = (_REPO / "static" / "style.css").read_text(encoding="utf-8")
_STYLE = app_css()
pytestmark = pytest.mark.skipif(not shutil.which("node"), reason="node not on PATH")
@@ -269,7 +270,7 @@ def test_refresh_and_reconnect_handlers_target_only_the_clicked_account():
def test_admin_renders_chatgpt_usage_collapsible_and_styled():
admin_source = (_REPO / "static" / "js" / "admin.js").read_text(encoding="utf-8")
style_source = (_REPO / "static" / "style.css").read_text(encoding="utf-8")
style_source = app_css()
load_block = admin_source[admin_source.index("async function loadEndpoints()"):admin_source.index("function initEndpointForm()")]
assert "adm-chatgpt-controls" in load_block
assert "adm-chatgpt-usage-toggle" in load_block
+5 -4
View File
@@ -1,5 +1,6 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
def test_compare_renders_ask_user_in_the_originating_pane():
@@ -80,7 +81,7 @@ def test_compare_pane_templates_hide_response_actions_until_response_exists():
root = Path(__file__).resolve().parents[1]
index = (root / "static/js/compare/index.js").read_text(encoding="utf-8")
panes = (root / "static/js/compare/panes.js").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
assert re.search(r"from './panes\.js\?v=[A-Za-z0-9_-]+'", index)
assert re.search(r"from './selector\.js\?v=[A-Za-z0-9_-]+'", index)
@@ -152,7 +153,7 @@ def test_compare_panes_have_visible_runtime_state_without_revealing_empty_action
root = Path(__file__).resolve().parents[1]
stream = (root / "static/js/compare/stream.js").read_text(encoding="utf-8")
panes = (root / "static/js/compare/panes.js").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
assert "_paneEl.classList.remove('is-done', 'is-failed', 'is-awaiting-input');" in stream
assert "_paneEl.classList.add('is-streaming');" in stream
@@ -179,7 +180,7 @@ def test_compare_panes_surface_compact_result_summary():
index = (root / "static/js/compare/index.js").read_text(encoding="utf-8")
panes = (root / "static/js/compare/panes.js").read_text(encoding="utf-8")
stream = (root / "static/js/compare/stream.js").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
assert 'pane-header-row pane-header-secondary' in index
assert 'class=\"pane-summary\" id=\"cmp-summary-' in index
@@ -203,7 +204,7 @@ def test_compare_panes_surface_compact_result_summary():
def test_compare_selector_surfaces_duplicate_warning_without_blocking_start():
root = Path(__file__).resolve().parents[1]
selector = (root / "static/js/compare/selector.js").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
assert "function _selectionKey(sel)" in selector
assert "function _duplicateSelectionKeys()" in selector
+4 -3
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parent.parent
@@ -32,7 +33,7 @@ def test_mobile_compare_reconciles_tabs_after_pane_lifecycle_changes():
def test_mobile_compare_css_shows_only_the_active_card():
css = _read("static/style.css")
css = app_css()
mobile = css[css.index("/* Compare uses one full-width response card on phones."):]
assert ".compare-mobile-tabs" in mobile
@@ -44,7 +45,7 @@ def test_mobile_compare_css_shows_only_the_active_card():
def test_probe_feedback_and_actions_use_the_shared_card_layout():
selector = _read("static/js/compare/selector.js")
css = _read("static/style.css")
css = app_css()
assert "probeFeedback.className = 'compare-probe-feedback'" in selector
assert "probeFeedback.appendChild(detail)" in selector
@@ -66,7 +67,7 @@ def test_probe_feedback_and_actions_use_the_shared_card_layout():
def test_probe_swap_reopens_and_highlights_the_failed_model_slot():
selector = _read("static/js/compare/selector.js")
css = _read("static/style.css")
css = app_css()
assert "function _expandModelSlot(slotIdx)" in selector
assert "row.dataset.slotIndex = String(idx);" in selector
+5 -4
View File
@@ -1,5 +1,6 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parent.parent
@@ -11,7 +12,7 @@ def _read(path: str) -> str:
def test_compare_shuffle_shows_center_notice_with_dice_icon():
panes = _read("static/js/compare/panes.js")
css = _read("static/style.css")
css = app_css()
assert "ICON_DICE" in panes
assert "compare-shuffle-notice" in panes
@@ -23,7 +24,7 @@ def test_compare_shuffle_shows_center_notice_with_dice_icon():
def test_compare_chat_and_agent_panes_expose_per_pane_inference_settings():
index = _read("static/js/compare/index.js")
panes = _read("static/js/compare/panes.js")
css = _read("static/style.css")
css = app_css()
assert "pane-settings-btn" in panes
assert "paneSettingsButtonHtml" in index
@@ -41,7 +42,7 @@ def test_compare_chat_and_agent_panes_expose_per_pane_inference_settings():
def test_compare_probe_control_has_requested_vertical_alignment():
index = _read("static/js/compare/index.js")
probe = _read("static/js/compare/probe.js")
css = _read("static/style.css")
css = app_css()
assert 'class="compare-check-icon"' in index
assert '<span class="compare-check-label">Probe</span>' in index
@@ -75,7 +76,7 @@ def test_compare_cache_key_bumped_for_shuffle_notice():
def test_compare_score_button_label_is_nudged_up():
vote = _read("static/js/compare/vote.js")
css = _read("static/style.css")
css = app_css()
assert '<span class="compare-score-label">Score</span>' in vote
assert ".compare-score-label" in css
+2 -1
View File
@@ -10,6 +10,7 @@ of the other tests in this suite.
"""
import re
from pathlib import Path
from tests.helpers.stylesheets import app_css
_REPO = Path(__file__).resolve().parent.parent
_INDEX = (_REPO / "static" / "index.html").read_text(encoding="utf-8")
@@ -47,7 +48,7 @@ def test_styled_confirm_and_prompt_are_modal_dialogs():
def test_styled_confirm_cancel_or_close_label_is_shifted_without_moving_button():
css = (_REPO / "static" / "style.css").read_text(encoding="utf-8")
css = app_css()
assert "cancelLabel.textContent = cancelText;" in _UI
assert "cancelBtn.appendChild(cancelLabel);" in _UI
+3 -1
View File
@@ -18,6 +18,7 @@ import re
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
SRC = Path(__file__).resolve().parent.parent / "static/js/documentLibrary.js"
@@ -96,7 +97,7 @@ def test_mobile_explicit_load_restores_full_editor_from_bottom_dock():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
const state = await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&mobile-library-open-test=1');
mod.init('/api');
@@ -134,6 +135,7 @@ def test_mobile_explicit_load_restores_full_editor_from_bottom_dock():
console.log(JSON.stringify(state));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=SRC.parents[1],
+2 -1
View File
@@ -1,12 +1,13 @@
"""Regression guards for document-selection references in chat bubbles."""
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
RENDERER = (ROOT / "static/js/chatRenderer.js").read_text(encoding="utf-8")
DOCUMENT = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
INDEX = (ROOT / "static/index.html").read_text(encoding="utf-8")
APP = (ROOT / "static/app.js").read_text(encoding="utf-8")
+2 -1
View File
@@ -9,11 +9,12 @@ document.js is browser-coupled and not importable in pytest.
"""
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE_CSS = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE_CSS = app_css()
def test_document_textarea_scrollbar_is_visible():
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -24,7 +25,7 @@ def test_mobile_rich_text_history_state_and_document_switch():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&history-controls-test=1');
window.documentModuleForTest = mod;
@@ -85,6 +86,7 @@ def test_mobile_rich_text_history_state_and_document_switch():
console.log(JSON.stringify({ initial, typed, undone, redone, switched, overflow }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+5 -2
View File
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
SOURCE = (ROOT / "static/js/documentLibrary.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_mobile_footer_exposes_delete_open_and_more():
@@ -53,7 +55,7 @@ def test_mobile_open_in_new_chat_copies_to_materialized_session():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 }, hasTouch: true });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
const state = await page.evaluate(async () => {
let currentSession = 'current-chat';
let createDirectCalls = 0;
@@ -106,6 +108,7 @@ def test_mobile_open_in_new_chat_copies_to_materialized_session():
console.log(JSON.stringify(state));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+6 -4
View File
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def _run_node(script: str):
@@ -41,7 +43,7 @@ def test_markdown_outline_parses_structure_and_ignores_fenced_code():
'#### Final `code` section',
].join('\n');
console.log(JSON.stringify(parseMarkdownOutline(source)));
"""
""".replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
)
assert [(entry["level"], entry["text"]) for entry in data] == [
(1, "Overview"),
@@ -71,7 +73,7 @@ def test_outline_jumps_in_markdown_and_rich_text_and_fits_mobile():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentOutline.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&outline-test=1');
mod.init('/api');
@@ -126,7 +128,7 @@ def test_outline_jumps_in_markdown_and_rich_text_and_fits_mobile():
console.log(JSON.stringify({ markdownLabels, selected, liveLabels, mobileBox, richLabels, richCaretHeading }));
await browser.close();
"""
""".replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
)
assert data["markdownLabels"] == ["Intro", "Details", "End"]
assert data["selected"] == "## Details"
+2 -1
View File
@@ -1,11 +1,12 @@
"""Regression guards for the Markdown preview hover-to-edit control."""
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE_CSS = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE_CSS = app_css()
def test_preview_installs_hover_edit_button():
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -26,7 +27,7 @@ def test_enter_creates_unchecked_task_and_empty_enter_exits_cleanly():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentOutline.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&checklist-enter-test=1');
mod.init('/api');
@@ -78,6 +79,7 @@ def test_enter_creates_unchecked_task_and_empty_enter_exits_cleanly():
console.log(JSON.stringify({ afterFirstEnter, ...data }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_color_controls_have_theme_reset_and_split_palettes():
@@ -33,7 +35,7 @@ def test_rich_colors_follow_theme_and_undo_as_one_edit():
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent(`<style>
:root { --fg:#d8dee9; --bg:#17191f; --panel:#20232b; --border:#444; --red:#e45b6c; --accent-primary:#e45b6c; }
</style><link rel="stylesheet" href="/static/style.css?rich-color-test=1">
</style>__ODY_STYLESHEETS__
<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>`);
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?rich-color-test=' + Date.now());
@@ -86,6 +88,7 @@ def test_rich_colors_follow_theme_and_undo_as_one_edit():
console.log(JSON.stringify({ palette, highlighted, highlightUndone, defaultColor, changedThemeColor, colorUndone }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+5 -2
View File
@@ -5,6 +5,7 @@ import subprocess
import tempfile
import zipfile
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -64,7 +65,7 @@ def test_browser_word_export_contains_native_rich_docx_ooxml():
contentType: 'image/png',
body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64'),
}}));
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {{
const mod = await import('/static/js/document.js?v=20260831richtexttools91&docx-export-test=1');
mod.init('/api');
@@ -89,6 +90,7 @@ def test_browser_word_export_contains_native_rich_docx_ooxml():
}}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -158,7 +160,7 @@ def test_browser_markdown_word_export_keeps_heading_and_inline_formatting():
acceptDownloads: true,
}});
await page.goto(`${{process.env.ODYSSEUS_TEST_STATIC_ORIGIN}}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {{
const mod = await import('/static/js/document.js?v=20260831richtexttools91&markdown-docx-export-test=1');
mod.init('/api');
@@ -180,6 +182,7 @@ def test_browser_markdown_word_export_keeps_heading_and_inline_formatting():
console.log(JSON.stringify({{ failure: await download.failure() }}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -25,7 +26,7 @@ def test_find_rejects_cross_block_matches_but_supports_inline_matches_and_replac
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentOutline.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&find-boundaries-test=1');
mod.init('/api');
@@ -73,6 +74,7 @@ def test_find_rejects_cross_block_matches_but_supports_inline_matches_and_replac
console.log(JSON.stringify({ crossParagraph, crossBreak, crossInline, ...data }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -6,6 +6,7 @@ import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -94,7 +95,7 @@ def test_numeric_font_size_and_custom_colors_work_on_desktop_and_mobile():
async function exercise(viewport, suffix) {
const page = await browser.newPage({ viewport });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async suffix => {
const mod = await import(`/static/js/document.js?v=20260831richtexttools91&font-color=${suffix}`);
mod.init('/api');
@@ -190,6 +191,7 @@ def test_numeric_font_size_and_custom_colors_work_on_desktop_and_mobile():
console.log(JSON.stringify({ desktop, mobile }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+4 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -29,7 +30,7 @@ def test_mobile_heading_enter_exits_cleanly_and_is_one_step_undoable():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&heading-enter=1');
mod.init('/api');
@@ -73,6 +74,7 @@ def test_mobile_heading_enter_exits_cleanly_and_is_one_step_undoable():
console.log(JSON.stringify({ entered, undone, redone }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -152,6 +154,7 @@ def test_heading_enter_preserves_shift_middle_and_empty_heading_semantics():
console.log(JSON.stringify(state));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -5,6 +5,7 @@ import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -36,7 +37,7 @@ def test_mobile_image_caption_survives_resize_history_and_empty_removal():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&image-caption=1');
mod.init('/api');
@@ -116,6 +117,7 @@ def test_mobile_image_caption_survives_resize_history_and_empty_removal():
console.log(JSON.stringify({ added, undone, redone, resized, removed, removalUndone, menuRect, overflow }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -5,6 +5,7 @@ import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -28,7 +29,7 @@ def test_typing_markers_converts_blocks_and_preserves_following_text():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentOutline.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&input-rules-test=1');
mod.init('/api');
@@ -101,6 +102,7 @@ def test_typing_markers_converts_blocks_and_preserves_following_text():
console.log(JSON.stringify(data));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -16,7 +17,7 @@ def test_rich_document_shortcuts_work_at_desktop_and_mobile_widths():
async function exercise(viewport, suffix) {
const page = await browser.newPage({ viewport });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async suffix => {
const mod = await import(`/static/js/document.js?v=20260831richtexttools91&keyboard-shortcuts=${suffix}`);
mod.init('/api');
@@ -94,6 +95,7 @@ def test_rich_document_shortcuts_work_at_desktop_and_mobile_widths():
console.log(JSON.stringify({ desktop, mobile }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_selection_toolbar_uses_shared_formatting_path_and_preserves_range():
@@ -26,7 +28,7 @@ def test_selection_toolbar_formats_and_stays_inside_desktop_and_mobile_viewports
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentOutline.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&selection-toolbar-test=1');
mod.init('/api');
@@ -102,6 +104,7 @@ def test_selection_toolbar_formats_and_stays_inside_desktop_and_mobile_viewports
console.log(JSON.stringify({ activeBold, desktopGeometry, formattedHtml, selectedAfterFormat, dismissed, mobileGeometry }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+5 -2
View File
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_slash_menu_reuses_rich_text_actions_and_is_accessible():
@@ -41,7 +43,7 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentOutline.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&slash-menu-test=1');
mod.init('/api');
@@ -149,6 +151,7 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
console.log(JSON.stringify({ filtered, heading, noHeadingQuery, tableFiltered, table, noTableQuery, mobile, homeOptions, homeLabel, escaped, slashRemains, cleanedAria }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -14,7 +15,7 @@ def test_rich_url_paste_links_selections_and_plain_urls_without_unsafe_autolinks
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&smart-link-paste=1');
mod.init('/api');
@@ -133,6 +134,7 @@ def test_rich_url_paste_links_selections_and_plain_urls_without_unsafe_autolinks
console.log(JSON.stringify({ selectedLink, storedAfterPaste, afterUndo, afterRedoLinks, caretLink, plainResults, emailLink, crossBlock }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+5 -2
View File
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_heading_levels_and_page_break_are_exposed_everywhere():
@@ -44,7 +46,7 @@ def test_mobile_headings_page_break_history_and_persistence():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&structure-tools-test=1');
window.__structureToolsDocModule = mod;
@@ -152,6 +154,7 @@ def test_mobile_headings_page_break_history_and_persistence():
}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -31,7 +32,7 @@ def test_mobile_table_cell_alignment_tracks_state_and_native_history():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&table-cell-alignment=1');
mod.init('/api');
@@ -122,6 +123,7 @@ def test_mobile_table_cell_alignment_tracks_state_and_native_history():
}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -36,7 +37,7 @@ def test_mobile_structural_edits_preserve_header_modes_and_history():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&table-header-preservation-test=1');
mod.init('/api');
@@ -119,6 +120,7 @@ def test_mobile_structural_edits_preserve_header_modes_and_history():
}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -40,7 +41,7 @@ def test_mobile_header_row_and_column_toggle_independently_with_undo():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&table-header-test=1');
mod.init('/api');
@@ -117,6 +118,7 @@ def test_mobile_header_row_and_column_toggle_independently_with_undo():
console.log(JSON.stringify({ initial, menuRect, columnOn, undone, redone, bothOn, rowOff, overflow }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -34,7 +35,7 @@ def test_mobile_merge_split_round_trip_preserves_headers_formatting_and_history(
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&table-merge-split=1');
mod.init('/api');
@@ -138,6 +139,7 @@ def test_mobile_merge_split_round_trip_preserves_headers_formatting_and_history(
}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -31,7 +32,7 @@ def test_mobile_table_tab_navigation_row_creation_and_history():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&table-tab-history-test=1');
mod.init('/api');
@@ -98,6 +99,7 @@ def test_mobile_table_tab_navigation_row_creation_and_history():
console.log(JSON.stringify({ forward, backward, appended, undone, redone, overflow }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+9 -4
View File
@@ -3,11 +3,13 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import app_css
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_rich_toolbar_menus_expose_keyboard_and_context_state():
@@ -58,7 +60,7 @@ def test_mobile_toolbar_uses_native_momentum_and_distinct_activation_tokens():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 }, hasTouch: true });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&toggle-test=1');
mod.init('/api');
@@ -101,6 +103,7 @@ def test_mobile_toolbar_uses_native_momentum_and_distinct_activation_tokens():
console.log(JSON.stringify({ opened, closed, sliderOpened, sliderClosed, pickerOpened, colorClosed, before, scroll }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -127,7 +130,7 @@ def test_mobile_toolbar_menu_preserves_selection_and_restores_focus():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&toolbar-menu-test=1');
mod.init('/api');
@@ -204,6 +207,7 @@ def test_mobile_toolbar_menu_preserves_selection_and_restores_focus():
}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
@@ -242,7 +246,7 @@ def test_rich_toolbar_menus_track_live_formatting_values():
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&toolbar-state-test=1');
mod.init('/api');
@@ -322,6 +326,7 @@ def test_rich_toolbar_menus_track_live_formatting_values():
}));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -38,7 +39,7 @@ def test_ctrl_s_saves_rich_text_immediately_once_and_updates_status():
});
});
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&save-shortcut-test=1');
mod.init('/api');
@@ -71,6 +72,7 @@ def test_ctrl_s_saves_rich_text_immediately_once_and_updates_status():
console.log(JSON.stringify({ dirty, saved, badge, requests }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -32,7 +33,7 @@ def test_save_status_is_dirty_race_safe_and_reports_failures():
active -= 1;
});
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=20260831richtexttools91&save-status-test=1');
mod.init('/api');
@@ -81,6 +82,7 @@ def test_save_status_is_dirty_race_safe_and_reports_failures():
console.log(JSON.stringify({ initial, dirty, duringSave, afterSave, afterFailure, bodies, maxActive, footer, mobile }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+2 -1
View File
@@ -1,11 +1,12 @@
"""Regression guards for exact inline document-suggestion highlighting."""
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE_CSS = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE_CSS = app_css()
def test_suggestion_highlight_measures_exact_referenced_range():
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -6,7 +7,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_toolbar_arrows_have_real_flex_slots_outside_icon_scroller():
script = (ROOT / "static/js/document.js").read_text()
styles = (ROOT / "static/style.css").read_text()
styles = app_css()
leading = script.index('class="md-toolbar-leading-controls"')
left_arrow = script.index('id="md-scroll-left"')
+3 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -38,7 +39,7 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
async function inspect(viewport, suffix) {
const page = await browser.newPage({ viewport });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css?v=20260831richtexttools91"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async suffix => {
const mod = await import(`/static/js/document.js?v=20260831richtexttools91&toolbar-order=${suffix}`);
mod.init('/api');
@@ -79,6 +80,7 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
console.log(JSON.stringify({ desktop, mobile }));
await browser.close();
"""
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -49,7 +50,7 @@ def test_group_rows_support_keyboard_selection():
def test_keyboard_focus_is_visible_for_layer_and_group_rows():
styles = (ROOT / "static/style.css").read_text()
styles = app_css()
assert ".ge-layer-item:focus-visible," in styles
assert ".ge-layer-group-row:focus-visible" in styles
+2 -1
View File
@@ -1,9 +1,10 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
EDITOR = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_quick_edit_has_persistent_expanded_identity_and_labeled_input():
+2 -1
View File
@@ -1,11 +1,12 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
TOPBAR = (ROOT / "static/js/editor/build/topbar.js").read_text(encoding="utf-8")
OVERFLOW = (ROOT / "static/js/editor/wire-topbar-overflow.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_primary_editor_text_actions_use_stacked_toolbar_contract():
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -6,7 +7,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_folder_chip_stays_with_date_and_moves_down():
source = (ROOT / "static" / "js" / "emailLibrary.js").read_text()
css = (ROOT / "static" / "style.css").read_text()
css = app_css()
assert 'class="email-meta-date-group"' in source
group_markup = source[source.index('class="email-meta-date-group"'):][:180]
+2 -1
View File
@@ -1,7 +1,8 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
CSS = (Path(__file__).parents[1] / "static" / "style.css").read_text(encoding="utf-8")
CSS = app_css()
def _rule(selector: str) -> str:
+2 -1
View File
@@ -5,11 +5,12 @@ import shutil
import subprocess
from pathlib import Path
import pytest
from tests.helpers.stylesheets import app_css
_REPO = Path(__file__).resolve().parent.parent
_ADMIN_JS = _REPO / "static" / "js" / "admin.js"
_ADMIN = _ADMIN_JS.read_text(encoding="utf-8")
_STYLE = (_REPO / "static" / "style.css").read_text(encoding="utf-8")
_STYLE = app_css()
pytestmark = pytest.mark.skipif(not shutil.which("node"), reason="node not on PATH")
+4 -1
View File
@@ -3,6 +3,7 @@
import json
import subprocess
from pathlib import Path
from tests.helpers.stylesheets import stylesheet_link_tags
ROOT = Path(__file__).resolve().parents[1]
@@ -14,7 +15,7 @@ def test_rich_escape_closes_toolbar_then_selection_badge() -> None:
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`${process.env.ODYSSEUS_TEST_STATIC_ORIGIN}/static/js/documentStats.js`);
await page.setContent('<link rel="stylesheet" href="/static/style.css"><div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.setContent('__ODY_STYLESHEETS__<div id="toast"></div><div id="chat-container"></div><div id="sidebar"></div>');
await page.evaluate(async () => {
const mod = await import('/static/js/document.js?v=escape-regression-1');
mod.init('/api');
@@ -55,6 +56,7 @@ def test_rich_escape_closes_toolbar_then_selection_badge() -> None:
console.log(JSON.stringify({ before, afterOne, afterTwo }));
await browser.close();
'''
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
['node', '--input-type=module', '-e', script],
cwd=ROOT,
@@ -114,6 +116,7 @@ def test_email_escape_closes_inner_states_without_closing_library() -> None:
console.log(JSON.stringify({ settings, select, reading }));
await browser.close();
'''
script = script.replace("__ODY_STYLESHEETS__", stylesheet_link_tags())
result = subprocess.run(
['node', '--input-type=module', '-e', script],
cwd=ROOT,
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -15,7 +16,7 @@ def test_image_settings_only_list_online_served_image_models():
def test_deep_research_fields_are_constrained_to_their_card():
styles = (ROOT / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
rule = styles[styles.index("/* Deep Research uses long labels"):]
assert ".admin-card:has(#set-researchSearch) .settings-row { min-width: 0; }" in rule
@@ -1,11 +1,12 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
CHAT = (ROOT / "static/js/chat.js").read_text()
RENDERER = (ROOT / "static/js/chatRenderer.js").read_text()
STYLE = (ROOT / "static/style.css").read_text()
STYLE = app_css()
APP = (ROOT / "static/app.js").read_text()
INDEX = (ROOT / "static/index.html").read_text()
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -6,7 +7,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_search_popup_freezes_offset_before_mobile_keyboard_focus() -> None:
source = (ROOT / "static/js/search-chat.js").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
set_offset = source.index("--search-overlay-top")
show_overlay = source.index("overlay.classList.remove('hidden')", set_offset)
+3 -2
View File
@@ -1,11 +1,12 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
def test_mobile_keyboard_fades_welcome_without_moving_it() -> None:
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
init_js = (ROOT / "static/js/init.js").read_text(encoding="utf-8")
kb_rule = css.split("#welcome-screen.kb-hidden {", 1)[1].split("}", 1)[0]
@@ -22,7 +23,7 @@ def test_mobile_keyboard_fades_welcome_without_moving_it() -> None:
def test_nobody_label_collapses_by_width_not_keyboard_height() -> None:
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
label_rule = css.rsplit(".incognito-btn .incognito-label {", 1)[0]
media_header = label_rule.rsplit("@media", 1)[1].split("{", 1)[0]
+2 -1
View File
@@ -1,7 +1,8 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
CSS = Path("static/style.css").read_text(encoding="utf-8")
CSS = app_css()
INIT_JS = Path("static/js/init.js").read_text(encoding="utf-8")
+6 -5
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -6,7 +7,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_notes_sidebar_new_action_matches_library_structure_and_animation():
html = (ROOT / "static/index.html").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert 'class="list-item-plus-btn sidebar-new-item-btn" id="notes-new-note-btn"' in html
assert '<span class="list-item-plus-label">new</span>' in html[html.index('id="notes-new-note-btn"'):]
@@ -51,7 +52,7 @@ def test_checklist_preview_starts_at_first_unfinished_item():
def test_checklist_preview_releases_scroll_at_its_boundaries():
notes = (ROOT / "static/js/notes.js").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert "el.addEventListener('wheel'" in notes
assert "el.closest('.notes-pane-body')" in notes
@@ -60,7 +61,7 @@ def test_checklist_preview_releases_scroll_at_its_boundaries():
def test_notes_tags_toggle_is_nudged_down():
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
rule = css[css.index(".notes-search-bar .notes-label-toggle {"):]
rule = rule[:rule.index("}")]
@@ -68,7 +69,7 @@ def test_notes_tags_toggle_is_nudged_down():
def test_notes_body_and_tag_arrow_are_nudged_down():
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert css.count("padding: 10px 8px 8px") >= 2
assert ".notes-pane-body .doclib-chip-scroll-arrow.right" in css
@@ -76,7 +77,7 @@ def test_notes_body_and_tag_arrow_are_nudged_down():
def test_notes_tag_strip_cannot_grow_into_blank_space():
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
rule = css[css.index(".notes-pane-body > .doclib-chip-scroll-frame:has(> .notes-labels-bar) {"):]
rule = rule[:rule.index("}")]
+5 -2
View File
@@ -16,6 +16,7 @@ import textwrap
from pathlib import Path
import pytest
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -100,9 +101,11 @@ def test_late_routed_dropdowns_use_top_portal_z(rel):
assert "topPortalZ()" in src, f"{rel} must call topPortalZ() for its dropdown z"
@pytest.mark.parametrize("rel", ["static/js/tasks.js", "static/js/skills.js", "static/style.css"])
@pytest.mark.parametrize("rel", ["static/js/tasks.js", "static/js/skills.js", "app-css"])
def test_no_hardcoded_portal_z_literals_remain(rel):
src = (ROOT / rel).read_text()
# "app-css" is the whole cascade: a moved rule must not escape this check
# by landing in a stylesheet this list does not name.
src = app_css() if rel == "app-css" else (ROOT / rel).read_text()
# Match the exact 100000/100002 these dropdowns used; the trailing-digit
# guard avoids false-matching an unrelated 1000000 elsewhere.
hits = re.findall(r"z-index:\s*10000[02](?!\d)", src)
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -6,7 +7,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_research_settings_use_custom_pickers_and_shared_provider_icons():
panel = (ROOT / "static/js/research/panel.js").read_text(encoding="utf-8")
style = (ROOT / "static/style.css").read_text(encoding="utf-8")
style = app_css()
settings = (ROOT / "static/js/settings.js").read_text(encoding="utf-8")
icons = (ROOT / "static/js/searchProviderIcons.js").read_text(encoding="utf-8")
+2 -1
View File
@@ -9,6 +9,7 @@ from pathlib import Path
from types import SimpleNamespace
from routes.history.history_routes import _keep_count_before_message
from tests.helpers.stylesheets import app_css
_REPO = Path(__file__).resolve().parent.parent
@@ -76,7 +77,7 @@ def test_footer_resend_uses_default_replacement_behavior():
def test_footer_resend_uses_round_svg_icon_not_text_glyph():
renderer = _CHAT_RENDERER_JS.read_text(encoding="utf-8")
style = (_REPO / "static" / "style.css").read_text(encoding="utf-8")
style = app_css()
assert "const RESEND_ICON =" in renderer
assert "resend-message-icon" in renderer
+3 -4
View File
@@ -1,11 +1,10 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
STYLE_CSS = Path(__file__).resolve().parents[1] / "static" / "style.css"
STYLE_CSS_TEXT = app_css()
def _style_text() -> str:
return STYLE_CSS.read_text(encoding="utf-8")
return STYLE_CSS_TEXT
def test_native_select_options_use_theme_tokens():
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -6,7 +7,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_selection_overlays_have_individual_clear_controls():
js = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert "function clearSelectionAt(index)" in js
assert "className = 'doc-selection-overlay-clear'" in js
+3 -4
View File
@@ -14,6 +14,7 @@ import subprocess
from pathlib import Path
import pytest
from tests.helpers.stylesheets import app_css
_REPO = Path(__file__).resolve().parent.parent
@@ -22,11 +23,9 @@ _COORDINATOR_HELPER = (
_REPO / "tests" / "helpers" / "test_settings_shell_coordinator.mjs"
)
_HAS_NODE = shutil.which("node") is not None
_STYLE = _REPO / "static" / "style.css"
_STYLE_TEXT = app_css()
def test_settings_desktop_width_targets_settings_not_cookbook():
source = _STYLE.read_text(encoding="utf-8")
source = _STYLE_TEXT
settings_rule = re.search(
r"(?ms)^\.settings-modal-content\s*\{[^}]*"
+2 -1
View File
@@ -1,8 +1,9 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
INDEX = (ROOT / "static/index.html").read_text(encoding="utf-8")
CHIP_SCROLL = (ROOT / "static/js/chipScroll.js").read_text(encoding="utf-8")
@@ -1,11 +1,12 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parent.parent
def test_sidebar_chat_list_uses_content_height_when_collapsed():
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
desktop = css.split("#sessions-section #session-list {", 1)[1].split("}", 1)[0]
mobile = css.split("#sessions-section #session-list {", 2)[2].split("}", 1)[0]
+6 -5
View File
@@ -1,5 +1,6 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -102,7 +103,7 @@ def test_markdown_flattens_legacy_notes_more_details():
def test_markdown_expands_note_and_skill_more_links_without_details():
markdown_src = (ROOT / "static/js/markdown.js").read_text()
style_src = (ROOT / "static/style.css").read_text()
style_src = app_css()
assert "function extractMoreListPayloads" in markdown_src
assert "ody-more-(notes|skills|memories|events|sessions)" in markdown_src
@@ -119,7 +120,7 @@ def test_markdown_expands_note_and_skill_more_links_without_details():
def test_terminal_skill_listing_uses_clickable_bounded_formatter():
agent_src = (ROOT / "src/agent_loop.py").read_text()
style_src = (ROOT / "static/style.css").read_text()
style_src = app_css()
assert "_qwen_skills_terminal_summary = _skills_list_summary_from_tool_output(" in agent_src
assert '.msg-ai .body a[href^="#skill-"]' in style_src
@@ -127,7 +128,7 @@ def test_terminal_skill_listing_uses_clickable_bounded_formatter():
def test_list_links_share_compact_typography():
style_src = (ROOT / "static/style.css").read_text()
style_src = app_css()
assert 'a.chat-link[href^="#events-more-"]' in style_src
assert "font-size: 11px;" in style_src
@@ -145,7 +146,7 @@ def test_open_skills_also_persists_skill_list():
def test_memory_deeplink_scrolls_and_flashes_even_when_filtered():
memory_src = (ROOT / "static/js/memory.js").read_text()
style_src = (ROOT / "static/style.css").read_text()
style_src = app_css()
assert "function _resetMemoryDeepLinkFilters()" in memory_src
assert "activeCategory = 'all';" in memory_src
@@ -158,7 +159,7 @@ def test_memory_deeplink_scrolls_and_flashes_even_when_filtered():
def test_skill_and_memory_links_use_polished_chat_link_style():
style_src = (ROOT / "static/style.css").read_text()
style_src = app_css()
assert '.msg-ai .body a[href^="#skill-"]' in style_src
assert '.msg-ai .body a[href^="#memory-"]' in style_src
@@ -1,11 +1,12 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
CHAT = (ROOT / "static/js/chat.js").read_text(encoding="utf-8")
UI = (ROOT / "static/js/ui.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
STYLE = app_css()
def test_terminal_and_canonical_renders_preserve_chat_scroll_anchor():
+54
View File
@@ -0,0 +1,54 @@
"""Tests must reason about the whole cascade, not one file of it.
``static/style.css`` is being decomposed. A test that reads that file alone,
or builds a synthetic page linking only that file, silently loses every rule
that has moved: it keeps passing while covering less. Both mistakes existed
and are cheap to detect, so this fails on either.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SELF = Path(__file__).name
# The helper module and the manifest/snapshot tests are about the stylesheet
# set itself, so naming the file is the point rather than a mistake.
ALLOWED = {SELF, "test_static_stylesheet_manifest.py", "test_css_computed_style_snapshot.py"}
_DIRECT_READ = re.compile(r'["\']static/style\.css["\']|"static"\s*/\s*"style\.css"')
_LONE_LINK = re.compile(r'<link[^>]*href="/static/style\.css')
def _test_sources():
return [p for p in sorted((ROOT / "tests").glob("*.py")) if p.name not in ALLOWED]
def test_sources_are_discoverable() -> None:
"""Guard the guard: a layout change must not make this vacuous."""
assert len(_test_sources()) > 100
def test_no_test_reads_style_css_as_the_whole_cascade() -> None:
offenders = [
p.name for p in _test_sources()
if _DIRECT_READ.search(p.read_text(encoding="utf-8"))
]
assert offenders == [], (
"read the cascade with tests.helpers.stylesheets.app_css() instead of "
f"static/style.css alone: {offenders}"
)
def test_no_synthetic_page_links_style_css_alone() -> None:
offenders = [
p.name for p in _test_sources()
if _LONE_LINK.search(p.read_text(encoding="utf-8"))
]
assert offenders == [], (
"build synthetic pages with tests.helpers.stylesheets.stylesheet_link_tags() "
f"so they get every stylesheet index.html loads: {offenders}"
)
+3 -4
View File
@@ -1,11 +1,10 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
STYLE = Path(__file__).resolve().parents[1] / "static" / "style.css"
STYLE_TEXT = app_css()
def test_mobile_sidebar_uses_the_sidebar_theme_surface():
css = STYLE.read_text()
css = STYLE_TEXT
mobile_drawer = css[css.index("/* Sidebar overlays chat on mobile */"):css.index("/* Backdrop behind sidebar */")]
assert "background: var(--sidebar-bg, var(--panel)) !important;" in mobile_drawer
assert "background: var(--panel) !important;" not in mobile_drawer
+3 -2
View File
@@ -1,6 +1,7 @@
"""Regression coverage for transient toast polish and accurate welcome tips."""
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parent.parent
@@ -9,7 +10,7 @@ ROOT = Path(__file__).resolve().parent.parent
def test_notification_history_does_not_clutter_navigation_or_capture_toasts():
html = (ROOT / "static/index.html").read_text(encoding="utf-8")
ui = (ROOT / "static/js/ui.js").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert 'id="rail-notifications"' not in html
assert 'id="sidebar-notifications-btn"' not in html
@@ -32,7 +33,7 @@ def test_action_hint_is_part_of_action_button_and_close_is_grouped_beside_it():
def test_welcome_tips_are_plain_and_brief():
html = (ROOT / "static/index.html").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert "Tip: Ctrl+K searches chats." in html
assert "Tip: Ctrl+Alt+B toggles the sidebar." in html
+3 -2
View File
@@ -1,5 +1,6 @@
from pathlib import Path
import re
from tests.helpers.stylesheets import app_css
def test_tool_approval_bypasses_polymorphic_send_button_actions():
@@ -22,7 +23,7 @@ def test_tool_approval_bypasses_polymorphic_send_button_actions():
def test_ask_user_card_has_no_close_button_and_chat_scale_text():
root = Path(__file__).resolve().parents[1]
renderer = (root / "static/js/chatRenderer.js").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
assert "closeBtn.className = 'modal-close ask-user-close';" not in renderer
assert "closeBtn.setAttribute('aria-label', 'Dismiss question');" not in renderer
@@ -64,7 +65,7 @@ def test_ask_user_card_has_no_close_button_and_chat_scale_text():
def test_scroll_bottom_button_uses_dropdown_caret_glyph():
root = Path(__file__).resolve().parents[1]
html = (root / "static/index.html").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8")
styles = app_css()
assert 'class="scroll-nav-caret"' in html
assert "&#9662;" in html
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import Path
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -7,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_tool_header_has_semantic_icons_for_live_and_saved_calls():
chat = (ROOT / "static/js/chat.js").read_text(encoding="utf-8")
renderer = (ROOT / "static/js/chatRenderer.js").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
css = app_css()
assert "manage_calendar" in renderer
assert "manage_memory" in renderer
+2 -1
View File
@@ -5,6 +5,7 @@ import pytest
from src.deep_research import CATEGORY_PROMPTS, _infer_research_category
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.visual_report import _standard_visual_variant
from tests.helpers.stylesheets import app_css
ROOT = Path(__file__).resolve().parents[1]
@@ -20,7 +21,7 @@ def _tool_schema(name: str) -> dict:
def test_visual_research_mode_is_removed_from_ui_prompt_and_tool_schema():
panel_source = (ROOT / "static/js/research/panel.js").read_text(encoding="utf-8")
style_source = (ROOT / "static/style.css").read_text(encoding="utf-8")
style_source = app_css()
routes_source = (ROOT / "routes/research/research_routes.py").read_text(encoding="utf-8")
category_schema = _tool_schema("trigger_research")["parameters"]["properties"]["category"]
rounds_schema = _tool_schema("trigger_research")["parameters"]["properties"]["max_rounds"]