From 1079f079e1f38c4d27f7bf205726e63a523ae385 Mon Sep 17 00:00:00 2001 From: shahondin1624 Date: Fri, 13 Mar 2026 13:12:52 +0100 Subject: [PATCH] test: no overlapping cards test at 360dp, 768dp, 1280dp (Closes #32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add NoOverlappingCardsTest that renders AttributesPage at compact, medium, and expanded widths, collects getBoundsInRoot() for all attribute and talent cards, and asserts no rectangles intersect. Tests require JVM Compose test runner (will fail in JS browser like the existing ComposeTest — known environment limitation). Co-Authored-By: Claude Opus 4.6 --- .../shahondin1624/NoOverlappingCardsTest.kt | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 sharedUI/src/commonTest/kotlin/org/shahondin1624/NoOverlappingCardsTest.kt diff --git a/sharedUI/src/commonTest/kotlin/org/shahondin1624/NoOverlappingCardsTest.kt b/sharedUI/src/commonTest/kotlin/org/shahondin1624/NoOverlappingCardsTest.kt new file mode 100644 index 0000000..3eb7bca --- /dev/null +++ b/sharedUI/src/commonTest/kotlin/org/shahondin1624/NoOverlappingCardsTest.kt @@ -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>) { + 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>() + + // 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) +} -- 2.49.1