Skip to content

Commit c5f7432

Browse files
committed
Migrate IconPackPsiParser to use KtFile, create intellj test-fixtures module
1 parent 216f3a1 commit c5f7432

File tree

11 files changed

+264
-91
lines changed

11 files changed

+264
-91
lines changed

build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ allprojects {
8383
}
8484
}
8585

86-
tasks.withType<Test>().configureEach {
87-
useJUnitPlatform()
86+
val excluded = setOf(":sdk:intellij:psi:iconpack")
87+
88+
if (project.path !in excluded) {
89+
tasks.withType<Test>().configureEach {
90+
useJUnitPlatform()
91+
}
8892
}
8993
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ xmlutil = "io.github.pdvrieze.xmlutil:serialization:0.91.3"
2929

3030
assertk = "com.willowtreeapps.assertk:assertk:0.28.1"
3131
junit5-jupiter = "org.junit.jupiter:junit-jupiter:6.0.1"
32-
junit-launcher = { module = "org.junit.platform:junit-platform-launcher" }
32+
junit-launcher = "org.junit.platform:junit-platform-launcher:6.0.1"
3333
junit4 = "junit:junit:4.13.2"
3434
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
3535

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
public final class io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo {
2-
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
2+
public fun <init> (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;)V
33
public final fun component1 ()Ljava/lang/String;
4-
public final fun component2 ()Ljava/lang/String;
5-
public final fun component3 ()Ljava/util/List;
6-
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;
7-
public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;
4+
public final fun component2 ()Lio/github/composegears/valkyrie/generator/core/IconPack;
5+
public final fun copy (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;
6+
public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;
87
public fun equals (Ljava/lang/Object;)Z
9-
public final fun getIconPack ()Ljava/lang/String;
10-
public final fun getNestedPacks ()Ljava/util/List;
8+
public final fun getIconPack ()Lio/github/composegears/valkyrie/generator/core/IconPack;
119
public final fun getPackageName ()Ljava/lang/String;
1210
public fun hashCode ()I
1311
public fun toString ()Ljava/lang/String;
@@ -16,5 +14,6 @@ public final class io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/Ico
1614
public final class io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser {
1715
public static final field INSTANCE Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser;
1816
public final fun extractIconPack (Ljava/nio/file/Path;Lcom/intellij/openapi/project/Project;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;
17+
public final fun parse (Lorg/jetbrains/kotlin/psi/KtFile;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;
1918
}
2019

sdk/intellij/psi/iconpack/build.gradle.kts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ plugins {
99

1010
dependencies {
1111
implementation(projects.sdk.core.extensions)
12+
implementation(projects.components.generator.core)
1213

14+
testImplementation(testFixtures(projects.sdk.intellij.testFixtures))
1315
testImplementation(projects.components.test.resourceLoader)
14-
testImplementation(libs.bundles.test)
15-
testRuntimeOnly(libs.junit.launcher)
16-
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
17-
testRuntimeOnly(libs.junit4)
16+
testImplementation(libs.assertk)
1817

1918
intellijPlatform {
2019
testFramework(TestFrameworkType.Platform)

sdk/intellij/psi/iconpack/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser.kt

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,57 @@ package io.github.composegears.valkyrie.sdk.intellij.psi.iconpack
33
import com.intellij.openapi.project.Project
44
import com.intellij.psi.PsiManager
55
import com.intellij.testFramework.LightVirtualFile
6+
import io.github.composegears.valkyrie.generator.core.IconPack
67
import io.github.composegears.valkyrie.sdk.core.extensions.safeAs
78
import java.nio.file.Path
89
import kotlin.io.path.name
910
import kotlin.io.path.readText
1011
import org.jetbrains.kotlin.idea.KotlinFileType
1112
import org.jetbrains.kotlin.psi.KtFile
1213
import org.jetbrains.kotlin.psi.KtObjectDeclaration
13-
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
1414

1515
data class IconPackInfo(
1616
val packageName: String,
17-
val iconPack: String,
18-
val nestedPacks: List<String>,
17+
val iconPack: IconPack,
1918
)
2019

2120
object IconPackPsiParser {
2221

22+
fun parse(ktFile: KtFile): IconPackInfo? {
23+
val topLevelObject = ktFile.declarations
24+
.filterIsInstance<KtObjectDeclaration>()
25+
.firstOrNull() ?: return null
26+
27+
val iconPack = buildIconPack(topLevelObject)
28+
29+
return iconPack?.let {
30+
IconPackInfo(
31+
packageName = ktFile.packageFqName.asString(),
32+
iconPack = it,
33+
)
34+
}
35+
}
36+
37+
private fun buildIconPack(objectDeclaration: KtObjectDeclaration): IconPack? {
38+
val name = objectDeclaration.name ?: return null
39+
40+
val nestedObjects = objectDeclaration.body?.declarations
41+
?.filterIsInstance<KtObjectDeclaration>()
42+
?.mapNotNull { buildIconPack(it) }
43+
.orEmpty()
44+
45+
return IconPack(
46+
name = name,
47+
nested = nestedObjects,
48+
)
49+
}
50+
51+
@Deprecated("Use ktFile version instead")
2352
fun extractIconPack(path: Path, project: Project): IconPackInfo? {
2453
val ktFile = PsiManager.getInstance(project)
2554
.findFile(LightVirtualFile(path.name, KotlinFileType.INSTANCE, path.readText()))
2655
.safeAs<KtFile>() ?: return null
2756

28-
var iconPackName: String? = null
29-
val nestedPacks = mutableListOf<String>()
30-
31-
ktFile.accept(
32-
object : KtTreeVisitorVoid() {
33-
override fun visitObjectDeclaration(declaration: KtObjectDeclaration) {
34-
super.visitObjectDeclaration(declaration)
35-
36-
if (declaration.isTopLevel()) {
37-
iconPackName = declaration.name
38-
} else {
39-
declaration.name?.let {
40-
nestedPacks.add(it)
41-
}
42-
}
43-
}
44-
},
45-
)
46-
47-
return when {
48-
iconPackName != null -> {
49-
IconPackInfo(
50-
packageName = ktFile.packageFqName.asString(),
51-
iconPack = iconPackName!!,
52-
nestedPacks = nestedPacks,
53-
)
54-
}
55-
else -> null
56-
}
57+
return parse(ktFile)
5758
}
5859
}

sdk/intellij/psi/iconpack/src/test/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParserTest.kt

Lines changed: 122 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,146 @@ import assertk.assertThat
44
import assertk.assertions.containsExactly
55
import assertk.assertions.isEqualTo
66
import assertk.assertions.isNotNull
7-
import com.intellij.openapi.project.Project
8-
import com.intellij.testFramework.ProjectExtension
9-
import com.intellij.testFramework.runInEdtAndGet
10-
import io.github.composegears.valkyrie.resource.loader.ResourceLoader.getResourcePath
11-
import org.junit.jupiter.api.Test
12-
import org.junit.jupiter.api.extension.RegisterExtension
13-
14-
class IconPackPsiParserTest {
15-
16-
companion object {
17-
@RegisterExtension
18-
val projectExtension = ProjectExtension()
19-
}
7+
import io.github.composegears.valkyrie.generator.core.IconPack
8+
import io.github.composegears.valkyrie.sdk.intellij.testfixtures.KotlinLightTestCase
9+
import org.junit.Test
2010

21-
private val project: Project
22-
get() = projectExtension.project
11+
class IconPackPsiParserTest : KotlinLightTestCase() {
2312

2413
@Test
2514
fun `simple icon pack parser`() {
26-
val path = getResourcePath("SimpleIconPack.kt")
27-
28-
runInEdtAndGet {
29-
val iconPackInfo = IconPackPsiParser.extractIconPack(path, project)
15+
val ktFile = loadKtFile("SimpleIconPack.kt")
16+
val iconPackInfo = IconPackPsiParser.parse(ktFile)
3017

31-
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
32-
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
33-
assertThat(packInfo.iconPack).isEqualTo("SimpleIconPack")
34-
assertThat(packInfo.nestedPacks.size).isEqualTo(0)
35-
}
18+
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
19+
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
20+
assertThat(packInfo.iconPack.name).isEqualTo("SimpleIconPack")
21+
assertThat(packInfo.iconPack.nested.size).isEqualTo(0)
3622
}
3723
}
3824

3925
@Test
4026
fun `nested icon pack parser`() {
41-
val path = getResourcePath("NestedIconPack.kt")
42-
43-
runInEdtAndGet {
44-
val iconPackInfo = IconPackPsiParser.extractIconPack(path, project)
27+
val ktFile = loadKtFile("NestedIconPack.kt")
28+
val iconPackInfo = IconPackPsiParser.parse(ktFile)
4529

46-
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
47-
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
48-
assertThat(packInfo.iconPack).isEqualTo("NestedIconPack")
49-
assertThat(packInfo.nestedPacks.size).isEqualTo(5)
50-
assertThat(packInfo.nestedPacks).containsExactly("Filled", "Outlined", "TwoTone", "Sharp", "Round")
51-
}
30+
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
31+
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
32+
assertThat(packInfo.iconPack.name).isEqualTo("NestedIconPack")
33+
assertThat(packInfo.iconPack.nested.size).isEqualTo(5)
34+
assertThat(packInfo.iconPack.nested.map { it.name }).containsExactly(
35+
"Filled",
36+
"Outlined",
37+
"TwoTone",
38+
"Sharp",
39+
"Round",
40+
)
5241
}
5342
}
5443

5544
@Test
5645
fun `data object icon pack parser`() {
57-
val path = getResourcePath("DataObjectIconPack.kt")
46+
val ktFile = loadKtFile("DataObjectIconPack.kt")
47+
val iconPackInfo = IconPackPsiParser.parse(ktFile)
48+
49+
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
50+
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
51+
assertThat(packInfo.iconPack.name).isEqualTo("DataObjectIconPack")
52+
assertThat(packInfo.iconPack.nested.size).isEqualTo(0)
53+
}
54+
}
55+
56+
@Test
57+
fun `deep nested icon pack parser`() {
58+
val ktFile = loadKtFile("DeepNestedIconPack.kt")
59+
val iconPackInfo = IconPackPsiParser.parse(ktFile)
60+
61+
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
62+
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
63+
packInfo.iconPack.assertStructure(
64+
expectedName = "DeepNestedIconPack",
65+
expectedNestedCount = 4,
66+
expectedNestedNames = listOf("Level1", "Branch", "Wide", "Single"),
67+
)
68+
69+
// Verify deep linear chain: Level1 -> Level2 -> Level3 -> Level4 -> Level5
70+
packInfo.iconPack.navigate("Level1").assertStructure("Level1", 1)
71+
packInfo.iconPack.navigate("Level1.Level2").assertStructure("Level2", 1)
72+
packInfo.iconPack.navigate("Level1.Level2.Level3").assertStructure("Level3", 1)
73+
packInfo.iconPack.navigate("Level1.Level2.Level3.Level4").assertStructure("Level4", 1)
74+
packInfo.iconPack.navigate("Level1.Level2.Level3.Level4.Level5").assertStructure("Level5", 0)
75+
76+
// Verify Branch with multiple sub-branches
77+
packInfo.iconPack.navigate("Branch").assertStructure(
78+
expectedName = "Branch",
79+
expectedNestedCount = 3,
80+
expectedNestedNames = listOf("Left", "Middle", "Right"),
81+
)
82+
packInfo.iconPack.navigate("Branch.Left").assertStructure("Left", 1)
83+
packInfo.iconPack.navigate("Branch.Left.LeftDeep1").assertStructure("LeftDeep1", 1)
84+
packInfo.iconPack.navigate("Branch.Left.LeftDeep1.LeftDeep2").assertStructure("LeftDeep2", 0)
85+
packInfo.iconPack.navigate("Branch.Middle").assertStructure("Middle", 0)
86+
packInfo.iconPack.navigate("Branch.Right")
87+
.assertStructure(
88+
expectedName = "Right",
89+
expectedNestedCount = 2,
90+
expectedNestedNames = listOf("RightDeep1", "RightDeep2"),
91+
)
5892

59-
runInEdtAndGet {
60-
val iconPackInfo = IconPackPsiParser.extractIconPack(path, project)
93+
// Verify wide tree with multiple children
94+
packInfo.iconPack.navigate("Wide")
95+
.assertStructure(
96+
expectedName = "Wide",
97+
expectedNestedCount = 5,
98+
expectedNestedNames = listOf("Item1", "Item2", "Item3", "Item4", "Item5"),
99+
)
100+
101+
// Verify single leaf node
102+
packInfo.iconPack.navigate("Single").assertStructure("Single", 0)
103+
104+
// Verify toRawString produces correct paths
105+
val rawString = IconPack.toRawString(packInfo.iconPack)
106+
val expectedPaths = listOf(
107+
"DeepNestedIconPack.Level1.Level2.Level3.Level4.Level5",
108+
"DeepNestedIconPack.Branch.Left.LeftDeep1.LeftDeep2",
109+
"DeepNestedIconPack.Branch.Middle",
110+
"DeepNestedIconPack.Branch.Right.RightDeep1",
111+
"DeepNestedIconPack.Branch.Right.RightDeep2",
112+
"DeepNestedIconPack.Wide.Item1",
113+
"DeepNestedIconPack.Wide.Item2",
114+
"DeepNestedIconPack.Wide.Item3",
115+
"DeepNestedIconPack.Wide.Item4",
116+
"DeepNestedIconPack.Wide.Item5",
117+
"DeepNestedIconPack.Single",
118+
)
119+
assertThat(rawString).isEqualTo(expectedPaths.joinToString(","))
120+
121+
// Verify round-trip conversion (toRawString -> fromString)
122+
val reconstructed = IconPack.fromString(rawString)
123+
assertThat(reconstructed).isEqualTo(packInfo.iconPack)
124+
}
125+
}
126+
127+
private fun IconPack.navigate(path: String): IconPack {
128+
val parts = path.split('.')
129+
var current = this
130+
131+
parts.forEach { part ->
132+
current = current.nested.first { it.name == part }
133+
}
134+
135+
return current
136+
}
61137

62-
assertThat(iconPackInfo).isNotNull().transform { packInfo ->
63-
assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi")
64-
assertThat(packInfo.iconPack).isEqualTo("DataObjectIconPack")
65-
assertThat(packInfo.nestedPacks.size).isEqualTo(0)
66-
}
138+
private fun IconPack.assertStructure(
139+
expectedName: String,
140+
expectedNestedCount: Int,
141+
expectedNestedNames: List<String> = emptyList(),
142+
) {
143+
assertThat(name).isEqualTo(expectedName)
144+
assertThat(nested.size).isEqualTo(expectedNestedCount)
145+
if (expectedNestedNames.isNotEmpty()) {
146+
assertThat(nested.map { it.name }).containsExactly(*expectedNestedNames.toTypedArray())
67147
}
68148
}
69149
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.github.composegears.valkyrie.psi
2+
3+
object DeepNestedIconPack {
4+
// Deep linear chain
5+
object Level1 {
6+
object Level2 {
7+
object Level3 {
8+
object Level4 {
9+
object Level5
10+
}
11+
}
12+
}
13+
}
14+
15+
// Multiple branches at different levels
16+
object Branch {
17+
object Left {
18+
object LeftDeep1 {
19+
object LeftDeep2
20+
}
21+
}
22+
23+
object Middle
24+
25+
object Right {
26+
object RightDeep1
27+
object RightDeep2
28+
}
29+
}
30+
31+
// Wide tree
32+
object Wide {
33+
object Item1
34+
object Item2
35+
object Item3
36+
object Item4
37+
object Item5
38+
}
39+
40+
// Single leaf
41+
object Single
42+
}
43+

0 commit comments

Comments
 (0)