import json import subprocess from pathlib import Path SOURCE = Path(__file__).parents[1] / "frontend" / "today-timer.js" def test_timer_adopts_claimed_elapsed_time(): script = SOURCE.read_text() + r""" const values = new Map(); const storage = { getItem:key=>values.get(key)||null, setItem:(key,value)=>values.set(key,value), }; let now = 100000; const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>now}); const adopted = timer.adopt('issue:r:42:', 90000, true); now = 105000; process.stdout.write(JSON.stringify({adopted, snapshot:timer.snapshot()})); """ completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) result = json.loads(completed.stdout) assert result == { "adopted": True, "snapshot": {"identity": "issue:r:42:", "elapsed_ms": 95000, "running": True}, } def test_timer_reports_durable_changes_for_session_sync(): script = SOURCE.read_text() + r""" const values = new Map(); const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}; let now = 0; const changes = []; const timer = createTodayTimer({ storage, getLogin:()=> 'timmy', now:()=>now, onChange:snapshot=>changes.push(snapshot), }); timer.activate('issue:r:42:'); now = 5000; timer.pause(); process.stdout.write(JSON.stringify(changes)); """ completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) assert json.loads(completed.stdout) == [ {"identity": "issue:r:42:", "elapsed_ms": 0, "running": True}, {"identity": "issue:r:42:", "elapsed_ms": 5000, "running": False}, ] def test_quick_capture_pauses_only_its_running_item_and_restores_once(): script = SOURCE.read_text() + r""" const values = new Map(); const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}; let now = 1000; const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>now}); timer.activate('issue:r:42:'); now = 6000; const interruption = timer.beginCapture(); now = 16000; const paused = timer.snapshot(); const returned = timer.returnFromCapture(); now = 18000; const restored = timer.snapshot(); const repeated = timer.returnFromCapture(); process.stdout.write(JSON.stringify({interruption, paused, returned, restored, repeated})); """ completed = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert completed.returncode == 0, completed.stderr assert json.loads(completed.stdout) == { "interruption": {"identity": "issue:r:42:", "resume": True}, "paused": {"identity": "issue:r:42:", "elapsed_ms": 5000, "running": False}, "returned": {"identity": "issue:r:42:", "resumed": True}, "restored": {"identity": "issue:r:42:", "elapsed_ms": 7000, "running": True}, "repeated": None, } def test_quick_capture_does_not_resume_an_already_paused_or_replaced_item(): script = SOURCE.read_text() + r""" const values = new Map(); const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}; let now = 0; const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>now}); timer.adopt('issue:r:1:', 3000, false); const pausedInterruption = timer.beginCapture(); const pausedReturn = timer.returnFromCapture(); const stayedPaused = timer.snapshot(); timer.activate('issue:r:1:'); const runningInterruption = timer.beginCapture(); timer.activate('issue:r:2:'); const replacedReturn = timer.returnFromCapture(); const replacement = timer.snapshot(); process.stdout.write(JSON.stringify({pausedInterruption, pausedReturn, stayedPaused, runningInterruption, replacedReturn, replacement})); """ completed = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert completed.returncode == 0, completed.stderr assert json.loads(completed.stdout) == { "pausedInterruption": {"identity": "issue:r:1:", "resume": False}, "pausedReturn": {"identity": "issue:r:1:", "resumed": False}, "stayedPaused": {"identity": "issue:r:1:", "elapsed_ms": 3000, "running": False}, "runningInterruption": {"identity": "issue:r:1:", "resume": True}, "replacedReturn": {"identity": "issue:r:1:", "resumed": False}, "replacement": {"identity": "issue:r:2:", "elapsed_ms": 0, "running": True}, } def test_create_and_start_abandons_the_interrupted_timer(): script = SOURCE.read_text() + r""" const values = new Map(); const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}; const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>5000}); timer.activate('issue:r:1:'); timer.beginCapture(); const abandoned = timer.abandonCapture(); const snapshot = timer.snapshot(); const repeated = timer.returnFromCapture(); process.stdout.write(JSON.stringify({abandoned, snapshot, repeated})); """ completed = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert completed.returncode == 0, completed.stderr assert json.loads(completed.stdout) == { "abandoned": {"identity": "issue:r:1:", "resumed": False}, "snapshot": {"identity": "issue:r:1:", "elapsed_ms": 0, "running": False}, "repeated": None, } def test_capture_interruption_view_names_work_and_returns_to_today(): script = SOURCE.read_text() + r""" const values = new Map(); const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}; const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>1000}); const banner = {hidden:true}; const label = {textContent:''}; const returns = []; const view = createTodayTimer.createCaptureInterruption({ timer, banner, label, getItemLabel:identity=>identity === 'issue:r:1:' ? 'Ship release' : '', onReturn:result=>returns.push(result), }); timer.activate('issue:r:1:'); const opened = view.open(); const shown = {hidden:banner.hidden, text:label.textContent, timer:timer.snapshot()}; const finished = view.finish(); const closed = {hidden:banner.hidden, timer:timer.snapshot(), returns}; timer.beginCapture(); const transferred = view.transfer(); process.stdout.write(JSON.stringify({opened, shown, finished, closed, transferred, final:timer.snapshot()})); """ completed = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert completed.returncode == 0, completed.stderr assert json.loads(completed.stdout) == { "opened": {"identity": "issue:r:1:", "resume": True}, "shown": { "hidden": False, "text": "Today paused ยท Ship release", "timer": {"identity": "issue:r:1:", "elapsed_ms": 0, "running": False}, }, "finished": {"identity": "issue:r:1:", "resumed": True}, "closed": { "hidden": True, "timer": {"identity": "issue:r:1:", "elapsed_ms": 0, "running": True}, "returns": [{"identity": "issue:r:1:", "resumed": True}], }, "transferred": {"identity": "issue:r:1:", "resumed": False}, "final": {"identity": "issue:r:1:", "elapsed_ms": 0, "running": False}, }