Compare commits
9 Commits
door/issue
...
fix/123
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b4a70d5fe | ||
|
|
bc02ca54ec | ||
| d412939b4f | |||
| 07c582aa08 | |||
| 5f95dc1e39 | |||
| b1f3cac36d | |||
| 07b3f67845 | |||
| c22bbbaf65 | |||
| 543cb1d40f |
@@ -104,13 +104,9 @@ MEDIUM_INDICATORS = [
|
||||
r"\blost\s+all\s+hope\b",
|
||||
r"\bno\s+tomorrow\b",
|
||||
# Contextual versions (from crisis_detector.py legacy)
|
||||
# Keep only medium-only patterns here; stronger overlaps live in HIGH_INDICATORS.
|
||||
r"\bfeel(?:s|ing)?\s+(?:so\s+)?worthless\b",
|
||||
r"\bfeel(?:s|ing)?\s+(?:so\s+)?hopeless\b",
|
||||
r"\bfeel(?:s|ing)?\s+trapped\b",
|
||||
r"\bfeel(?:s|ing)?\s+desperate\b",
|
||||
r"\bno\s+future\s+(?:for\s+me|ahead|left)\b",
|
||||
r"\bnothing\s+left\s+(?:to\s+(?:live|hope)\s+for|inside)\b",
|
||||
r"\bgive(?:n)?\s*up\s+on\s+myself\b",
|
||||
]
|
||||
|
||||
LOW_INDICATORS = [
|
||||
|
||||
@@ -680,7 +680,7 @@ html, body {
|
||||
|
||||
<!-- Footer -->
|
||||
<footer id="footer">
|
||||
<a href="/about" aria-label="About The Door">about</a>
|
||||
<a href="/about.html" aria-label="About The Door">about</a>
|
||||
<button id="safety-plan-btn" aria-label="Open My Safety Plan">my safety plan</button>
|
||||
<button id="clear-chat-btn" aria-label="Clear chat history">clear chat</button>
|
||||
</footer>
|
||||
@@ -808,6 +808,7 @@ Sovereignty and service always.`;
|
||||
var crisisPanel = document.getElementById('crisis-panel');
|
||||
var crisisOverlay = document.getElementById('crisis-overlay');
|
||||
var overlayDismissBtn = document.getElementById('overlay-dismiss-btn');
|
||||
var overlayCallLink = document.querySelector('.overlay-call');
|
||||
var statusDot = document.querySelector('.status-dot');
|
||||
var statusText = document.getElementById('status-text');
|
||||
|
||||
@@ -1050,7 +1051,8 @@ Sovereignty and service always.`;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
overlayDismissBtn.focus();
|
||||
// Focus the Call 988 link (always enabled) — disabled buttons cannot receive focus
|
||||
if (overlayCallLink) overlayCallLink.focus();
|
||||
}
|
||||
|
||||
// Register focus trap on document (always listening, gated by class check)
|
||||
|
||||
@@ -52,6 +52,34 @@ class TestCrisisOverlayFocusTrap(unittest.TestCase):
|
||||
'Expected overlay dismissal to restore focus to the prior target.',
|
||||
)
|
||||
|
||||
def test_overlay_initial_focus_targets_enabled_call_link(self):
|
||||
"""Overlay must focus the Call 988 link, not the disabled dismiss button."""
|
||||
# Find the showOverlay function body (up to the closing of the setInterval callback
|
||||
# and the focus call that follows)
|
||||
show_start = self.html.find('function showOverlay()')
|
||||
self.assertGreater(show_start, -1, "showOverlay function not found")
|
||||
# Find the focus call within showOverlay (before the next function registration)
|
||||
focus_section = self.html[show_start:show_start + 2000]
|
||||
self.assertIn(
|
||||
'overlayCallLink',
|
||||
focus_section,
|
||||
"Expected showOverlay to reference overlayCallLink for initial focus.",
|
||||
)
|
||||
# Ensure the old buggy pattern is gone
|
||||
focus_line_region = self.html[show_start + 800:show_start + 1200]
|
||||
self.assertNotIn(
|
||||
'overlayDismissBtn.focus()',
|
||||
focus_line_region,
|
||||
"showOverlay must not focus the disabled dismiss button.",
|
||||
)
|
||||
|
||||
def test_overlay_call_link_variable_is_declared(self):
|
||||
self.assertIn(
|
||||
"querySelector('.overlay-call')",
|
||||
self.html,
|
||||
"Expected a JS reference to the .overlay-call link element.",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
50
tests/test_indicator_dedup.py
Normal file
50
tests/test_indicator_dedup.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Regression tests for duplicate crisis indicators cleanup (#123)."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from crisis.detect import detect_crisis
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DETECT_FILE = ROOT / "crisis" / "detect.py"
|
||||
|
||||
|
||||
class TestCrisisIndicatorDedup(unittest.TestCase):
|
||||
def _extract_indicator_list(self, name: str) -> set[str]:
|
||||
text = DETECT_FILE.read_text(encoding="utf-8")
|
||||
match = re.search(rf"{name}\s*=\s*\[(.*?)\n\]", text, re.S)
|
||||
self.assertIsNotNone(match, f"{name} list missing")
|
||||
return {
|
||||
line.strip().rstrip(",")
|
||||
for line in match.group(1).splitlines()
|
||||
if line.strip().startswith('r"')
|
||||
}
|
||||
|
||||
def test_high_and_medium_indicator_lists_do_not_overlap(self):
|
||||
high = self._extract_indicator_list("HIGH_INDICATORS")
|
||||
medium = self._extract_indicator_list("MEDIUM_INDICATORS")
|
||||
overlap = high & medium
|
||||
self.assertEqual(set(), overlap, f"duplicate cross-tier patterns found: {sorted(overlap)}")
|
||||
|
||||
def test_removed_duplicates_still_classify_at_high_tier(self):
|
||||
cases = {
|
||||
"I feel hopeless": "HIGH",
|
||||
"I feel trapped": "HIGH",
|
||||
"I feel desperate": "HIGH",
|
||||
"I have no future ahead": "HIGH",
|
||||
"I have given up on myself": "HIGH",
|
||||
}
|
||||
for text, expected in cases.items():
|
||||
with self.subTest(text=text):
|
||||
result = detect_crisis(text)
|
||||
self.assertEqual(expected, result.level)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -50,6 +50,22 @@ class TestCrisisOfflinePage(unittest.TestCase):
|
||||
for phrase in required_phrases:
|
||||
self.assertIn(phrase, self.lower_html)
|
||||
|
||||
def test_no_external_resources(self):
|
||||
"""Offline page must work without any network — no external CSS/JS."""
|
||||
import re
|
||||
html = self.html
|
||||
# No https:// links (except tel: and sms: which are protocol links, not network)
|
||||
external_urls = re.findall(r'href=["\']https://|src=["\']https://', html)
|
||||
self.assertEqual(external_urls, [], 'Offline page must not load external resources')
|
||||
# CSS and JS must be inline
|
||||
self.assertIn('<style>', html, 'CSS must be inline')
|
||||
self.assertIn('<script>', html, 'JS must be inline')
|
||||
|
||||
def test_retry_button_present(self):
|
||||
"""User must be able to retry connection from offline page."""
|
||||
self.assertIn('retry-connection', self.html)
|
||||
self.assertIn('Retry connection', self.html)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user