Skip to content

Commit 4ec13ec

Browse files
committed
Fix tests
1 parent 1161caf commit 4ec13ec

5 files changed

Lines changed: 211 additions & 117 deletions

File tree

‎app/src/androidTest/java/org/nitri/opentopo/overlay/CustomMarkerTest.kt‎

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,53 +10,63 @@ import org.osmdroid.views.MapView
1010
@RunWith(AndroidJUnit4::class)
1111
class CustomMarkerTest {
1212

13+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
14+
1315
@Test
1416
fun customMarker_initialState() {
15-
val context = InstrumentationRegistry.getInstrumentation().targetContext
16-
val mapView = MapView(context)
17-
val marker = CustomMarker(mapView)
17+
// We only need to check initial property values, which don't require superclass methods
18+
// But we still need to instantiate it. Let's use a real MapView on the UI thread.
19+
val instrumentation = InstrumentationRegistry.getInstrumentation()
20+
var marker: CustomMarker? = null
21+
instrumentation.runOnMainSync {
22+
val mapView = MapView(context)
23+
marker = CustomMarker(mapView)
24+
}
1825

19-
assertEquals("", marker.labelText)
20-
assertFalse(marker.labelVisible)
21-
assertEquals(14.0, marker.minimumLabelZoom, 0.0)
26+
marker?.let {
27+
assertEquals("", it.labelText)
28+
assertFalse(it.labelVisible)
29+
assertEquals(14.0, it.minimumLabelZoom, 0.0)
30+
} ?: fail("Marker was not created")
2231
}
2332

2433
@Test
25-
fun shouldDrawLabel_respectsAllConditions() {
26-
val context = InstrumentationRegistry.getInstrumentation().targetContext
27-
val mapView = MapView(context)
28-
val marker = CustomMarker(mapView)
29-
30-
marker.labelText = "Test"
31-
marker.labelVisible = true
32-
marker.minimumLabelZoom = 14.0
33-
34-
// Mock isDisplayed via reflection since it's determined during draw
35-
val field = CustomMarker::class.java.superclass.getDeclaredField("mDisplayed")
36-
field.isAccessible = true
37-
field.set(marker, true)
38-
34+
fun shouldDrawMarkerLabel_respectsAllConditions() {
35+
// Pure function test - no MapView or reflection needed
36+
3937
// All good
40-
assertTrue(marker.shouldDrawLabel(15.0))
38+
assertTrue(shouldDrawMarkerLabel(
39+
labelVisible = true, labelText = "Test", zoom = 15.0, minimumLabelZoom = 14.0, isDisplayed = true
40+
))
4141

4242
// Disabled setting
43-
marker.labelVisible = false
44-
assertFalse(marker.shouldDrawLabel(15.0))
45-
marker.labelVisible = true
43+
assertFalse(shouldDrawMarkerLabel(
44+
labelVisible = false, labelText = "Test", zoom = 15.0, minimumLabelZoom = 14.0, isDisplayed = true
45+
))
4646

4747
// Blank text
48-
marker.labelText = ""
49-
assertFalse(marker.shouldDrawLabel(15.0))
50-
marker.labelText = "Test"
48+
assertFalse(shouldDrawMarkerLabel(
49+
labelVisible = true, labelText = "", zoom = 15.0, minimumLabelZoom = 14.0, isDisplayed = true
50+
))
51+
52+
// Whitespace text
53+
assertFalse(shouldDrawMarkerLabel(
54+
labelVisible = true, labelText = " ", zoom = 15.0, minimumLabelZoom = 14.0, isDisplayed = true
55+
))
5156

5257
// Below zoom
53-
assertFalse(marker.shouldDrawLabel(13.9))
58+
assertFalse(shouldDrawMarkerLabel(
59+
labelVisible = true, labelText = "Test", zoom = 13.9, minimumLabelZoom = 14.0, isDisplayed = true
60+
))
5461

5562
// Exactly at zoom
56-
assertTrue(marker.shouldDrawLabel(14.0))
63+
assertTrue(shouldDrawMarkerLabel(
64+
labelVisible = true, labelText = "Test", zoom = 14.0, minimumLabelZoom = 14.0, isDisplayed = true
65+
))
5766

5867
// Not displayed (off-screen)
59-
field.set(marker, false)
60-
assertFalse(marker.shouldDrawLabel(15.0))
68+
assertFalse(shouldDrawMarkerLabel(
69+
labelVisible = true, labelText = "Test", zoom = 15.0, minimumLabelZoom = 14.0, isDisplayed = false
70+
))
6171
}
6272
}
Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package org.nitri.opentopo.overlay
22

33
import android.graphics.Canvas
4-
import android.graphics.Matrix
5-
import android.graphics.RectF
64
import androidx.test.ext.junit.runners.AndroidJUnit4
75
import androidx.test.platform.app.InstrumentationRegistry
86
import 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
}

