mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-06 11:40:52 -06:00
- Normalize line endings from CRLF to LF across all files to match .editorconfig - Standardize quote style from single quotes to double quotes - Normalize whitespace and formatting throughout codebase - Apply consistent code style across 372 files including: * Application code (models, routes, services, utils) * Test files * Configuration files * CI/CD workflows This ensures consistency with the project's .editorconfig settings and improves code maintainability.
314 lines
12 KiB
Python
314 lines
12 KiB
Python
"""
|
|
Test suite for keyboard shortcuts input field fix.
|
|
|
|
This test ensures that keyboard shortcuts (like 'gr') do not trigger
|
|
when typing in input fields, textareas, or other editable elements.
|
|
"""
|
|
|
|
import pytest
|
|
from flask import url_for
|
|
from app import db
|
|
from app.models import User, Project, Client
|
|
|
|
|
|
class TestKeyboardShortcutsInputFix:
|
|
"""Test keyboard shortcuts behavior in input fields."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, admin_authenticated_client, admin_user, test_client):
|
|
"""Set up test fixtures."""
|
|
self.client = admin_authenticated_client
|
|
self.user = admin_user
|
|
self.test_client = test_client
|
|
|
|
def test_create_project_page_loads(self):
|
|
"""Test that create project page loads successfully."""
|
|
response = self.client.get("/projects/create")
|
|
assert response.status_code == 200
|
|
assert b"Create Project" in response.data or b"New Project" in response.data
|
|
|
|
def test_create_project_with_gr_in_name(self):
|
|
"""Test creating a project with 'gr' in the name (e.g., 'program')."""
|
|
response = self.client.post(
|
|
"/projects/create",
|
|
data={
|
|
"name": "Program Development",
|
|
"description": "A program for testing",
|
|
"status": "active",
|
|
"hourly_rate": "50.00",
|
|
"client_id": self.test_client.id,
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
|
|
# Should successfully create the project
|
|
assert response.status_code == 200
|
|
|
|
# Verify project was created
|
|
project = Project.query.filter_by(name="Program Development").first()
|
|
assert project is not None
|
|
assert project.name == "Program Development"
|
|
assert "program" in project.description.lower()
|
|
|
|
def test_create_task_with_trigger_in_name(self):
|
|
"""Test creating a task with shortcut trigger keys in the name."""
|
|
# First create a project
|
|
project = Project(name="Test Project", description="Test", client_id=self.test_client.id, status="active")
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
# Create task with 'gr' in the name
|
|
response = self.client.post(
|
|
"/tasks/create",
|
|
data={
|
|
"name": "Upgrade system",
|
|
"description": "Migrate program to new version",
|
|
"project_id": project.id,
|
|
"status": "todo",
|
|
"priority": "medium",
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
def test_project_name_with_multiple_triggers(self):
|
|
"""Test project names containing multiple keyboard shortcut triggers."""
|
|
test_names = [
|
|
"Program Graphics Design", # Contains 'gr'
|
|
"Client Portal Development", # Contains 'port'
|
|
"Integration Testing", # Contains 'int'
|
|
"Graphics Rendering Engine", # Contains 'gr'
|
|
]
|
|
|
|
for name in test_names:
|
|
response = self.client.post(
|
|
"/projects/create",
|
|
data={
|
|
"name": name,
|
|
"description": f"Testing {name}",
|
|
"status": "active",
|
|
"hourly_rate": "50.00",
|
|
"client_id": self.test_client.id,
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
|
|
# Should successfully create without triggering shortcuts
|
|
project = Project.query.filter_by(name=name).first()
|
|
assert project is not None, f"Failed to create project: {name}"
|
|
|
|
def test_keyboard_shortcuts_js_loaded(self):
|
|
"""Test that keyboard shortcuts JavaScript files are loaded."""
|
|
response = self.client.get("/")
|
|
assert response.status_code == 200
|
|
|
|
# Check that at least one keyboard shortcuts file is referenced
|
|
# (The actual check depends on how the JS is loaded in your templates)
|
|
data = response.data.decode("utf-8")
|
|
assert "keyboard" in data.lower() or "shortcut" in data.lower()
|
|
|
|
|
|
class TestKeyboardShortcutsJavaScriptLogic:
|
|
"""Test JavaScript keyboard shortcuts logic (documentation tests)."""
|
|
|
|
def test_istyping_method_exists(self):
|
|
"""
|
|
Documentation test: Verify isTyping/isTypingContext methods exist.
|
|
|
|
The keyboard shortcut files should have methods to detect
|
|
when user is typing in an input field:
|
|
- keyboard-shortcuts.js: isTyping()
|
|
- keyboard-shortcuts-enhanced.js: isTypingContext()
|
|
- keyboard-shortcuts-advanced.js: isTyping()
|
|
"""
|
|
# Read the JavaScript files with UTF-8 encoding
|
|
with open("app/static/keyboard-shortcuts.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "isTyping" in content, "isTyping method not found in keyboard-shortcuts.js"
|
|
assert "tagName === 'input'" in content or 'tagname === "input"' in content.lower()
|
|
|
|
with open("app/static/keyboard-shortcuts-enhanced.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "isTypingContext" in content, "isTypingContext method not found"
|
|
assert "tagName" in content or "tagname" in content.lower()
|
|
|
|
with open("app/static/keyboard-shortcuts-advanced.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "isTyping" in content, "isTyping method not found in keyboard-shortcuts-advanced.js"
|
|
|
|
def test_input_check_before_sequences(self):
|
|
"""
|
|
Documentation test: Verify input checks happen before sequence handling.
|
|
|
|
The keyboard shortcut handlers should check if user is typing
|
|
BEFORE processing key sequences like 'g r'.
|
|
"""
|
|
with open("app/static/keyboard-shortcuts.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
# Should check isTyping before handling sequences
|
|
assert "isTyping" in content
|
|
assert "keySequence" in content
|
|
|
|
def test_sequence_cleared_when_typing(self):
|
|
"""
|
|
Documentation test: Verify key sequences are cleared when typing.
|
|
|
|
When user starts typing in an input field, any existing
|
|
key sequence should be cleared to prevent partial matches.
|
|
"""
|
|
with open("app/static/keyboard-shortcuts.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
# Look for sequence clearing logic
|
|
assert "keySequence = []" in content or "keySequence=[]" in content
|
|
|
|
def test_allowed_shortcuts_in_inputs(self):
|
|
"""
|
|
Documentation test: Verify certain shortcuts are allowed in inputs.
|
|
|
|
Some shortcuts like Ctrl+K (command palette), Ctrl+/ (search),
|
|
and Shift+? (help) should work even when in an input field.
|
|
"""
|
|
# Check keyboard-shortcuts-enhanced.js for allowed shortcuts
|
|
with open("app/static/keyboard-shortcuts-enhanced.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "isAllowedInInput" in content, "isAllowedInInput method not found"
|
|
# Should include ctrl+k, ctrl+/, shift+?
|
|
assert "ctrl+k" in content.lower() or "ctrlKey" in content
|
|
|
|
def test_contenteditable_check(self):
|
|
"""
|
|
Documentation test: Verify contentEditable elements are handled.
|
|
|
|
The isTyping check should also cover contentEditable elements,
|
|
not just input and textarea.
|
|
"""
|
|
with open("app/static/keyboard-shortcuts.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "isContentEditable" in content or "contentEditable" in content
|
|
|
|
def test_rich_text_editor_detection(self):
|
|
"""
|
|
Documentation test: Verify rich text editors are detected.
|
|
|
|
The isTyping check should detect popular rich text editors like:
|
|
- Toast UI Editor (used in this project)
|
|
- TinyMCE
|
|
- Quill
|
|
- CodeMirror
|
|
"""
|
|
with open("app/static/keyboard-shortcuts.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
# Should check for Toast UI Editor
|
|
assert "toastui-editor" in content.lower(), "Toast UI Editor detection not found"
|
|
# Should check for other popular editors
|
|
assert "CodeMirror" in content or "codemirror" in content.lower()
|
|
assert "closest" in content, "Should use closest() to check parent elements"
|
|
|
|
with open("app/static/keyboard-shortcuts-enhanced.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "toastui-editor" in content.lower(), "Toast UI Editor detection not found in enhanced"
|
|
|
|
with open("app/static/keyboard-shortcuts-advanced.js", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
assert "toastui-editor" in content.lower(), "Toast UI Editor detection not found in advanced"
|
|
|
|
|
|
class TestKeyboardShortcutsBugScenarios:
|
|
"""Test specific bug scenarios reported by users."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, admin_authenticated_client, admin_user, test_client):
|
|
"""Set up test fixtures."""
|
|
self.client = admin_authenticated_client
|
|
self.user = admin_user
|
|
self.test_client = test_client
|
|
|
|
def test_reported_bug_typing_program(self):
|
|
"""
|
|
Test the exact bug scenario: typing 'program' in create project.
|
|
|
|
Bug report: When typing 'gr' in text field (e.g., in 'program'),
|
|
it triggers the 'g r' shortcut (Go to Reports).
|
|
|
|
Expected: Should NOT trigger the shortcut, should type normally.
|
|
"""
|
|
response = self.client.post(
|
|
"/projects/create",
|
|
data={
|
|
"name": "New program",
|
|
"description": "program for programming",
|
|
"status": "active",
|
|
"hourly_rate": "75.00",
|
|
"client_id": self.test_client.id,
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
|
|
# Should create project successfully without being redirected to reports
|
|
assert response.status_code == 200
|
|
|
|
# Verify we're not on the reports page
|
|
assert b"Reports" not in response.data or b"New program" in response.data
|
|
|
|
# Verify project was created
|
|
project = Project.query.filter_by(name="New program").first()
|
|
assert project is not None
|
|
assert "program" in project.description
|
|
|
|
def test_all_shortcut_triggers_in_text(self):
|
|
"""
|
|
Test typing text that contains all common shortcut triggers.
|
|
|
|
Common shortcuts:
|
|
- g d: Go to Dashboard
|
|
- g p: Go to Projects
|
|
- g t: Go to Tasks
|
|
- g r: Go to Reports
|
|
- g i: Go to Invoices
|
|
"""
|
|
test_text = "a program with great ideas and graphics"
|
|
|
|
response = self.client.post(
|
|
"/projects/create",
|
|
data={
|
|
"name": test_text,
|
|
"description": "This project has: goals, graphics, great progress, grand ideas",
|
|
"status": "active",
|
|
"hourly_rate": "60.00",
|
|
"client_id": self.test_client.id,
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Verify project was created with the full text
|
|
project = Project.query.filter_by(name=test_text).first()
|
|
assert project is not None
|
|
|
|
|
|
def test_smoke_keyboard_shortcuts_on_multiple_pages(admin_authenticated_client):
|
|
"""
|
|
Smoke test: Verify keyboard shortcuts don't interfere on multiple pages.
|
|
|
|
This test loads various pages to ensure the keyboard shortcuts
|
|
JavaScript is properly loaded and configured on all pages.
|
|
"""
|
|
# Test pages that have text inputs
|
|
pages_with_inputs = [
|
|
"/projects/create",
|
|
"/tasks/create",
|
|
"/clients/create",
|
|
"/timer/manual-entry",
|
|
]
|
|
|
|
for page in pages_with_inputs:
|
|
response = admin_authenticated_client.get(page)
|
|
# Should load successfully (200) or redirect to valid page (302)
|
|
assert response.status_code in [
|
|
200,
|
|
302,
|
|
404,
|
|
], f"Page {page} returned unexpected status: {response.status_code}"
|