@@ -751,4 +751,188 @@ mod tests {
751751 assert ! ( !styles. get( "CompanyHeader" ) . unwrap( ) . builtin) ;
752752 assert ! ( !styles. get( "LegalDisclaimer" ) . unwrap( ) . builtin) ;
753753 }
754+
755+ // ==================== Sprint 9: StyleSheet Edge Cases ====================
756+
757+ #[ test]
758+ fn test_resolve_chain_circular_reference ( ) {
759+ // Create styles with circular inheritance: A→B→A
760+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
761+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
762+ <w:style w:type="paragraph" w:styleId="StyleA">
763+ <w:name w:val="Style A"/>
764+ <w:basedOn w:val="StyleB"/>
765+ </w:style>
766+ <w:style w:type="paragraph" w:styleId="StyleB">
767+ <w:name w:val="Style B"/>
768+ <w:basedOn w:val="StyleA"/>
769+ </w:style>
770+ </w:styles>"# ;
771+
772+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
773+
774+ // Should not panic or loop forever - breaks at cycle detection
775+ let chain = styles. resolve_chain ( "StyleA" ) ;
776+
777+ // Chain should contain at most both styles (breaks at seen check)
778+ assert ! ( chain. len( ) <= 2 ) ;
779+ assert_eq ! ( chain[ 0 ] . id, "StyleA" ) ;
780+ }
781+
782+ #[ test]
783+ fn test_resolve_chain_self_reference ( ) {
784+ // Create a style that references itself
785+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
786+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
787+ <w:style w:type="paragraph" w:styleId="SelfRef">
788+ <w:name w:val="Self Reference"/>
789+ <w:basedOn w:val="SelfRef"/>
790+ </w:style>
791+ </w:styles>"# ;
792+
793+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
794+
795+ // Should not loop - breaks immediately on self-reference
796+ let chain = styles. resolve_chain ( "SelfRef" ) ;
797+
798+ assert_eq ! ( chain. len( ) , 1 ) ;
799+ assert_eq ! ( chain[ 0 ] . id, "SelfRef" ) ;
800+ }
801+
802+ #[ test]
803+ fn test_resolve_chain_deep_inheritance ( ) {
804+ // Create a deep inheritance chain: A→B→C→D→E
805+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
806+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
807+ <w:style w:type="paragraph" w:styleId="StyleA">
808+ <w:name w:val="Style A"/>
809+ <w:basedOn w:val="StyleB"/>
810+ </w:style>
811+ <w:style w:type="paragraph" w:styleId="StyleB">
812+ <w:name w:val="Style B"/>
813+ <w:basedOn w:val="StyleC"/>
814+ </w:style>
815+ <w:style w:type="paragraph" w:styleId="StyleC">
816+ <w:name w:val="Style C"/>
817+ <w:basedOn w:val="StyleD"/>
818+ </w:style>
819+ <w:style w:type="paragraph" w:styleId="StyleD">
820+ <w:name w:val="Style D"/>
821+ <w:basedOn w:val="StyleE"/>
822+ </w:style>
823+ <w:style w:type="paragraph" w:styleId="StyleE">
824+ <w:name w:val="Style E"/>
825+ </w:style>
826+ </w:styles>"# ;
827+
828+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
829+
830+ let chain = styles. resolve_chain ( "StyleA" ) ;
831+
832+ // Should resolve entire chain
833+ assert_eq ! ( chain. len( ) , 5 ) ;
834+ assert_eq ! ( chain[ 0 ] . id, "StyleA" ) ;
835+ assert_eq ! ( chain[ 1 ] . id, "StyleB" ) ;
836+ assert_eq ! ( chain[ 2 ] . id, "StyleC" ) ;
837+ assert_eq ! ( chain[ 3 ] . id, "StyleD" ) ;
838+ assert_eq ! ( chain[ 4 ] . id, "StyleE" ) ;
839+ }
840+
841+ #[ test]
842+ fn test_resolve_chain_missing_base ( ) {
843+ // Style references a base that doesn't exist
844+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
845+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
846+ <w:style w:type="paragraph" w:styleId="Orphan">
847+ <w:name w:val="Orphan Style"/>
848+ <w:basedOn w:val="NonExistent"/>
849+ </w:style>
850+ </w:styles>"# ;
851+
852+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
853+
854+ let chain = styles. resolve_chain ( "Orphan" ) ;
855+
856+ // Should only contain the orphan style (base not found)
857+ assert_eq ! ( chain. len( ) , 1 ) ;
858+ assert_eq ! ( chain[ 0 ] . id, "Orphan" ) ;
859+ assert_eq ! ( chain[ 0 ] . based_on, Some ( "NonExistent" . to_string( ) ) ) ;
860+ }
861+
862+ #[ test]
863+ fn test_resolve_chain_empty_for_unknown ( ) {
864+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
865+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
866+ <w:style w:type="paragraph" w:styleId="Normal">
867+ <w:name w:val="Normal"/>
868+ </w:style>
869+ </w:styles>"# ;
870+
871+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
872+
873+ // Unknown style returns empty chain
874+ let chain = styles. resolve_chain ( "DoesNotExist" ) ;
875+ assert ! ( chain. is_empty( ) ) ;
876+ }
877+
878+ #[ test]
879+ fn test_is_heading_and_level ( ) {
880+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
881+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
882+ <w:style w:type="paragraph" w:styleId="Heading1">
883+ <w:name w:val="Heading 1"/>
884+ <w:pPr>
885+ <w:outlineLvl w:val="0"/>
886+ </w:pPr>
887+ </w:style>
888+ <w:style w:type="paragraph" w:styleId="Heading3">
889+ <w:name w:val="Heading 3"/>
890+ <w:pPr>
891+ <w:outlineLvl w:val="2"/>
892+ </w:pPr>
893+ </w:style>
894+ <w:style w:type="paragraph" w:styleId="Normal">
895+ <w:name w:val="Normal"/>
896+ </w:style>
897+ </w:styles>"# ;
898+
899+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
900+
901+ assert ! ( styles. is_heading( "Heading1" ) ) ;
902+ assert ! ( styles. is_heading( "Heading3" ) ) ;
903+ assert ! ( !styles. is_heading( "Normal" ) ) ;
904+ assert ! ( !styles. is_heading( "Unknown" ) ) ;
905+
906+ assert_eq ! ( styles. heading_level( "Heading1" ) , Some ( 1 ) ) ;
907+ assert_eq ! ( styles. heading_level( "Heading3" ) , Some ( 3 ) ) ;
908+ assert_eq ! ( styles. heading_level( "Normal" ) , None ) ;
909+ assert_eq ! ( styles. heading_level( "Unknown" ) , None ) ;
910+ }
911+
912+ #[ test]
913+ fn test_stylesheet_iteration ( ) {
914+ let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
915+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
916+ <w:style w:type="paragraph" w:styleId="Para1">
917+ <w:name w:val="Paragraph 1"/>
918+ </w:style>
919+ <w:style w:type="table" w:styleId="Table1">
920+ <w:name w:val="Table 1"/>
921+ </w:style>
922+ <w:style w:type="paragraph" w:styleId="Para2">
923+ <w:name w:val="Paragraph 2"/>
924+ </w:style>
925+ </w:styles>"# ;
926+
927+ let styles = StyleSheet :: parse ( xml) . unwrap ( ) ;
928+
929+ // Test paragraph iteration
930+ let para_styles: Vec < _ > = styles. paragraph_styles ( ) . collect ( ) ;
931+ assert_eq ! ( para_styles. len( ) , 2 ) ;
932+
933+ // Test table iteration
934+ let table_styles: Vec < _ > = styles. table_styles ( ) . collect ( ) ;
935+ assert_eq ! ( table_styles. len( ) , 1 ) ;
936+ assert_eq ! ( table_styles[ 0 ] . id, "Table1" ) ;
937+ }
754938}
0 commit comments