@@ -1495,6 +1495,99 @@ class TestNSString: LoopbackServerTest {
1495
1495
}
1496
1496
}
1497
1497
1498
+ func test_paragraphRange( ) {
1499
+ let text = " Klaatu \n barada \r \n nikto. \r Remember 🟨those \u{2029} words. "
1500
+ let nsText = text as NSString
1501
+
1502
+ // Expected paragraph ranges in test string
1503
+ let paragraphRanges = [
1504
+ NSRange ( location: 0 , length: 7 ) ,
1505
+ NSRange ( location: 7 , length: 8 ) ,
1506
+ NSRange ( location: 15 , length: 7 ) ,
1507
+ NSRange ( location: 22 , length: 17 ) ,
1508
+ NSRange ( location: 39 , length: 6 ) ,
1509
+ ]
1510
+
1511
+ // We also will check ranges across two consecutive paragraphs.
1512
+ // Generate pairs from plain array.
1513
+ let paragraphPairs = paragraphRanges. enumerated ( ) . compactMap { i, range -> ( NSRange , NSRange ) ? in
1514
+ guard i < paragraphRanges. count - 1 else {
1515
+ return nil
1516
+ }
1517
+
1518
+ return ( range, paragraphRanges [ i + 1 ] )
1519
+ }
1520
+
1521
+ // Helper function. Generates all possible subranges in provided range.
1522
+ // Interrupts if handler returns false.
1523
+ func subranges( in range: NSRange , with handler: ( NSRange ) -> Bool ) {
1524
+ for location in range. location..< ( range. location + range. length) {
1525
+ let maxLength = range. length - ( location - range. location)
1526
+ for length in 0 ... maxLength {
1527
+ let generatedRange = NSRange ( location: location, length: length)
1528
+
1529
+ guard handler ( generatedRange) else {
1530
+ return
1531
+ }
1532
+ }
1533
+ }
1534
+ }
1535
+
1536
+ // Simplest check. Whole string is one or more
1537
+ // paragraphs, so result range should cover it completely.
1538
+ let wholeStringRange = NSRange ( location: 0 , length: nsText. length)
1539
+ let allParagrapsRange = nsText. paragraphRange ( for: wholeStringRange)
1540
+ XCTAssertEqual ( wholeStringRange, allParagrapsRange)
1541
+
1542
+ // Every paragraph is checked against all possible subranges in it.
1543
+ for expectedRange in paragraphRanges {
1544
+ subranges ( in: expectedRange) { generatedRange in
1545
+ let calculatedRange = nsText. paragraphRange ( for: generatedRange)
1546
+
1547
+ // One fail report is enough.
1548
+ // Otherwise there will be hundreds.
1549
+ // Using manual check (not XCTAssertEqual)
1550
+ // for early exit.
1551
+ guard calculatedRange == expectedRange else {
1552
+ XCTFail ( " paragraphRange(for:) returned \( calculatedRange) for \( generatedRange) , but expected is \( expectedRange) " )
1553
+ return false
1554
+ }
1555
+
1556
+ return true
1557
+ }
1558
+ }
1559
+
1560
+ // Every paragraph pair is checked against all possible
1561
+ // subranges in single continuous range of both paragraphs.
1562
+ for paragraphPair in paragraphPairs {
1563
+ let paragraphPairRange = NSRange ( location: paragraphPair. 0 . location, length: paragraphPair. 0 . length + paragraphPair. 1 . length)
1564
+ subranges ( in: paragraphPairRange) { generatedRange in
1565
+ let calculatedRange = nsText. paragraphRange ( for: generatedRange)
1566
+
1567
+ let expectedRange : NSRange = {
1568
+ // Does it fit in first paragraph range?
1569
+ if paragraphPair. 0 . intersection ( generatedRange) == generatedRange {
1570
+ return paragraphPair. 0
1571
+ }
1572
+ // Does it fit in second paragraph range?
1573
+ if paragraphPair. 1 . intersection ( generatedRange) == generatedRange {
1574
+ return paragraphPair. 1
1575
+ }
1576
+ // Neither completely in first, nor in second. Must be partially in both.
1577
+ return paragraphPairRange
1578
+ } ( )
1579
+
1580
+ // Again, manual check with early exit
1581
+ guard calculatedRange == expectedRange else {
1582
+ XCTFail ( " paragraphRange(for:) returned \( calculatedRange) for \( generatedRange) , but expected \( expectedRange) " )
1583
+ return false
1584
+ }
1585
+
1586
+ return true
1587
+ }
1588
+ }
1589
+ }
1590
+
1498
1591
static var allTests : [ ( String , ( TestNSString ) -> ( ) throws -> Void ) ] {
1499
1592
return [
1500
1593
( " test_initData " , test_initData) ,
@@ -1567,6 +1660,7 @@ class TestNSString: LoopbackServerTest {
1567
1660
( " test_lineRangeFor " , test_lineRangeFor) ,
1568
1661
( " test_fileSystemRepresentation " , test_fileSystemRepresentation) ,
1569
1662
( " test_enumerateSubstrings " , test_enumerateSubstrings) ,
1663
+ ( " test_paragraphRange " , test_paragraphRange) ,
1570
1664
]
1571
1665
}
1572
1666
}
0 commit comments