Skip to content

Commit ea702df

Browse files
Alexmacappleclaude
andcommitted
test: ajoute tests unitaires pour 4 hooks RGAA (100% coverage)
Ajoute 39 tests pour atteindre 100% de couverture sur les 4 hooks RGAA : - test_hooks_card_heading_level.py (8 tests) - h5 vers h3 - test_hooks_search_input_label.py (9 tests) - title recherche - test_hooks_pagination_title.py (11 tests) - title pagination - test_hooks_fix_sidemenu_ids.py (11 tests) - IDs uniques sidemenu Résultats : 58 tests passés, 100% coverage (90 statements, 0 missed) Fix GitHub Actions coverage failure (40% vers 100%) Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b0f4816 commit ea702df

4 files changed

Lines changed: 476 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Tests unitaires pour hooks/card_heading_level.py"""
2+
3+
from hooks.card_heading_level import on_page_content
4+
5+
6+
def test_transform_simple_card():
7+
"""Vérifie la transformation basique h5 → h3 pour une carte DSFR."""
8+
html = '<h5 class="fr-card__title" id="sircom">SIRCOM</h5>'
9+
result = on_page_content(html, None, None, None)
10+
assert '<h3 class="fr-card__title" id="sircom">SIRCOM</h3>' in result
11+
assert '<h5' not in result
12+
13+
14+
def test_transform_multiple_cards():
15+
"""Vérifie que plusieurs cartes sont transformées."""
16+
html = """
17+
<h5 class="fr-card__title" id="card1">Carte 1</h5>
18+
<p>Contenu</p>
19+
<h5 class="fr-card__title" id="card2">Carte 2</h5>
20+
"""
21+
result = on_page_content(html, None, None, None)
22+
assert result.count('<h3 class="fr-card__title"') == 2
23+
assert '<h5' not in result
24+
25+
26+
def test_preserve_card_title_attributes():
27+
"""Vérifie que tous les attributs des titres de cartes sont préservés."""
28+
html = '<h5 class="fr-card__title custom-class" id="test" data-attr="value">Titre</h5>'
29+
result = on_page_content(html, None, None, None)
30+
assert '<h3 class="fr-card__title custom-class" id="test" data-attr="value">Titre</h3>' in result
31+
32+
33+
def test_only_transform_card_titles():
34+
"""Vérifie que seuls les h5 avec fr-card__title sont transformés."""
35+
html = """
36+
<h5>Titre normal H5</h5>
37+
<h5 class="other-class">Autre H5</h5>
38+
<h5 class="fr-card__title">Carte</h5>
39+
"""
40+
result = on_page_content(html, None, None, None)
41+
assert '<h3 class="fr-card__title">Carte</h3>' in result
42+
43+
44+
def test_no_card_titles():
45+
"""Vérifie que le HTML sans titres de cartes n'est pas modifié."""
46+
html = "<p>Pas de cartes</p><div>Contenu</div>"
47+
result = on_page_content(html, None, None, None)
48+
assert result == html
49+
50+
51+
def test_card_title_with_rich_content():
52+
"""Vérifie que le contenu riche du titre est préservé."""
53+
html = '<h5 class="fr-card__title"><strong>SIRCOM</strong> - <em>Service</em></h5>'
54+
result = on_page_content(html, None, None, None)
55+
assert '<h3 class="fr-card__title"><strong>SIRCOM</strong> - <em>Service</em></h3>' in result
56+
57+
58+
def test_card_title_multiline():
59+
"""Vérifie le traitement des titres sur plusieurs lignes."""
60+
html = """
61+
<h5 class="fr-card__title" id="card">
62+
Titre multiligne
63+
</h5>
64+
"""
65+
result = on_page_content(html, None, None, None)
66+
assert '<h3 class="fr-card__title" id="card">' in result
67+
assert '</h3>' in result
68+
assert '<h5' not in result
69+
70+
71+
def test_card_title_with_various_class_orders():
72+
"""Vérifie que la classe fr-card__title est détectée quelle que soit sa position."""
73+
html = """
74+
<h5 class="fr-card__title">Ordre 1</h5>
75+
<h5 class="custom fr-card__title">Ordre 2</h5>
76+
<h5 class="fr-card__title custom other">Ordre 3</h5>
77+
"""
78+
result = on_page_content(html, None, None, None)
79+
assert result.count('<h3') == 3
80+
assert '<h5' not in result
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"""Tests unitaires pour hooks/fix_sidemenu_ids.py"""
2+
3+
from hooks.fix_sidemenu_ids import on_post_page
4+
5+
6+
def test_fix_single_sidemenu_id():
7+
"""Vérifie la correction d'un seul ID dupliqué dans le sidemenu."""
8+
html = """
9+
<button aria-controls="fr-sidemenu-wrapper">Section 1</button>
10+
<div id="fr-sidemenu-wrapper">Contenu</div>
11+
"""
12+
result = on_post_page(html, None, None)
13+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
14+
assert 'id="fr-sidemenu-wrapper-1"' in result
15+
assert 'fr-sidemenu-wrapper"' not in result
16+
17+
18+
def test_fix_multiple_sidemenu_ids():
19+
"""Vérifie la correction de plusieurs IDs dupliqués."""
20+
html = """
21+
<button aria-controls="fr-sidemenu-wrapper">Section 1</button>
22+
<div id="fr-sidemenu-wrapper">Contenu 1</div>
23+
<button aria-controls="fr-sidemenu-wrapper">Section 2</button>
24+
<div id="fr-sidemenu-wrapper">Contenu 2</div>
25+
"""
26+
result = on_post_page(html, None, None)
27+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
28+
assert 'id="fr-sidemenu-wrapper-1"' in result
29+
assert 'aria-controls="fr-sidemenu-wrapper-2"' in result
30+
assert 'id="fr-sidemenu-wrapper-2"' in result
31+
assert 'fr-sidemenu-wrapper"' not in result
32+
33+
34+
def test_fix_aria_controls_and_id_pairs():
35+
"""Vérifie que aria-controls et id sont tous deux numérotés."""
36+
html = """
37+
<button aria-controls="fr-sidemenu-wrapper">Titre</button>
38+
<div class="fr-collapse" id="fr-sidemenu-wrapper">Liste</div>
39+
"""
40+
result = on_post_page(html, None, None)
41+
assert result.count('-1"') == 2 # aria-controls-1 et id-1
42+
43+
44+
def test_preserve_other_attributes():
45+
"""Vérifie que les autres attributs sont préservés."""
46+
html = """
47+
<button class="fr-sidemenu__btn" aria-controls="fr-sidemenu-wrapper" aria-expanded="false">Section</button>
48+
<div class="fr-collapse" id="fr-sidemenu-wrapper" data-section="intro">Contenu</div>
49+
"""
50+
result = on_post_page(html, None, None)
51+
assert 'class="fr-sidemenu__btn"' in result
52+
assert 'aria-expanded="false"' in result
53+
assert 'class="fr-collapse"' in result
54+
assert 'data-section="intro"' in result
55+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
56+
assert 'id="fr-sidemenu-wrapper-1"' in result
57+
58+
59+
def test_no_sidemenu():
60+
"""Vérifie que le HTML sans sidemenu n'est pas modifié."""
61+
html = "<p>Pas de menu</p><div id='autre-id'>Contenu</div>"
62+
result = on_post_page(html, None, None)
63+
assert result == html
64+
65+
66+
def test_sidemenu_with_different_id():
67+
"""Vérifie que seuls les IDs fr-sidemenu-wrapper sont modifiés."""
68+
html = """
69+
<button aria-controls="fr-sidemenu-wrapper">Section</button>
70+
<div id="fr-sidemenu-wrapper">Menu</div>
71+
<button aria-controls="autre-id">Autre</button>
72+
<div id="autre-id">Autre contenu</div>
73+
"""
74+
result = on_post_page(html, None, None)
75+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
76+
assert 'id="fr-sidemenu-wrapper-1"' in result
77+
assert 'aria-controls="autre-id"' in result # Inchangé
78+
assert 'id="autre-id"' in result # Inchangé
79+
80+
81+
def test_three_or_more_sidemenus():
82+
"""Vérifie le traitement de 3+ sections de menu."""
83+
html = """
84+
<button aria-controls="fr-sidemenu-wrapper">S1</button>
85+
<div id="fr-sidemenu-wrapper">C1</div>
86+
<button aria-controls="fr-sidemenu-wrapper">S2</button>
87+
<div id="fr-sidemenu-wrapper">C2</div>
88+
<button aria-controls="fr-sidemenu-wrapper">S3</button>
89+
<div id="fr-sidemenu-wrapper">C3</div>
90+
"""
91+
result = on_post_page(html, None, None)
92+
assert 'wrapper-1"' in result
93+
assert 'wrapper-2"' in result
94+
assert 'wrapper-3"' in result
95+
assert result.count('aria-controls="fr-sidemenu-wrapper-') == 3
96+
assert result.count('id="fr-sidemenu-wrapper-') == 3
97+
98+
99+
def test_sidemenu_structure_complete():
100+
"""Vérifie le traitement d'une structure sidemenu DSFR complète."""
101+
html = """
102+
<nav class="fr-sidemenu" role="navigation">
103+
<div class="fr-sidemenu__inner">
104+
<button class="fr-sidemenu__btn" aria-controls="fr-sidemenu-wrapper" aria-expanded="false">
105+
Introduction
106+
</button>
107+
<div class="fr-collapse" id="fr-sidemenu-wrapper">
108+
<ul class="fr-sidemenu__list">
109+
<li><a href="#section1">Section 1</a></li>
110+
</ul>
111+
</div>
112+
</div>
113+
</nav>
114+
"""
115+
result = on_post_page(html, None, None)
116+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
117+
assert 'id="fr-sidemenu-wrapper-1"' in result
118+
assert 'class="fr-sidemenu"' in result
119+
assert 'class="fr-sidemenu__btn"' in result
120+
assert 'class="fr-collapse"' in result
121+
122+
123+
def test_sidemenu_multiline():
124+
"""Vérifie le traitement des éléments sur plusieurs lignes."""
125+
html = """
126+
<button
127+
class="fr-sidemenu__btn"
128+
aria-controls="fr-sidemenu-wrapper"
129+
aria-expanded="false">
130+
Titre
131+
</button>
132+
<div
133+
class="fr-collapse"
134+
id="fr-sidemenu-wrapper">
135+
Contenu
136+
</div>
137+
"""
138+
result = on_post_page(html, None, None)
139+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
140+
assert 'id="fr-sidemenu-wrapper-1"' in result
141+
142+
143+
def test_mismatched_aria_id_counts():
144+
"""Vérifie le traitement avec nombres différents d'aria-controls et id (edge case)."""
145+
# Cas anormal mais le hook doit continuer sans crash
146+
html = """
147+
<button aria-controls="fr-sidemenu-wrapper">S1</button>
148+
<button aria-controls="fr-sidemenu-wrapper">S2</button>
149+
<div id="fr-sidemenu-wrapper">C1</div>
150+
"""
151+
result = on_post_page(html, None, None)
152+
# Le hook traite quand même, même si la structure HTML est cassée
153+
assert 'aria-controls="fr-sidemenu-wrapper-1"' in result
154+
assert 'aria-controls="fr-sidemenu-wrapper-2"' in result
155+
assert 'id="fr-sidemenu-wrapper-1"' in result
156+
157+
158+
def test_sidemenu_ids_sequential():
159+
"""Vérifie que la numérotation est séquentielle et cohérente."""
160+
html = """
161+
<button aria-controls="fr-sidemenu-wrapper">S1</button>
162+
<div id="fr-sidemenu-wrapper">C1</div>
163+
<button aria-controls="fr-sidemenu-wrapper">S2</button>
164+
<div id="fr-sidemenu-wrapper">C2</div>
165+
<button aria-controls="fr-sidemenu-wrapper">S3</button>
166+
<div id="fr-sidemenu-wrapper">C3</div>
167+
"""
168+
result = on_post_page(html, None, None)
169+
170+
# Vérifier ordre séquentiel
171+
pos_aria_1 = result.find('aria-controls="fr-sidemenu-wrapper-1"')
172+
pos_id_1 = result.find('id="fr-sidemenu-wrapper-1"')
173+
pos_aria_2 = result.find('aria-controls="fr-sidemenu-wrapper-2"')
174+
pos_id_2 = result.find('id="fr-sidemenu-wrapper-2"')
175+
pos_aria_3 = result.find('aria-controls="fr-sidemenu-wrapper-3"')
176+
pos_id_3 = result.find('id="fr-sidemenu-wrapper-3"')
177+
178+
# Ordre: aria-1 < id-1 < aria-2 < id-2 < aria-3 < id-3
179+
assert pos_aria_1 < pos_id_1 < pos_aria_2 < pos_id_2 < pos_aria_3 < pos_id_3
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Tests unitaires pour hooks/pagination_title.py"""
2+
3+
from hooks.pagination_title import on_post_page
4+
5+
6+
def test_fix_next_pagination_title():
7+
"""Vérifie la correction du title pour lien 'Page suivante'."""
8+
html = '<a class="fr-pagination__link fr-pagination__link--next" href="/page2/" title="SPAN (SG)">Page suivante</a>'
9+
result = on_post_page(html, None, None)
10+
assert 'title="Page suivante : SPAN (SG)"' in result
11+
12+
13+
def test_fix_prev_pagination_title():
14+
"""Vérifie la correction du title pour lien 'Page précédente'."""
15+
html = '<a class="fr-pagination__link fr-pagination__link--prev" href="/page1/" title="Accueil">Page précédente</a>'
16+
result = on_post_page(html, None, None)
17+
assert 'title="Page précédente : Accueil"' in result
18+
19+
20+
def test_preserve_next_and_prev_both():
21+
"""Vérifie que les deux liens de pagination sont corrigés."""
22+
html = """
23+
<a class="fr-pagination__link fr-pagination__link--prev" href="/page1/" title="Intro">Page précédente</a>
24+
<a class="fr-pagination__link fr-pagination__link--next" href="/page3/" title="Conclusion">Page suivante</a>
25+
"""
26+
result = on_post_page(html, None, None)
27+
assert 'title="Page précédente : Intro"' in result
28+
assert 'title="Page suivante : Conclusion"' in result
29+
30+
31+
def test_preserve_other_attributes():
32+
"""Vérifie que tous les attributs du lien sont préservés."""
33+
html = '<a class="fr-pagination__link fr-pagination__link--next custom-class" href="/page2/" title="SPAN" id="next-link" data-page="2">Page suivante</a>'
34+
result = on_post_page(html, None, None)
35+
assert 'href="/page2/"' in result
36+
assert 'id="next-link"' in result
37+
assert 'data-page="2"' in result
38+
assert 'custom-class' in result
39+
assert 'title="Page suivante : SPAN"' in result
40+
41+
42+
def test_no_pagination_links():
43+
"""Vérifie que le HTML sans pagination n'est pas modifié."""
44+
html = "<p>Pas de pagination</p><a href='/autre'>Lien normal</a>"
45+
result = on_post_page(html, None, None)
46+
assert result == html
47+
48+
49+
def test_pagination_title_already_compliant():
50+
"""Vérifie le traitement d'un title déjà conforme RGAA."""
51+
html = '<a class="fr-pagination__link fr-pagination__link--next" href="/page2/" title="Titre original">Page suivante</a>'
52+
result = on_post_page(html, None, None)
53+
# Le hook ajoute toujours le préfixe, même si déjà présent
54+
assert 'title="Page suivante : Titre original"' in result
55+
56+
57+
def test_pagination_with_additional_classes():
58+
"""Vérifie le traitement avec classes CSS supplémentaires."""
59+
html1 = '<a class="fr-pagination__link--next other-class" href="/p2/" title="Page 2">Page suivante</a>'
60+
html2 = '<a class="other-class fr-pagination__link--prev" href="/p1/" title="Page 1">Page précédente</a>'
61+
62+
result1 = on_post_page(html1, None, None)
63+
result2 = on_post_page(html2, None, None)
64+
65+
assert 'title="Page suivante : Page 2"' in result1
66+
assert 'title="Page précédente : Page 1"' in result2
67+
68+
69+
def test_pagination_multiline():
70+
"""Vérifie le traitement des liens sur plusieurs lignes."""
71+
html = """
72+
<a class="fr-pagination__link fr-pagination__link--next"
73+
href="/page2/"
74+
title="SPAN (SG)">
75+
Page suivante
76+
</a>
77+
"""
78+
result = on_post_page(html, None, None)
79+
assert 'title="Page suivante : SPAN (SG)"' in result
80+
81+
82+
def test_various_attribute_orders():
83+
"""Vérifie que la classe fr-pagination__link peut être à différentes positions."""
84+
# Note: Le pattern regex nécessite class="..." avant title="..." pour matcher
85+
html1 = '<a class="fr-pagination__link--next" title="Page 2" href="/p2/">Page suivante</a>'
86+
html2 = '<a class="fr-pagination__link--next" href="/p2/" title="Page 2">Page suivante</a>'
87+
html3 = '<a href="/p2/" class="fr-pagination__link--next" title="Page 2">Page suivante</a>'
88+
89+
result1 = on_post_page(html1, None, None)
90+
result2 = on_post_page(html2, None, None)
91+
result3 = on_post_page(html3, None, None)
92+
93+
assert 'title="Page suivante : Page 2"' in result1
94+
assert 'title="Page suivante : Page 2"' in result2
95+
assert 'title="Page suivante : Page 2"' in result3
96+
97+
98+
def test_pagination_with_special_chars_in_title():
99+
"""Vérifie le traitement des caractères spéciaux dans les titres."""
100+
html1 = '<a class="fr-pagination__link--next" href="/p2/" title="L\'accessibilité">Page suivante</a>'
101+
html2 = '<a class="fr-pagination__link--next" href="/p2/" title="État & Services">Page suivante</a>'
102+
html3 = '<a class="fr-pagination__link--next" href="/p2/" title="Données (2025)">Page suivante</a>'
103+
104+
result1 = on_post_page(html1, None, None)
105+
result2 = on_post_page(html2, None, None)
106+
result3 = on_post_page(html3, None, None)
107+
108+
assert "title=\"Page suivante : L'accessibilité\"" in result1
109+
assert 'title="Page suivante : État & Services"' in result2
110+
assert 'title="Page suivante : Données (2025)"' in result3
111+
112+
113+
def test_pagination_with_different_page_titles():
114+
"""Vérifie le traitement de divers titres de pages."""
115+
html = """
116+
<a class="fr-pagination__link--prev" href="/intro/" title="Introduction générale">Page précédente</a>
117+
<a class="fr-pagination__link--next" href="/module-sircom/" title="SIRCOM - Services d'information">Page suivante</a>
118+
"""
119+
result = on_post_page(html, None, None)
120+
assert 'title="Page précédente : Introduction générale"' in result
121+
assert 'title="Page suivante : SIRCOM - Services d\'information"' in result

0 commit comments

Comments
 (0)