Skip to content

Commit

Permalink
Merge pull request #86 from KakaoCup/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Vacxe authored Jun 25, 2024
2 parents e2b5271 + 0386c4f commit 6b44916
Show file tree
Hide file tree
Showing 21 changed files with 1,512 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.github.kakaocup.compose.node.assertion
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import io.github.kakaocup.compose.node.assertion.NodeAssertions

/**
* Basic assertions for working with lists.
*/
interface ListNodeAssertions : NodeAssertions {

/**
* Semantic property for the list length.
*/
val lengthSemanticsPropertyKey: SemanticsPropertyKey<Int>

/**
* Checks that the length of the list is equal to [length].
*
* @throws [AssertionError] if the list length is not equal to [length].
* @throws [IllegalStateException] if the list does not provide the [lengthSemanticsPropertyKey] property.
*/
fun assertLengthEquals(length: Int) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) {
assert(hasListLength(length))
}
}

private fun hasListLength(length: Int): SemanticsMatcher {
return SemanticsMatcher(
"The length of the list is expected to be $length, but the actual size is different"
) { node ->
val actualLength = node.config.getOrNull(lengthSemanticsPropertyKey)
?: error("List does not contain $lengthSemanticsPropertyKey modifier")

actualLength == length
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package io.github.kakaocup.compose.node.core
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.hasAnyAncestor
import io.github.kakaocup.compose.KakaoCompose
import io.github.kakaocup.compose.intercept.delegate.ComposeDelegate
import io.github.kakaocup.compose.intercept.delegate.ComposeInterceptable
import io.github.kakaocup.compose.node.action.NodeActions
Expand All @@ -18,17 +17,16 @@ import io.github.kakaocup.compose.utilities.orGlobal

@ComposeMarker
abstract class BaseNode<out T : BaseNode<T>> constructor(
@PublishedApi internal val semanticsProvider: SemanticsNodeInteractionsProvider? = null,
private val nodeMatcher: NodeMatcher,
private val parentNode: BaseNode<*>? = null,
@PublishedApi internal var semanticsProvider: SemanticsNodeInteractionsProvider? = null,
private var nodeMatcher: NodeMatcher? = null,
private var parentNode: BaseNode<*>? = null,
) : KDSL<T>,
NodeAssertions,
TextResourcesNodeAssertions,
NodeActions,
TextActions,
ComposeInterceptable {


constructor(
semanticsProvider: SemanticsNodeInteractionsProvider? = null,
viewBuilderAction: ViewBuilder.() -> Unit,
Expand All @@ -52,8 +50,8 @@ abstract class BaseNode<out T : BaseNode<T>> constructor(
nodeProvider = NodeProvider(
nodeMatcher = NodeMatcher(
matcher = combineSemanticMatchers(),
position = nodeMatcher.position,
useUnmergedTree = nodeMatcher.useUnmergedTree
position = nodeMatcher.checkNotNull().position,
useUnmergedTree = nodeMatcher.checkNotNull().useUnmergedTree
),
semanticsProvider = semanticsProvider.orGlobal().checkNotNull()
),
Expand All @@ -67,12 +65,26 @@ abstract class BaseNode<out T : BaseNode<T>> constructor(
NodeMatcher::class.java,
BaseNode::class.java,
).newInstance(
semanticsProvider,
semanticsProvider.orGlobal().checkNotNull(),
ViewBuilder().apply(function).build(),
this,
)
}

/**
* Method for deferred initialization of [BaseNode] constructor parameters.
* Simplifies the description of child nodes in list nodes.
*/
fun initSemantics(
semanticsProvider: SemanticsNodeInteractionsProvider,
nodeMatcher: NodeMatcher,
parentNode: BaseNode<*>? = null,
) {
this.semanticsProvider = semanticsProvider
this.nodeMatcher = nodeMatcher
this.parentNode = parentNode
}

/***
* Combines semantic matchers from all ancestor nodes
*/
Expand All @@ -81,10 +93,10 @@ abstract class BaseNode<out T : BaseNode<T>> constructor(
var parent = this.parentNode

while (parent != null) {
semanticsMatcherList.add(hasAnyAncestor(parent.nodeMatcher.matcher))
semanticsMatcherList.add(hasAnyAncestor(parent.nodeMatcher.checkNotNull().matcher))
parent = parent.parentNode
}
semanticsMatcherList.add(this.nodeMatcher.matcher)
semanticsMatcherList.add(this.nodeMatcher.checkNotNull().matcher)

return semanticsMatcherList.reduce { finalMatcher, matcher -> finalMatcher and matcher }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.github.kakaocup.compose.node.element

import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import io.github.kakaocup.compose.KakaoCompose
import io.github.kakaocup.compose.node.core.ComposeMarker
import io.github.kakaocup.compose.node.core.BaseNode
import io.github.kakaocup.compose.node.builder.NodeMatcher
Expand Down Expand Up @@ -43,7 +42,7 @@ open class ComposeScreen<out T : ComposeScreen<T>> : BaseNode<T> {
companion object {
inline fun <reified T : ComposeScreen<T>> onComposeScreen(
semanticsProvider: SemanticsNodeInteractionsProvider,
noinline function: T.() -> Unit
noinline function: T.() -> Unit,
): T = T::class.java
.getDeclaredConstructor(
SemanticsNodeInteractionsProvider::class.java
Expand All @@ -52,7 +51,7 @@ open class ComposeScreen<out T : ComposeScreen<T>> : BaseNode<T> {
.apply { this(function) }

inline fun <reified T : ComposeScreen<T>> onComposeScreen(
noinline function: T.() -> Unit
noinline function: T.() -> Unit,
): T =
T::class.java.getDeclaredConstructor()
.newInstance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.onChildren
import io.github.kakaocup.compose.KakaoCompose
import io.github.kakaocup.compose.exception.KakaoComposeException
import io.github.kakaocup.compose.node.assertion.LazyListNodeAssertions
import io.github.kakaocup.compose.node.builder.NodeMatcher
import io.github.kakaocup.compose.node.builder.ViewBuilder
Expand Down Expand Up @@ -81,9 +79,7 @@ class KLazyListNode(

function(provideItem(
semanticsNode,
semanticsProvider
.orGlobal()
.checkNotNull()
semanticsProvider.orGlobal().checkNotNull()
) as T)
}

Expand Down Expand Up @@ -114,9 +110,7 @@ class KLazyListNode(

return provideItem(
semanticsNode,
semanticsProvider
.orGlobal()
.checkNotNull()
semanticsProvider.orGlobal().checkNotNull()
) as T
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.kakaocup.compose.node.element.list

import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsMatcher
import io.github.kakaocup.compose.node.builder.NodeMatcher
import io.github.kakaocup.compose.node.core.BaseNode
import io.github.kakaocup.compose.utilities.checkNotNull
import io.github.kakaocup.compose.utilities.orGlobal

/**
* Base class for all child nodes within [KListNode].
*
* The constructor is declared as `protected` so that only inheritors have the right to call this constructor.
*
* Warning! Manually creating list items is **not necessary**.
*/
open class KListItemNode<out T : KListItemNode<T>> protected constructor() : BaseNode<T>() {

companion object {

/**
* Method for correctly initializing the necessary parameters of [BaseNode].
* This method allows us to keep the main constructor of the element empty, which greatly
* simplifies the description of subclass elements.
*
* @param listNode The root node of the list within which we need to interact with the list item.
* @param semanticsNode A list of key/value pairs associated with the layout node or its subtree.
* @param useUnmergedTree If true, the unmerged semantic tree will be used to work with the node.
*/
inline fun <reified T : KListItemNode<*>> newInstance(
listNode: KListNode,
semanticsNode: SemanticsNode,
useUnmergedTree: Boolean,
): T {
val instance = T::class.java.getDeclaredConstructor().newInstance()

instance.initSemantics(
semanticsProvider = listNode.semanticsProvider.orGlobal().checkNotNull(),
nodeMatcher = NodeMatcher(
matcher = SemanticsMatcher(
description = "Semantics node id = ${semanticsNode.id}",
matcher = { it.id == semanticsNode.id },
),
useUnmergedTree = useUnmergedTree,
),
parentNode = listNode,
)

return instance
}

}

}
Loading

0 comments on commit 6b44916

Please sign in to comment.