Files
2026-07-13 13:12:33 +08:00

199 lines
7.5 KiB
Python

"""Static smoke tests for the WebUI agents view + new UI primitives."""
from pathlib import Path
AGENTS_JS = Path("src/opensquilla/gateway/static/js/views/agents.js")
AGENTS_CSS = Path("src/opensquilla/gateway/static/css/views/agents.css")
SESSIONS_JS = Path("src/opensquilla/gateway/static/js/views/sessions.js")
SESSIONS_CSS = Path("src/opensquilla/gateway/static/css/views/sessions.css")
COMPONENTS_JS = Path("src/opensquilla/gateway/static/js/components.js")
COMPONENTS_CSS = Path("src/opensquilla/gateway/static/css/components.css")
MOBILE_CSS = Path("src/opensquilla/gateway/static/css/mobile.css")
def test_agents_view_keeps_inline_create_form_and_drawer_for_view_edit() -> None:
source = AGENTS_JS.read_text(encoding="utf-8")
# Lightweight inline create form is the only create entry point.
assert 'id="agent-add-form"' in source
assert "_onInlineAdd" in source
assert "_rpc.call('agents.create'" in source
# View / edit goes through the drawer; create does NOT.
assert "_openAgentDrawer" in source
assert "UI.drawer(" in source
assert "data-edit-agent" in source
assert "data-customize-agent" in source
# No native confirm() dialogs (custom modal stand-in instead).
assert "confirm(" not in source
# Brain section was removed from the drawer (no Model / System prompt edit).
assert "data-bind=\"systemPrompt\"" not in source
assert "data-bind=\"model\"" not in source
# Update RPC still wired for the remaining editable fields.
assert "_rpc.call('agents.update'" in source
assert "_rpc.call('agents.delete'" in source
# Builtin agents render Customize… (which pre-fills the inline form).
assert "Customize" in source
def test_agents_view_css_has_inline_form_and_drawer_styles() -> None:
css = AGENTS_CSS.read_text(encoding="utf-8")
# Inline create form selectors are present.
assert ".ag-create" in css
assert ".ag-create__form" in css
# Drawer selectors used by view/edit are present.
assert ".ag-drawer__sections" in css
assert ".ag-drawer__section" in css
# Dead "live preview" rules were pruned.
assert ".ag-drawer__preview" not in css
assert ".ag-drawer__layout" not in css
def test_agents_inline_actions_keep_touch_friendly_hit_areas() -> None:
css = AGENTS_CSS.read_text(encoding="utf-8")
rule = css[css.index(".ag-iconbtn {") : css.index("}", css.index(".ag-iconbtn {"))]
input_start = css.index(".ag-input:not(textarea) {")
input_rule = css[input_start : css.index("}", input_start)]
assert "min-height: 40px" in rule
assert "height: 40px" in input_rule
def test_agents_card_header_stacks_actions_on_narrow_mobile() -> None:
css = AGENTS_CSS.read_text(encoding="utf-8")
mobile = css.split("@media (max-width: 420px)", 1)[1]
head_rule = mobile.split(".ag-card__head", 1)[1].split("}", 1)[0]
id_rule = mobile.split(".ag-card__id-block", 1)[1].split("}", 1)[0]
actions_rule = mobile.split(".ag-card__actions {", 1)[1].split("}", 1)[0]
action_button_rule = mobile.split(".ag-card__actions .ag-iconbtn", 1)[1].split(
"}", 1
)[0]
assert "flex-direction: column" in head_rule
assert "align-items: stretch" in head_rule
assert "flex-wrap: wrap" in id_rule
assert "width: 100%" in id_rule
assert "justify-content: stretch" in actions_rule
assert "flex: 1 1 96px" in action_button_rule
assert "justify-content: center" in action_button_rule
def test_agents_card_variable_copy_wraps_inside_mobile_cards() -> None:
css = AGENTS_CSS.read_text(encoding="utf-8")
for selector in (".ag-card__id {", ".ag-card__name {", ".ag-card__desc {"):
rule = css[css.index(selector) : css.index("}", css.index(selector))]
assert "max-width: 100%" in rule, selector
assert "min-width: 0" in rule, selector
assert "overflow-wrap: anywhere" in rule, selector
def test_agents_card_metadata_wraps_long_runtime_values() -> None:
css = AGENTS_CSS.read_text(encoding="utf-8")
rule = css[
css.index(".ag-card__meta dd {") : css.index(
"}", css.index(".ag-card__meta dd {")
)
]
assert "max-width: 100%" in rule
assert "min-width: 0" in rule
assert "white-space: normal" in rule
assert "overflow-wrap: anywhere" in rule
assert "text-overflow: clip" in rule
def test_global_buttons_keep_stable_hit_area() -> None:
css = COMPONENTS_CSS.read_text(encoding="utf-8")
btn_rule = css[css.index(".btn {") : css.index("}", css.index(".btn {"))]
sm_rule = css[css.index(".btn--sm {") : css.index("}", css.index(".btn--sm {"))]
icon_rule = css[css.index(".btn--icon {") : css.index("}", css.index(".btn--icon {"))]
mobile_css = MOBILE_CSS.read_text(encoding="utf-8")
mobile_sm_rule = mobile_css[
mobile_css.index(".btn--sm {") : mobile_css.index("}", mobile_css.index(".btn--sm {"))
]
assert "min-height: 40px" in btn_rule
assert "min-height: 40px" in sm_rule
assert "min-width: 40px" in icon_rule
assert "min-height: 40px" in icon_rule
assert "min-height: 40px" in mobile_sm_rule
def test_sessions_view_uses_combobox_and_admin_agent_create_rpc() -> None:
source = SESSIONS_JS.read_text(encoding="utf-8")
assert "UI.combobox(" in source
assert "_rpc.call('agents.create'" in source
assert "_rpc.call('sessions.create'" in source
assert "createAgentIfMissing" not in source
# Inline error rendering keeps the modal open on RPC failure.
assert "data-ns-error" in source
# Optional Model input was removed — sessions inherit the agent default.
assert 'id="ns-model"' not in source
# Orphan-agent badge wiring.
assert "_agentSubline" in source
assert "_agentsById" in source
assert "Orphaned" in source
def test_sessions_view_css_has_orphan_chip_styles() -> None:
css = SESSIONS_CSS.read_text(encoding="utf-8")
assert ".chip-warn" in css
assert ".sess-key__sub" in css
assert ".sess-key__agent--orphan" in css
def test_sessions_table_keeps_table_cells_structural() -> None:
source = SESSIONS_JS.read_text(encoding="utf-8")
css = SESSIONS_CSS.read_text(encoding="utf-8")
assert '<td class="sess-table__cell--key">' in source
assert '<div class="sess-table__key-content">' in source
key_cell_start = css.index(".sess-table__cell--key {")
key_cell_rule = css[key_cell_start : css.index("}", key_cell_start)]
assert "display:" not in key_cell_rule
assert ".sess-table__key-content" in css
assert "display: flex" in css[css.index(".sess-table__key-content") :]
def test_components_js_exposes_drawer_and_combobox() -> None:
source = COMPONENTS_JS.read_text(encoding="utf-8")
# Public exports on UI singleton.
assert "drawer," in source and "combobox," in source
# Promise-based drawer + beforeClose hook.
assert "beforeClose" in source
assert "result =" in source or "result:" in source
# Combobox create-on-miss API surface.
assert "allowCreate" in source
assert "onCreate" in source
assert "removeEventListener('mousedown'" in source
# Keyboard nav is wired (basic substring smoke).
assert "ArrowDown" in source and "ArrowUp" in source
def test_components_css_has_drawer_and_combobox_styles() -> None:
css = COMPONENTS_CSS.read_text(encoding="utf-8")
# Drawer scaffolding.
assert ".drawer-backdrop" in css
assert ".drawer__head" in css
assert ".drawer__body" in css
assert ".drawer__foot" in css
# Combobox scaffolding.
assert ".ui-combo" in css
assert ".ui-combo__list" in css
assert ".ui-combo__option--create" in css