stackchain-dashboard/tests/test_today_summary.py
timmy b3a359760c
Some checks failed
CI / lint (pull_request) Successful in 3m20s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Failing after 3m9s
CI / release-candidate (pull_request) Has been skipped
feat: share private Today summaries (Closes #1090)
2026-08-18 18:39:01 +00:00

156 lines
6.3 KiB
Python

import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).parents[1]
TODAY_SUMMARY = ROOT / "frontend" / "today-summary.js"
def run_node(script: str):
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def test_summary_begins_with_exact_worked_and_tomorrow_items_but_keeps_time_private():
script = f"""
const createSummary = require({json.dumps(str(TODAY_SUMMARY))});
const values = new Map();
const storage = {{
getItem:key => values.has(key) ? values.get(key) : null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
const summary = createSummary({{storage,getLogin:()=> ' Timmy '}});
const snapshot = summary.begin(
[{{identity:'issue:acme/mobile:41:',title:'Ship mobile capture',context:'acme/mobile#41',actual_minutes:42}}],
[{{identity:'issue:acme/mobile:42:',title:'Review release',context:'acme/mobile#42'}}],
);
process.stdout.write(JSON.stringify({{
snapshot,
text:summary.text(),
keys:[...values.keys()],
}}));
"""
output = run_node(script)
assert output["snapshot"]["worked"] == [{
"identity": "issue:acme/mobile:41:",
"title": "Ship mobile capture",
"context": "acme/mobile#41",
"actual_minutes": 42,
"include": True,
}]
assert output["snapshot"]["tomorrow"] == [{
"identity": "issue:acme/mobile:42:",
"title": "Review release",
"context": "acme/mobile#42",
"include": True,
}]
assert output["snapshot"]["include_actuals"] is False
assert output["text"] == "Today\n- Ship mobile capture\n\nTomorrow\n- Review release"
assert output["keys"] == ["stackchain.today-summary-draft.v1.timmy"]
def test_summary_privacy_choices_and_note_survive_reload_for_the_same_account():
script = f"""
const createSummary = require({json.dumps(str(TODAY_SUMMARY))});
const values = new Map();
const storage = {{
getItem:key => values.has(key) ? values.get(key) : null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
const options = {{storage,getLogin:()=> 'timmy'}};
const summary = createSummary(options);
summary.begin(
[{{identity:'issue:acme/mobile:41:',title:'Ship mobile capture',context:'acme/mobile#41',actual_minutes:42}}],
[{{identity:'issue:acme/mobile:42:',title:'Review release',context:'acme/mobile#42'}}],
);
summary.includeActuals(true);
summary.setNote(' Waiting on review ');
summary.choose('tomorrow', 'issue:acme/mobile:42:', false);
const restored = createSummary(options);
process.stdout.write(JSON.stringify({{
restored:restored.restore(),
snapshot:restored.snapshot(),
text:restored.text(),
}}));
"""
output = run_node(script)
assert output["restored"] is True
assert output["snapshot"]["include_actuals"] is True
assert output["snapshot"]["note"] == "Waiting on review"
assert output["snapshot"]["tomorrow"][0]["include"] is False
assert output["text"] == "Today\n- Ship mobile capture (42m)\n\nNote\nWaiting on review"
def test_summary_shares_natively_or_once_to_clipboard_and_keeps_canceled_drafts():
script = f"""
const createSummary = require({json.dumps(str(TODAY_SUMMARY))});
const values = new Map();
const storage = {{
getItem:key => values.has(key) ? values.get(key) : null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
const row = [{{identity:'issue:acme/mobile:41:',title:'Ship mobile capture',context:'acme/mobile#41',actual_minutes:42}}];
const canceled = createSummary({{storage,getLogin:()=> 'timmy',share:async()=>{{const error=new Error('cancel');error.name='AbortError';throw error;}}}});
canceled.begin(row, []);
const canceledResult = await canceled.shareSummary();
const keptAfterCancel = values.has('stackchain.today-summary-draft.v1.timmy');
const copies = [];
const fallback = createSummary({{storage,getLogin:()=> 'timmy',copy:async text=>copies.push(text)}});
fallback.restore();
const copiedResult = await fallback.shareSummary();
process.stdout.write(JSON.stringify({{
canceledResult, keptAfterCancel, copiedResult, copies,
keptAfterCopy:values.has('stackchain.today-summary-draft.v1.timmy'),
}}));
"""
output = run_node(f"(async()=>{{{script}}})().catch(error=>{{console.error(error);process.exit(1);}})")
assert output["canceledResult"] == {"status": "canceled"}
assert output["keptAfterCancel"] is True
assert output["copiedResult"] == {"status": "copied"}
assert output["copies"] == ["Today\n- Ship mobile capture"]
assert output["keptAfterCopy"] is False
def test_dashboard_connects_recap_and_wrap_up_to_a_mobile_summary_review_sheet():
from src import main
html = main.FRONTEND_BUILD.dashboard_html
css = (ROOT / "frontend" / "dashboard.css").read_text()
dashboard = (ROOT / "frontend" / "dashboard.js").read_text()
recap = (ROOT / "frontend" / "today-recap.js").read_text()
wrap_up = (ROOT / "frontend" / "today-wrap-up.js").read_text()
assert 'id="today-summary-sheet" role="dialog"' in html
assert 'id="today-summary-worked"' in html
assert 'id="today-summary-tomorrow"' in html
assert 'id="today-summary-include-actuals"' in html
assert 'id="share-today-summary"' in html
assert "static/today-summary.js" in main.FRONTEND_BUILD.page_sources
assert "setupTodaySummary({" in dashboard
assert "todaySummaryView.open(workedItems, tomorrowItems)" in dashboard
assert "openWrapUp(handoff.actual_minutes, workedItems)" in recap
assert "onComplete(result, actualMinutes, workedItems, tomorrowItems)" in wrap_up
assert ".today-summary-actions button { min-height:44px;" in css
assert ".today-summary-panel" in css and "overflow-x:hidden" in css
assert "tests/e2e/test_mobile_today_summary_release.py" in (
ROOT / ".gitea" / "workflows" / "ci.yml"
).read_text()
def test_summary_uses_the_human_label_from_recap_feedback_rows():
script = f"""
const createSummary = require({json.dumps(str(TODAY_SUMMARY))});
const summary = createSummary();
summary.begin([{{
identity:'issue:acme/mobile:41:', label:'Ship mobile capture',
context:'acme/mobile#41', actual_minutes:42,
}}], []);
process.stdout.write(JSON.stringify({{text:summary.text()}}));
"""
assert run_node(script)["text"] == "Today\n- Ship mobile capture"