1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.xr.compose.testing
18 
19 import android.app.Activity
20 import android.view.Display
21 import androidx.activity.ComponentActivity
22 import androidx.annotation.NonNull
23 import androidx.annotation.RestrictTo
24 import androidx.compose.runtime.Composable
25 import androidx.compose.ui.test.junit4.AndroidComposeTestRule
26 import androidx.xr.compose.platform.SceneManager
27 import androidx.xr.compose.spatial.Subspace
28 import androidx.xr.compose.subspace.SubspaceComposable
29 import androidx.xr.runtime.Session
30 import androidx.xr.runtime.internal.JxrPlatformAdapter
31 import androidx.xr.runtime.testing.FakeRuntimeFactory
32 import androidx.xr.scenecore.impl.JxrPlatformAdapterAxr
33 import androidx.xr.scenecore.impl.extensions.XrExtensionsProvider
34 import androidx.xr.scenecore.impl.perception.PerceptionLibrary
35 import androidx.xr.scenecore.testing.FakeImpressApi
36 import androidx.xr.scenecore.testing.FakeScheduledExecutorService
37 import com.android.extensions.xr.XrExtensions
38 import com.google.androidxr.splitengine.SplitEngineSubspaceManager
39 import com.google.ar.imp.view.splitengine.ImpSplitEngineRenderer
40 import org.mockito.Mockito.mock
41 import org.robolectric.shadows.ShadowDisplay
42 
43 /** Custom test class that should be used for testing [SubspaceComposable] content. */
44 @Suppress("ForbiddenSuperClass")
45 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
46 public class SubspaceTestingActivity : ComponentActivity() {
47     public val extensions: XrExtensions = XrExtensionsProvider.getXrExtensions()!!
48     @Suppress("MutableBareField") public lateinit var session: Session
49 
50     /** Throws an exception by default under test; return Robolectric Display impl instead. */
getDisplaynull51     @NonNull override fun getDisplay(): Display = ShadowDisplay.getDefaultDisplay()
52 
53     override fun onStart() {
54         SceneManager.start()
55         super.onStart()
56     }
57 
onDestroynull58     override fun onDestroy() {
59         SceneManager.stop()
60         super.onDestroy()
61     }
62 }
63 
64 /** Analog to [AndroidComposeTestRule.setContent] for testing [SubspaceComposable] content. */
65 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
66 public fun AndroidComposeTestRule<*, SubspaceTestingActivity>.setSubspaceContent(
67     content: @Composable @SubspaceComposable () -> Unit
68 ) {
<lambda>null69     setContent { TestSetup { Subspace { content() } } }
70 }
71 
72 /** Analog to [AndroidComposeTestRule.setContent] for testing [SubspaceComposable] content. */
73 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
74 public fun AndroidComposeTestRule<*, SubspaceTestingActivity>.setSubspaceContent(
75     uiContent: @Composable () -> Unit,
76     content: @Composable @SubspaceComposable () -> Unit,
77 ) {
<lambda>null78     setContent {
79         TestSetup {
80             uiContent()
81             Subspace { content() }
82         }
83     }
84 }
85 
86 /** Subspace version of onNode in Compose. */
87 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
onSubspaceNodenull88 public fun AndroidComposeTestRule<*, SubspaceTestingActivity>.onSubspaceNode(
89     matcher: SubspaceSemanticsMatcher
90 ): SubspaceSemanticsNodeInteraction =
91     SubspaceSemanticsNodeInteraction(SubspaceTestContext(this), matcher)
92 
93 /** Subspace version of onAllNodes in Compose. */
94 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
95 public fun AndroidComposeTestRule<*, SubspaceTestingActivity>.onAllSubspaceNodes(
96     matcher: SubspaceSemanticsMatcher
97 ): SubspaceSemanticsNodeInteractionCollection =
98     SubspaceSemanticsNodeInteractionCollection(SubspaceTestContext(this), matcher)
99 
100 /** Subspace version of onNodeWithTag in Compose. */
101 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
102 public fun AndroidComposeTestRule<*, SubspaceTestingActivity>.onSubspaceNodeWithTag(
103     testTag: String
104 ): SubspaceSemanticsNodeInteraction = onSubspaceNode(hasTestTag(testTag))
105 
106 /** Subspace version of onAllNodesWithTag in Compose. */
107 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
108 public fun AndroidComposeTestRule<*, SubspaceTestingActivity>.onAllSubspaceNodesWithTag(
109     testTag: String
110 ): SubspaceSemanticsNodeInteractionCollection = onAllSubspaceNodes(hasTestTag(testTag))
111 
112 /**
113  * Create a fake [Session] for testing.
114  *
115  * A convenience method that creates a fake [Session] for testing. If runtime is not provided, a
116  * fake [JxrPlatformAdapter] will be created by default.
117  *
118  * @param activity The [SubspaceTestingActivity] to use for the [Session].
119  * @param runtime The [JxrPlatformAdapter] to use for the [Session].
120  */
121 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
122 public fun createFakeSession(
123     activity: SubspaceTestingActivity,
124     runtime: JxrPlatformAdapter = createFakeRuntime(activity),
125 ): Session = Session(activity, FakeRuntimeFactory().createRuntime(activity), runtime)
126 
127 /**
128  * Create a fake [Session] for testing using configs from [TestJxrPlatformAdapter].
129  *
130  * A convenience method that creates a fake [Session] for testing. If runtime is not provided, a
131  * fake [JxrPlatformAdapter] using [TestJxrPlatformAdapter] will be created by default.
132  *
133  * @param activity The [Activity] to use for the [Session].
134  * @param runtime The [JxrPlatformAdapter] to use for the [Session].
135  */
136 internal fun createFakeSessionWithTestConfigs(
137     activity: Activity,
138     runtime: JxrPlatformAdapter = createFakeRuntime(activity),
139 ): Session = Session(activity, FakeRuntimeFactory().createRuntime(activity), runtime)
140 
141 /**
142  * Create a fake [JxrPlatformAdapter] for testing.
143  *
144  * A convenience method that creates a fake [JxrPlatformAdapter] for testing.
145  *
146  * @param activity The [Activity] to use for the [JxrPlatformAdapter].
147  */
148 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
149 public fun createFakeRuntime(activity: Activity): JxrPlatformAdapter =
150     // FakeJxrPlatformAdapterFactory().createPlatformAdapter(activity)
151     JxrPlatformAdapterAxr.create(
152         /* activity = */ activity,
153         /* executor = */ FakeScheduledExecutorService(),
154         /* extensions = */ (activity as SubspaceTestingActivity).extensions,
155         /* impressApi = */ FakeImpressApi(),
156         /* perceptionLibrary = */ PerceptionLibrary(),
157         /* splitEngineSubspaceManager = */ mock(SplitEngineSubspaceManager::class.java),
158         /* splitEngineRenderer = */ mock(ImpSplitEngineRenderer::class.java),
159     )
160