]*>(cisco-sa-[A-Za-z0-9._-]+)", section)
+ self.assertEqual(sorted(listed), sorted(linked),
+ "an unlinked identifier means a reader has to scroll back up")
+
+ def test_an_advisory_without_a_url_still_renders(self):
+ """A row whose facts could not be fetched must not lose its identifier."""
+ bare = advisory(FMC_ADV)
+ bare["publicationUrl"] = ""
+ devices = [device("a", "FMC", advisories=[bare])]
+ html, _ = rendered(devices=devices,
+ bundle=a_bundle(advisory_ids=(FMC_ADV,)), records={})
+ section = self._index_section(html)
+ self.assertIn(FMC_ADV, section)
+ self.assertNotIn('href=""', section)
+
+ def test_columns_do_not_reflow_when_a_disclosure_opens(self):
+ """Fixed layout: expanding one row must not shift the rows being compared."""
+ html, _ = rendered()
+ section = self._index_section(html)
+ self.assertIn('class="plain table-fixed"', section)
+ for css_class in ("col-dev", "col-model", "col-rel", "col-count", "col-which"):
+ self.assertIn(css_class, section)
+ self.assertIn("table-layout: fixed", html)
+
+ def test_every_fixed_column_has_a_declared_width(self):
+ html, _ = rendered()
+ for css_class in ("col-dev", "col-model", "col-rel", "col-count",
+ "col-which", "col-why"):
+ self.assertRegex(html, rf"\.{css_class}\s*\{{[^}}]*width:")
+
+ def test_undetermined_table_is_also_fixed(self):
+ """Same disclosure-reflow problem, same fix."""
+ html, _ = rendered()
+ start = html.index("could not be assessed")
+ section = html[start:html.index("", start)]
+ self.assertIn("table-fixed", section)
+ self.assertIn("col-why", section)
+
+ def test_long_values_wrap_rather_than_widening_a_column(self):
+ html, _ = rendered()
+ self.assertIn("overflow-wrap: anywhere", html)
+
+ def test_identifiers_are_not_broken_mid_token(self):
+ """An identifier split across four lines is unreadable and uncopyable."""
+ html, _ = rendered()
+ self.assertRegex(html, r"\.idlist a\s*\{[^}]*overflow-wrap:\s*normal")
+ self.assertRegex(html, r"\.idlist a\s*\{[^}]*word-break:\s*keep-all")
+
+ def test_fixed_tables_have_a_minimum_width_and_scroll_when_narrower(self):
+ """Fixed columns must not squeeze the advisory column on a narrow window.
+
+ The longest identifier measures ~316px in this monospace at this size, so the
+ advisory column needs ~334px including the list indent. 40% of the 900px floor
+ gives 360px; anything much narrower wraps every identifier.
+ """
+ html, _ = rendered()
+ self.assertRegex(html, r"table\.table-fixed\s*\{[^}]*min-width:\s*900px")
+ self.assertRegex(html, r"\.tscroll\s*\{[^}]*overflow-x:\s*auto")
+ section = self._index_section(html)
+ self.assertIn('', section)
+
+ def test_each_fixed_table_column_set_sums_to_one_hundred_percent(self):
+ """A set summing to anything else makes the declared widths advisory, not fixed."""
+ html, _ = rendered()
+ width = {}
+ for name in ("col-dev", "col-model", "col-rel", "col-count", "col-which", "col-why"):
+ found = re.search(rf"\.{name}\s*\{{[^}}]*width:\s*(\d+)%", html)
+ self.assertIsNotNone(found, f"{name} has no percentage width")
+ width[name] = int(found.group(1))
+ device_index = ("col-dev", "col-model", "col-rel", "col-count", "col-which")
+ undetermined = ("col-dev", "col-model", "col-rel", "col-why")
+ self.assertEqual(sum(width[c] for c in device_index), 100)
+ self.assertEqual(sum(width[c] for c in undetermined), 100)
+
+
class Output(unittest.TestCase):
def test_filename_carries_the_bundle_and_keeps_the_prefix(self):
with tempfile.TemporaryDirectory() as tmp: