@@ -331,3 +331,265 @@ def test_populate_skips_for_record_conditions(scenario, record_stub, mocker):
331331 cc .populate (records )
332332 # Assert
333333 assert not cc .chapters ["Bugs" ].rows
334+
335+
336+ # Tests for hidden flag functionality
337+
338+
339+ def test_from_yaml_array_hidden_true ():
340+ # Arrange
341+ cc = CustomChapters ()
342+ # Act
343+ cc .from_yaml_array ([{"title" : "Hidden Chapter" , "labels" : "bug" , "hidden" : True }])
344+ # Assert
345+ assert "Hidden Chapter" in cc .chapters
346+ assert cc .chapters ["Hidden Chapter" ].hidden is True
347+
348+
349+ def test_from_yaml_array_hidden_false ():
350+ # Arrange
351+ cc = CustomChapters ()
352+ # Act
353+ cc .from_yaml_array ([{"title" : "Visible Chapter" , "labels" : "bug" , "hidden" : False }])
354+ # Assert
355+ assert "Visible Chapter" in cc .chapters
356+ assert cc .chapters ["Visible Chapter" ].hidden is False
357+
358+
359+ def test_from_yaml_array_hidden_omitted ():
360+ # Arrange
361+ cc = CustomChapters ()
362+ # Act
363+ cc .from_yaml_array ([{"title" : "Default Chapter" , "labels" : "bug" }])
364+ # Assert
365+ assert "Default Chapter" in cc .chapters
366+ assert cc .chapters ["Default Chapter" ].hidden is False
367+
368+
369+ @pytest .mark .parametrize (
370+ "hidden_value, expected_hidden, should_warn" ,
371+ [
372+ pytest .param ("true" , True , False , id = "string-true-lowercase" ),
373+ pytest .param ("True" , True , False , id = "string-true-capitalized" ),
374+ pytest .param ("false" , False , False , id = "string-false-lowercase" ),
375+ pytest .param ("False" , False , False , id = "string-false-capitalized" ),
376+ pytest .param ("invalid" , False , True , id = "invalid-string" ),
377+ pytest .param (123 , False , True , id = "integer-type" ),
378+ pytest .param ([], False , True , id = "list-type" ),
379+ ],
380+ )
381+ def test_from_yaml_array_hidden_validation (hidden_value , expected_hidden , should_warn , caplog ):
382+ # Arrange
383+ caplog .set_level ("WARNING" , logger = "release_notes_generator.chapters.custom_chapters" )
384+ cc = CustomChapters ()
385+ # Act
386+ cc .from_yaml_array ([{"title" : "Test Chapter" , "labels" : "bug" , "hidden" : hidden_value }])
387+ # Assert
388+ assert "Test Chapter" in cc .chapters
389+ assert cc .chapters ["Test Chapter" ].hidden is expected_hidden
390+ if should_warn :
391+ assert any ("invalid 'hidden' value" in r .message .lower () for r in caplog .records )
392+ else :
393+ assert not any ("invalid 'hidden' value" in r .message .lower () for r in caplog .records )
394+
395+
396+ def test_from_yaml_array_multi_label_with_hidden ():
397+ # Arrange
398+ cc = CustomChapters ()
399+ # Act
400+ cc .from_yaml_array ([{"title" : "Multi" , "labels" : ["bug" , "enhancement" ], "hidden" : True }])
401+ # Assert
402+ assert "Multi" in cc .chapters
403+ assert cc .chapters ["Multi" ].labels == ["bug" , "enhancement" ]
404+ assert cc .chapters ["Multi" ].hidden is True
405+
406+
407+ def test_from_yaml_array_legacy_single_label_with_hidden ():
408+ # Arrange
409+ cc = CustomChapters ()
410+ # Act
411+ cc .from_yaml_array ([{"title" : "Legacy" , "label" : "bug" , "hidden" : True }])
412+ # Assert
413+ assert "Legacy" in cc .chapters
414+ assert cc .chapters ["Legacy" ].labels == ["bug" ]
415+ assert cc .chapters ["Legacy" ].hidden is True
416+
417+
418+ def test_populate_hidden_chapter_assigns_records (record_stub ):
419+ # Arrange
420+ cc = CustomChapters ()
421+ cc .from_yaml_array ([{"title" : "Hidden Bugs" , "labels" : "bug" , "hidden" : True }])
422+ record = record_stub ("org/repo#1" , ["bug" ])
423+ records = {"org/repo#1" : record }
424+ # Act
425+ cc .populate (records )
426+ # Assert - record is assigned to hidden chapter
427+ assert "org/repo#1" in cc .chapters ["Hidden Bugs" ].rows
428+
429+
430+ def test_populate_hidden_chapter_no_duplicity_count (record_stub , mocker ):
431+ # Arrange
432+ cc = CustomChapters ()
433+ cc .from_yaml_array ([{"title" : "Hidden" , "labels" : "bug" , "hidden" : True }])
434+ record = record_stub ("org/repo#1" , ["bug" ])
435+ records = {"org/repo#1" : record }
436+ # Mock to_chapter_row to track calls
437+ original_to_chapter_row = record .to_chapter_row
438+ mock_to_chapter_row = mocker .Mock (side_effect = original_to_chapter_row )
439+ record .to_chapter_row = mock_to_chapter_row
440+ # Act
441+ cc .populate (records )
442+ # Assert - to_chapter_row called with add_into_chapters=False for hidden chapter
443+ mock_to_chapter_row .assert_called_once_with (False )
444+
445+
446+ def test_populate_visible_chapter_duplicity_count (record_stub , mocker ):
447+ # Arrange
448+ cc = CustomChapters ()
449+ cc .from_yaml_array ([{"title" : "Visible" , "labels" : "bug" , "hidden" : False }])
450+ record = record_stub ("org/repo#1" , ["bug" ])
451+ records = {"org/repo#1" : record }
452+ # Mock to_chapter_row to track calls
453+ original_to_chapter_row = record .to_chapter_row
454+ mock_to_chapter_row = mocker .Mock (side_effect = original_to_chapter_row )
455+ record .to_chapter_row = mock_to_chapter_row
456+ # Act
457+ cc .populate (records )
458+ # Assert - to_chapter_row called with add_into_chapters=True for visible chapter
459+ mock_to_chapter_row .assert_called_once_with (True )
460+
461+
462+ def test_populate_mixed_visible_hidden_duplicity (record_stub ):
463+ # Arrange
464+ cc = CustomChapters ()
465+ cc .from_yaml_array ([
466+ {"title" : "Visible1" , "labels" : "bug" , "hidden" : False },
467+ {"title" : "Hidden1" , "labels" : "bug" , "hidden" : True },
468+ {"title" : "Visible2" , "labels" : "bug" , "hidden" : False },
469+ ])
470+ record = record_stub ("org/repo#1" , ["bug" ])
471+ records = {"org/repo#1" : record }
472+ # Act
473+ cc .populate (records )
474+ # Assert - record appears in all chapters but only counted in visible ones
475+ assert "org/repo#1" in cc .chapters ["Visible1" ].rows
476+ assert "org/repo#1" in cc .chapters ["Hidden1" ].rows
477+ assert "org/repo#1" in cc .chapters ["Visible2" ].rows
478+ # Record should be marked as present in 2 chapters (only visible ones)
479+ assert record .chapter_presence_count () == 2
480+
481+
482+ def test_to_string_hidden_chapter_excluded ():
483+ # Arrange
484+ cc = CustomChapters ()
485+ cc .from_yaml_array ([
486+ {"title" : "Visible" , "labels" : "bug" },
487+ {"title" : "Hidden" , "labels" : "feature" , "hidden" : True },
488+ ])
489+ cc .chapters ["Visible" ].add_row (1 , "Bug fix" )
490+ cc .chapters ["Hidden" ].add_row (2 , "Hidden feature" )
491+ # Act
492+ result = cc .to_string ()
493+ # Assert
494+ assert "Visible" in result
495+ assert "Bug fix" in result
496+ assert "Hidden" not in result
497+ assert "Hidden feature" not in result
498+
499+
500+ def test_to_string_all_hidden_returns_empty ():
501+ # Arrange
502+ cc = CustomChapters ()
503+ cc .from_yaml_array ([
504+ {"title" : "Hidden1" , "labels" : "bug" , "hidden" : True },
505+ {"title" : "Hidden2" , "labels" : "feature" , "hidden" : True },
506+ ])
507+ cc .chapters ["Hidden1" ].add_row (1 , "Bug fix" )
508+ cc .chapters ["Hidden2" ].add_row (2 , "Feature" )
509+ # Act
510+ result = cc .to_string ()
511+ # Assert
512+ assert result == ""
513+
514+
515+ def test_to_string_hidden_empty_chapter_not_shown ():
516+ # Arrange
517+ cc = CustomChapters ()
518+ cc .print_empty_chapters = True
519+ cc .from_yaml_array ([{"title" : "Hidden Empty" , "labels" : "bug" , "hidden" : True }])
520+ # Act
521+ result = cc .to_string ()
522+ # Assert - hidden chapters never shown, even when print_empty_chapters is True
523+ assert result == ""
524+
525+
526+ def test_to_string_debug_logging_for_hidden (caplog ):
527+ # Arrange
528+ caplog .set_level ("DEBUG" )
529+ cc = CustomChapters ()
530+ cc .from_yaml_array ([{"title" : "Hidden" , "labels" : "bug" , "hidden" : True }])
531+ cc .chapters ["Hidden" ].add_row (1 , "Bug fix" )
532+ # Mock verbose mode
533+ import release_notes_generator .action_inputs
534+ original_get_verbose = release_notes_generator .action_inputs .ActionInputs .get_verbose
535+ release_notes_generator .action_inputs .ActionInputs .get_verbose = staticmethod (lambda : True )
536+ # Act
537+ try :
538+ cc .to_string ()
539+ # Assert
540+ assert any ("Skipping hidden chapter" in r .message for r in caplog .records )
541+ assert any ("Hidden" in r .message and "1 records tracked" in r .message for r in caplog .records )
542+ finally :
543+ # Restore
544+ release_notes_generator .action_inputs .ActionInputs .get_verbose = original_get_verbose
545+
546+
547+ def test_populate_debug_logging_for_hidden_assignment (record_stub , caplog ):
548+ # Arrange
549+ caplog .set_level ("DEBUG" )
550+ cc = CustomChapters ()
551+ cc .from_yaml_array ([{"title" : "Hidden" , "labels" : "bug" , "hidden" : True }])
552+ record = record_stub ("org/repo#1" , ["bug" ])
553+ records = {"org/repo#1" : record }
554+ # Mock verbose mode
555+ import release_notes_generator .action_inputs
556+ original_get_verbose = release_notes_generator .action_inputs .ActionInputs .get_verbose
557+ release_notes_generator .action_inputs .ActionInputs .get_verbose = staticmethod (lambda : True )
558+ # Act
559+ try :
560+ cc .populate (records )
561+ # Assert
562+ assert any ("assigned to hidden chapter" in r .message .lower () for r in caplog .records )
563+ assert any ("not counted for duplicity" in r .message .lower () for r in caplog .records )
564+ finally :
565+ # Restore
566+ release_notes_generator .action_inputs .ActionInputs .get_verbose = original_get_verbose
567+
568+
569+ def test_hidden_chapter_info_logging (caplog ):
570+ # Arrange
571+ caplog .set_level ("INFO" )
572+ cc = CustomChapters ()
573+ # Act
574+ cc .from_yaml_array ([{"title" : "Hidden" , "labels" : "bug" , "hidden" : True }])
575+ # Assert
576+ assert any (
577+ "marked as hidden" in r .message .lower () and "will not appear in output" in r .message .lower ()
578+ for r in caplog .records
579+ )
580+
581+
582+ def test_backward_compatibility_no_hidden_field ():
583+ # Arrange - test that chapters without hidden field work as before
584+ cc = CustomChapters ()
585+ # Act
586+ cc .from_yaml_array ([
587+ {"title" : "Breaking Changes 💥" , "label" : "breaking-change" },
588+ {"title" : "New Features 🎉" , "labels" : ["enhancement" , "feature" ]},
589+ ])
590+ # Assert
591+ assert "Breaking Changes 💥" in cc .chapters
592+ assert "New Features 🎉" in cc .chapters
593+ assert cc .chapters ["Breaking Changes 💥" ].hidden is False
594+ assert cc .chapters ["New Features 🎉" ].hidden is False
595+
0 commit comments