@granite-js/screen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/GraniteScreen.podspec +25 -0
  3. package/LICENSE +202 -0
  4. package/android/CMakeLists.txt +62 -0
  5. package/android/build.gradle +63 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/BundleEvaluator.cpp +27 -0
  8. package/android/src/main/cpp/BundleEvaluator.h +17 -0
  9. package/android/src/main/cpp/onLoad.cpp +6 -0
  10. package/android/src/main/kotlin/run/granite/BundleEvaluator.kt +50 -0
  11. package/android/src/main/kotlin/run/granite/BundleLoader.kt +40 -0
  12. package/android/src/main/kotlin/run/granite/DefaultBundleLoader.kt +20 -0
  13. package/android/src/main/kotlin/run/granite/DefaultErrorView.kt +58 -0
  14. package/android/src/main/kotlin/run/granite/DefaultLoadingView.kt +51 -0
  15. package/android/src/main/kotlin/run/granite/GraniteReactDelegate.kt +76 -0
  16. package/android/src/main/kotlin/run/granite/GraniteReactDelegateImpl.kt +448 -0
  17. package/android/src/main/kotlin/run/granite/GraniteReactHost.kt +113 -0
  18. package/android/src/main/kotlin/run/granite/ReactHostFactory.kt +106 -0
  19. package/gradle-plugin/LICENSE +201 -0
  20. package/gradle-plugin/README.md +578 -0
  21. package/gradle-plugin/build.gradle.kts +97 -0
  22. package/gradle-plugin/gradle/libs.versions.toml +17 -0
  23. package/gradle-plugin/gradle/wrapper/gradle-wrapper.jar +0 -0
  24. package/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +7 -0
  25. package/gradle-plugin/gradle.properties +12 -0
  26. package/gradle-plugin/gradlew +248 -0
  27. package/gradle-plugin/gradlew.bat +93 -0
  28. package/gradle-plugin/settings.gradle.kts +1 -0
  29. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteExtension.kt +225 -0
  30. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GranitePlugin.kt +784 -0
  31. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootExtension.kt +107 -0
  32. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootProjectPlugin.kt +290 -0
  33. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/BuildConfigConfigurator.kt +69 -0
  34. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyConfigurator.kt +232 -0
  35. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyCoordinates.kt +29 -0
  36. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DevServerResourceConfigurator.kt +101 -0
  37. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/JniPackagingConfigurator.kt +160 -0
  38. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/NdkConfigurator.kt +135 -0
  39. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/RepositoryConfigurator.kt +148 -0
  40. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/ResourceConfigurator.kt +56 -0
  41. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CMakeGenerator.kt +105 -0
  42. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CppAutolinkingGenerator.kt +152 -0
  43. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/EntryPointGenerator.kt +100 -0
  44. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AndroidDependencyConfig.kt +23 -0
  45. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AutolinkingConfig.kt +89 -0
  46. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/CMakeEntry.kt +47 -0
  47. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/NativeModule.kt +177 -0
  48. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AssetPackagingTask.kt +194 -0
  49. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AutolinkingTask.kt +431 -0
  50. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/BundleTask.kt +275 -0
  51. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenArtifactsTask.kt +218 -0
  52. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenSchemaTask.kt +186 -0
  53. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/AutolinkingParser.kt +128 -0
  54. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ConflictDetector.kt +121 -0
  55. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/JdkValidator.kt +73 -0
  56. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/NodeExecutableFinder.kt +43 -0
  57. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ReactNativeVersionReader.kt +329 -0
  58. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/TaskDependencyValidator.kt +198 -0
  59. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteExtensionTest.kt +191 -0
  60. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GranitePluginTest.kt +156 -0
  61. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteRootProjectPluginTest.kt +87 -0
  62. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/BuildConfigConfiguratorTest.kt +115 -0
  63. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DependencyConfiguratorTest.kt +338 -0
  64. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DevServerResourceConfiguratorTest.kt +205 -0
  65. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/ResourceConfiguratorTest.kt +131 -0
  66. package/gradle-plugin/src/test/kotlin/run/granite/gradle/fixtures/NativeModuleFixtures.kt +67 -0
  67. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CMakeGeneratorTest.kt +71 -0
  68. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CppAutolinkingGeneratorTest.kt +344 -0
  69. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/EntryPointGeneratorTest.kt +40 -0
  70. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/AutolinkingConfigTest.kt +350 -0
  71. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/CMakeEntryTest.kt +200 -0
  72. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/NativeModuleTest.kt +562 -0
  73. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AssetPackagingTaskTest.kt +318 -0
  74. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AutolinkingTaskTest.kt +89 -0
  75. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/BundleTaskTest.kt +68 -0
  76. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/CodegenTasksTest.kt +410 -0
  77. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/AutolinkingParserTest.kt +335 -0
  78. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ConflictDetectorTest.kt +75 -0
  79. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/JdkValidatorTest.kt +88 -0
  80. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ReactNativeVersionReaderTest.kt +585 -0
  81. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskDependencyValidatorTest.kt +123 -0
  82. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskTestUtils.kt +88 -0
  83. package/gradle-plugin/src/test/resources/fixtures/sample-rn-config.json +45 -0
  84. package/ios/BundleLoader/BundleEvaluator.h +16 -0
  85. package/ios/BundleLoader/BundleEvaluator.mm +76 -0
  86. package/ios/BundleLoader/BundleLoadable.swift +91 -0
  87. package/ios/GraniteBundleLoaderTypes.swift +7 -0
  88. package/ios/GraniteScreen.h +12 -0
  89. package/ios/ReactNativeHosting/DefaultViews.swift +138 -0
  90. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.h +24 -0
  91. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.mm +22 -0
  92. package/ios/ReactNativeHosting/GraniteHostingHelper.swift +103 -0
  93. package/ios/ReactNativeHosting/GraniteNativeFactory.swift +35 -0
  94. package/ios/ReactNativeHosting/GraniteNativeFactoryDelegateImpl.swift +30 -0
  95. package/ios/ReactNativeHosting/GraniteNativeFactoryImpl.swift +24 -0
  96. package/ios/ReactNativeHosting/GraniteReactHost.swift +39 -0
  97. package/ios/ReactNativeHosting/GraniteScreen-Bridging-Header.h +12 -0
  98. package/package.json +59 -0
  99. package/react-native.config.js +8 -0
