Skip to content

Commit 544516a

Browse files
authored
Fixed Circuit presenter class generation with @CircuitInject (#98)
There was a bug from the kotlinpoet update that caused our `.implements(…)` check to fail due to the invalid comparison of TypeName <> ClassName that we were trying to do.
1 parent ee8d593 commit 544516a

File tree

5 files changed

+115
-2
lines changed

5 files changed

+115
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
### Other Notes & Contributions
2222
-->
2323

24+
### Fixed
25+
26+
- Fixed Circuit presenter code generator - [#98](https://github.com/r0adkll/kimchi/pull/98)
27+
2428
## [0.5.0] - 2024-12-19
2529

2630
### Added
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (C) 2025 r0adkll
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.r0adkll.kimchi.circuit
4+
5+
import com.r0adkll.kimchi.annotations.ContributesTo
6+
import com.r0adkll.kimchi.hasAnnotation
7+
import com.r0adkll.kimchi.hasReturnType
8+
import com.r0adkll.kimchi.implements
9+
import com.r0adkll.kimchi.isTypeOf
10+
import com.r0adkll.kimchi.kotlinClass
11+
import com.r0adkll.kimchi.parameter
12+
import com.r0adkll.kimchi.primaryConstructor
13+
import com.r0adkll.kimchi.withAnnotation
14+
import com.r0adkll.kimchi.withFunction
15+
import com.slack.circuit.runtime.CircuitContext
16+
import com.slack.circuit.runtime.Navigator
17+
import com.slack.circuit.runtime.presenter.Presenter
18+
import java.io.File
19+
import me.tatarka.inject.annotations.Inject
20+
import me.tatarka.inject.annotations.IntoSet
21+
import me.tatarka.inject.annotations.Provides
22+
import org.junit.jupiter.api.Test
23+
import org.junit.jupiter.api.io.CleanupMode
24+
import org.junit.jupiter.api.io.TempDir
25+
import strikt.api.expectThat
26+
import strikt.assertions.isEqualTo
27+
import strikt.assertions.isNotNull
28+
import strikt.assertions.withElementAt
29+
30+
class PresenterClassFactoryTest {
31+
32+
@TempDir(cleanup = CleanupMode.ON_SUCCESS)
33+
lateinit var workingDir: File
34+
35+
@Test
36+
fun `Presenter class generates factory and contributed component`() {
37+
compileKimchiWithTestSources(
38+
"""
39+
package kimchi
40+
41+
import androidx.compose.runtime.Composable
42+
import me.tatarka.inject.annotations.Assisted
43+
import me.tatarka.inject.annotations.Inject
44+
import com.r0adkll.kimchi.circuit.annotations.CircuitInject
45+
import com.slack.circuit.runtime.Navigator
46+
import com.slack.circuit.runtime.presenter.Presenter
47+
import com.slack.circuit.runtime.CircuitContext
48+
49+
@CircuitInject(TestScreen::class, TestScope::class)
50+
@Inject
51+
class TestPresenter(
52+
@Assisted private val screen: TestScreen,
53+
@Assisted private val navigator: Navigator,
54+
@Assisted private val context: CircuitContext,
55+
) : Presenter<TestUiState> {
56+
57+
@Composable
58+
override fun present(): TestUiState {
59+
return TestUiState()
60+
}
61+
}
62+
""".trimIndent(),
63+
workingDir = workingDir,
64+
) {
65+
val factory = kotlinClass("kimchi.TestPresenterFactory")
66+
expectThat(factory)
67+
.hasAnnotation(Inject::class)
68+
.implements(Presenter.Factory::class)
69+
.primaryConstructor()
70+
.isNotNull()
71+
.parameter(0)
72+
.isTypeOf(Function3::class)
73+
.with({ type.arguments }) {
74+
withElementAt(0) {
75+
get { type!!.classifier } isEqualTo testScreen
76+
}
77+
withElementAt(1) {
78+
get { type!!.classifier } isEqualTo Navigator::class
79+
}
80+
withElementAt(2) {
81+
get { type!!.classifier } isEqualTo CircuitContext::class
82+
}
83+
withElementAt(3) {
84+
get { type!!.classifier } isEqualTo kotlinClass("kimchi.TestPresenter")
85+
}
86+
}
87+
88+
val component = kotlinClass("kimchi.TestPresenterFactoryComponent")
89+
expectThat(component)
90+
.withAnnotation<ContributesTo> {
91+
get { scope } isEqualTo testScope
92+
}
93+
.withFunction("bindTestPresenterFactory") {
94+
hasAnnotation(IntoSet::class)
95+
hasAnnotation(Provides::class)
96+
parameter(1)
97+
.isTypeOf(factory)
98+
hasReturnType(Presenter.Factory::class)
99+
}
100+
}
101+
}
102+
}

circuit/compiler/src/test/kotlin/com/r0adkll/kimchi/circuit/UiFunctionFactoryTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import strikt.assertions.isNotNull
2626

2727
class UiFunctionFactoryTest {
2828

29-
@TempDir(cleanup = CleanupMode.NEVER)
29+
@TempDir(cleanup = CleanupMode.ON_SUCCESS)
3030
lateinit var workingDir: File
3131

3232
@Test

compiler-utils/src/main/kotlin/com/r0adkll/kimchi/util/ksp/ClassDeclarations.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.google.devtools.ksp.getAllSuperTypes
66
import com.google.devtools.ksp.symbol.KSClassDeclaration
77
import com.google.devtools.ksp.symbol.KSName
88
import com.squareup.kotlinpoet.ClassName
9+
import com.squareup.kotlinpoet.ParameterizedTypeName
910
import com.squareup.kotlinpoet.ksp.toTypeName
1011

1112
/**
@@ -14,7 +15,10 @@ import com.squareup.kotlinpoet.ksp.toTypeName
1415
*/
1516
public fun KSClassDeclaration.implements(className: ClassName): Boolean {
1617
return getAllSuperTypes().any {
17-
it.toTypeName() == className
18+
when (val typeName = it.toTypeName()) {
19+
is ParameterizedTypeName -> typeName.rawType == className
20+
else -> typeName == className
21+
}
1822
}
1923
}
2024

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ org.gradle.daemon=true
44
org.gradle.parallel=true
55
org.gradle.caching=true
66

7+
#Dokka v2
8+
org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers
9+
710
#Kotlin
811
kotlin.code.style=official
912

0 commit comments

Comments
 (0)