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

Commit

Permalink
Merge pull request #24 from square/zachklipp/better-root
Browse files Browse the repository at this point in the history
Make ViewFactory.showRendering function responsible for applying the ComposeViewFactoryRoot.
  • Loading branch information
zach-klippenstein authored May 18, 2020
2 parents fc180e3 + acfbee0 commit a58aafe
Show file tree
Hide file tree
Showing 12 changed files with 480 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
name: Instrumentation tests
needs: assemble
runs-on: macos-latest
timeout-minutes: 20
timeout-minutes: 30
strategy:
# Allow tests to continue on other devices if they fail on one device.
fail-fast: false
Expand Down
1 change: 1 addition & 0 deletions core-compose/api/core-compose.api
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Compa
}

public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRootKt {
public static final fun <clinit> ()V
public static final fun ComposeViewFactoryRoot (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot;
public static final fun withComposeViewFactoryRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2020 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.workflow.ui.compose

import android.content.Context
import android.widget.FrameLayout
import androidx.compose.FrameManager
import androidx.compose.mutableStateOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.ui.foundation.Text
import androidx.ui.layout.Column
import androidx.ui.test.assertIsDisplayed
import androidx.ui.test.createComposeRule
import androidx.ui.test.findByText
import com.squareup.workflow.ui.ViewEnvironment
import com.squareup.workflow.ui.ViewRegistry
import com.squareup.workflow.ui.WorkflowViewStub
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ComposeViewFactoryTest {

@Rule @JvmField val composeRule = createComposeRule()

@Test fun wrapsFactoryWithRoot() {
val wrapperText = mutableStateOf("one")
val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory))
.withComposeViewFactoryRoot { content ->
Column {
Text(wrapperText.value)
content()
}
}

composeRule.setContent {
// This is valid Compose code, but the IDE doesn't know that yet so it will show an
// unsuppressable error.
RootView(viewEnvironment = viewEnvironment)
}

findByText("one\ntwo").assertIsDisplayed()
FrameManager.framed {
wrapperText.value = "ENO"
}
findByText("ENO\ntwo").assertIsDisplayed()
}

private class RootView(context: Context) : FrameLayout(context) {
private val stub = WorkflowViewStub(context).also(::addView)

fun setViewEnvironment(viewEnvironment: ViewEnvironment) {
stub.update(TestRendering("two"), viewEnvironment)
}
}

private data class TestRendering(val text: String)

private companion object {
val TestFactory = bindCompose<TestRendering> { rendering, _ ->
Text(rendering.text)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2020 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry")

package com.squareup.workflow.ui.compose.internal

import android.content.Context
import android.widget.FrameLayout
import androidx.compose.Composable
import androidx.compose.CompositionReference
import androidx.compose.FrameManager
import androidx.compose.Providers
import androidx.compose.Recomposer
import androidx.compose.ambientOf
import androidx.compose.compositionReference
import androidx.compose.currentComposer
import androidx.compose.mutableStateOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.ui.foundation.Text
import androidx.ui.test.assertIsDisplayed
import androidx.ui.test.createComposeRule
import androidx.ui.test.findByText
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ComposeSupportTest {

@Rule @JvmField val composeRule = createComposeRule()

@Test fun ambientsPassThroughSubcomposition() {
composeRule.setContent {
TestComposable("foo")
}

findByText("foo").assertIsDisplayed()
}

@Test fun ambientChangesPassThroughSubcomposition() {
val ambientValue = mutableStateOf("foo")
composeRule.setContent {
TestComposable(ambientValue.value)
}

findByText("foo").assertIsDisplayed()
FrameManager.framed {
ambientValue.value = "bar"
}
findByText("bar").assertIsDisplayed()
}

@Composable private fun TestComposable(ambientValue: String) {
Providers(TestAmbient provides ambientValue) {
LegacyHostComposable {
Text(TestAmbient.current)
}
}
}

@Composable private fun LegacyHostComposable(leafContent: @Composable() () -> Unit) {
val wormhole = Wormhole(currentComposer.recomposer, compositionReference(), leafContent)
// This is valid Compose code, but the IDE doesn't know that yet so it will show an
// unsuppressable error.
WormholeView(wormhole = wormhole)
}

private class Wormhole(
val recomposer: Recomposer,
val parentReference: CompositionReference,
val childContent: @Composable() () -> Unit
)

private class WormholeView(context: Context) : FrameLayout(context) {
fun setWormhole(wormhole: Wormhole) {
setContent(wormhole.recomposer, wormhole.parentReference, wormhole.childContent)
}
}

private companion object {
val TestAmbient = ambientOf<String> { error("Ambient not provided") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2020 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.workflow.ui.compose.internal

import androidx.compose.FrameManager
import androidx.compose.mutableStateOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.ui.foundation.Text
import androidx.ui.layout.Column
import androidx.ui.test.assertIsDisplayed
import androidx.ui.test.createComposeRule
import androidx.ui.test.findByText
import com.squareup.workflow.ui.ViewEnvironment
import com.squareup.workflow.ui.ViewRegistry
import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot
import com.squareup.workflow.ui.compose.wrapWithRootIfNecessary
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ComposeViewFactoryRootTest {

@Rule @JvmField val composeRule = createComposeRule()

@Test fun wrapWithRootIfNecessary_handlesNoRoot() {
val viewEnvironment = ViewEnvironment(ViewRegistry())

composeRule.setContent {
wrapWithRootIfNecessary(viewEnvironment) {
Text("foo")
}
}

findByText("foo").assertIsDisplayed()
}

@Test fun wrapWithRootIfNecessary_wrapsWhenNecessary() {
val viewEnvironment = ViewEnvironment(ViewRegistry())
.withComposeViewFactoryRoot { content ->
Column {
Text("one")
content()
}
}

composeRule.setContent {
wrapWithRootIfNecessary(viewEnvironment) {
Text("two")
}
}

findByText("one\ntwo").assertIsDisplayed()
}

@Test fun wrapWithRootIfNecessary_onlyWrapsOnce() {
val viewEnvironment = ViewEnvironment(ViewRegistry())
.withComposeViewFactoryRoot { content ->
Column {
Text("one")
content()
}
}

composeRule.setContent {
wrapWithRootIfNecessary(viewEnvironment) {
Text("two")
wrapWithRootIfNecessary(viewEnvironment) {
Text("three")
}
}
}

findByText("one\ntwo\nthree").assertIsDisplayed()
}

@Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() {
val wrapperText = mutableStateOf("one")
val viewEnvironment = ViewEnvironment(ViewRegistry())
.withComposeViewFactoryRoot { content ->
Column {
Text(wrapperText.value)
content()
}
}

composeRule.setContent {
wrapWithRootIfNecessary(viewEnvironment) {
Text("two")
}
}

findByText("one\ntwo").assertIsDisplayed()
FrameManager.framed {
wrapperText.value = "ENO"
}
findByText("ENO\ntwo").assertIsDisplayed()
}

@Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() {
val viewEnvironment1 = ViewEnvironment(ViewRegistry())
.withComposeViewFactoryRoot { content ->
Column {
Text("one")
content()
}
}
val viewEnvironment2 = ViewEnvironment(ViewRegistry())
.withComposeViewFactoryRoot { content ->
Column {
Text("ENO")
content()
}
}
val viewEnvironment = mutableStateOf(viewEnvironment1)

composeRule.setContent {
wrapWithRootIfNecessary(viewEnvironment.value) {
Text("two")
}
}

findByText("one\ntwo").assertIsDisplayed()
FrameManager.framed {
viewEnvironment.value = viewEnvironment2
}
findByText("ENO\ntwo").assertIsDisplayed()
}
}
Loading

0 comments on commit a58aafe

Please sign in to comment.