All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m10s
- Nieuw core/categories.py: zeventien categorieen, elk met naam (nl/en), kleur en pictogram. Kleuren zijn middentonen die in licht en donker leesbaar zijn; de interface gebruikt ze als tekstkleur op een doorzichtige variant. - Alle 96 apps ingedeeld via metadata.categories. Een app mag in meerdere categorieen zitten; 39 apps doen dat (Frigate onder domotica en media, Immich onder fotos en opslag, Kavita onder media en documenten). - Apps uit externe repos geven geen categorieen op; die worden afgeleid uit tags en omschrijving, zodat ook zij een kleur en een plek in het filter krijgen. - App store: gekleurde categoriebalk met aantallen, klikbare badges per app, werkt naast het bestaande zoekveld en de tagfilters. - Stackslijst: dezelfde balk met alleen de categorieen die je draait, gekleurde rand per kaart en klikbare badges. - Nieuw /api/categories geeft de definities met kleur, zodat interface en backend dezelfde kleur voor dezelfde categorie gebruiken. /api/stacks geeft de categorieen mee; een stack van voor deze versie valt terug op "overig" in plaats van kleurloos te blijven. - Tests: elke app heeft geldige categorieen, kleuren zijn uniek, en er staat geen categorie in het filter zonder apps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
"""Categorieën: indeling, kleuren en hoe ze door de API komen."""
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
WORTEL = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(WORTEL / "server-up"))
|
|
|
|
from core import boilerplates as bp
|
|
from core import categories as cat
|
|
|
|
from conftest import login
|
|
|
|
APPS = sorted(d for d in (WORTEL / "apps").iterdir() if d.is_dir())
|
|
|
|
|
|
def test_elke_categorie_heeft_naam_en_kleur():
|
|
for cid, c in cat.CATEGORIES.items():
|
|
assert c["nl"] and c["en"], cid
|
|
assert c["color"].startswith("#") and len(c["color"]) == 7, cid
|
|
assert c["icon"].startswith("mdi-"), cid
|
|
|
|
|
|
def test_kleuren_zijn_uniek():
|
|
"""Twee categorieën met dezelfde kleur zijn niet uit elkaar te houden."""
|
|
kleuren = [c["color"] for c in cat.CATEGORIES.values()]
|
|
assert len(kleuren) == len(set(kleuren))
|
|
|
|
|
|
@pytest.mark.parametrize("app", APPS, ids=[d.name for d in APPS])
|
|
def test_elke_app_heeft_geldige_categorieen(app):
|
|
cats = bp.metadata(app)["categories"]
|
|
assert cats, "geen categorie"
|
|
for c in cats:
|
|
assert c in cat.CATEGORIES, f"onbekende categorie '{c}'"
|
|
assert len(cats) == len(set(cats)), "dubbele categorie"
|
|
|
|
|
|
def test_apps_mogen_in_meerdere_categorieen():
|
|
meervoudig = [d.name for d in APPS if len(bp.metadata(d)["categories"]) > 1]
|
|
assert len(meervoudig) >= 20, "vrijwel niemand valt onder meerdere categorieën"
|
|
|
|
|
|
def test_elke_categorie_wordt_gebruikt():
|
|
"""Een categorie die nergens voorkomt, is alleen maar ruis in het filter."""
|
|
gebruikt = {c for d in APPS for c in bp.metadata(d)["categories"]}
|
|
ongebruikt = set(cat.CATEGORIES) - gebruikt - {"overig"}
|
|
assert not ongebruikt, f"categorieën zonder apps: {sorted(ongebruikt)}"
|
|
|
|
|
|
def test_afleiden_uit_tags_voor_externe_repos():
|
|
"""Apps uit een externe repo geven geen categorieën op; dan leiden we ze af."""
|
|
assert "media" in cat.derive(["films", "series"])
|
|
assert "domotica" in cat.derive(["zigbee", "mqtt"])
|
|
assert "beveiliging" in cat.derive(["wachtwoorden"])
|
|
assert cat.derive([]) == ["overig"]
|
|
assert cat.derive(["iets-heel-onbekends"]) == ["overig"]
|
|
|
|
|
|
def test_afleiden_geeft_hoogstens_drie():
|
|
veel = ["films", "zigbee", "wachtwoorden", "backup", "wiki", "taken", "ai"]
|
|
assert len(cat.derive(veel)) <= 3
|
|
|
|
|
|
def test_normalize_gooit_onbekende_weg():
|
|
assert cat.normalize(["media", "bestaatniet", "media"]) == ["media"]
|
|
|
|
|
|
def test_normalize_valt_terug_op_tags():
|
|
assert cat.normalize([], ["films"]) == ["media"]
|
|
|
|
|
|
def test_endpoint_geeft_kleuren(client):
|
|
login(client)
|
|
d = client.get("/api/categories").get_json()
|
|
ids = {c["id"] for c in d["categories"]}
|
|
assert "media" in ids and "domotica" in ids
|
|
media = next(c for c in d["categories"] if c["id"] == "media")
|
|
assert media["color"].startswith("#") and media["name"]
|
|
|
|
|
|
def test_endpoint_in_het_engels(client):
|
|
login(client)
|
|
d = client.get("/api/categories?lang=en").get_json()
|
|
media = next(c for c in d["categories"] if c["id"] == "media")
|
|
assert media["name"] == "Media"
|
|
fam = next(c for c in d["categories"] if c["id"] == "gezin")
|
|
assert fam["name"] == "Family & home"
|
|
|
|
|
|
def test_stacklijst_geeft_categorieen(client, env):
|
|
login(client)
|
|
stack = env["lib"] / "jellyfin"
|
|
stack.mkdir(parents=True)
|
|
(stack / "docker-compose.yml").write_text("services:\n a:\n image: x\n")
|
|
(stack / ".serverup.json").write_text(json.dumps(
|
|
{"source": "jellyfin", "name": "Jellyfin", "categories": ["media"]}))
|
|
d = client.get("/api/stacks").get_json()
|
|
assert d[0]["categories"] == ["media"]
|
|
|
|
|
|
def test_oude_installatie_zonder_categorieen(client, env):
|
|
"""Een stack van vóór deze versie heeft geen categorieën opgeslagen; die
|
|
mogen niet leeg blijven, anders valt de kleurmarkering weg."""
|
|
login(client)
|
|
stack = env["lib"] / "oud"
|
|
stack.mkdir(parents=True)
|
|
(stack / "docker-compose.yml").write_text("services:\n a:\n image: x\n")
|
|
(stack / ".serverup.json").write_text(json.dumps({"source": "oud", "name": "Oud"}))
|
|
d = client.get("/api/stacks").get_json()
|
|
assert d[0]["categories"] == ["overig"]
|