@@ -192,6 +192,54 @@ def test_every_cycle_has_a_usable_tag(self, cycle_config, at_repo_root, cycle_fi
192192 assert tag and not set (tag ) & cycle_config .TAG_FORBIDDEN
193193
194194
195+ @needs_script
196+ class TestReleaseTitle :
197+ """The title people actually read on the releases page.
198+
199+ Reproduced from the titles written by hand for years rather than invented:
200+ cycle, then the level after a space -- the tag runs them together, the
201+ title does not -- then the date with no comma, which is how all but one of
202+ the previous titles read.
203+ """
204+
205+ @pytest .mark .parametrize ("cycle,level,date,expected" , [
206+ # the real titles of these releases, to the letter
207+ ("2026_03" , "" , (2026 , 8 , 22 ), "WinPython 2026-03 of August 22nd 2026" ),
208+ ("2026_03" , "b3" , (2026 , 8 , 8 ), "WinPython 2026-03 b3 of August 8th 2026" ),
209+ ("2026_02" , "b2" , (2026 , 5 , 1 ), "WinPython 2026-02 b2 of May 1st 2026" ),
210+ ("2026_01" , "final" , (2026 , 3 , 10 ), "WinPython 2026-01 final of March 10th 2026" ),
211+ ("2025_05" , "rc" , (2025 , 12 , 22 ), "WinPython 2025-05 rc of December 22nd 2025" ),
212+ ("2026_01" , "b3" , (2026 , 2 , 24 ), "WinPython 2026-01 b3 of February 24th 2026" ),
213+ ])
214+ def test_matches_the_titles_used_before (self , cycle_config , cycle , level , date , expected ):
215+ import datetime
216+
217+ cfg = {"release_level" : level } if level else {}
218+ assert cycle_config .release_title (cfg , cycle , datetime .date (* date )) == expected
219+
220+ def test_no_double_space_when_there_is_no_level (self , cycle_config ):
221+ """Some older titles read "2026-02 of May 17th": a level that was empty."""
222+ import datetime
223+
224+ title = cycle_config .release_title ({"release_level" : "" }, "2026_04" , datetime .date (2026 , 5 , 17 ))
225+ assert " " not in title
226+
227+ @pytest .mark .parametrize ("day,expected" , [
228+ (1 , "1st" ), (2 , "2nd" ), (3 , "3rd" ), (4 , "4th" ),
229+ (11 , "11th" ), (12 , "12th" ), (13 , "13th" ), # not 11st/12nd/13rd
230+ (21 , "21st" ), (22 , "22nd" ), (23 , "23rd" ), (30 , "30th" ), (31 , "31st" ),
231+ ])
232+ def test_ordinals (self , cycle_config , day , expected ):
233+ assert cycle_config .ordinal (day ) == expected
234+
235+ @needs_cycles
236+ @pytest .mark .parametrize ("cycle_file" , cycle_files , ids = lambda p : p .stem )
237+ def test_every_cycle_produces_a_title (self , cycle_config , at_repo_root , cycle_file ):
238+ title = config_for (cycle_config , cycle_file )["release_title" ]
239+ assert title .startswith ("WinPython " ) and " of " in title
240+ assert "\n " not in title , "a GITHUB_OUTPUT value has to stay on one line"
241+
242+
195243@needs_script
196244@needs_workflow
197245@needs_cycles
@@ -260,6 +308,11 @@ def test_publishing_is_gated_both_ways(self, workflow_text):
260308 assert "if: ${{ inputs.publish }}" in workflow_text
261309 assert "if: ${{ !inputs.publish }}" in workflow_text
262310
311+ def test_the_release_title_is_built_by_the_script (self , workflow_text ):
312+ """Ordinal dates are miserable in shell, and untestable there."""
313+ assert 'TITLE: ${{ needs.config.outputs.release_title }}' in workflow_text
314+ assert '--title "$TITLE"' in workflow_text
315+
263316 def test_the_changelog_name_carries_the_release_level (self , workflow_text ):
264317 """A beta and the final it becomes share a ver2.
265318
0 commit comments