@@ -1403,7 +1403,7 @@ def test_wait_for_any_content_exact_match(wait_pane: Pane) -> None:
1403
1403
1404
1404
# Capture the current content to match it exactly later
1405
1405
content = wait_pane .capture_pane ()
1406
- content_str = "\n " .join (content )
1406
+ content_str = "\n " .join (content if isinstance ( content , list ) else [ content ] )
1407
1407
1408
1408
# Run a test that won't match exactly
1409
1409
non_matching_result = wait_for_any_content (
@@ -1665,3 +1665,228 @@ def mock_time_time() -> float:
1665
1665
1666
1666
# We're not asserting elapsed_time anymore since we're using a direct mock
1667
1667
# to test the control flow, not actual timing
1668
+
1669
+
1670
+ def test_match_regex_across_lines_with_line_numbers (wait_pane : Pane ) -> None :
1671
+ """Test the _match_regex_across_lines with line numbers.
1672
+
1673
+ This test specifically targets the line 1169 where matches are identified
1674
+ across multiple lines, including the fallback case when no specific line
1675
+ was matched.
1676
+ """
1677
+ # Create content with newlines that we know exactly
1678
+ content_list = [
1679
+ "line1" ,
1680
+ "line2" ,
1681
+ "line3" ,
1682
+ "line4" ,
1683
+ "multi" ,
1684
+ "line" ,
1685
+ "content" ,
1686
+ ]
1687
+
1688
+ # Create a pattern that will match across lines but not on a single line
1689
+ pattern = re .compile (r"line2.*line3" , re .DOTALL )
1690
+
1691
+ # Call _match_regex_across_lines directly with our controlled content
1692
+ matched , matched_text , match_line = _match_regex_across_lines (content_list , pattern )
1693
+
1694
+ assert matched is True
1695
+ assert matched_text is not None
1696
+ assert "line2" in matched_text
1697
+ assert "line3" in matched_text
1698
+
1699
+ # Now test with a pattern that matches in a specific line
1700
+ pattern = re .compile (r"line3" )
1701
+ matched , matched_text , match_line = _match_regex_across_lines (content_list , pattern )
1702
+
1703
+ assert matched is True
1704
+ assert matched_text == "line3"
1705
+ assert match_line is not None
1706
+ assert match_line == 2 # 0-indexed, so line "line3" is at index 2
1707
+
1708
+ # Test the fallback case - match in joined content but not individual lines
1709
+ complex_pattern = re .compile (r"line1.*multi" , re .DOTALL )
1710
+ matched , matched_text , match_line = _match_regex_across_lines (
1711
+ content_list , complex_pattern
1712
+ )
1713
+
1714
+ assert matched is True
1715
+ assert matched_text is not None
1716
+ assert "line1" in matched_text
1717
+ assert "multi" in matched_text
1718
+ # In this case, match_line might be None since it's across multiple lines
1719
+
1720
+ # Test no match case
1721
+ pattern = re .compile (r"not_in_content" )
1722
+ matched , matched_text , match_line = _match_regex_across_lines (content_list , pattern )
1723
+
1724
+ assert matched is False
1725
+ assert matched_text is None
1726
+ assert match_line is None
1727
+
1728
+
1729
+ def test_contains_and_regex_match_fallbacks () -> None :
1730
+ """Test the fallback logic in _contains_match and _regex_match.
1731
+
1732
+ This test specifically targets lines 1108 and 1141 which handle the case
1733
+ when a match is found in joined content but not in individual lines.
1734
+ """
1735
+ # Create content with newlines inside that will create a match when joined
1736
+ # but not in any individual line (notice the split between "first part" and "of")
1737
+ content_with_newlines = [
1738
+ "first part" ,
1739
+ "of a sentence" ,
1740
+ "another line" ,
1741
+ ]
1742
+
1743
+ # Test _contains_match where the match spans across lines
1744
+ # Match "first part" + newline + "of a"
1745
+ search_str = "first part\n of a"
1746
+ matched , matched_text , match_line = _contains_match (
1747
+ content_with_newlines , search_str
1748
+ )
1749
+
1750
+ # The match should be found in the joined content, but not in any individual line
1751
+ assert matched is True
1752
+ assert matched_text == search_str
1753
+ assert match_line is None # This is the fallback case we're testing
1754
+
1755
+ # Test _regex_match where the match spans across lines
1756
+ pattern = re .compile (r"first part\nof" )
1757
+ matched , matched_text , match_line = _regex_match (content_with_newlines , pattern )
1758
+
1759
+ # The match should be found in the joined content, but not in any individual line
1760
+ assert matched is True
1761
+ assert matched_text is not None
1762
+ assert "first part" in matched_text
1763
+ assert match_line is None # This is the fallback case we're testing
1764
+
1765
+ # Test with a pattern that matches at the end of one line and beginning of another
1766
+ pattern = re .compile (r"part\nof" )
1767
+ matched , matched_text , match_line = _regex_match (content_with_newlines , pattern )
1768
+
1769
+ assert matched is True
1770
+ assert matched_text is not None
1771
+ assert "part\n of" in matched_text
1772
+ assert match_line is None # Fallback case since match spans multiple lines
1773
+
1774
+
1775
+ def test_wait_for_pane_content_specific_type_errors (wait_pane : Pane ) -> None :
1776
+ """Test specific type error handling in wait_for_pane_content.
1777
+
1778
+ This test targets lines 445-451, 461-465, 481-485 which handle
1779
+ various type error conditions in different match types.
1780
+ """
1781
+ # Import error message constants from the module
1782
+ from libtmux .test .waiter import (
1783
+ ERR_CONTAINS_TYPE ,
1784
+ ERR_EXACT_TYPE ,
1785
+ ERR_PREDICATE_TYPE ,
1786
+ ERR_REGEX_TYPE ,
1787
+ )
1788
+
1789
+ # Test EXACT match with non-string pattern
1790
+ with pytest .raises (TypeError ) as excinfo :
1791
+ wait_for_pane_content (
1792
+ wait_pane ,
1793
+ 123 , # type: ignore
1794
+ ContentMatchType .EXACT ,
1795
+ timeout = 0.1 ,
1796
+ )
1797
+ assert ERR_EXACT_TYPE in str (excinfo .value )
1798
+
1799
+ # Test CONTAINS match with non-string pattern
1800
+ with pytest .raises (TypeError ) as excinfo :
1801
+ wait_for_pane_content (
1802
+ wait_pane ,
1803
+ 123 , # type: ignore
1804
+ ContentMatchType .CONTAINS ,
1805
+ timeout = 0.1 ,
1806
+ )
1807
+ assert ERR_CONTAINS_TYPE in str (excinfo .value )
1808
+
1809
+ # Test REGEX match with invalid pattern type
1810
+ with pytest .raises (TypeError ) as excinfo :
1811
+ wait_for_pane_content (
1812
+ wait_pane ,
1813
+ 123 , # type: ignore
1814
+ ContentMatchType .REGEX ,
1815
+ timeout = 0.1 ,
1816
+ )
1817
+ assert ERR_REGEX_TYPE in str (excinfo .value )
1818
+
1819
+ # Test PREDICATE match with non-callable pattern
1820
+ with pytest .raises (TypeError ) as excinfo :
1821
+ wait_for_pane_content (
1822
+ wait_pane ,
1823
+ "not callable" ,
1824
+ ContentMatchType .PREDICATE ,
1825
+ timeout = 0.1 ,
1826
+ )
1827
+ assert ERR_PREDICATE_TYPE in str (excinfo .value )
1828
+
1829
+
1830
+ def test_wait_for_pane_content_exact_match_detailed (wait_pane : Pane ) -> None :
1831
+ """Test wait_for_pane_content with EXACT match type in detail.
1832
+
1833
+ This test specifically targets lines 447-451 where the exact
1834
+ match type is handled, including the code path where a match
1835
+ is found and validated.
1836
+ """
1837
+ # Clear the pane first to have more predictable content
1838
+ wait_pane .clear ()
1839
+ time .sleep (0.3 ) # Give time for clear to take effect
1840
+
1841
+ # Send a unique string that we can test with an exact match
1842
+ wait_pane .send_keys ("UNIQUE_TEST_STRING_123" , literal = True )
1843
+ time .sleep (0.3 ) # Give more time for content to appear
1844
+
1845
+ # Get the current content to work with
1846
+ content = wait_pane .capture_pane ()
1847
+ content_str = "\n " .join (content if isinstance (content , list ) else [content ])
1848
+
1849
+ # Verify our test string is in the content
1850
+ assert "UNIQUE_TEST_STRING_123" in content_str
1851
+
1852
+ # Test with CONTAINS match type first (more reliable)
1853
+ result = wait_for_pane_content (
1854
+ wait_pane ,
1855
+ "UNIQUE_TEST_STRING_123" ,
1856
+ ContentMatchType .CONTAINS ,
1857
+ timeout = 1.0 ,
1858
+ interval = 0.1 ,
1859
+ )
1860
+ assert result .success
1861
+
1862
+ # Now test with EXACT match but with a simpler approach
1863
+ # Find the exact line that contains our test string
1864
+ for line in content :
1865
+ if "UNIQUE_TEST_STRING_123" in line :
1866
+ exact_line = line
1867
+ break
1868
+ else :
1869
+ # If we can't find the line, use a fallback
1870
+ exact_line = "UNIQUE_TEST_STRING_123"
1871
+
1872
+ # Test the EXACT match against just the line containing our test string
1873
+ result = wait_for_pane_content (
1874
+ wait_pane ,
1875
+ exact_line ,
1876
+ ContentMatchType .EXACT ,
1877
+ timeout = 1.0 ,
1878
+ interval = 0.1 ,
1879
+ )
1880
+
1881
+ assert result .success
1882
+ assert result .matched_content == exact_line
1883
+
1884
+ # Test EXACT match failing case
1885
+ with pytest .raises (WaitTimeout ):
1886
+ wait_for_pane_content (
1887
+ wait_pane ,
1888
+ "content that definitely doesn't exist" ,
1889
+ ContentMatchType .EXACT ,
1890
+ timeout = 0.2 ,
1891
+ interval = 0.1 ,
1892
+ )
0 commit comments