"""The t7log bundle (goal 41 T7, ux-notes structural bet 3): the task
page <-> event log unification. The two surfaces are ONE investigation
flow: the task page mounts the log as its "Event log" tab (the same
task-log-feed partial the standalone /tasks/{id}/log route renders),
the entrypoint publishes both slots, and no link to the log opens a
new tab any more.
Fixture-driven like the pages bundle: a task + session are inserted as
rows, events land through the push wire (/api/saddle/chunks), and the
assertions read page HTML, entrypoint slot state and the SSE stream.
"""
from harness import * # noqa: F401,F403 — the shared wire helpers
# =============================================================================
# Bundle: t7log — task page <-> event log unification.
# =============================================================================
def run(ctx):
auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
print("== B1: the task page embeds the log as its second tab ==")
# A working task with one session, then a real event through the
# push wire (the same path the daemon writes).
conn = db()
conn.execute(
"insert into tasks (project_id, title, status, priority, created_at)"
" values (1, 'T7 unified log task', 'working', 5,"
" strftime('%s','now'))")
task_id = conn.execute("select max(id) from tasks").fetchone()[0]
conn.execute(
"insert into sessions (task_id, saddle_id, started_at)"
" values (?, ?, strftime('%s','now'))", (task_id, fake_id))
conn.commit()
session_id = conn.execute("select max(id) from sessions").fetchone()[0]
conn.close()
status, body = saddle_api("POST", "/api/saddle/chunks", fake_token, {
"session_id": session_id,
"events": [{"seq": 101, "kind": "comment", "actor": "user:admin",
"payload": {"body": "T7 probe comment",
"author": "user:admin"}}]})
check(status == 200 and int(body.get("stored") or 0) == 1,
"the fixture event stores through the push wire")
# The tab markup: Spry's CSS tabs (radios first, then .tab-list
# labels, then .tab-panels), Overview checked by default.
task_page = http_get_page(auth, "/tasks/%d" % task_id)
check('class="tabs task-tabs"' in task_page,
"the task page carries the tab strip")
check(task_page.index('id="task-view-overview"')
< task_page.index('class="tab-list"')
< task_page.index('class="tab-panels"'),
"the radios lead the tab markup (Spry's nth-of-type contract)")
check('id="task-view-overview" checked' in task_page,
"Overview is the default (checked) tab")
check('' in task_page
and '' in task_page,
"the tab list names Overview and Event log")
check('class="task-overview-panel"' in task_page
and 'class="task-log-panel"' in task_page,
"the panels carry the overview and the log")
print("== B2: one shared shell, both surfaces ==")
# The SAME partial ships both pages: the interactive hooks the
# client engine binds appear exactly once on each, and the
# compiled shell markup (the partial's) rides both.
task_page = http_get_page(auth, "/tasks/%d" % task_id)
log_page = http_get_page(auth, "/tasks/%d/log" % task_id)
for surface, name in ((task_page, "the task page"),
(log_page, "the log page")):
check('class="log-shell"' in surface and "data-log-filter" in surface,
"%s renders the shared log shell" % name)
# Hook multiplicity: each interactive hook appears exactly as
# many times as the ONE shared shell carries it (5 filter
# buttons, 1 feed/more/search/jump) — proving neither surface
# ships a second, hand-copied shell.
for hook, times in (("data-log-feed", 1), ("data-log-more", 1),
("data-log-filter", 5), ("data-log-search", 1),
("data-log-jump", 1)):
check(task_page.count(hook) == times
and log_page.count(hook) == times,
"the %s hook appears exactly %d time(s) on both surfaces"
% (hook, times))
print("== B3: no log link opens a new tab any more ==")
# The Progress cluster's control is a