Native Qt tooltips on macOS are unreliable (delayed or invisible). Replace with a QFrame popup that appears on click, positioned below the ? button, and closes when clicking elsewhere (Qt.Popup flag handles ESC and outside-click). - Store help text in self._help_text instead of setToolTip() - Add _toggle_help_popup() method: creates/destroys floating QFrame - Update test to check _help_text attribute instead of toolTip() Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
194 lines
7.0 KiB
Python
194 lines
7.0 KiB
Python
"""UX tests for Jira Project Knowledge help tooltips and validation.
|
|
|
|
Verifies that the JiraConnectDialog shows contextual help icons for
|
|
Project ID and Jira Key, renders correct help text, supports keyboard
|
|
accessibility, and validates common user mistakes (e.g. entering ABC-123
|
|
instead of ABC).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
|
|
class _Config:
|
|
"""Minimal config stub for JiraConnectDialog."""
|
|
|
|
def __init__(self):
|
|
self.data = {
|
|
"jira": {"base_url": "", "email": "", "api_token": ""},
|
|
"jira_knowledge": {"enabled": False, "projects": {}},
|
|
}
|
|
|
|
def save(self):
|
|
pass
|
|
|
|
|
|
class _Ctx:
|
|
def __init__(self):
|
|
self.config = _Config()
|
|
|
|
def save(self):
|
|
self.config.save()
|
|
|
|
|
|
@pytest.fixture
|
|
def dialog(qapp):
|
|
from cowork_local.ui.connectors_panel import JiraConnectDialog
|
|
ctx = _Ctx()
|
|
dlg = JiraConnectDialog(ctx)
|
|
yield dlg
|
|
dlg.deleteLater()
|
|
|
|
|
|
# ---- Help icon presence ---------------------------------------------------
|
|
|
|
def test_help_icon_exists(dialog):
|
|
"""The mapping label row must contain a help button (?)."""
|
|
from PySide6.QtWidgets import QToolButton
|
|
dlg = dialog
|
|
help_icons = dlg.findChildren(QToolButton)
|
|
assert len(help_icons) >= 1, "Help button (?) not found in JiraConnectDialog"
|
|
|
|
|
|
def test_help_icon_has_tooltip(dialog):
|
|
"""Help icon must store help text with both Project ID and Jira Key explanations."""
|
|
from PySide6.QtWidgets import QToolButton
|
|
dlg = dialog
|
|
help_icons = dlg.findChildren(QToolButton)
|
|
assert help_icons, "No help button found"
|
|
# Help text is stored in _help_text for click-based popup
|
|
help_text = getattr(dlg, '_help_text', '') or help_icons[0].toolTip()
|
|
assert help_text, "Help button has no help content"
|
|
assert "Project ID" in help_text, "Help content missing Project ID explanation"
|
|
assert "Jira Key" in help_text, "Help content missing Jira Key explanation"
|
|
|
|
|
|
# ---- Help text content ----------------------------------------------------
|
|
|
|
def test_project_id_help_content(dialog):
|
|
"""Project ID help must explain what it is, where to find it, example, and common mistake."""
|
|
from cowork_local.i18n import tr
|
|
text = tr("connectors.jira_kb_project_id_help")
|
|
assert "cowork-local" in text.lower() or "Cowork" in text, "Missing example"
|
|
# Must warn against entering Jira keys
|
|
lower = text.lower()
|
|
assert "jira" in lower and ("key" in lower or "issue" in lower), \
|
|
"Missing common mistake warning about Jira keys"
|
|
|
|
|
|
def test_jira_key_help_content(dialog):
|
|
"""Jira Key help must include the ABC-123 → ABC example."""
|
|
from cowork_local.i18n import tr
|
|
text = tr("connectors.jira_kb_jira_key_help")
|
|
assert "ABC-123" in text, "Missing ABC-123 example"
|
|
assert "ABC" in text, "Missing ABC extraction example"
|
|
|
|
|
|
def test_jira_key_help_warns_against_issue_key(dialog):
|
|
"""Jira Key help must explicitly warn not to enter ABC-123."""
|
|
from cowork_local.i18n import tr
|
|
text = tr("connectors.jira_kb_jira_key_help")
|
|
lower = text.lower()
|
|
# Should contain a warning like "Do not enter ABC-123" or "Không nhập ABC-123"
|
|
assert "abc-123" in lower, "Missing warning about entering issue key format"
|
|
|
|
|
|
# ---- Validation -----------------------------------------------------------
|
|
|
|
def test_validation_shows_on_issue_key_pattern(dialog):
|
|
"""Typing 'proj:ABC-123' should show the validation warning."""
|
|
dlg = dialog
|
|
dlg.show()
|
|
dlg.project_mapping.setText("myproject:ABC-123")
|
|
assert not dlg.mapping_validation.isHidden(), \
|
|
"Validation hint should be shown when Issue Key pattern detected"
|
|
assert dlg.mapping_validation.text(), "Validation hint should have text"
|
|
|
|
|
|
def test_validation_hides_on_correct_input(dialog):
|
|
"""Typing 'proj:ABC' should NOT show the validation warning."""
|
|
dlg = dialog
|
|
dlg.show()
|
|
dlg.project_mapping.setText("myproject:ABC")
|
|
assert dlg.mapping_validation.isHidden(), \
|
|
"Validation hint should be hidden for correct Jira Key format"
|
|
|
|
|
|
def test_validation_hides_on_empty(dialog):
|
|
"""Empty input should not show validation warning."""
|
|
dlg = dialog
|
|
dlg.show()
|
|
dlg.project_mapping.setText("")
|
|
assert dlg.mapping_validation.isHidden(), \
|
|
"Validation hint should be hidden on empty input"
|
|
|
|
|
|
def test_validation_multiple_mappings(dialog):
|
|
"""Validation should detect issue key pattern even in multi-mapping strings."""
|
|
dlg = dialog
|
|
dlg.show()
|
|
dlg.project_mapping.setText("proj-a:ALPHA, proj-b:DEF-456")
|
|
assert not dlg.mapping_validation.isHidden(), \
|
|
"Validation should trigger when any mapping contains an Issue Key pattern"
|
|
|
|
|
|
# ---- Existing behavior unchanged ------------------------------------------
|
|
|
|
def test_save_still_works(dialog):
|
|
"""Saving with valid mapping still produces correct config structure."""
|
|
dlg = dialog
|
|
dlg.url.setText("https://example.atlassian.net")
|
|
dlg.email.setText("user@example.com")
|
|
dlg.token.setText("test-token")
|
|
dlg.project_mapping.setText("myproject:MYKEY")
|
|
dlg.kb_enabled.setChecked(True)
|
|
dlg._save()
|
|
jira_kb = dlg.ctx.config.data.get("jira_knowledge", {})
|
|
assert jira_kb["enabled"] is True
|
|
assert jira_kb["projects"] == {"myproject": "MYKEY"}
|
|
|
|
|
|
def test_sync_button_present(dialog):
|
|
"""Sync Now button must still exist and be functional."""
|
|
dlg = dialog
|
|
assert dlg.sync_btn is not None
|
|
assert dlg.sync_btn.text(), "Sync button should have text"
|
|
|
|
|
|
# ---- Accessibility --------------------------------------------------------
|
|
|
|
def test_help_icon_cursor(dialog):
|
|
"""Help button should be a QToolButton (clickable by nature)."""
|
|
from PySide6.QtWidgets import QToolButton
|
|
dlg = dialog
|
|
help_icons = dlg.findChildren(QToolButton)
|
|
assert help_icons, "No help button found"
|
|
# QToolButton is inherently clickable, no need for cursor check
|
|
assert help_icons[0].text() == "?", "Help button should display '?' text"
|
|
|
|
|
|
# ---- i18n keys exist for all three languages ------------------------------
|
|
|
|
@pytest.mark.parametrize("lang", ["en", "vi", "ja"])
|
|
def test_i18n_keys_exist(lang):
|
|
"""All Jira KB help keys must have translations for en, vi, ja."""
|
|
from cowork_local.i18n import STRINGS
|
|
required_keys = [
|
|
"connectors.jira_kb_section",
|
|
"connectors.jira_kb_enable",
|
|
"connectors.jira_kb_mapping_label",
|
|
"connectors.jira_kb_project_id_title",
|
|
"connectors.jira_kb_jira_key_title",
|
|
"connectors.jira_kb_project_id_help",
|
|
"connectors.jira_kb_jira_key_help",
|
|
"connectors.jira_kb_validation_issue_key",
|
|
"connectors.jira_kb_sync_now",
|
|
"connectors.jira_kb_not_configured",
|
|
"connectors.jira_kb_disabled",
|
|
"connectors.jira_kb_syncing",
|
|
]
|
|
for key in required_keys:
|
|
assert key in STRINGS, f"Missing i18n key: {key}"
|
|
entry = STRINGS[key]
|
|
assert lang in entry, f"Missing '{lang}' translation for key: {key}"
|
|
assert entry[lang], f"Empty '{lang}' translation for key: {key}" |