first commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""Tests des routes écrans / éléments posés sur un écran (via le client de
|
||||
test Flask, sur une vraie base SQLite du jeu de test)."""
|
||||
import re
|
||||
|
||||
|
||||
def _create_screen(client, slug, name="Accueil"):
|
||||
resp = client.post(f"/game/{slug}/screens/new", data={"name": name}, follow_redirects=False)
|
||||
assert resp.status_code == 302
|
||||
return int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1))
|
||||
|
||||
|
||||
def test_screen_crud(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
resp = client.get(f"/game/{game}/screens/{screen_id}/edit")
|
||||
assert resp.status_code == 200
|
||||
assert b"Accueil" in resp.data
|
||||
|
||||
resp = client.post(f"/game/{game}/screens/{screen_id}/rename", data={"name": "Menu"}, follow_redirects=False)
|
||||
assert resp.status_code == 302
|
||||
resp = client.get(f"/game/{game}/screens/{screen_id}/edit")
|
||||
assert b"Menu" in resp.data
|
||||
|
||||
resp = client.post(f"/game/{game}/screens/{screen_id}/delete", follow_redirects=False)
|
||||
assert resp.status_code == 302
|
||||
resp = client.get(f"/game/{game}/screens/{screen_id}/edit")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_add_element_and_save_properties(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/elements/add",
|
||||
data={"widget": "titre"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
element_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
|
||||
resp = client.post(
|
||||
f"/game/{game}/elements/{element_id}/save",
|
||||
data={"ctrl_content": "Bienvenue", "ctrl_niveau": "1"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
|
||||
assert "Bienvenue" in html
|
||||
|
||||
|
||||
def test_container_child_flex_row_no_collision(client, game):
|
||||
"""Régression : un conteneur en disposition 'ligne' ne doit plus forcer
|
||||
chacun de ses enfants à 100% de largeur (voir _style_string /
|
||||
_parent_flex_direction) — sinon deux enfants entrent en collision."""
|
||||
screen_id = _create_screen(client, game)
|
||||
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "conteneur"})
|
||||
container_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
client.post(
|
||||
f"/game/{game}/elements/{container_id}/save",
|
||||
data={"ctrl_disposition_interne": "ligne"},
|
||||
)
|
||||
resp = client.post(f"/game/{game}/elements/{container_id}/children/add", data={"widget": "titre"})
|
||||
child1_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
resp = client.post(f"/game/{game}/elements/{container_id}/children/add", data={"widget": "texte"})
|
||||
child2_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
|
||||
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
|
||||
idx1 = html.find(f'id="elt-{child1_id}"')
|
||||
idx2 = html.find(f'id="elt-{child2_id}"')
|
||||
style1 = html[max(0, idx1 - 400):idx1]
|
||||
style2 = html[max(0, idx2 - 400):idx2]
|
||||
# Aucun des deux enfants d'un conteneur en LIGNE ne doit avoir
|
||||
# width:100% forcé (ce qui causait la collision avant correction).
|
||||
assert "width:100%;" not in style1.split('style="')[-1] if 'style="' in style1 else True
|
||||
assert "width:auto" in style1 or "width:auto" in style2
|
||||
|
||||
|
||||
def test_element_type_template_case_a_and_case_b(client, game, mail_object):
|
||||
slug, definition_id = mail_object["slug"], mail_object["definition_id"]
|
||||
client.post(f"/game/{slug}/objects/{definition_id}/data/new", data={"sujet": "Sujet 1", "expediteur": "a@test.com"})
|
||||
client.post(f"/game/{slug}/objects/{definition_id}/data/new", data={"sujet": "Sujet 2", "expediteur": "b@test.com"})
|
||||
|
||||
resp = client.post(
|
||||
f"/game/{slug}/element-types",
|
||||
data={"name": "CarteMail", "icon": "📧", "bound_definition_id": str(definition_id)},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
html = client.get(f"/game/{slug}/element-types").data.decode()
|
||||
m = re.search(r"/screens/(\d+)/edit", html)
|
||||
assert m, "le lien vers l'écran-modèle doit apparaître dans le catalogue"
|
||||
template_screen_id = int(m.group(1))
|
||||
|
||||
# L'écran-modèle est un écran caché (is_template) : absent de la liste
|
||||
# normale des écrans jouables.
|
||||
screens_html = client.get(f"/game/{slug}/screens").data.decode()
|
||||
assert "CarteMail" not in screens_html or "Modèle" not in screens_html
|
||||
|
||||
# Construit le contenu du modèle : un titre lié au champ "sujet".
|
||||
resp = client.post(
|
||||
f"/game/{slug}/screens/{template_screen_id}/elements/add",
|
||||
data={"widget": "titre"},
|
||||
)
|
||||
titre_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
client.post(f"/game/{slug}/elements/{titre_id}/save", data={"ctrl_content": "{{sujet}}"})
|
||||
|
||||
element_type_id = int(re.search(r"element-types/(\d+)/delete", html).group(1))
|
||||
|
||||
# ---- Case A : posé directement sur un écran réel -> copie indépendante ----
|
||||
real_screen_id = _create_screen(client, slug, "Accueil")
|
||||
resp = client.post(
|
||||
f"/game/{slug}/screens/{real_screen_id}/elements/add",
|
||||
data={"widget": "__catalogue__", "element_type_id": str(element_type_id)},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
html = client.get(f"/game/{slug}/screens/{real_screen_id}/edit").data.decode()
|
||||
assert "{{sujet}}" in html # non résolu hors répéteur : limitation connue et documentée
|
||||
|
||||
# ---- Case B : modèle de ligne d'un Répéteur -> résolu en direct par ligne ----
|
||||
resp = client.post(f"/game/{slug}/screens/{real_screen_id}/elements/add", data={"widget": "repeteur_donnees"})
|
||||
rep_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
client.post(
|
||||
f"/game/{slug}/elements/{rep_id}/save",
|
||||
data={"ctrl_definition_id": str(definition_id), "ctrl_template_type_id": str(element_type_id)},
|
||||
)
|
||||
html = client.get(f"/game/{slug}/screens/{real_screen_id}/edit").data.decode()
|
||||
assert "Sujet 1" in html
|
||||
assert "Sujet 2" in html
|
||||
|
||||
|
||||
def test_unrelated_property_save_does_not_bake_in_default_background(client, game):
|
||||
"""Régression : changer un réglage (ex. la disposition interne) via le
|
||||
panneau de propriétés ne doit PAS écrire la couleur de fond par défaut
|
||||
du conteneur dans son style — cette valeur n'est qu'un aperçu affiché
|
||||
dans le sélecteur de couleur tant qu'elle n'a pas été choisie
|
||||
explicitement. Avant le correctif, comme tous les réglages du widget
|
||||
sont envoyés dans UN SEUL formulaire, sauvegarder n'importe lequel
|
||||
d'entre eux figeait aussi la couleur par défaut (#1f2430)."""
|
||||
screen_id = _create_screen(client, game)
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "conteneur"}, follow_redirects=False
|
||||
)
|
||||
container_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
|
||||
html_before = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={container_id}").data.decode()
|
||||
assert "background-color" not in html_before
|
||||
|
||||
# On ne touche qu'au préréglage de disposition, rien d'autre.
|
||||
client.post(
|
||||
f"/game/{game}/elements/{container_id}/save",
|
||||
data={"ctrl_disposition_interne": "ligne"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
html_after = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={container_id}").data.decode()
|
||||
assert "flex-direction:row" in html_after
|
||||
assert "background-color" not in html_after, (
|
||||
"La couleur de fond par défaut a été écrite alors qu'elle n'a jamais été choisie."
|
||||
)
|
||||
|
||||
# Si on choisit vraiment une couleur, elle doit en revanche être conservée.
|
||||
client.post(
|
||||
f"/game/{game}/elements/{container_id}/save",
|
||||
data={"ctrl_disposition_interne": "ligne", "ctrl_background": "#ff0000"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
html_custom = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={container_id}").data.decode()
|
||||
assert "background-color:#ff0000" in html_custom
|
||||
|
||||
|
||||
def test_image_gets_object_fit_cover_by_default(client, game):
|
||||
"""Régression : une image fraîchement posée doit démarrer avec
|
||||
object-fit:cover (pour se recadrer proprement une fois redimensionnée)
|
||||
même si le panneau de propriétés n'a jamais été enregistré — ce réglage
|
||||
ne doit pas dépendre du même mécanisme de sauvegarde que la couleur de
|
||||
fond (voir test ci-dessus), sous peine de régresser vers le
|
||||
comportement par défaut du navigateur (image étirée/déformée)."""
|
||||
screen_id = _create_screen(client, game)
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "image"}, follow_redirects=False
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
|
||||
assert "object-fit:cover" in html
|
||||
|
||||
|
||||
def test_delete_element_type_removes_hidden_template_screen(client, game):
|
||||
resp = client.post(f"/game/{game}/element-types", data={"name": "ToDelete", "icon": "🧩"}, follow_redirects=False)
|
||||
html = client.get(f"/game/{game}/element-types").data.decode()
|
||||
m = re.search(r"/screens/(\d+)/edit", html)
|
||||
template_screen_id = int(m.group(1))
|
||||
element_type_id = int(re.search(r"element-types/(\d+)/delete", html).group(1))
|
||||
|
||||
assert client.get(f"/game/{game}/screens/{template_screen_id}/edit").status_code == 200
|
||||
client.post(f"/game/{game}/element-types/{element_type_id}/delete")
|
||||
assert client.get(f"/game/{game}/screens/{template_screen_id}/edit").status_code == 404
|
||||
Reference in New Issue
Block a user