docs: the switches page states the width cap its checker enforces

docs/switches.md's 'Adding a key' bullets are what a key author follows, and they stated three of the
four rules tests/test_switches.py holds a picture to: JPEG or PNG, 16:9 and under 300 KB, but not the
1920 px width cap. A 4K screencap scaled to a 144 KB JPEG satisfies everything the page asks for and
fails the checker with 'is wider than 1920; 1280x720 is enough'.

The same page promised a screenshot for every key in the Keys table while 78 of the 98 keys sit on the
checker's wanted list and have none, so the opening paragraph and the sentence about the checker now
say what is actually true: a new key needs one, and the older ones are wanted.
This commit is contained in:
xbsheng committed 2026-10-24 18:25:39 +08:00
1 parent 9dc78f8dd5
commit ae4bd5e1bb
2 files changed
+144 -6

No files matched your search

+8 -6
View File
@@ -1,22 +1,24 @@
# Switches on a TV
Every key a theme can set (the Keys table in [`themes/README.md`](../themes/README.md)) gets a
screenshot of what it does on a real TV. This table is where they are listed: the repo and
[cleanyour.tv/addons#switches](https://cleanyour.tv/addons#switches) both show them from here.
Every new key a theme can set (the Keys table in [`themes/README.md`](../themes/README.md)) gets a
screenshot of what it does on a real TV, and this table is where they are listed: the repo and
[cleanyour.tv/addons#switches](https://cleanyour.tv/addons#switches) both show them from here. Most
keys from before this rule still have none; a screenshot for one of them is welcome.
## Adding a key
A new key needs a screenshot. Put it in `docs/switches/<key>.jpg` (an existing screenshot in the
repo that shows the key at work also counts) and add a row below:
- From a real TV, with the key set, as a JPEG or PNG, 16:9 (1280×720 is plenty), under 300 KB.
- From a real TV, with the key set, as a JPEG or PNG, 16:9 and no wider than 1920 px (1280×720 is
plenty), under 300 KB.
- Show where the key changes something you can see. For a key that maps a remote button, show the
screen the button opens; for one that changes behaviour only, show the setting's effect, such as
the panel, the menu or the app that comes up.
- Blur anything personal: Wi-Fi names, people, addresses.
`tests/test_switches.py` checks that every key has a row here with an image that exists and fits
the rules. Keys from before this rule are on its wanted list; a screenshot for one of them is welcome.
`tests/test_switches.py` checks that every new key has a row here with an image that exists and
fits the rules; keys from before the rule sit on its wanted list until a screenshot arrives.
## Screenshots
+136
View File
@@ -0,0 +1,136 @@
"""The picture rules docs/switches.md states are the ones tests/test_switches.py enforces.
A key author follows the page's "Adding a key" bullets and never opens the checker, so each limit in
the bullets has to be the limit the checker holds and has to be stated there rather than somewhere
else on the page. The numbers are read out of the two checkers instead of being copied in, so a
checker that stops enforcing one fails here rather than letting the page drift.
Run from the repository root: python3 -m unittest discover -s tests -v
"""
import re
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
ROOT = Path(__file__).resolve().parents[1]
PAGE = ROOT / "docs" / "switches.md"
SWITCH_TEST = ROOT / "tests" / "test_switches.py"
THEME_TEST = ROOT / "tests" / "test_themes.py"
SECTION = "Adding a key"
def section(text, heading):
"""The lines under '## <heading>', up to the next '## ' heading."""
out, on = [], False
for line in text.split("\n"):
if re.match(r"^##\s", line):
on = line.strip() == f"## {heading}"
continue
if on:
out.append(line)
return "\n".join(out)
def bullets(block):
"""Bullet items of a section, a wrapped continuation joined onto the item it belongs to."""
items, current = [], None
for line in block.split("\n"):
if re.match(r"^-\s", line):
if current:
items.append(current)
current = line[1:].strip()
elif current is not None and line[:1] in (" ", "\t") and line.strip():
current += " " + line.strip()
elif not line.strip() and current is not None:
items.append(current)
current = None
if current:
items.append(current)
return items
def size_limit_bytes():
"""MAX_SCREENSHOT in tests/test_themes.py, the cap test_switches.py checks every picture against."""
m = re.search(r"MAX_SCREENSHOT\s*=\s*(\d+)\s*\*\s*(\d+)", THEME_TEST.read_text(encoding="utf-8"))
return int(m.group(1)) * int(m.group(2)) if m else None
def width_limit_px():
"""The width cap test_switches.py puts on a listed picture (1280x720 is plenty, 4K is not)."""
m = re.search(r"assertLessEqual\(\s*width\s*,\s*(\d+)", SWITCH_TEST.read_text(encoding="utf-8"))
return int(m.group(1)) if m else None
def aspect_checked():
return bool(re.search(r"assertEqual\(\s*width\s*\*\s*9\s*,\s*height\s*\*\s*16",
SWITCH_TEST.read_text(encoding="utf-8")))
def stated_rules(text):
"""What the 'Adding a key' bullets say, lower-cased and joined, so a wrapped bullet reads whole."""
return " ".join(bullets(section(text, SECTION))).lower()
class SwitchesPageRulesTest(unittest.TestCase):
def test_the_checker_still_enforces_each_limit(self):
self.assertIsNotNone(size_limit_bytes(), "test_themes.py no longer defines MAX_SCREENSHOT")
self.assertIsNotNone(width_limit_px(),
"test_switches.py no longer caps the width: check the page and this test")
self.assertTrue(aspect_checked(), "test_switches.py no longer checks the 16:9 aspect")
def test_the_size_bullet_states_the_cap(self):
limit = size_limit_bytes()
self.assertIn(f"{limit // 1024} kb", stated_rules(PAGE.read_text(encoding="utf-8")),
"the Adding a key bullets do not say the size cap the checker enforces")
def test_the_size_bullet_states_the_width_cap(self):
limit = width_limit_px()
self.assertIn(str(limit), stated_rules(PAGE.read_text(encoding="utf-8")),
f"the Adding a key bullets do not say a picture may be no wider than {limit} px: "
"a 4K screencap that fits every other rule fails the checker")
def test_the_size_bullet_states_the_aspect(self):
self.assertIn("16:9", stated_rules(PAGE.read_text(encoding="utf-8")),
"the Adding a key bullets do not say 16:9")
def test_the_size_bullet_states_the_formats(self):
rules = stated_rules(PAGE.read_text(encoding="utf-8"))
self.assertIn("jpeg", rules, "the Adding a key bullets do not name JPEG")
self.assertIn("png", rules, "the Adding a key bullets do not name PNG")
def test_a_page_that_states_the_limits_passes(self):
limit = width_limit_px()
page = ("# Switches\n\nEvery new key gets a screenshot.\n\n## Adding a key\n\n"
f"- From a real TV, as a JPEG or PNG, 16:9, no wider than {limit} px, under "
f"{size_limit_bytes() // 1024} KB.\n\n## Screenshots\n\n| Key | Screenshot |\n")
rules = stated_rules(page)
self.assertIn(str(limit), rules)
self.assertIn(f"{size_limit_bytes() // 1024} kb", rules)
def test_the_real_page_lists_at_least_three_bullets(self):
items = bullets(section(PAGE.read_text(encoding="utf-8"), SECTION))
self.assertGreaterEqual(len(items), 3, "the Adding a key bullets read as empty")
def test_bullets_join_a_wrapped_line(self):
block = "- From a real TV, as a JPEG, 16:9 and no wider\n than 1920 px.\n- Blur names.\n"
self.assertEqual(bullets(block), ["From a real TV, as a JPEG, 16:9 and no wider than 1920 px.",
"Blur names."])
def test_a_bullet_in_another_section_is_not_read(self):
page = ("## Screenshots\n\n- no wider than 1920 px, under 300 KB.\n\n"
"## Adding a key\n\n- From a real TV.\n")
self.assertEqual(stated_rules(page), "from a real tv.")
def test_a_limit_in_the_prose_does_not_count(self):
page = ("## Adding a key\n\nA picture no wider than 1920 px, under 300 KB.\n\n"
"- From a real TV.\n")
self.assertNotIn("1920", stated_rules(page))
def test_a_page_without_the_section_reads_as_no_bullets(self):
self.assertEqual(stated_rules("# Switches\n\nNothing here.\n"), "")
if __name__ == "__main__":
unittest.main()