~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-android

1 collaborator
0
1
main
118 lines · 4.2 KB Raw History
package social.grain

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import social.grain.ui.components.ImageZoomHost
import social.grain.ui.components.LocalImageZoom
import social.grain.ui.components.ZoomablePhoto
import social.grain.ui.theme.GrainTheme

/**
 * Pinch-to-zoom, which is drawn by a host at the root of the app rather than in
 * place.
 *
 * A photo blown up inside the card is clipped twice over — by the pager and by
 * the lazy list — so the zoom is a copy in a layer above both. What that buys
 * is only visible in a gesture: the state has to end up on the shared host, and
 * it has to be given back when the fingers lift.
 */
@RunWith(AndroidJUnit4::class)
class ZoomablePhotoTest {

    @get:Rule
    val composeRule = createComposeRule()

    @Test
    fun aPhotoOutsideAHostIsStillJustAPhoto() {
        composeRule.setContent {
            GrainTheme {
                ZoomablePhoto(
                    model = "https://cdn.grain.test/full/0.jpg",
                    contentDescription = "A tree",
                    modifier = Modifier.size(200.dp),
                )
            }
        }

        composeRule.onNodeWithContentDescription("A tree").assertExists()
    }

    @Test
    fun pinchingInsideAHostHandsTheZoomToTheHostAndGivesItBack() {
        lateinit var zoom: social.grain.ui.components.ImageZoomState
        composeRule.setContent {
            GrainTheme {
                ImageZoomHost(Modifier.fillMaxSize()) {
                    zoom = LocalImageZoom.current!!
                    ZoomablePhoto(
                        model = "https://cdn.grain.test/full/0.jpg",
                        contentDescription = "A tree",
                        modifier = Modifier.size(200.dp),
                    )
                }
            }
        }

        composeRule.onNodeWithContentDescription("A tree").performTouchInput {
            down(0, Offset(width * 0.4f, height * 0.5f))
            down(1, Offset(width * 0.6f, height * 0.5f))
            moveTo(0, Offset(width * 0.1f, height * 0.5f))
            moveTo(1, Offset(width * 0.9f, height * 0.5f))
        }
        composeRule.waitForIdle()

        assertTrue("The fingers spread, so the photo grew", zoom.scale > 1f)

        composeRule.onNodeWithContentDescription("A tree").performTouchInput {
            up(0)
            up(1)
        }
        composeRule.waitUntil(10_000) { zoom.source == null }
        assertEquals(1f, zoom.scale, 0.001f)
    }

    @Test
    fun aPinchOnADisabledPhotoIsIgnored() {
        lateinit var zoom: social.grain.ui.components.ImageZoomState
        composeRule.setContent {
            GrainTheme {
                ImageZoomHost(Modifier.fillMaxSize()) {
                    zoom = LocalImageZoom.current!!
                    Box(Modifier.size(200.dp)) {
                        ZoomablePhoto(
                            model = "https://cdn.grain.test/full/0.jpg",
                            contentDescription = "Covered",
                            enabled = false,
                            modifier = Modifier.fillMaxSize(),
                        )
                    }
                }
            }
        }

        composeRule.onNodeWithContentDescription("Covered").performTouchInput {
            down(0, Offset(width * 0.4f, height * 0.5f))
            down(1, Offset(width * 0.6f, height * 0.5f))
            moveTo(0, Offset(width * 0.1f, height * 0.5f))
            moveTo(1, Offset(width * 0.9f, height * 0.5f))
            up(0)
            up(1)
        }
        composeRule.waitForIdle()

        assertEquals("A covered photo must not zoom past its label", 1f, zoom.scale, 0.001f)
    }
}