-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
76ce6d4
commit d29335a
Showing
14 changed files
with
368 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
plugins { | ||
alias(libs.plugins.jetbrains.compose) apply false | ||
alias(libs.plugins.kotlin.serialization) apply false | ||
alias(libs.plugins.ktlint) apply false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
plugins { | ||
kotlin("multiplatform") | ||
alias(libs.plugins.jetbrains.compose) | ||
id("org.jlleitschuh.gradle.ktlint") | ||
id("org.jetbrains.kotlinx.kover") | ||
alias(libs.plugins.maven.publish) | ||
} | ||
|
||
applyBasicSetup() | ||
|
||
kotlin { | ||
sourceSets { | ||
commonMain { | ||
dependencies { | ||
api(projects.core) | ||
implementation(libs.compose.runtime) | ||
} | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
compose/common/src/dev/programadorthi/routing/compose/ComposeManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.MutableState | ||
import dev.programadorthi.routing.core.application.Application | ||
import dev.programadorthi.routing.core.application.ApplicationCall | ||
import io.ktor.util.AttributeKey | ||
|
||
public typealias Content = @Composable () -> Unit | ||
|
||
private val ComposeRoutingAttributeKey: AttributeKey<MutableState<Content>> = | ||
AttributeKey("ComposeRoutingAttributeKey") | ||
|
||
internal var ApplicationCall.content: Content | ||
get() = application.contentState.value | ||
set(value) { | ||
application.contentState.value = value | ||
} | ||
|
||
internal var Application.contentState: MutableState<Content> | ||
get() = attributes[ComposeRoutingAttributeKey] | ||
set(value) { | ||
attributes.put(ComposeRoutingAttributeKey, value) | ||
} |
22 changes: 22 additions & 0 deletions
22
compose/common/src/dev/programadorthi/routing/compose/ComposeRouting.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import dev.programadorthi.routing.core.Routing | ||
import dev.programadorthi.routing.core.application | ||
|
||
@Composable | ||
public fun Routing( | ||
routing: Routing, | ||
initial: Content, | ||
) { | ||
val composable by remember(routing) { | ||
mutableStateOf(initial).also { | ||
routing.application.contentState = it | ||
} | ||
} | ||
|
||
composable() | ||
} |
34 changes: 34 additions & 0 deletions
34
compose/common/src/dev/programadorthi/routing/compose/ComposeRoutingBuilder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import androidx.compose.runtime.Composable | ||
import dev.programadorthi.routing.core.Route | ||
import dev.programadorthi.routing.core.RouteMethod | ||
import dev.programadorthi.routing.core.application.ApplicationCall | ||
import dev.programadorthi.routing.core.application.call | ||
import dev.programadorthi.routing.core.route | ||
import io.ktor.util.KtorDsl | ||
import io.ktor.util.pipeline.PipelineContext | ||
|
||
@KtorDsl | ||
public fun Route.composable( | ||
path: String, | ||
name: String? = null, | ||
body: @Composable PipelineContext<Unit, ApplicationCall>.() -> Unit, | ||
): Route = route(path = path, name = name) { composable(body) } | ||
|
||
@KtorDsl | ||
public fun Route.composable( | ||
path: String, | ||
method: RouteMethod, | ||
name: String? = null, | ||
body: @Composable PipelineContext<Unit, ApplicationCall>.() -> Unit, | ||
): Route = route(path = path, name = name, method = method) { composable(body) } | ||
|
||
@KtorDsl | ||
public fun Route.composable( | ||
body: @Composable PipelineContext<Unit, ApplicationCall>.() -> Unit, | ||
) { | ||
handle { | ||
call.content = { body(this) } | ||
} | ||
} |
189 changes: 189 additions & 0 deletions
189
compose/common/test/dev/programadorthi/routing/compose/ComposeRoutingTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import dev.programadorthi.routing.core.RouteMethod | ||
import dev.programadorthi.routing.core.application | ||
import dev.programadorthi.routing.core.application.ApplicationCall | ||
import dev.programadorthi.routing.core.route | ||
import dev.programadorthi.routing.core.routing | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.test.advanceTimeBy | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
internal class ComposeRoutingTest { | ||
|
||
@Test | ||
fun shouldInvokeInitialContentWhenThereIsNoEmittedComposable() = | ||
runComposeTest { coroutineContext, composition, clock -> | ||
// GIVEN | ||
val routing = routing(parentCoroutineContext = coroutineContext) {} | ||
val fakeContent = FakeContent() | ||
|
||
// WHEN | ||
composition.setContent { | ||
Routing( | ||
routing = routing, | ||
initial = { | ||
fakeContent.content = "I'm the initial content" | ||
fakeContent.Composable() | ||
}, | ||
) | ||
} | ||
clock.sendFrame(0L) // Ask for recomposition | ||
|
||
// THEN | ||
assertEquals("I'm the initial content", fakeContent.result) | ||
} | ||
|
||
@Test | ||
fun shouldComposeByPath() = | ||
runComposeTest { coroutineContext, composition, clock -> | ||
// GIVEN | ||
val fakeContent = FakeContent() | ||
|
||
val routing = routing(parentCoroutineContext = coroutineContext) { | ||
composable(path = "/path") { | ||
fakeContent.content = "I'm the path based content" | ||
fakeContent.Composable() | ||
} | ||
} | ||
|
||
composition.setContent { | ||
Routing( | ||
routing = routing, | ||
initial = { | ||
fakeContent.content = "I'm the initial content" | ||
fakeContent.Composable() | ||
}, | ||
) | ||
} | ||
|
||
// WHEN | ||
routing.execute( | ||
ApplicationCall( | ||
application = routing.application, | ||
uri = "/path", | ||
) | ||
) | ||
advanceTimeBy(99) // Ask for routing | ||
clock.sendFrame(0L) // Ask for recomposition | ||
|
||
// THEN | ||
assertEquals("I'm the path based content", fakeContent.result) | ||
} | ||
|
||
@Test | ||
fun shouldComposeByName() = | ||
runComposeTest { coroutineContext, composition, clock -> | ||
// GIVEN | ||
val fakeContent = FakeContent() | ||
|
||
val routing = routing(parentCoroutineContext = coroutineContext) { | ||
composable(path = "/path", name = "path") { | ||
fakeContent.content = "I'm the name based content" | ||
fakeContent.Composable() | ||
} | ||
} | ||
|
||
composition.setContent { | ||
Routing( | ||
routing = routing, | ||
initial = { | ||
fakeContent.content = "I'm the initial content" | ||
fakeContent.Composable() | ||
}, | ||
) | ||
} | ||
|
||
// WHEN | ||
routing.execute( | ||
ApplicationCall( | ||
application = routing.application, | ||
name = "path", | ||
) | ||
) | ||
advanceTimeBy(99) // Ask for routing | ||
clock.sendFrame(0L) // Ask for recomposition | ||
|
||
// THEN | ||
assertEquals("I'm the name based content", fakeContent.result) | ||
} | ||
|
||
@Test | ||
fun shouldComposeByCustomRouteMethod() = | ||
runComposeTest { coroutineContext, composition, clock -> | ||
// GIVEN | ||
val fakeContent = FakeContent() | ||
|
||
val routing = routing(parentCoroutineContext = coroutineContext) { | ||
composable(path = "/path", method = RouteMethod.Empty) { | ||
fakeContent.content = "I'm the route method based content" | ||
fakeContent.Composable() | ||
} | ||
} | ||
|
||
composition.setContent { | ||
Routing( | ||
routing = routing, | ||
initial = { | ||
fakeContent.content = "I'm the initial content" | ||
fakeContent.Composable() | ||
}, | ||
) | ||
} | ||
|
||
// WHEN | ||
routing.execute( | ||
ApplicationCall( | ||
application = routing.application, | ||
uri = "/path", | ||
routeMethod = RouteMethod.Empty, | ||
) | ||
) | ||
advanceTimeBy(99) // Ask for routing | ||
clock.sendFrame(0L) // Ask for recomposition | ||
|
||
// THEN | ||
assertEquals("I'm the route method based content", fakeContent.result) | ||
} | ||
|
||
@Test | ||
fun shouldComposeByAnyRoute() = | ||
runComposeTest { coroutineContext, composition, clock -> | ||
// GIVEN | ||
val fakeContent = FakeContent() | ||
|
||
val routing = routing(parentCoroutineContext = coroutineContext) { | ||
route(path = "/any") { | ||
composable { | ||
fakeContent.content = "I'm the generic based content" | ||
fakeContent.Composable() | ||
} | ||
} | ||
} | ||
|
||
composition.setContent { | ||
Routing( | ||
routing = routing, | ||
initial = { | ||
fakeContent.content = "I'm the initial content" | ||
fakeContent.Composable() | ||
}, | ||
) | ||
} | ||
|
||
// WHEN | ||
routing.execute( | ||
ApplicationCall( | ||
application = routing.application, | ||
uri = "/any", | ||
) | ||
) | ||
advanceTimeBy(99) // Ask for routing | ||
clock.sendFrame(0L) // Ask for recomposition | ||
|
||
// THEN | ||
assertEquals("I'm the generic based content", fakeContent.result) | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
compose/common/test/dev/programadorthi/routing/compose/ComposeTestHelper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import androidx.compose.runtime.BroadcastFrameClock | ||
import androidx.compose.runtime.Composition | ||
import androidx.compose.runtime.Recomposer | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.test.TestScope | ||
import kotlinx.coroutines.test.runTest | ||
import kotlin.coroutines.CoroutineContext | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
internal fun runComposeTest( | ||
content: TestScope.(CoroutineContext, Composition, BroadcastFrameClock) -> Unit | ||
) = runTest { | ||
val job = Job() | ||
val clock = BroadcastFrameClock() | ||
val scope = CoroutineScope(coroutineContext + job + clock) | ||
val recomposer = Recomposer(scope.coroutineContext) | ||
val runner = scope.launch { | ||
recomposer.runRecomposeAndApplyChanges() | ||
} | ||
val composition = Composition(TestApplier(), recomposer) | ||
try { | ||
content(scope.coroutineContext, composition, clock) | ||
} finally { | ||
runner.cancel() | ||
recomposer.close() | ||
job.cancel() | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
compose/common/test/dev/programadorthi/routing/compose/FakeContent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import androidx.compose.runtime.Composable | ||
|
||
internal class FakeContent { | ||
var content = "" | ||
|
||
var result = "" | ||
private set | ||
|
||
@Composable | ||
fun Composable() { | ||
result = content | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
compose/common/test/dev/programadorthi/routing/compose/TestApplier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package dev.programadorthi.routing.compose | ||
|
||
import androidx.compose.runtime.Applier | ||
|
||
internal class TestApplier : Applier<Unit> { | ||
override val current: Unit | ||
get() = Unit | ||
|
||
override fun down(node: Unit) {} | ||
|
||
override fun up() {} | ||
|
||
override fun insertTopDown(index: Int, instance: Unit) {} | ||
|
||
override fun insertBottomUp(index: Int, instance: Unit) {} | ||
|
||
override fun remove(index: Int, count: Int) {} | ||
|
||
override fun move(from: Int, to: Int, count: Int) {} | ||
|
||
override fun clear() {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
POM_NAME=Compose | ||
POM_ARTIFACT_ID=compose |
Oops, something went wrong.