@@ -1403,7 +1403,7 @@ def test_wait_for_any_content_exact_match(wait_pane: Pane) -> None:
14031403
14041404 # Capture the current content to match it exactly later
14051405 content = wait_pane .capture_pane ()
1406- content_str = "\n " .join (content )
1406+ content_str = "\n " .join (content if isinstance ( content , list ) else [ content ] )
14071407
14081408 # Run a test that won't match exactly
14091409 non_matching_result = wait_for_any_content (
@@ -1665,3 +1665,228 @@ def mock_time_time() -> float:
16651665
16661666 # We're not asserting elapsed_time anymore since we're using a direct mock
16671667 # 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