11package org.nitri.opentopo.overlay
22
33import android.graphics.Canvas
4- import android.graphics.Matrix
5- import android.graphics.RectF
64import androidx.test.ext.junit.runners.AndroidJUnit4
75import androidx.test.platform.app.InstrumentationRegistry
86import org.junit.Assert.*
@@ -17,6 +15,7 @@ class MarkerAnnotationGeometryTest {
1715
1816 private lateinit var renderer: MarkerAnnotationRenderer
1917 private val context = InstrumentationRegistry .getInstrumentation().targetContext
18+ private val EPSILON = 0.1f
2019
2120 @Before
2221 fun setup () {
@@ -29,49 +28,98 @@ class MarkerAnnotationGeometryTest {
2928 }
3029
3130 @Test
32- fun hitTest_rotationCorrectness () {
31+ fun clear_resetsDrawnStateAndGeometry () {
3332 val canvas = mock(Canvas ::class .java)
3433 val projection = mock(Projection ::class .java)
34+ `when `(projection.orientation).thenReturn(0f )
3535
36- // Pivot at (500, 500), 90 degrees rotation
37- `when `(projection.orientation).thenReturn(90f )
38- renderer.draw(canvas, projection, 500f , 500f , " Label" , 50 , 1.0f , 1000 , 1000 )
36+ renderer.draw(canvas, projection, 500f , 500f , " Test" , 50 , 1.0f , 1000 , 1000 )
37+ assertNotNull(renderer.getLastGeometry())
3938
40- // At 90 degrees map orientation, our label is counter-rotated -90 around (500, 500).
41- // It should still be horizontal on screen.
42- // Let's use the actual matrix to find a point that SHOULD hit.
39+ renderer.clear()
40+ assertNull(renderer.getLastGeometry())
41+ assertFalse(renderer.hitTest(500f , 400f ))
42+ }
43+
44+ @Test
45+ fun geometry_rotationCorrectness () {
46+ val canvas = mock(Canvas ::class .java)
47+ val projection = mock(Projection ::class .java)
4348
44- val matrix = Matrix ()
45- matrix.setRotate(- 90f , 500f , 500f )
49+ val orientations = listOf (0f , 45f , 90f , 180f , 270f )
4650
47- // The unrotated background rect is above the anchor.
48- // Let's find it via reflection or just use the same logic.
49- val density = context.resources.displayMetrics.density
50- val gapPx = 2f * density
51- // Approximate height based on 12sp text
52- val labelHeight = 15f * density
51+ for (orientation in orientations) {
52+ `when `(projection.orientation).thenReturn(orientation)
53+ renderer.draw(canvas, projection, 500f , 500f , " Label" , 50 , 1.0f , 1000 , 1000 )
54+
55+ val geometry = renderer.getLastGeometry()!!
56+
57+ // Local center
58+ val localPoint = floatArrayOf(
59+ geometry.localBounds.centerX(),
60+ geometry.localBounds.centerY()
61+ )
62+
63+ // Mathematically transform to screen space using the same matrix exposed by renderer
64+ val transformedPoint = FloatArray (2 )
65+ geometry.localToScreen.mapPoints(transformedPoint, localPoint)
66+
67+ // Must hit the transformed center
68+ assertTrue(" Should hit at orientation $orientation " , renderer.hitTest(transformedPoint[0 ], transformedPoint[1 ]))
69+
70+ // Point well outside
71+ assertFalse(" Should not hit outside at orientation $orientation " , renderer.hitTest(transformedPoint[0 ] + 500f , transformedPoint[1 ]))
72+ }
73+ }
74+
75+ @Test
76+ fun clamping_keepsAllCornersInsideViewport () {
77+ val canvas = mock(Canvas ::class .java)
78+ val projection = mock(Projection ::class .java)
79+ val viewWidth = 1000
80+ val viewHeight = 1000
5381
54- // A point that would be inside the unrotated label (centered above anchor)
55- val localPoint = floatArrayOf(500f , 500f - 50f - gapPx - labelHeight / 2f )
56- val transformedPoint = FloatArray (2 )
57- matrix.mapPoints(transformedPoint, localPoint)
82+ val orientations = listOf (0f , 45f , 90f , 135f , 180f , 225f , 270f , 315f )
83+ val corners = listOf (
84+ Pair (0f , 0f ), // Top-left
85+ Pair (1000f , 0f ), // Top-right
86+ Pair (0f , 1000f ), // Bottom-left
87+ Pair (1000f , 1000f ) // Bottom-right
88+ )
5889
59- // Tapping the mathematically transformed point should hit
60- assertTrue(renderer.hitTest(transformedPoint[0 ], transformedPoint[1 ]))
90+ for (orientation in orientations) {
91+ for ((x, y) in corners) {
92+ `when `(projection.orientation).thenReturn(orientation)
93+ renderer.draw(canvas, projection, x, y, " Long Label Content" , 50 , 1.0f , viewWidth, viewHeight)
94+
95+ val geometry = renderer.getLastGeometry()!!
96+
97+ // Assert that the screen-space bounding box is within the viewport
98+ assertTrue(" Left clamped at orientation $orientation at ($x , $y ). Bounds: ${geometry.screenBounds} " , geometry.screenBounds.left >= - EPSILON )
99+ assertTrue(" Top clamped at orientation $orientation at ($x , $y ). Bounds: ${geometry.screenBounds} " , geometry.screenBounds.top >= - EPSILON )
100+ assertTrue(" Right clamped at orientation $orientation at ($x , $y ). Bounds: ${geometry.screenBounds} " , geometry.screenBounds.right <= viewWidth + EPSILON )
101+ assertTrue(" Bottom clamped at orientation $orientation at ($x , $y ). Bounds: ${geometry.screenBounds} " , geometry.screenBounds.bottom <= viewHeight + EPSILON )
102+ }
103+ }
61104 }
62105
63106 @Test
64- fun clamping_keepsLabelInViewport () {
107+ fun textWidth_isConstrainedAndEllipsized () {
65108 val canvas = mock(Canvas ::class .java)
66109 val projection = mock(Projection ::class .java)
67- `when `(projection.orientation).thenReturn(45f )
110+ `when `(projection.orientation).thenReturn(0f )
68111
69- // Marker at (0, 0) - rotated label would be mostly off-screen
70- renderer.draw(canvas, projection, 0f , 0f , " Label" , 50 , 1.0f , 1000 , 1000 )
112+ val longText = " This is a very long marker name that should definitely be ellipsized to stay within the 120dp limit."
113+ renderer.draw(canvas, projection, 500f , 500f , longText, 50 , 1.0f , 1000 , 1000 )
114+
115+ val geometry = renderer.getLastGeometry()!!
116+
117+ val density = context.resources.displayMetrics.density
118+ val maxAllowedContentWidth = 120f * density
119+ val horizontalPadding = 4f * density * 2
71120
72- // After clamping, hit testing a point at (10, 10) should likely hit
73- // (depending on label size, but it should be shifted into view)
74- // More robust: verify it hits SOME point that was previously off-screen
75- assertTrue(renderer.hitTest(15f , 15f ))
121+ // geometry.localBounds.width() should be contentWidth + padding
122+ assertTrue(" Label width ${geometry.localBounds.width()} should be constrained near ${maxAllowedContentWidth + horizontalPadding} " ,
123+ geometry.localBounds.width() <= maxAllowedContentWidth + horizontalPadding + EPSILON )
76124 }
77125}
0 commit comments