@@ -0,0 +1,350 @@
1
+ package run.granite.gradle.models
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.jupiter.api.Test
5
+
6
+ /**
7
+ * Unit tests for AutolinkingConfig filtering logic.
8
+ * Tests androidDependencies, javaModules, cxxModules, fabricModules, cmakeModules filtering.
9
+ */
10
+ class AutolinkingConfigTest {
11
+
12
+ @Test
13
+ fun `androidDependencies filters only modules with Android platform config`() {
14
+ val config = AutolinkingConfig(
15
+ project = ProjectInfo(
16
+ name = "test-project",
17
+ version = "1.0.0",
18
+ ios = null,
19
+ android = AndroidProjectConfig(
20
+ sourceDir = "android",
21
+ manifestPath = "android/AndroidManifest.xml",
22
+ packageName = "com.example.test",
23
+ ),
24
+ ),
25
+ dependencies = mapOf(
26
+ "module1" to DependencyConfig(
27
+ name = "module1",
28
+ root = "/path/to/module1",
29
+ platforms = PlatformConfig(
30
+ ios = null,
31
+ android = AndroidDependencyConfig(
32
+ sourceDir = "android",
33
+ packageImportPath = "com.module1.Package",
34
+ packageInstance = "new Package()",
35
+ dependencyConfiguration = null,
36
+ buildTypes = null,
37
+ libraryName = "module1",
38
+ componentDescriptors = null,
39
+ cmakeListsPath = null,
40
+ cxxModuleCMakeListsPath = null,
41
+ cxxModuleCMakeListsModuleName = null,
42
+ cxxModuleHeaderName = null,
43
+ isPureCxxDependency = null,
44
+ ),
45
+ ),
46
+ ),
47
+ "module2" to DependencyConfig(
48
+ name = "module2",
49
+ root = "/path/to/module2",
50
+ platforms = null, // No platforms - should be excluded
51
+ ),
52
+ "module3" to DependencyConfig(
53
+ name = "module3",
54
+ root = "/path/to/module3",
55
+ platforms = PlatformConfig(
56
+ ios = emptyMap(),
57
+ android = null, // No Android config - should be excluded
58
+ ),
59
+ ),
60
+ ),
61
+ )
62
+
63
+ val androidModules = config.androidDependencies()
64
+
65
+ // Only module1 should be included
66
+ assertThat(androidModules).hasSize(1)
67
+ assertThat(androidModules[0].name).isEqualTo("module1")
68
+ }
69
+
70
+ @Test
71
+ fun `javaModules filters only modules with Java implementation`() {
72
+ val config = AutolinkingConfig(
73
+ project = ProjectInfo("test", null, null, null),
74
+ dependencies = mapOf(
75
+ "java-module" to DependencyConfig(
76
+ name = "java-module",
77
+ root = "/path",
78
+ platforms = PlatformConfig(
79
+ ios = null,
80
+ android = AndroidDependencyConfig(
81
+ sourceDir = "android",
82
+ packageImportPath = "com.example.Package",
83
+ packageInstance = "new Package()",
84
+ dependencyConfiguration = null,
85
+ buildTypes = null,
86
+ libraryName = "javamodule",
87
+ componentDescriptors = null,
88
+ cmakeListsPath = null,
89
+ cxxModuleCMakeListsPath = null,
90
+ cxxModuleCMakeListsModuleName = null,
91
+ cxxModuleHeaderName = null,
92
+ isPureCxxDependency = null,
93
+ ),
94
+ ),
95
+ ),
96
+ "cxx-only-module" to DependencyConfig(
97
+ name = "cxx-only-module",
98
+ root = "/path",
99
+ platforms = PlatformConfig(
100
+ ios = null,
101
+ android = AndroidDependencyConfig(
102
+ sourceDir = "android",
103
+ packageImportPath = null, // No Java
104
+ packageInstance = null,
105
+ dependencyConfiguration = null,
106
+ buildTypes = null,
107
+ libraryName = "cxxmodule",
108
+ componentDescriptors = null,
109
+ cmakeListsPath = null,
110
+ cxxModuleCMakeListsPath = null,
111
+ cxxModuleCMakeListsModuleName = null,
112
+ cxxModuleHeaderName = "CxxModule",
113
+ isPureCxxDependency = true,
114
+ ),
115
+ ),
116
+ ),
117
+ ),
118
+ )
119
+
120
+ val javaModules = config.javaModules()
121
+
122
+ // Only java-module should be included
123
+ assertThat(javaModules).hasSize(1)
124
+ assertThat(javaModules[0].name).isEqualTo("java-module")
125
+ assertThat(javaModules[0].hasJavaImplementation).isTrue
126
+ }
127
+
128
+ @Test
129
+ fun `cxxModules filters only modules with C++ implementation`() {
130
+ val config = AutolinkingConfig(
131
+ project = ProjectInfo("test", null, null, null),
132
+ dependencies = mapOf(
133
+ "java-only-module" to DependencyConfig(
134
+ name = "java-only-module",
135
+ root = "/path",
136
+ platforms = PlatformConfig(
137
+ ios = null,
138
+ android = AndroidDependencyConfig(
139
+ sourceDir = "android",
140
+ packageImportPath = "com.example.Package",
141
+ packageInstance = "new Package()",
142
+ dependencyConfiguration = null,
143
+ buildTypes = null,
144
+ libraryName = "javamodule",
145
+ componentDescriptors = null,
146
+ cmakeListsPath = null,
147
+ cxxModuleCMakeListsPath = null,
148
+ cxxModuleCMakeListsModuleName = null,
149
+ cxxModuleHeaderName = null, // No C++
150
+ isPureCxxDependency = null,
151
+ ),
152
+ ),
153
+ ),
154
+ "cxx-module" to DependencyConfig(
155
+ name = "cxx-module",
156
+ root = "/path",
157
+ platforms = PlatformConfig(
158
+ ios = null,
159
+ android = AndroidDependencyConfig(
160
+ sourceDir = "android",
161
+ packageImportPath = null,
162
+ packageInstance = null,
163
+ dependencyConfiguration = null,
164
+ buildTypes = null,
165
+ libraryName = "cxxmodule",
166
+ componentDescriptors = null,
167
+ cmakeListsPath = null,
168
+ cxxModuleCMakeListsPath = null,
169
+ cxxModuleCMakeListsModuleName = null,
170
+ cxxModuleHeaderName = "CxxModule", // Has C++
171
+ isPureCxxDependency = true,
172
+ ),
173
+ ),
174
+ ),
175
+ ),
176
+ )
177
+
178
+ val cxxModules = config.cxxModules()
179
+
180
+ // Only cxx-module should be included
181
+ assertThat(cxxModules).hasSize(1)
182
+ assertThat(cxxModules[0].name).isEqualTo("cxx-module")
183
+ assertThat(cxxModules[0].hasCxxImplementation).isTrue
184
+ }
185
+
186
+ @Test
187
+ fun `fabricModules filters only modules with Fabric components`() {
188
+ val config = AutolinkingConfig(
189
+ project = ProjectInfo("test", null, null, null),
190
+ dependencies = mapOf(
191
+ "no-fabric-module" to DependencyConfig(
192
+ name = "no-fabric-module",
193
+ root = "/path",
194
+ platforms = PlatformConfig(
195
+ ios = null,
196
+ android = AndroidDependencyConfig(
197
+ sourceDir = "android",
198
+ packageImportPath = "com.example.Package",
199
+ packageInstance = "new Package()",
200
+ dependencyConfiguration = null,
201
+ buildTypes = null,
202
+ libraryName = "nofabric",
203
+ componentDescriptors = null, // No Fabric
204
+ cmakeListsPath = null,
205
+ cxxModuleCMakeListsPath = null,
206
+ cxxModuleCMakeListsModuleName = null,
207
+ cxxModuleHeaderName = null,
208
+ isPureCxxDependency = null,
209
+ ),
210
+ ),
211
+ ),
212
+ "fabric-module" to DependencyConfig(
213
+ name = "fabric-module",
214
+ root = "/path",
215
+ platforms = PlatformConfig(
216
+ ios = null,
217
+ android = AndroidDependencyConfig(
218
+ sourceDir = "android",
219
+ packageImportPath = null,
220
+ packageInstance = null,
221
+ dependencyConfiguration = null,
222
+ buildTypes = null,
223
+ libraryName = "fabricmodule",
224
+ componentDescriptors = listOf("MyComponent", "AnotherComponent"), // Has Fabric
225
+ cmakeListsPath = null,
226
+ cxxModuleCMakeListsPath = null,
227
+ cxxModuleCMakeListsModuleName = null,
228
+ cxxModuleHeaderName = null,
229
+ isPureCxxDependency = null,
230
+ ),
231
+ ),
232
+ ),
233
+ ),
234
+ )
235
+
236
+ val fabricModules = config.fabricModules()
237
+
238
+ // Only fabric-module should be included
239
+ assertThat(fabricModules).hasSize(1)
240
+ assertThat(fabricModules[0].name).isEqualTo("fabric-module")
241
+ assertThat(fabricModules[0].hasFabricComponents).isTrue
242
+ assertThat(fabricModules[0].componentDescriptors).hasSize(2)
243
+ }
244
+
245
+ @Test
246
+ fun `cmakeModules filters only modules with CMake configuration`() {
247
+ val config = AutolinkingConfig(
248
+ project = ProjectInfo("test", null, null, null),
249
+ dependencies = mapOf(
250
+ "no-cmake-module" to DependencyConfig(
251
+ name = "no-cmake-module",
252
+ root = "/path",
253
+ platforms = PlatformConfig(
254
+ ios = null,
255
+ android = AndroidDependencyConfig(
256
+ sourceDir = "android",
257
+ packageImportPath = "com.example.Package",
258
+ packageInstance = "new Package()",
259
+ dependencyConfiguration = null,
260
+ buildTypes = null,
261
+ libraryName = "nocmake",
262
+ componentDescriptors = null,
263
+ cmakeListsPath = null, // No CMake
264
+ cxxModuleCMakeListsPath = null,
265
+ cxxModuleCMakeListsModuleName = null,
266
+ cxxModuleHeaderName = null,
267
+ isPureCxxDependency = null,
268
+ ),
269
+ ),
270
+ ),
271
+ "cmake-module" to DependencyConfig(
272
+ name = "cmake-module",
273
+ root = "/path",
274
+ platforms = PlatformConfig(
275
+ ios = null,
276
+ android = AndroidDependencyConfig(
277
+ sourceDir = "android",
278
+ packageImportPath = null,
279
+ packageInstance = null,
280
+ dependencyConfiguration = null,
281
+ buildTypes = null,
282
+ libraryName = "cmakemodule",
283
+ componentDescriptors = null,
284
+ cmakeListsPath = "android/CMakeLists.txt", // Has CMake
285
+ cxxModuleCMakeListsPath = null,
286
+ cxxModuleCMakeListsModuleName = null,
287
+ cxxModuleHeaderName = null,
288
+ isPureCxxDependency = null,
289
+ ),
290
+ ),
291
+ ),
292
+ ),
293
+ )
294
+
295
+ val cmakeModules = config.cmakeModules()
296
+
297
+ // Only cmake-module should be included
298
+ assertThat(cmakeModules).hasSize(1)
299
+ assertThat(cmakeModules[0].name).isEqualTo("cmake-module")
300
+ assertThat(cmakeModules[0].hasCMakeConfiguration).isTrue
301
+ }
302
+
303
+ @Test
304
+ fun `filtering methods work correctly with mixed modules`() {
305
+ val config = AutolinkingConfig(
306
+ project = ProjectInfo("test", null, null, null),
307
+ dependencies = mapOf(
308
+ "java-only" to createDependency("java-only", hasJava = true),
309
+ "cxx-only" to createDependency("cxx-only", hasCxx = true),
310
+ "fabric-only" to createDependency("fabric-only", hasFabric = true),
311
+ "cmake-only" to createDependency("cmake-only", hasCMake = true),
312
+ "mixed" to createDependency("mixed", hasJava = true, hasCxx = true, hasFabric = true, hasCMake = true),
313
+ ),
314
+ )
315
+
316
+ assertThat(config.androidDependencies()).hasSize(5)
317
+ assertThat(config.javaModules()).hasSize(2) // java-only, mixed
318
+ assertThat(config.cxxModules()).hasSize(2) // cxx-only, mixed
319
+ assertThat(config.fabricModules()).hasSize(2) // fabric-only, mixed
320
+ assertThat(config.cmakeModules()).hasSize(2) // cmake-only, mixed
321
+ }
322
+
323
+ private fun createDependency(
324
+ name: String,
325
+ hasJava: Boolean = false,
326
+ hasCxx: Boolean = false,
327
+ hasFabric: Boolean = false,
328
+ hasCMake: Boolean = false,
329
+ ): DependencyConfig = DependencyConfig(
330
+ name = name,
331
+ root = "/path/to/$name",
332
+ platforms = PlatformConfig(
333
+ ios = null,
334
+ android = AndroidDependencyConfig(
335
+ sourceDir = "android",
336
+ packageImportPath = if (hasJava) "com.example.$name.Package" else null,
337
+ packageInstance = if (hasJava) "new Package()" else null,
338
+ dependencyConfiguration = null,
339
+ buildTypes = null,
340
+ libraryName = name,
341
+ componentDescriptors = if (hasFabric) listOf("${name}Component") else null,
342
+ cmakeListsPath = if (hasCMake) "android/CMakeLists.txt" else null,
343
+ cxxModuleCMakeListsPath = null,
344
+ cxxModuleCMakeListsModuleName = null,
345
+ cxxModuleHeaderName = if (hasCxx) "${name}Module" else null,
346
+ isPureCxxDependency = hasCxx,
347
+ ),
348
+ ),
349
+ )
350
+ }
@@ -0,0 +1,200 @@
1
+ package run.granite.gradle.models
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.jupiter.api.Test
5
+ import org.junit.jupiter.api.io.TempDir
6
+ import java.io.File
7
+
8
+ /**
9
+ * Unit tests for CMakeEntry core logic.
10
+ * Tests path sanitization and CMake command generation.
11
+ */
12
+ class CMakeEntryTest {
13
+
14
+ @TempDir
15
+ lateinit var tempDir: File
16
+
17
+ @Test
18
+ fun `sanitizedPath removes trailing CMakeLists txt`() {
19
+ val entry = CMakeEntry(
20
+ sourcePath = "android/CMakeLists.txt",
21
+ buildDirName = "mylib_build",
22
+ libraryTargets = listOf("react_codegen_mylib"),
23
+ )
24
+
25
+ val moduleRoot = File(tempDir, "mymodule")
26
+ moduleRoot.mkdirs()
27
+ File(moduleRoot, "android").mkdirs()
28
+
29
+ val sanitized = entry.sanitizedPath(moduleRoot)
30
+
31
+ // Should remove /CMakeLists.txt suffix
32
+ assertThat(sanitized).endsWith("android")
33
+ assertThat(sanitized).doesNotContain("CMakeLists.txt")
34
+ }
35
+
36
+ @Test
37
+ fun `sanitizedPath removes trailing backslash CMakeLists txt`() {
38
+ val entry = CMakeEntry(
39
+ sourcePath = "android\\CMakeLists.txt",
40
+ buildDirName = "mylib_build",
41
+ libraryTargets = listOf("react_codegen_mylib"),
42
+ )
43
+
44
+ val moduleRoot = File(tempDir, "mymodule")
45
+ moduleRoot.mkdirs()
46
+
47
+ val sanitized = entry.sanitizedPath(moduleRoot)
48
+
49
+ // Should remove \CMakeLists.txt suffix
50
+ assertThat(sanitized).doesNotContain("CMakeLists.txt")
51
+ }
52
+
53
+ @Test
54
+ fun `sanitizedPath converts backslashes to forward slashes`() {
55
+ val entry = CMakeEntry(
56
+ sourcePath = "android\\src\\main",
57
+ buildDirName = "mylib_build",
58
+ libraryTargets = listOf("react_codegen_mylib"),
59
+ )
60
+
61
+ val moduleRoot = File(tempDir, "mymodule")
62
+ moduleRoot.mkdirs()
63
+
64
+ val sanitized = entry.sanitizedPath(moduleRoot)
65
+
66
+ // Should replace backslashes with forward slashes
67
+ assertThat(sanitized).doesNotContain("\\")
68
+ assertThat(sanitized).contains("/")
69
+ }
70
+
71
+ @Test
72
+ fun `sanitizedPath converts relative to absolute path`() {
73
+ val entry = CMakeEntry(
74
+ sourcePath = "android",
75
+ buildDirName = "mylib_build",
76
+ libraryTargets = listOf("react_codegen_mylib"),
77
+ )
78
+
79
+ val moduleRoot = File(tempDir, "mymodule")
80
+ moduleRoot.mkdirs()
81
+ File(moduleRoot, "android").mkdirs()
82
+
83
+ val sanitized = entry.sanitizedPath(moduleRoot)
84
+
85
+ // Should be absolute path (canonicalPath resolves symlinks and may differ from absolutePath)
86
+ assertThat(File(sanitized).isAbsolute).isTrue()
87
+ assertThat(sanitized).contains("android")
88
+ }
89
+
90
+ @Test
91
+ fun `sanitizedPath handles already absolute path`() {
92
+ val absolutePath = File(tempDir, "absolute/path").absolutePath
93
+ val entry = CMakeEntry(
94
+ sourcePath = absolutePath,
95
+ buildDirName = "mylib_build",
96
+ libraryTargets = listOf("react_codegen_mylib"),
97
+ )
98
+
99
+ val moduleRoot = File(tempDir, "mymodule")
100
+ val sanitized = entry.sanitizedPath(moduleRoot)
101
+
102
+ // Should preserve absolute path (but canonicalize it)
103
+ assertThat(File(sanitized).isAbsolute).isTrue
104
+ }
105
+
106
+ @Test
107
+ fun `toCMakeCommand generates correct add_subdirectory command with existence check`() {
108
+ val entry = CMakeEntry(
109
+ sourcePath = "android",
110
+ buildDirName = "mylib_autolinked_build",
111
+ libraryTargets = listOf("react_codegen_mylib"),
112
+ )
113
+
114
+ val moduleRoot = File(tempDir, "mymodule")
115
+ moduleRoot.mkdirs()
116
+ File(moduleRoot, "android").mkdirs()
117
+
118
+ val command = entry.toCMakeCommand(moduleRoot)
119
+
120
+ // Should wrap with if(EXISTS ...) to handle clean scenarios
121
+ assertThat(command).startsWith("if(EXISTS \"")
122
+ assertThat(command).contains("add_subdirectory(\"")
123
+ assertThat(command).contains("mylib_autolinked_build")
124
+ assertThat(command).endsWith("endif()")
125
+ }
126
+
127
+ @Test
128
+ fun `toCMakeCommand uses unique build directory name`() {
129
+ val entry1 = CMakeEntry(
130
+ sourcePath = "android",
131
+ buildDirName = "lib1_autolinked_build",
132
+ libraryTargets = listOf("react_codegen_lib1"),
133
+ )
134
+
135
+ val entry2 = CMakeEntry(
136
+ sourcePath = "android",
137
+ buildDirName = "lib2_autolinked_build",
138
+ libraryTargets = listOf("react_codegen_lib2"),
139
+ )
140
+
141
+ val moduleRoot = File(tempDir, "mymodule")
142
+ moduleRoot.mkdirs()
143
+ File(moduleRoot, "android").mkdirs()
144
+
145
+ val command1 = entry1.toCMakeCommand(moduleRoot)
146
+ val command2 = entry2.toCMakeCommand(moduleRoot)
147
+
148
+ // Build directory names should be unique
149
+ assertThat(command1).contains("lib1_autolinked_build")
150
+ assertThat(command2).contains("lib2_autolinked_build")
151
+ assertThat(command1).isNotEqualTo(command2)
152
+ }
153
+
154
+ @Test
155
+ fun `toCMakeCommand quotes path correctly`() {
156
+ val entry = CMakeEntry(
157
+ sourcePath = "android/path with spaces",
158
+ buildDirName = "mylib_build",
159
+ libraryTargets = listOf("react_codegen_mylib"),
160
+ )
161
+
162
+ val moduleRoot = File(tempDir, "mymodule")
163
+ moduleRoot.mkdirs()
164
+ File(moduleRoot, "android").mkdirs()
165
+
166
+ val command = entry.toCMakeCommand(moduleRoot)
167
+
168
+ // Path should be quoted in both if(EXISTS) and add_subdirectory
169
+ assertThat(command).contains("if(EXISTS \"")
170
+ assertThat(command).contains("add_subdirectory(\"")
171
+ }
172
+
173
+ @Test
174
+ fun `libraryTargets can contain multiple targets`() {
175
+ val entry = CMakeEntry(
176
+ sourcePath = "android",
177
+ buildDirName = "mylib_build",
178
+ libraryTargets = listOf("react_codegen_mylib", "mylib_cxx", "mylib_fabric"),
179
+ )
180
+
181
+ // Should store all targets
182
+ assertThat(entry.libraryTargets).hasSize(3)
183
+ assertThat(entry.libraryTargets).containsExactly(
184
+ "react_codegen_mylib",
185
+ "mylib_cxx",
186
+ "mylib_fabric",
187
+ )
188
+ }
189
+
190
+ @Test
191
+ fun `libraryTargets can be empty`() {
192
+ val entry = CMakeEntry(
193
+ sourcePath = "android",
194
+ buildDirName = "mylib_build",
195
+ libraryTargets = emptyList(),
196
+ )
197
+
198
+ assertThat(entry.libraryTargets).isEmpty()
199
+ }
200
+ }