- 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>
193 lines
6.8 KiB
Python
193 lines
6.8 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 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}" |