かなり基本的なことなんですが、よく分かっていない方も多いのではと思い書いています。
Jetpack Composeで開発していて以下のようなエラーに遭遇しました。
Composeのコード
NavigationBarItem(
selected = selected,
onClick = {
...
},
icon = {
Icon(
imageVector = tab.icon,
contentDescription = "Hoge",
)
},
)
テスト
composeTestRule
.onNodeWithContentDescription("Hoge")
.performClick()
エラー
java.lang.AssertionError: Failed to inject touch input.
Reason: Expected exactly '1' node but could not find any node that satisfies: (ContentDescription = 'Hoge' (ignoreCase: false))
However, the unmerged tree contains '1' node that matches. Are you missing `useUnmergedNode = true` in your finder?
訳: Hoge = ContentDescription
を持つノードが見つからなかったよ。
でも、unmerged treeには見つかったから、useUnmergedNode = true
忘れていませんか?
さてさて、useUnmergedNode = trueにしたらいいのかな?
ドキュメントを見る
Using the unmerged tree
というドキュメントが普通に分かりやすいです。
ボタンとかいくつかのコンポーネントは子のコンポーネントをテストからデフォルトで見える木では勝手にマージしちゃうようです。
MyButton {
Text("Hello")
Text("World")
}
が以下のようになります。
Node #1 at (...)px
|-Node #2 at (...)px
Role = 'Button'
// **2つあったテキストが一個になってまとまっている**
Text = '[Hello, World]'
Actions = [OnClick, GetTextLayoutResult]
MergeDescendants = 'true'
useUnmergedNode = trueを使うと以下のようになります。 (ドキュメントに書いてあるとおりですが。)
Node #1 at (...)px
|-Node #2 at (...)px
OnClick = '...'
// ここにMergeDescendants = 'true'がある
MergeDescendants = 'true'
|-Node #3 at (...)px
// ちゃんとTextが分かれている
| Text = '[Hello]'
|-Node #5 at (83.0, 86.0, 191.0, 135.0)px
Text = '[World]'
useUnmergedNode = trueを使うとwithTextなどで"Hello"などが探せる。というわけです。
なぜこのまとめる現象が起こるのか
Modifierに.semantics()という関数が生えています。これに引数をsemantics(mergeDescendants = true)
のように渡すことで子孫がテストでマージされます。
これをButtonの中で使っています。
正確にはButtonの中でModifier.clickable()を使っていて、これを使うと this.semantics(mergeDescendants = true)
が使われます。
つまりクリックできる要素があるとだいたいmergeDescendants = trueされて、その子孫がまとめられる。 ということなので、テストを書くときに意識したほうが良さそうです。
で、結論としてuseUnmergedNode = true使う?
多分今回に関してはuseUnmergedNode = trueにしてクリックするのでも動くんですが、クリックする要素などにTestTagなどをつけて、それをテストで使うほうがクリックしたいものとテストを関連付けする意図では良さそうに見えました。
参考