Files
TimeTracker/tests/test_new_features.py
Dries Peeters 2230d5b909 feat: Add quick task creation from Start Timer modal
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.
2025-12-12 22:35:38 +01:00

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"