Files
hermes-webui/tests/test_reasoning_chip_js_behaviour.py
T
nesquena-hermes c6370235be feat(composer): fit footer controls by overflow (#4657, rebased onto master)
Rebased Paladin173's #4657 onto current master. The per-control visibility
toggles half already shipped via his own #4598, so this rebase carries only the
net-new JS fit-by-overflow engine (_fitComposerFooter: ResizeObserver +
MutationObserver measure .composer-left overflow, step full -> cf-icons ->
cf-burger). Co-authored-by: Paladin173 <Paladin173@users.noreply.github.com>
2026-06-29 01:19:02 +00:00

204 lines
7.2 KiB
Python

"""Behavioural tests that drive the actual `_applyReasoningChip()` from
static/ui.js via node, not just a regex over the source.
The static checks in test_reasoning_chip_btw_fixes.py confirm the *shape*
of the function (no `display='none'`, the right toggle call exists, etc.)
but they pass even if a runtime detail is wrong — e.g. if `inactive` were
inverted, or `_normalizeReasoningEffort` mishandled whitespace, or the
label fell through to a wrong value for an unknown input.
This file pins the actual rendered output for every effort state so the
chip's None/Default visibility cannot silently regress.
"""
import os
import shutil
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).parent.parent.resolve()
UI_JS_PATH = REPO_ROOT / "static" / "ui.js"
NODE = shutil.which("node")
pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH")
_DRIVER_SRC = r"""
const fs = require('fs');
const src = fs.readFileSync(process.argv[2], 'utf8');
function makeEl() {
return {
style: {},
_attrs: {},
setAttribute(k, v){this._attrs[k] = String(v)},
getAttribute(k){return this._attrs[k]},
classList: {
_set: new Set(),
add(c){this._set.add(c)},
remove(c){this._set.delete(c)},
toggle(c, on){
const want = on === undefined ? !this._set.has(c) : Boolean(on);
if (want) this._set.add(c); else this._set.delete(c);
},
contains(c){return this._set.has(c)},
},
dataset: {},
title: '',
textContent: '',
querySelectorAll(){return []},
};
}
const els = {
composerReasoningWrap: makeEl(),
composerReasoningLabel: makeEl(),
composerReasoningChip: makeEl(),
composerReasoningDropdown: makeEl(),
};
els.composerReasoningWrap.style.display = 'none'; // mirrors the HTML default
global.window = {};
global.document = {
createElement: () => makeEl(),
addEventListener: () => {},
querySelectorAll: () => [],
querySelector: () => null,
};
global.$ = id => els[id] || null;
global.api = () => ({ then: () => ({ catch: () => {} }), catch: () => {} });
function extractFunc(name) {
const re = new RegExp('function\\s+' + name + '\\s*\\(');
const start = src.search(re);
if (start < 0) throw new Error(name + ' not found');
let i = src.indexOf('{', start);
let depth = 1; i++;
while (depth > 0 && i < src.length) {
if (src[i] === '{') depth++;
else if (src[i] === '}') depth--;
i++;
}
return src.slice(start, i);
}
eval(extractFunc('_normalizeReasoningEffort'));
eval(extractFunc('_formatReasoningEffortLabel'));
eval(extractFunc('_highlightReasoningOption'));
eval(extractFunc('_applyReasoningChip'));
const input = JSON.parse(process.argv[3]);
_applyReasoningChip(input);
const result = {
display: els.composerReasoningWrap.style.display,
label: els.composerReasoningLabel.textContent,
inactive: els.composerReasoningChip.classList.contains('inactive'),
title: els.composerReasoningChip.title,
};
process.stdout.write(JSON.stringify(result));
"""
@pytest.fixture(scope="module")
def driver_path(tmp_path_factory):
p = tmp_path_factory.mktemp("reasoning_driver") / "driver.js"
p.write_text(_DRIVER_SRC, encoding="utf-8")
return str(p)
def _apply(driver_path, value):
"""Run _applyReasoningChip(value) against the actual ui.js."""
import json as _json
result = subprocess.run(
[NODE, driver_path, str(UI_JS_PATH), _json.dumps(value)],
capture_output=True, text=True, timeout=30,
)
if result.returncode != 0:
raise RuntimeError(f"node driver failed: {result.stderr}")
return _json.loads(result.stdout)
# ─────────────────────────────────────────────────────────────────────────────
# The chip MUST stay visible for every effort state (issue #1068). This used
# to be hidden for !eff and 'none', and the source-regex tests in
# test_reasoning_chip_btw_fixes.py verify the literal `display='none'` is gone
# — but only a behavioural check confirms the wrap actually receives `''`.
# ─────────────────────────────────────────────────────────────────────────────
class TestChipAlwaysVisible:
def test_empty_string_shows_chip_with_default_label(self, driver_path):
out = _apply(driver_path, "")
assert out["display"] == "", f"empty effort must show the chip: {out}"
assert out["label"] == "Default"
assert out["inactive"] is True
def test_null_shows_chip_with_default_label(self, driver_path):
out = _apply(driver_path, None)
assert out["display"] == ""
assert out["label"] == "Default"
assert out["inactive"] is True
def test_none_shows_chip_with_none_label(self, driver_path):
"""The bug from #1068 — 'none' must NOT hide the chip."""
out = _apply(driver_path, "none")
assert out["display"] == "", (
f"'none' must show the chip (the regression that started #1068): {out}"
)
assert out["label"] == "None"
assert out["inactive"] is True
def test_low_shows_chip_active(self, driver_path):
out = _apply(driver_path, "low")
assert out["display"] == ""
assert out["label"] == "Low"
assert out["inactive"] is False
def test_high_shows_chip_active(self, driver_path):
out = _apply(driver_path, "high")
assert out["display"] == ""
assert out["label"] == "High"
assert out["inactive"] is False
class TestNormalizationEdgeCases:
"""Pin the input-normalisation contract so it can't silently shift."""
def test_uppercase_normalises(self, driver_path):
# Even though the API and slash command use lowercase, defensive
# normalisation matters — copy/paste of an uppercase value or a
# mis-cased server response shouldn't break the chip.
out = _apply(driver_path, "NONE")
assert out["label"] == "None"
assert out["inactive"] is True
def test_whitespace_trimmed(self, driver_path):
out = _apply(driver_path, " none ")
assert out["label"] == "None"
assert out["inactive"] is True
def test_unknown_value_falls_through_visible(self, driver_path):
# Defensive: unknown effort still shows the chip rather than hiding.
out = _apply(driver_path, "banana")
assert out["display"] == ""
assert out["label"] == "Banana"
assert out["inactive"] is False
class TestTitleAttributeAccessibility:
"""The chip's `title` is the hover tooltip and a screen-reader hint —
confirm it always carries the current state in human-readable form."""
def test_title_has_default_label_for_unset(self, driver_path):
out = _apply(driver_path, "")
assert out["title"] == "Reasoning effort: Default"
def test_title_has_none_label_for_none(self, driver_path):
out = _apply(driver_path, "none")
assert out["title"] == "Reasoning effort: None"
def test_title_has_active_label_for_high(self, driver_path):
out = _apply(driver_path, "high")
assert out["title"] == "Reasoning effort: High"