feat(jira-knowledge): add contextual help tooltips and inline validation for Project Mapping
- Add QToolButton help icon (?) next to Project Mapping label - Implement comprehensive tooltip explaining Project ID and Jira Key concepts - Add inline validation to detect common mistake: entering issue keys (ABC-123) instead of project keys (ABC) - Add 13 new i18n keys for help text in English, Vietnamese, and Japanese - Add 15 comprehensive UX tests covering icon presence, tooltip content, validation logic, and accessibility - Update jira-knowledge-guide.md with help icon reference and setup instructions - Fix missing _on_paste method that was causing AttributeError Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
"""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 have a non-empty tooltip 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"
|
||||
tooltip = help_icons[0].toolTip()
|
||||
assert tooltip, "Help button has no tooltip"
|
||||
assert "Project ID" in tooltip, "Tooltip missing Project ID explanation"
|
||||
assert "Jira Key" in tooltip, "Tooltip 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}"
|
||||
Reference in New Issue
Block a user