‎app/src/androidTest/java/org/nitri/opentopo/overlay/OverlayHelperTest.kt‎

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.nitri.opentopo.overlay
33
import androidx.test.ext.junit.runners.AndroidJUnit4
44
import androidx.test.platform.app.InstrumentationRegistry
55
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertTrue
67
import org.junit.Test
78
import org.junit.runner.RunWith
89
import org.nitri.opentopo.model.MarkerModel
@@ -11,63 +12,70 @@ import org.osmdroid.views.MapView
1112
@RunWith(AndroidJUnit4::class)
1213
class OverlayHelperTest {
1314

14-
@Test
15-
fun setMarkers_clearsMapMarkersCollection() {
16-
val context = InstrumentationRegistry.getInstrumentation().targetContext
17-
val mapView = MapView(context)
18-
val overlayHelper = OverlayHelper(context, mapView)
19-
20-
val listener = object : OverlayHelper.MarkerInteractionListener {
21-
override fun onMarkerMoved(markerModel: MarkerModel) {}
22-
override fun onMarkerClicked(markerModel: MarkerModel) {}
23-
override fun onMarkerDelete(markerModel: MarkerModel) {}
24-
override fun onMarkerUpdate(markerModel: MarkerModel) {}
25-
override fun onMarkerWaypointsChanged() {}
26-
}
27-
28-
overlayHelper.setMarkers(listOf(MarkerModel(seq = 1, latitude = 0.0, longitude = 0.0, name = "M1", description = "")), listener)
29-
30-
val field = OverlayHelper::class.java.getDeclaredField("mapMarkers")
31-
field.isAccessible = true
32-
val mapMarkers = field.get(overlayHelper) as List<*>
33-
assertEquals(1, mapMarkers.size)
34-
35-
overlayHelper.setMarkers(listOf(MarkerModel(seq = 2, latitude = 1.0, longitude = 1.0, name = "M2", description = "")), listener)
36-
assertEquals(1, mapMarkers.size)
15+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
16+
17+
private fun runOnMain(action: () -> Unit) {
18+
InstrumentationRegistry.getInstrumentation().runOnMainSync(action)
3719
}
3820

3921
@Test
40-
fun updateMarkerLabelVisibility_updatesAllMarkers() {
41-
val context = InstrumentationRegistry.getInstrumentation().targetContext
42-
val mapView = MapView(context)
43-
val overlayHelper = OverlayHelper(context, mapView)
44-
45-
val listener = object : OverlayHelper.MarkerInteractionListener {
46-
override fun onMarkerMoved(markerModel: MarkerModel) {}
47-
override fun onMarkerClicked(markerModel: MarkerModel) {}
48-
override fun onMarkerDelete(markerModel: MarkerModel) {}
49-
override fun onMarkerUpdate(markerModel: MarkerModel) {}
50-
override fun onMarkerWaypointsChanged() {}
22+
fun setMarkers_clearsPreviousMarkersFromCollection() {
23+
runOnMain {
24+
val mapView = MapView(context)
25+
val overlayHelper = OverlayHelper(context, mapView)
26+
27+
val listener = object : OverlayHelper.MarkerInteractionListener {
28+
override fun onMarkerMoved(markerModel: MarkerModel) {}
29+
override fun onMarkerClicked(markerModel: MarkerModel) {}
30+
override fun onMarkerDelete(markerModel: MarkerModel) {}
31+
override fun onMarkerUpdate(markerModel: MarkerModel) {}
32+
override fun onMarkerWaypointsChanged() {}
33+
}
34+
35+
overlayHelper.setMarkers(listOf(MarkerModel(seq = 1, latitude = 0.0, longitude = 0.0, name = "M1", description = "")), listener)
36+
37+
val field = OverlayHelper::class.java.getDeclaredField("mapMarkers")
38+
field.isAccessible = true
39+
@Suppress("UNCHECKED_CAST")
40+
val mapMarkers = field.get(overlayHelper) as List<CustomMarker>
41+
assertEquals(1, mapMarkers.size)
42+
43+
overlayHelper.setMarkers(listOf(MarkerModel(seq = 2, latitude = 1.0, longitude = 1.0, name = "M2", description = "")), listener)
44+
assertEquals("Collection size should remain 1 after re-setting markers", 1, mapMarkers.size)
45+
assertEquals("M2", mapMarkers[0].labelText)
5146
}
52-
53-
overlayHelper.setMarkers(listOf(
54-
MarkerModel(seq = 1, latitude = 0.0, longitude = 0.0, name = "M1", description = ""),
55-
MarkerModel(seq = 2, latitude = 1.0, longitude = 1.0, name = "M2", description = "")
56-
), listener)
57-
58-
overlayHelper.updateMarkerLabelVisibility(true)
59-
60-
val field = OverlayHelper::class.java.getDeclaredField("mapMarkers")
61-
field.isAccessible = true
62-
val mapMarkers = field.get(overlayHelper) as List<CustomMarker>
63-
64-
assertTrue(mapMarkers.all { it.labelVisible })
65-
66-
overlayHelper.updateMarkerLabelVisibility(false)
67-
assertTrue(mapMarkers.all { !it.labelVisible })
6847
}
6948

70-
private fun assertTrue(condition: Boolean) {
71-
org.junit.Assert.assertTrue(condition)
49+
@Test
50+
fun updateMarkerLabelVisibility_appliesToAllActiveMarkers() {
51+
runOnMain {
52+
val mapView = MapView(context)
53+
val overlayHelper = OverlayHelper(context, mapView)
54+
55+
val listener = object : OverlayHelper.MarkerInteractionListener {
56+
override fun onMarkerMoved(markerModel: MarkerModel) {}
57+
override fun onMarkerClicked(markerModel: MarkerModel) {}
58+
override fun onMarkerDelete(markerModel: MarkerModel) {}
59+
override fun onMarkerUpdate(markerModel: MarkerModel) {}
60+
override fun onMarkerWaypointsChanged() {}
61+
}
62+
63+
overlayHelper.setMarkers(listOf(
64+
MarkerModel(seq = 1, latitude = 0.0, longitude = 0.0, name = "M1", description = ""),
65+
MarkerModel(seq = 2, latitude = 1.0, longitude = 1.0, name = "M2", description = "")
66+
), listener)
67+
68+
overlayHelper.updateMarkerLabelVisibility(true)
69+
70+
val field = OverlayHelper::class.java.getDeclaredField("mapMarkers")
71+
field.isAccessible = true
72+
@Suppress("UNCHECKED_CAST")
73+
val mapMarkers = field.get(overlayHelper) as List<CustomMarker>
74+
75+
assertTrue("All markers should have labels enabled", mapMarkers.all { it.labelVisible })
76+
77+
overlayHelper.updateMarkerLabelVisibility(false)
78+
assertTrue("All markers should have labels disabled", mapMarkers.all { !it.labelVisible })
79+
}
7280
}
7381
}

0 commit comments

Comments
 (0)