@@ -1438,24 +1438,264 @@ - (void)testUpdateFirstRectForRange {
14381438 [inputView firstRectForRange: range]));
14391439}
14401440
1441- - (void )testFirstRectForRangeReturnsCorrectSelectionRect {
1441+ - (void )testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight {
14421442 FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
14431443 [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
14441444
1445- FlutterTextRange* range = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 1 )];
1446- CGRect testRect = CGRectMake (100 , 100 , 100 , 100 );
14471445 [inputView setSelectionRects: @[
14481446 [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 0U ],
1449- [FlutterTextSelectionRect selectionRectWithRect: testRect position: 1U ],
1450- [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 200 , 100 , 100 ) position: 2U ],
1447+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 1U ],
1448+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 2U ],
1449+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 3U ],
14511450 ]];
1452- XCTAssertTrue (CGRectEqualToRect (testRect, [inputView firstRectForRange: range]));
1451+ FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 1 )];
1452+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 100 , 100 ),
1453+ [inputView firstRectForRange: singleRectRange]));
1454+
1455+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 3 )];
1456+
1457+ if (@available (iOS 17 , *)) {
1458+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 300 , 100 ),
1459+ [inputView firstRectForRange: multiRectRange]));
1460+ } else {
1461+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 100 , 100 ),
1462+ [inputView firstRectForRange: multiRectRange]));
1463+ }
14531464
14541465 [inputView setTextInputState: @{@" text" : @" COM" }];
14551466 FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange: NSMakeRange (3 , 1 )];
14561467 XCTAssertTrue (CGRectEqualToRect (CGRectZero, [inputView firstRectForRange: rangeOutsideBounds]));
14571468}
14581469
1470+ - (void )testFirstRectForRangeReturnsCorrectRectOnASingleLineRightToLeft {
1471+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1472+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1473+
1474+ [inputView setSelectionRects: @[
1475+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 0U ],
1476+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 1U ],
1477+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 2U ],
1478+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 3U ],
1479+ ]];
1480+ FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 1 )];
1481+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (200 , 0 , 100 , 100 ),
1482+ [inputView firstRectForRange: singleRectRange]));
1483+
1484+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 3 )];
1485+ if (@available (iOS 17 , *)) {
1486+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (0 , 0 , 300 , 100 ),
1487+ [inputView firstRectForRange: multiRectRange]));
1488+ } else {
1489+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (200 , 0 , 100 , 100 ),
1490+ [inputView firstRectForRange: multiRectRange]));
1491+ }
1492+
1493+ [inputView setTextInputState: @{@" text" : @" COM" }];
1494+ FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange: NSMakeRange (3 , 1 )];
1495+ XCTAssertTrue (CGRectEqualToRect (CGRectZero, [inputView firstRectForRange: rangeOutsideBounds]));
1496+ }
1497+
1498+ - (void )testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight {
1499+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1500+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1501+
1502+ [inputView setSelectionRects: @[
1503+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 0U ],
1504+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 1U ],
1505+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 2U ],
1506+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 3U ],
1507+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 100 , 100 , 100 ) position: 4U ],
1508+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 100 , 100 , 100 ) position: 5U ],
1509+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 100 , 100 , 100 ) position: 6U ],
1510+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 100 , 100 , 100 ) position: 7U ],
1511+ ]];
1512+ FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 1 )];
1513+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 100 , 100 ),
1514+ [inputView firstRectForRange: singleRectRange]));
1515+
1516+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 4 )];
1517+
1518+ if (@available (iOS 17 , *)) {
1519+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 300 , 100 ),
1520+ [inputView firstRectForRange: multiRectRange]));
1521+ } else {
1522+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 100 , 100 ),
1523+ [inputView firstRectForRange: multiRectRange]));
1524+ }
1525+ }
1526+
1527+ - (void )testFirstRectForRangeReturnsCorrectRectOnMultipleLinesRightToLeft {
1528+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1529+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1530+
1531+ [inputView setSelectionRects: @[
1532+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 0U ],
1533+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 1U ],
1534+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 2U ],
1535+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 3U ],
1536+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 100 , 100 , 100 ) position: 4U ],
1537+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 100 , 100 , 100 ) position: 5U ],
1538+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 100 , 100 , 100 ) position: 6U ],
1539+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 100 , 100 , 100 ) position: 7U ],
1540+ ]];
1541+ FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 1 )];
1542+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (200 , 0 , 100 , 100 ),
1543+ [inputView firstRectForRange: singleRectRange]));
1544+
1545+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 4 )];
1546+ if (@available (iOS 17 , *)) {
1547+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (0 , 0 , 300 , 100 ),
1548+ [inputView firstRectForRange: multiRectRange]));
1549+ } else {
1550+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (200 , 0 , 100 , 100 ),
1551+ [inputView firstRectForRange: multiRectRange]));
1552+ }
1553+ }
1554+
1555+ - (void )testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYLeftToRight {
1556+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1557+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1558+
1559+ [inputView setSelectionRects: @[
1560+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 0U ],
1561+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 10 , 100 , 80 )
1562+ position: 1U ], // shorter
1563+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , -10 , 100 , 120 )
1564+ position: 2U ], // taller
1565+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 3U ],
1566+ ]];
1567+
1568+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 3 )];
1569+
1570+ if (@available (iOS 17 , *)) {
1571+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , -10 , 300 , 120 ),
1572+ [inputView firstRectForRange: multiRectRange]));
1573+ } else {
1574+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 10 , 100 , 80 ),
1575+ [inputView firstRectForRange: multiRectRange]));
1576+ }
1577+ }
1578+
1579+ - (void )testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYRightToLeft {
1580+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1581+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1582+
1583+ [inputView setSelectionRects: @[
1584+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 0U ],
1585+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , -10 , 100 , 120 )
1586+ position: 1U ], // taller
1587+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 10 , 100 , 80 )
1588+ position: 2U ], // shorter
1589+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 3U ],
1590+ ]];
1591+
1592+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 3 )];
1593+
1594+ if (@available (iOS 17 , *)) {
1595+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (0 , -10 , 300 , 120 ),
1596+ [inputView firstRectForRange: multiRectRange]));
1597+ } else {
1598+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (200 , -10 , 100 , 120 ),
1599+ [inputView firstRectForRange: multiRectRange]));
1600+ }
1601+ }
1602+
1603+ - (void )testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdLeftToRight {
1604+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1605+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1606+
1607+ [inputView setSelectionRects: @[
1608+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 0U ],
1609+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 1U ],
1610+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 2U ],
1611+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 3U ],
1612+ // y=60 exceeds threshold, so treat it as a new line.
1613+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 60 , 100 , 100 ) position: 4U ],
1614+ ]];
1615+
1616+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 4 )];
1617+
1618+ if (@available (iOS 17 , *)) {
1619+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 300 , 100 ),
1620+ [inputView firstRectForRange: multiRectRange]));
1621+ } else {
1622+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 100 , 100 ),
1623+ [inputView firstRectForRange: multiRectRange]));
1624+ }
1625+ }
1626+
1627+ - (void )testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdRightToLeft {
1628+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1629+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1630+
1631+ [inputView setSelectionRects: @[
1632+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 0U ],
1633+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 1U ],
1634+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 2U ],
1635+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 3U ],
1636+ // y=60 exceeds threshold, so treat it as a new line.
1637+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 60 , 100 , 100 ) position: 4U ],
1638+ ]];
1639+
1640+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 4 )];
1641+
1642+ if (@available (iOS 17 , *)) {
1643+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (0 , 0 , 300 , 100 ),
1644+ [inputView firstRectForRange: multiRectRange]));
1645+ } else {
1646+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (200 , 0 , 100 , 100 ),
1647+ [inputView firstRectForRange: multiRectRange]));
1648+ }
1649+ }
1650+
1651+ - (void )testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdLeftToRight {
1652+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1653+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1654+
1655+ [inputView setSelectionRects: @[
1656+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 0 , 100 , 100 ) position: 0U ],
1657+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 1U ],
1658+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 2U ],
1659+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 3U ],
1660+ // y=40 is within line threshold, so treat it as the same line
1661+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (400 , 40 , 100 , 100 ) position: 4U ],
1662+ ]];
1663+
1664+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 4 )];
1665+
1666+ if (@available (iOS 17 , *)) {
1667+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 400 , 140 ),
1668+ [inputView firstRectForRange: multiRectRange]));
1669+ } else {
1670+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (100 , 0 , 100 , 100 ),
1671+ [inputView firstRectForRange: multiRectRange]));
1672+ }
1673+ }
1674+
1675+ - (void )testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdRightToLeft {
1676+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
1677+ [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
1678+
1679+ [inputView setSelectionRects: @[
1680+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (400 , 0 , 100 , 100 ) position: 0U ],
1681+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (300 , 0 , 100 , 100 ) position: 1U ],
1682+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (200 , 0 , 100 , 100 ) position: 2U ],
1683+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (100 , 0 , 100 , 100 ) position: 3U ],
1684+ // y=40 is within line threshold, so treat it as the same line
1685+ [FlutterTextSelectionRect selectionRectWithRect: CGRectMake (0 , 40 , 100 , 100 ) position: 4U ],
1686+ ]];
1687+
1688+ FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange: NSMakeRange (1 , 4 )];
1689+
1690+ if (@available (iOS 17 , *)) {
1691+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (0 , 0 , 400 , 140 ),
1692+ [inputView firstRectForRange: multiRectRange]));
1693+ } else {
1694+ XCTAssertTrue (CGRectEqualToRect (CGRectMake (300 , 0 , 100 , 100 ),
1695+ [inputView firstRectForRange: multiRectRange]));
1696+ }
1697+ }
1698+
14591699- (void )testClosestPositionToPoint {
14601700 FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
14611701 [inputView setTextInputState: @{@" text" : @" COMPOSING" }];
0 commit comments