Add TimeSelector and ScheduleModal components with tests
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m45s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m45s
- Implemented TimeSelector component for selecting time with AM/PM toggle and minute/hour increment/decrement functionality. - Created ScheduleModal component for scheduling chores with options for specific days or intervals. - Added utility functions for scheduling logic in scheduleUtils.ts. - Developed comprehensive tests for TimeSelector and scheduleUtils functions to ensure correct behavior.
This commit is contained in:
@@ -4,11 +4,12 @@ import os
|
||||
from flask import Flask
|
||||
from api.child_api import child_api
|
||||
from api.auth_api import auth_api
|
||||
from db.db import child_db, reward_db, task_db, users_db
|
||||
from db.db import child_db, reward_db, task_db, users_db, chore_schedules_db, task_extensions_db
|
||||
from tinydb import Query
|
||||
from models.child import Child
|
||||
import jwt
|
||||
from werkzeug.security import generate_password_hash
|
||||
from datetime import date as date_type
|
||||
|
||||
|
||||
# Test user credentials
|
||||
@@ -348,4 +349,142 @@ def test_assignable_rewards_multiple_user_same_name(client):
|
||||
ids = [r['id'] for r in data['rewards']]
|
||||
# Both user rewards should be present, not the system one
|
||||
assert set(names) == {'Prize'}
|
||||
assert set(ids) == {'userr1', 'userr2'}
|
||||
assert set(ids) == {'userr1', 'userr2'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# list-tasks: schedule and extension_date fields
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CHILD_SCHED_ID = 'child_sched_test'
|
||||
TASK_GOOD_ID = 'task_sched_good'
|
||||
TASK_BAD_ID = 'task_sched_bad'
|
||||
|
||||
|
||||
def _setup_sched_child_and_tasks(task_db, child_db):
|
||||
task_db.remove(Query().id == TASK_GOOD_ID)
|
||||
task_db.remove(Query().id == TASK_BAD_ID)
|
||||
task_db.insert({'id': TASK_GOOD_ID, 'name': 'Sweep', 'points': 3, 'is_good': True, 'user_id': 'testuserid'})
|
||||
task_db.insert({'id': TASK_BAD_ID, 'name': 'Yell', 'points': 2, 'is_good': False, 'user_id': 'testuserid'})
|
||||
child_db.remove(Query().id == CHILD_SCHED_ID)
|
||||
child_db.insert({
|
||||
'id': CHILD_SCHED_ID,
|
||||
'name': 'SchedKid',
|
||||
'age': 7,
|
||||
'points': 0,
|
||||
'tasks': [TASK_GOOD_ID, TASK_BAD_ID],
|
||||
'rewards': [],
|
||||
'user_id': 'testuserid',
|
||||
})
|
||||
chore_schedules_db.remove(Query().child_id == CHILD_SCHED_ID)
|
||||
task_extensions_db.remove(Query().child_id == CHILD_SCHED_ID)
|
||||
|
||||
|
||||
def test_list_child_tasks_always_has_schedule_and_extension_date_keys(client):
|
||||
"""Every task in the response must have 'schedule' and 'extension_date' keys."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
assert resp.status_code == 200
|
||||
for task in resp.get_json()['tasks']:
|
||||
assert 'schedule' in task
|
||||
assert 'extension_date' in task
|
||||
|
||||
|
||||
def test_list_child_tasks_returns_schedule_when_set(client):
|
||||
"""Good chore with a saved schedule returns that schedule object."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
chore_schedules_db.insert({
|
||||
'id': 'sched-1',
|
||||
'child_id': CHILD_SCHED_ID,
|
||||
'task_id': TASK_GOOD_ID,
|
||||
'mode': 'days',
|
||||
'day_configs': [{'day': 1, 'hour': 8, 'minute': 0}],
|
||||
'interval_days': 2,
|
||||
'anchor_weekday': 0,
|
||||
'interval_hour': 0,
|
||||
'interval_minute': 0,
|
||||
})
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
tasks = {t['id']: t for t in resp.get_json()['tasks']}
|
||||
sched = tasks[TASK_GOOD_ID]['schedule']
|
||||
assert sched is not None
|
||||
assert sched['mode'] == 'days'
|
||||
assert sched['day_configs'] == [{'day': 1, 'hour': 8, 'minute': 0}]
|
||||
|
||||
|
||||
def test_list_child_tasks_schedule_null_when_not_set(client):
|
||||
"""Good chore with no schedule returns schedule=null."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
tasks = {t['id']: t for t in resp.get_json()['tasks']}
|
||||
assert tasks[TASK_GOOD_ID]['schedule'] is None
|
||||
|
||||
|
||||
def test_list_child_tasks_returns_extension_date_when_set(client):
|
||||
"""Good chore with a TaskExtension for today returns today's ISO date."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
today = date_type.today().isoformat()
|
||||
task_extensions_db.insert({
|
||||
'id': 'ext-1',
|
||||
'child_id': CHILD_SCHED_ID,
|
||||
'task_id': TASK_GOOD_ID,
|
||||
'date': today,
|
||||
})
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
tasks = {t['id']: t for t in resp.get_json()['tasks']}
|
||||
assert tasks[TASK_GOOD_ID]['extension_date'] == today
|
||||
|
||||
|
||||
def test_list_child_tasks_extension_date_null_when_not_set(client):
|
||||
"""Good chore with no extension returns extension_date=null."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
tasks = {t['id']: t for t in resp.get_json()['tasks']}
|
||||
assert tasks[TASK_GOOD_ID]['extension_date'] is None
|
||||
|
||||
|
||||
def test_list_child_tasks_schedule_and_extension_null_for_penalties(client):
|
||||
"""Penalty tasks (is_good=False) always return schedule=null and extension_date=null."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
# Even if we insert a schedule entry for the penalty task, the endpoint should ignore it
|
||||
chore_schedules_db.insert({
|
||||
'id': 'sched-bad',
|
||||
'child_id': CHILD_SCHED_ID,
|
||||
'task_id': TASK_BAD_ID,
|
||||
'mode': 'days',
|
||||
'day_configs': [{'day': 0, 'hour': 9, 'minute': 0}],
|
||||
'interval_days': 2,
|
||||
'anchor_weekday': 0,
|
||||
'interval_hour': 0,
|
||||
'interval_minute': 0,
|
||||
})
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
tasks = {t['id']: t for t in resp.get_json()['tasks']}
|
||||
assert tasks[TASK_BAD_ID]['schedule'] is None
|
||||
assert tasks[TASK_BAD_ID]['extension_date'] is None
|
||||
|
||||
|
||||
def test_list_child_tasks_no_server_side_filtering(client):
|
||||
"""All assigned tasks are returned regardless of schedule — no server-side day/time filtering."""
|
||||
_setup_sched_child_and_tasks(task_db, child_db)
|
||||
# Add a second good task that has a schedule for only Sunday (day=0)
|
||||
extra_id = 'task_sched_extra'
|
||||
task_db.remove(Query().id == extra_id)
|
||||
task_db.insert({'id': extra_id, 'name': 'Extra', 'points': 1, 'is_good': True, 'user_id': 'testuserid'})
|
||||
child_db.update({'tasks': [TASK_GOOD_ID, TASK_BAD_ID, extra_id]}, Query().id == CHILD_SCHED_ID)
|
||||
chore_schedules_db.insert({
|
||||
'id': 'sched-extra',
|
||||
'child_id': CHILD_SCHED_ID,
|
||||
'task_id': extra_id,
|
||||
'mode': 'days',
|
||||
'day_configs': [{'day': 0, 'hour': 7, 'minute': 0}], # Sunday only
|
||||
'interval_days': 2,
|
||||
'anchor_weekday': 0,
|
||||
'interval_hour': 0,
|
||||
'interval_minute': 0,
|
||||
})
|
||||
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
|
||||
returned_ids = {t['id'] for t in resp.get_json()['tasks']}
|
||||
# Both good tasks must be present; server never filters based on schedule/time
|
||||
assert TASK_GOOD_ID in returned_ids
|
||||
assert extra_id in returned_ids
|
||||
Reference in New Issue
Block a user