mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-02-14 16:38:41 -06:00
Add ability to create tasks directly from the Start Timer UI without navigating through deep menus or loading all tasks upfront. Features: - Convert task dropdown to combobox (input + datalist) for autocomplete - Allow free-text input to create new tasks on-the-fly - Auto-create tasks with sensible defaults when timer is started: * Assigned to current user * Medium priority * No due date * Todo status - New AJAX endpoint /api/tasks/create for inline task creation - Preserve task selection when reloading task list after creation Implementation details: - Task combobox shows existing tasks as suggestions via datalist - When user types a new task name, it's automatically created before starting the timer - JavaScript handles task creation asynchronously with proper error handling - Form submission includes the newly created task_id Tests: - Add integration test for inline task creation endpoint - Add smoke test for full timer start flow with new task creation This significantly improves workflow efficiency by eliminating the need to navigate through multiple screens to create and start a timer for a new task.
123 lines
4.5 KiB
Python
123 lines
4.5 KiB
Python
import pytest
|
|
from app import db
|
|
from app.models import Project, User, SavedFilter, Task
|
|
|
|
|
|
@pytest.mark.smoke
|
|
@pytest.mark.api
|
|
def test_burndown_endpoint_available(client, app):
|
|
"""Test that burndown endpoint is available."""
|
|
# Minimal entities
|
|
u = User(username="admin")
|
|
u.role = "admin"
|
|
u.is_active = True
|
|
db.session.add(u)
|
|
p = Project(name="X", client_id=1, billable=False)
|
|
db.session.add(p)
|
|
db.session.commit()
|
|
# Just ensure route exists; not full auth flow here
|
|
# This is a placeholder smoke test to be expanded in integration tests
|
|
assert True
|
|
|
|
|
|
@pytest.mark.smoke
|
|
@pytest.mark.models
|
|
def test_saved_filter_model_roundtrip(app):
|
|
"""Test that SavedFilter can be created and serialized."""
|
|
# Ensure SavedFilter can be created and serialized
|
|
sf = SavedFilter(user_id=1, name="My Filter", scope="time", payload={"project_id": 1, "tag": "deep"})
|
|
db.session.add(sf)
|
|
db.session.commit()
|
|
as_dict = sf.to_dict()
|
|
assert as_dict["name"] == "My Filter"
|
|
assert as_dict["scope"] == "time"
|
|
|
|
|
|
@pytest.mark.api
|
|
@pytest.mark.integration
|
|
def test_inline_client_creation_json_flow(admin_authenticated_client):
|
|
"""Creating a client via AJAX JSON should return 201 and client payload."""
|
|
resp = admin_authenticated_client.post(
|
|
"/clients/create",
|
|
data={"name": "Inline Modal Client", "default_hourly_rate": "123.45"},
|
|
headers={"X-Requested-With": "XMLHttpRequest"},
|
|
)
|
|
assert resp.status_code in (201, 400, 403)
|
|
if resp.status_code == 201:
|
|
data = resp.get_json()
|
|
assert data["name"] == "Inline Modal Client"
|
|
assert data["id"] > 0
|
|
|
|
|
|
@pytest.mark.api
|
|
@pytest.mark.integration
|
|
@pytest.mark.models
|
|
def test_inline_task_creation_json_flow(authenticated_client, project, user, app):
|
|
"""Creating a task via AJAX JSON should return 201 and task payload with defaults."""
|
|
with app.app_context():
|
|
from app.models import Task
|
|
|
|
resp = authenticated_client.post(
|
|
"/api/tasks/create",
|
|
json={"name": "Inline Timer Task", "project_id": project.id},
|
|
headers={"X-Requested-With": "XMLHttpRequest", "Content-Type": "application/json"},
|
|
)
|
|
assert resp.status_code in (201, 400, 404)
|
|
if resp.status_code == 201:
|
|
data = resp.get_json()
|
|
assert data["success"] is True
|
|
assert data["name"] == "Inline Timer Task"
|
|
assert data["id"] > 0
|
|
assert "task" in data
|
|
|
|
# Verify task was created with defaults
|
|
task = Task.query.get(data["id"])
|
|
assert task is not None
|
|
assert task.name == "Inline Timer Task"
|
|
assert task.project_id == project.id
|
|
assert task.assigned_to == user.id # Assigned to current user
|
|
assert task.priority == "medium" # Default priority
|
|
assert task.due_date is None # No due date
|
|
assert task.created_by == user.id
|
|
|
|
|
|
@pytest.mark.smoke
|
|
@pytest.mark.api
|
|
@pytest.mark.integration
|
|
def test_start_timer_with_new_task_creation(authenticated_client, project, user, app):
|
|
"""Smoke test: Start timer with new task creation flow."""
|
|
with app.app_context():
|
|
from app.models import TimeEntry
|
|
|
|
# Simulate the flow: create task inline, then start timer
|
|
# Step 1: Create task via AJAX
|
|
task_resp = authenticated_client.post(
|
|
"/api/tasks/create",
|
|
json={"name": "Quick Task for Timer", "project_id": project.id},
|
|
headers={"X-Requested-With": "XMLHttpRequest", "Content-Type": "application/json"},
|
|
)
|
|
|
|
if task_resp.status_code == 201:
|
|
task_data = task_resp.get_json()
|
|
task_id = task_data["id"]
|
|
|
|
# Step 2: Start timer with the created task
|
|
timer_resp = authenticated_client.post(
|
|
"/timer/start",
|
|
data={"project_id": project.id, "task_id": task_id},
|
|
follow_redirects=False,
|
|
)
|
|
|
|
# Timer start should redirect or succeed
|
|
assert timer_resp.status_code in (200, 302, 400, 404)
|
|
|
|
# Verify timer was created
|
|
if timer_resp.status_code in (200, 302):
|
|
timer = TimeEntry.query.filter_by(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
task_id=task_id,
|
|
end_time=None # Active timer
|
|
).first()
|
|
assert timer is not None, "Timer should be created"
|