test: no overlapping cards test at 360/768/1280dp (Closes #32) (#48)

This commit was merged in pull request #48.
This commit is contained in:
2026-03-13 13:13:05 +01:00
parent 2dd477591b
commit 72623ed0bc

View File

@@ -0,0 +1,110 @@
package org.shahondin1624
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.getBoundsInRoot
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.runComposeUiTest
import androidx.compose.ui.unit.dp
import org.shahondin1624.lib.components.TestTags
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
import org.shahondin1624.model.EXAMPLE_CHARACTER
import org.shahondin1624.model.attributes.AttributeType
import org.shahondin1624.theme.LocalWindowSizeClass
import org.shahondin1624.theme.WindowSizeClass
import kotlin.test.Test
import kotlin.test.assertFalse
/**
* Verifies that attribute and talent cards never overlap at any screen width.
* Tests at 360dp (compact/phone), 768dp (medium/tablet), and 1280dp (expanded/desktop).
*/
@OptIn(ExperimentalTestApi::class)
class NoOverlappingCardsTest {
private val allAttributeTags = AttributeType.entries.map { TestTags.attributeCard(it.name) }
private val character = EXAMPLE_CHARACTER
/**
* Collects the bounding rectangles for all attribute and talent cards,
* then asserts no two rectangles intersect.
*/
private fun assertNoOverlap(tag: String, bounds: List<Pair<String, Rect>>) {
for (i in bounds.indices) {
for (j in i + 1 until bounds.size) {
val (tagA, rectA) = bounds[i]
val (tagB, rectB) = bounds[j]
val overlaps = rectA.overlaps(rectB) &&
rectA.width > 0 && rectA.height > 0 &&
rectB.width > 0 && rectB.height > 0
assertFalse(
overlaps,
"Cards overlap at $tag width: '$tagA' $rectA intersects '$tagB' $rectB"
)
}
}
}
private fun runOverlapTestAtWidth(widthDp: Int) = runComposeUiTest {
val sizeClass = WindowSizeClass.fromWidth(widthDp.dp)
setContent {
CompositionLocalProvider(LocalWindowSizeClass provides sizeClass) {
Column(modifier = Modifier.width(widthDp.dp).fillMaxSize()) {
AttributesPage(character)
}
}
}
waitForIdle()
val bounds = mutableListOf<Pair<String, Rect>>()
// Collect attribute card bounds
for (tag in allAttributeTags) {
val nodes = onAllNodesWithTag(tag)
val count = nodes.fetchSemanticsNodes().size
for (i in 0 until count) {
val rect = nodes[i].getBoundsInRoot()
bounds.add(tag to Rect(
left = rect.left.value,
top = rect.top.value,
right = rect.right.value,
bottom = rect.bottom.value
))
}
}
// Collect talent card bounds (sample first few visible)
val talentTags = character.talents.talents.take(10).map { TestTags.talentCard(it.name) }
for (tag in talentTags) {
val nodes = onAllNodesWithTag(tag)
val count = nodes.fetchSemanticsNodes().size
for (i in 0 until count) {
val rect = nodes[i].getBoundsInRoot()
bounds.add(tag to Rect(
left = rect.left.value,
top = rect.top.value,
right = rect.right.value,
bottom = rect.bottom.value
))
}
}
assertNoOverlap("${widthDp}dp", bounds)
}
@Test
fun noOverlapAtCompactWidth() = runOverlapTestAtWidth(360)
@Test
fun noOverlapAtMediumWidth() = runOverlapTestAtWidth(768)
@Test
fun noOverlapAtExpandedWidth() = runOverlapTestAtWidth(1280)
}