diff --git a/forgetest/forgetest/page.py b/forgetest/forgetest/page.py index af860d8..a430d91 100644 --- a/forgetest/forgetest/page.py +++ b/forgetest/forgetest/page.py @@ -43,6 +43,28 @@ table{width:100%;border-collapse:collapse} th{font-size:11.5px;color:var(--dim);text-align:left;font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)} td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top} tr:last-child td{border-bottom:0} +/* One grid for every subsystem. The groups are separate tables, so + left to themselves each sizes its own columns from its own content + and no two line up. Fixed widths put them all on the same rails. */ +#groups .grp{overflow-x:auto} +#groups table{table-layout:fixed;min-width:700px} +#groups col.k{width:132px} +#groups col.s{width:250px} +#groups col.l{width:210px} +#groups col.a{width:96px} +/* Why a test stands where it does reads as one block under Status. It + used to sit under the Start button, where a requires list of three + test ids wrapped into a ragged stack and dragged that group's columns + out of line with every other group's. */ +#groups .req{overflow-wrap:break-word} +/* The details sit in a full-width row of their own: inside the Test + cell they would have to be read through a column, and opening one + would push that group's grid out of step with the rest. */ +#groups td{border-bottom:0} +#groups tr.detrow td{padding:0 8px;border-bottom:1px solid var(--line)} +#groups tr.detrow:last-child td{border-bottom:0} +#groups .details{margin:2px 0 8px} +#groups .details .dsc{margin-bottom:6px} .tid{color:var(--dim);font-size:11.5px;font-family:ui-monospace,Consolas,monospace} .badge{display:inline-block;font-size:10.5px;padding:2px 6px;border-radius:9px;margin-right:4px;background:#e8e9ec;color:#444;font-weight:600;letter-spacing:.2px;text-transform:uppercase} .badge.live{background:#fbe1e1;color:#a11} @@ -257,21 +279,25 @@ function renderGroups(){var ids=[];catalog.forEach(function(t){ids.push(t.id)}); var sig=catalogHash+'|'+ids.join(','); if(sig!==groupsKey){groupsKey=sig;buildGroups()} updateGroups()} +var COLS=""; function buildGroups(){var groups={},order=[]; catalog.forEach(function(t){if(!groups[t.subsystem]){groups[t.subsystem]=[];order.push(t.subsystem)}groups[t.subsystem].push(t)}); var h=''; - order.forEach(function(g){h+="

"+esc(g)+"

"; + order.forEach(function(g){h+="

"+esc(g)+"

TestKindStatusLast result
"+COLS+""; groups[g].forEach(function(t){var d=esc(t.id);var badges=''; if(t.always)badges+="core";badges+=""+esc(t.kind)+""; if(t.hardware==='takeover')badges+="takeover"; - var det="
"+esc(t.description||'')+ - (t.steps&&t.steps.length?"
Operator steps:
    "+t.steps.map(function(x){return '
  1. '+esc(x)+'
  2. '}).join('')+"
":'')+ + var det="
"+ + (t.description?("
"+esc(t.description)+"
"):'')+ + (t.steps&&t.steps.length?"Operator steps:
    "+t.steps.map(function(x){return '
  1. '+esc(x)+'
  2. '}).join('')+"
":'')+ "Requires: "+esc((t.requires||[]).join(', ')||'-')+"
Covers: "+esc((t.covers||[]).map(function(c){return c[0]+':'+c[1]}).join(', ')||'-')+ "
"; - h+="
"+ - ""}); + h+=""+ + ""+ + ""+ + ""+ + ""}); h+="
TestKindStatusLast result
"+esc(t.title)+"
"+d+" details
"+det+ - "
"+badges+""+ - "
"+esc(t.title)+"
"+d+" details
"+ + "
"+badges+"
"+det+"
"}); $('groups').innerHTML=h;rowEls={}; catalog.forEach(function(t){rowEls[t.id]={st:$('st-'+t.id),last:$('last-'+t.id),btn:$('btn-'+t.id), diff --git a/forgetest/tests/test_responsiveness.py b/forgetest/tests/test_responsiveness.py index 0980d4c..b9560f2 100644 --- a/forgetest/tests/test_responsiveness.py +++ b/forgetest/tests/test_responsiveness.py @@ -18,6 +18,7 @@ holds the shape of that rule. """ import json import os +import re import shutil import tempfile import unittest @@ -191,6 +192,40 @@ class PageTests(unittest.TestCase): self.assertNotIn("id='" + bid + "-", html) self.assertIn("function renderQueue()", html) + def test_every_group_is_built_on_the_same_grid(self): + """The subsystems are separate tables. Left to size themselves + from their own content no two line up, which is what made the + page look busy, so they share one colgroup and a fixed layout.""" + html = page.render("0" * 32) + self.assertIn("#groups table{table-layout:fixed", html) + # one colgroup definition, emitted into every group's table + self.assertEqual(html.count('var COLS="'), 1) + self.assertEqual(html.count('"+COLS+"'), 1, + "a group table built without the shared colgroup") + colgroup = re.search(r'var COLS="(.*?)";', html).group(1) + heads = ["Test", "Kind", "Status", "Last result"] + for h in heads: + self.assertIn("" % h, html) + self.assertEqual(len(re.findall(r"| )", colgroup)), len(heads) + 1, + "a column per header, the action column included") + # every fixed column carries a width, or the grid is only a wish + for cls in re.findall(r"", html) + self.assertIn("", html) + def test_page_is_self_contained_ascii(self): html = page.render("0" * 32) self.assertNotIn("__TOKEN__", html)
%s
", html) + # the status cell carries the status, the unmet note and the row + # message; the action cell carries the button and nothing else + self.assertIn("