Skip to content
This repository was archived by the owner on Feb 5, 2021. It is now read-only.

Commit a58aafe

Browse files
Merge pull request #24 from square/zachklipp/better-root
Make ViewFactory.showRendering function responsible for applying the ComposeViewFactoryRoot.
2 parents fc180e3 + acfbee0 commit a58aafe

File tree

12 files changed

+480
-63
lines changed

12 files changed

+480
-63
lines changed

Diff for: .github/workflows/kotlin.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ jobs:
9797
name: Instrumentation tests
9898
needs: assemble
9999
runs-on: macos-latest
100-
timeout-minutes: 20
100+
timeout-minutes: 30
101101
strategy:
102102
# Allow tests to continue on other devices if they fail on one device.
103103
fail-fast: false

Diff for: core-compose/api/core-compose.api

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Compa
2828
}
2929

3030
public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRootKt {
31+
public static final fun <clinit> ()V
3132
public static final fun ComposeViewFactoryRoot (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot;
3233
public static final fun withComposeViewFactoryRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment;
3334
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2020 Square Inc.
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+
package com.squareup.workflow.ui.compose
17+
18+
import android.content.Context
19+
import android.widget.FrameLayout
20+
import androidx.compose.FrameManager
21+
import androidx.compose.mutableStateOf
22+
import androidx.test.ext.junit.runners.AndroidJUnit4
23+
import androidx.ui.foundation.Text
24+
import androidx.ui.layout.Column
25+
import androidx.ui.test.assertIsDisplayed
26+
import androidx.ui.test.createComposeRule
27+
import androidx.ui.test.findByText
28+
import com.squareup.workflow.ui.ViewEnvironment
29+
import com.squareup.workflow.ui.ViewRegistry
30+
import com.squareup.workflow.ui.WorkflowViewStub
31+
import org.junit.Rule
32+
import org.junit.Test
33+
import org.junit.runner.RunWith
34+
35+
@RunWith(AndroidJUnit4::class)
36+
class ComposeViewFactoryTest {
37+
38+
@Rule @JvmField val composeRule = createComposeRule()
39+
40+
@Test fun wrapsFactoryWithRoot() {
41+
val wrapperText = mutableStateOf("one")
42+
val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory))
43+
.withComposeViewFactoryRoot { content ->
44+
Column {
45+
Text(wrapperText.value)
46+
content()
47+
}
48+
}
49+
50+
composeRule.setContent {
51+
// This is valid Compose code, but the IDE doesn't know that yet so it will show an
52+
// unsuppressable error.
53+
RootView(viewEnvironment = viewEnvironment)
54+
}
55+
56+
findByText("one\ntwo").assertIsDisplayed()
57+
FrameManager.framed {
58+
wrapperText.value = "ENO"
59+
}
60+
findByText("ENO\ntwo").assertIsDisplayed()
61+
}
62+
63+
private class RootView(context: Context) : FrameLayout(context) {
64+
private val stub = WorkflowViewStub(context).also(::addView)
65+
66+
fun setViewEnvironment(viewEnvironment: ViewEnvironment) {
67+
stub.update(TestRendering("two"), viewEnvironment)
68+
}
69+
}
70+
71+
private data class TestRendering(val text: String)
72+
73+
private companion object {
74+
val TestFactory = bindCompose<TestRendering> { rendering, _ ->
75+
Text(rendering.text)
76+
}
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2020 Square Inc.
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+
@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry")
17+
18+
package com.squareup.workflow.ui.compose.internal
19+
20+
import android.content.Context
21+
import android.widget.FrameLayout
22+
import androidx.compose.Composable
23+
import androidx.compose.CompositionReference
24+
import androidx.compose.FrameManager
25+
import androidx.compose.Providers
26+
import androidx.compose.Recomposer
27+
import androidx.compose.ambientOf
28+
import androidx.compose.compositionReference
29+
import androidx.compose.currentComposer
30+
import androidx.compose.mutableStateOf
31+
import androidx.test.ext.junit.runners.AndroidJUnit4
32+
import androidx.ui.foundation.Text
33+
import androidx.ui.test.assertIsDisplayed
34+
import androidx.ui.test.createComposeRule
35+
import androidx.ui.test.findByText
36+
import org.junit.Rule
37+
import org.junit.Test
38+
import org.junit.runner.RunWith
39+
40+
@RunWith(AndroidJUnit4::class)
41+
class ComposeSupportTest {
42+
43+
@Rule @JvmField val composeRule = createComposeRule()
44+
45+
@Test fun ambientsPassThroughSubcomposition() {
46+
composeRule.setContent {
47+
TestComposable("foo")
48+
}
49+
50+
findByText("foo").assertIsDisplayed()
51+
}
52+
53+
@Test fun ambientChangesPassThroughSubcomposition() {
54+
val ambientValue = mutableStateOf("foo")
55+
composeRule.setContent {
56+
TestComposable(ambientValue.value)
57+
}
58+
59+
findByText("foo").assertIsDisplayed()
60+
FrameManager.framed {
61+
ambientValue.value = "bar"
62+
}
63+
findByText("bar").assertIsDisplayed()
64+
}
65+
66+
@Composable private fun TestComposable(ambientValue: String) {
67+
Providers(TestAmbient provides ambientValue) {
68+
LegacyHostComposable {
69+
Text(TestAmbient.current)
70+
}
71+
}
72+
}
73+
74+
@Composable private fun LegacyHostComposable(leafContent: @Composable() () -> Unit) {
75+
val wormhole = Wormhole(currentComposer.recomposer, compositionReference(), leafContent)
76+
// This is valid Compose code, but the IDE doesn't know that yet so it will show an
77+
// unsuppressable error.
78+
WormholeView(wormhole = wormhole)
79+
}
80+
81+
private class Wormhole(
82+
val recomposer: Recomposer,
83+
val parentReference: CompositionReference,
84+
val childContent: @Composable() () -> Unit
85+
)
86+
87+
private class WormholeView(context: Context) : FrameLayout(context) {
88+
fun setWormhole(wormhole: Wormhole) {
89+
setContent(wormhole.recomposer, wormhole.parentReference, wormhole.childContent)
90+
}
91+
}
92+
93+
private companion object {
94+
val TestAmbient = ambientOf<String> { error("Ambient not provided") }
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2020 Square Inc.
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+
package com.squareup.workflow.ui.compose.internal
17+
18+
import androidx.compose.FrameManager
19+
import androidx.compose.mutableStateOf
20+
import androidx.test.ext.junit.runners.AndroidJUnit4
21+
import androidx.ui.foundation.Text
22+
import androidx.ui.layout.Column
23+
import androidx.ui.test.assertIsDisplayed
24+
import androidx.ui.test.createComposeRule
25+
import androidx.ui.test.findByText
26+
import com.squareup.workflow.ui.ViewEnvironment
27+
import com.squareup.workflow.ui.ViewRegistry
28+
import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot
29+
import com.squareup.workflow.ui.compose.wrapWithRootIfNecessary
30+
import org.junit.Rule
31+
import org.junit.Test
32+
import org.junit.runner.RunWith
33+
34+
@RunWith(AndroidJUnit4::class)
35+
class ComposeViewFactoryRootTest {
36+
37+
@Rule @JvmField val composeRule = createComposeRule()
38+
39+
@Test fun wrapWithRootIfNecessary_handlesNoRoot() {
40+
val viewEnvironment = ViewEnvironment(ViewRegistry())
41+
42+
composeRule.setContent {
43+
wrapWithRootIfNecessary(viewEnvironment) {
44+
Text("foo")
45+
}
46+
}
47+
48+
findByText("foo").assertIsDisplayed()
49+
}
50+
51+
@Test fun wrapWithRootIfNecessary_wrapsWhenNecessary() {
52+
val viewEnvironment = ViewEnvironment(ViewRegistry())
53+
.withComposeViewFactoryRoot { content ->
54+
Column {
55+
Text("one")
56+
content()
57+
}
58+
}
59+
60+
composeRule.setContent {
61+
wrapWithRootIfNecessary(viewEnvironment) {
62+
Text("two")
63+
}
64+
}
65+
66+
findByText("one\ntwo").assertIsDisplayed()
67+
}
68+
69+
@Test fun wrapWithRootIfNecessary_onlyWrapsOnce() {
70+
val viewEnvironment = ViewEnvironment(ViewRegistry())
71+
.withComposeViewFactoryRoot { content ->
72+
Column {
73+
Text("one")
74+
content()
75+
}
76+
}
77+
78+
composeRule.setContent {
79+
wrapWithRootIfNecessary(viewEnvironment) {
80+
Text("two")
81+
wrapWithRootIfNecessary(viewEnvironment) {
82+
Text("three")
83+
}
84+
}
85+
}
86+
87+
findByText("one\ntwo\nthree").assertIsDisplayed()
88+
}
89+
90+
@Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() {
91+
val wrapperText = mutableStateOf("one")
92+
val viewEnvironment = ViewEnvironment(ViewRegistry())
93+
.withComposeViewFactoryRoot { content ->
94+
Column {
95+
Text(wrapperText.value)
96+
content()
97+
}
98+
}
99+
100+
composeRule.setContent {
101+
wrapWithRootIfNecessary(viewEnvironment) {
102+
Text("two")
103+
}
104+
}
105+
106+
findByText("one\ntwo").assertIsDisplayed()
107+
FrameManager.framed {
108+
wrapperText.value = "ENO"
109+
}
110+
findByText("ENO\ntwo").assertIsDisplayed()
111+
}
112+
113+
@Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() {
114+
val viewEnvironment1 = ViewEnvironment(ViewRegistry())
115+
.withComposeViewFactoryRoot { content ->
116+
Column {
117+
Text("one")
118+
content()
119+
}
120+
}
121+
val viewEnvironment2 = ViewEnvironment(ViewRegistry())
122+
.withComposeViewFactoryRoot { content ->
123+
Column {
124+
Text("ENO")
125+
content()
126+
}
127+
}
128+
val viewEnvironment = mutableStateOf(viewEnvironment1)
129+
130+
composeRule.setContent {
131+
wrapWithRootIfNecessary(viewEnvironment.value) {
132+
Text("two")
133+
}
134+
}
135+
136+
findByText("one\ntwo").assertIsDisplayed()
137+
FrameManager.framed {
138+
viewEnvironment.value = viewEnvironment2
139+
}
140+
findByText("ENO\ntwo").assertIsDisplayed()
141+
}
142+
}

0 commit comments

Comments
 (0)