Skip to content

Commit

Permalink
Fix all tests to work with squoosh (#1859)
Browse files Browse the repository at this point in the history
Fixes #1834.
Set squoosh as the default renderer and update all tests to work with
squoosh. To enable this, two main changes to testing have been made.

1. Instead of testing for text existence using
onNodeWithText().assertExists(), add a new function assertHasText().
This works by adding all text rendered under a root node into a hash
stored in the root node's Modifier.semantics(). This hash is then
checked in the assertHasText() function.

2. All nodes with click/press interactions will get a Modifier.testTag()
set with its node name. Tests that perform clicks on nodes use
onNodeWithTag() instead of onNodeWithText().
  • Loading branch information
rylin8 authored Dec 18, 2024
1 parent ec24753 commit a85fb91
Show file tree
Hide file tree
Showing 72 changed files with 429 additions and 395 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package com.android.designcompose

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.tooling.preview.Preview
import androidx.test.platform.app.InstrumentationRegistry
import com.android.designcompose.common.DesignDocId
Expand Down Expand Up @@ -85,8 +84,9 @@ class RenderTests {
assertDCRenderStatus(DocRenderStatus.NotAvailable)

// The Node not found screen is shown
onNodeWithText("Document $helloWorldDocId not available", substring = true)
.assertExists()
// TODO resurrect this test once squoosh renders something for missing nodes
// onDCDoc(HelloWorldDoc).assertHasText("Document $helloWorldDocId not available",
// substring = true)
}
// It was not loaded from disk and did not render
with(DesignSettings.testOnlyFigmaFetchStatus(DesignDocId(helloWorldDocId))) {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ enum class DocRenderStatus {

val docRenderStatusSemanticsKey = SemanticsPropertyKey<DocRenderStatus>("DocRenderStatus")
var SemanticsPropertyReceiver.sDocRenderStatus by docRenderStatusSemanticsKey

val docRenderTextSemanticsKey = SemanticsPropertyKey<HashSet<String>>("DocRenderText")
var SemanticsPropertyReceiver.sDocRenderText by docRenderTextSemanticsKey
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import com.android.designcompose.squoosh.CustomVariantTransition
* better integrate with external layout.
*/
class DesignDocSettings(
val useSquoosh: Boolean = false,
val useSquoosh: Boolean = true,
val customVariantTransition: CustomVariantTransition? = null,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFontLoader
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Constraints
Expand Down Expand Up @@ -88,6 +89,7 @@ import com.android.designcompose.doc
import com.android.designcompose.getContent
import com.android.designcompose.getKey
import com.android.designcompose.getOpenLinkCallback
import com.android.designcompose.getTapCallback
import com.android.designcompose.proto.isPressOrClick
import com.android.designcompose.proto.isTimeout
import com.android.designcompose.proto.layoutStyle
Expand All @@ -97,6 +99,7 @@ import com.android.designcompose.registerOpenLinkCallback
import com.android.designcompose.rootNode
import com.android.designcompose.rootOverlays
import com.android.designcompose.sDocRenderStatus
import com.android.designcompose.sDocRenderText
import com.android.designcompose.serdegen.Layout
import com.android.designcompose.serdegen.OverflowDirection
import com.android.designcompose.serdegen.Size
Expand Down Expand Up @@ -345,6 +348,7 @@ fun SquooshRoot(
val childComposables: ArrayList<SquooshChildComposable> = arrayListOf()
keyEventTracker.clearListeners()
val variableState = VariableState.create()
val textHash = HashSet<String>()
val root =
resolveVariantsRecursively(
startFrame,
Expand All @@ -363,6 +367,7 @@ fun SquooshRoot(
variableState,
appContext = LocalContext.current,
textMeasureCache = textMeasureCache,
textHash = textHash,
customVariantTransition = LocalDesignDocSettings.current.customVariantTransition,
overlays = overlays,
isScrollComponent = isScrollComponent,
Expand Down Expand Up @@ -415,6 +420,7 @@ fun SquooshRoot(
VariableState.create(),
appContext = LocalContext.current,
textMeasureCache = textMeasureCache,
textHash = textHash,
customVariantTransition = LocalDesignDocSettings.current.customVariantTransition,
overlays = overlays,
isScrollComponent = isScrollComponent,
Expand Down Expand Up @@ -659,7 +665,10 @@ fun SquooshRoot(
appContext = LocalContext.current,
scrollOffset,
)
.semantics { sDocRenderStatus = DocRenderStatus.Rendered }
.semantics {
sDocRenderStatus = DocRenderStatus.Rendered
sDocRenderText = textHash
}
.then(scrollModifier),
measurePolicy =
squooshLayoutMeasurePolicy(
Expand Down Expand Up @@ -691,6 +700,7 @@ fun SquooshRoot(
drawContent()
}
.then(SquooshParentData(node = child.node))
.then(Modifier.testTag(child.node.view.name))

if (child.scrollView != null) {
// Compose a scrollable view as a separate composable in order to detect
Expand All @@ -714,7 +724,8 @@ fun SquooshRoot(
}
} else if (child.component == null) {
// If there are press or click reactions, composition is needed
var hasPressClick = false
var hasPressClick =
customizationContext.getTapCallback(child.node.view) != null
child.node.view.reactions.forEach {
if (it.trigger.isPressOrClick()) hasPressClick = true
else if (it.trigger.isTimeout()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ internal class TextMeasureCache {
val data: TextMeasureData,
val text: String?,
val style: TextStyle?,
val annotatedText: String,
// XXX: Do we need to use the annotated string? This impl might break localization.
)

Expand Down Expand Up @@ -116,6 +117,7 @@ internal fun squooshComputeTextInfo(
variableState: VariableState,
appContext: Context,
textMeasureCache: TextMeasureCache,
textHash: HashSet<String>,
): TextMeasureData? {
val customizedText =
customizations.getText(v.name) ?: customizations.getTextState(v.name)?.value
Expand All @@ -128,6 +130,7 @@ internal fun squooshComputeTextInfo(
cachedText.text == customizedText &&
cachedText.style == customTextStyle
) {
textHash.add(cachedText.annotatedText)
textMeasureCache.put(layoutId, cachedText)
return cachedText.data
}
Expand Down Expand Up @@ -336,8 +339,10 @@ internal fun squooshComputeTextInfo(

textMeasureCache.put(
layoutId,
TextMeasureCache.Entry(textMeasureData, customizedText, customTextStyle),
TextMeasureCache.Entry(textMeasureData, customizedText, customTextStyle, annotatedText.text),
)

textHash.add(annotatedText.text)

return textMeasureData
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ internal fun resolveVariantsRecursively(
appContext: Context,
customVariantTransition: CustomVariantTransition?,
textMeasureCache: TextMeasureCache,
textHash: HashSet<String>,
componentLayoutId: Int = 0,
overlays: List<View>? = null,
isScrollComponent: Boolean = false,
Expand Down Expand Up @@ -271,8 +272,9 @@ internal fun resolveVariantsRecursively(
customizations,
fontResourceLoader,
variableState,
appContext = appContext,
textMeasureCache = textMeasureCache,
appContext,
textMeasureCache,
textHash,
)
val resolvedView = SquooshResolvedNode(view, style, layoutId, textInfo, v.id, layoutNode = null)

Expand Down Expand Up @@ -402,6 +404,7 @@ internal fun resolveVariantsRecursively(
variableState,
appContext = appContext,
textMeasureCache = textMeasureCache,
textHash = textHash,
customVariantTransition = customVariantTransition,
componentLayoutId = componentLayoutId,
) ?: continue
Expand Down Expand Up @@ -447,6 +450,7 @@ internal fun resolveVariantsRecursively(
variableState,
appContext = appContext,
textMeasureCache = textMeasureCache,
textHash = textHash,
customVariantTransition = customVariantTransition,
componentLayoutId = componentLayoutId,
) ?: continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ package com.android.designcompose

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.designcompose.TestUtils.ClearStateTestRule
import com.android.designcompose.annotation.Design
import com.android.designcompose.annotation.DesignComponent
import com.android.designcompose.annotation.DesignDoc
import com.android.designcompose.common.DesignDocId
import com.android.designcompose.test.R
import com.android.designcompose.test.assertHasText
import com.android.designcompose.test.assertRenderStatus
import com.android.designcompose.test.onDCDoc
import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
Expand Down Expand Up @@ -65,9 +65,11 @@ class DesignRawResourceTest {
)
composeTestRule.setContent { HelloWorld() }

onDCDoc(HelloWorldDoc).assertRenderStatus(DocRenderStatus.Rendered)
onNodeWithText("Testers!", substring = true).assertExists()
onNodeWithText("Hello", substring = true).assertExists()
with(onDCDoc(HelloWorldDoc)) {
assertRenderStatus(DocRenderStatus.Rendered)
assertHasText("Testers!", substring = true)
assertHasText("Hello", substring = true)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@

package com.android.designcompose

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.designcompose.TestUtils.ClearStateTestRule
import com.android.designcompose.common.DesignDocId
import com.android.designcompose.test.Fetchable
import com.android.designcompose.test.assertHasText
import com.android.designcompose.test.assertRenderStatus
import com.android.designcompose.test.internal.captureRootRoboImage
import com.android.designcompose.test.internal.designComposeRoborazziRule
Expand All @@ -43,12 +52,18 @@ import org.robolectric.annotation.GraphicsMode
@Composable
fun DesignSwitcherTest(testName: TestName) {
val idState = remember { mutableStateOf(testName.methodName) }
DesignSwitcher(
doc = null,
currentDocId = DesignDocId(idState.value),
branchHash = null,
setDocId = {},
)
Row(
modifier = Modifier.size(360.dp, 640.dp).background(Color.Gray),
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.End,
) {
DesignSwitcher(
doc = null,
currentDocId = DesignDocId(idState.value),
branchHash = null,
setDocId = {},
)
}
}

@Category(Fetchable::class)
Expand All @@ -75,8 +90,10 @@ class DesignSwitcherBasicTests {

with(composeTestRule) {
captureRootRoboImage("CollapsedSwitcher")
onDCDoc(DesignSwitcherDoc).performClick()
onNodeWithText("Change").assertExists()
with(onDCDoc(DesignSwitcherDoc)) {
performClick()
assertHasText("Change")
}
captureRootRoboImage("ExpandedSwitcher")
}
}
Expand All @@ -87,8 +104,10 @@ class DesignSwitcherBasicTests {

with(composeTestRule) {
onDCDoc(DesignSwitcherDoc).performClick()
onNodeWithText("Change", useUnmergedTree = true).performClick()
onNodeWithText("Load", useUnmergedTree = true).assertExists()
with(onDCDoc(DesignSwitcherDoc)) {
onNodeWithTag("#ChangeButton").performClick()
assertHasText("Load")
}
captureRootRoboImage("ChangeFileScreen")
}
}
Expand All @@ -99,9 +118,9 @@ class DesignSwitcherBasicTests {

with(composeTestRule) {
onDCDoc(DesignSwitcherDoc).performClick()
onNodeWithText("Options", useUnmergedTree = true).performClick()
onNodeWithTag("#Options", useUnmergedTree = true).performClick()
captureRootRoboImage("OptionsScreenBeforeCheckingBox")
onAllNodes(hasClickAction())[1].performClick() // Currently the only way to find it
onAllNodes(hasClickAction())[0].performClick() // Currently the only way to find it
captureRootRoboImage("OptionsScreenAfterCheckingBox")
}
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,18 @@

package com.android.designcompose.testapp.validation

import android.app.Activity
import android.app.Instrumentation
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextLayoutResult
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
import androidx.test.espresso.intent.rule.IntentsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.designcompose.testapp.common.InterFontTestRule
import com.android.designcompose.testapp.validation.examples.HyperlinkTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Integration test on clicking on the urls in texts. */
Expand All @@ -46,6 +37,8 @@ class HyperlinkTests {
@get:Rule val interFontRule = InterFontTestRule()
@get:Rule var intentsRule: IntentsRule = IntentsRule()

// These are disabled because squoosh does not support clickable hyperlink text
/*
@Test
fun testClickSingleNodeLink() {
val url = "https://github.com/google/automotive-design-compose"
Expand Down Expand Up @@ -75,6 +68,7 @@ class HyperlinkTests {
intended(hasData(url))
}
*/

/** Use BoundsAssertions.getPartialBoundsOfLinks when it is available */
@OptIn(ExperimentalTextApi::class)
Expand Down
Loading

0 comments on commit a85fb91

Please sign in to comment.