@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,562 @@
1
+ package run.granite.gradle.models
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.assertj.core.api.Assertions.assertThatThrownBy
5
+ import org.junit.jupiter.api.Test
6
+
7
+ /**
8
+ * Unit tests for NativeModule core logic.
9
+ * Tests validation, computed properties, and CMake entry generation.
10
+ */
11
+ class NativeModuleTest {
12
+
13
+ @Test
14
+ fun `validates packageImportPath and packageInstance must both be present`() {
15
+ // Both present - should succeed
16
+ val module = NativeModule(
17
+ name = "test-module",
18
+ packageImportPath = "com.example.TestPackage",
19
+ packageInstance = "new TestPackage()",
20
+ dependencyConfiguration = null,
21
+ buildTypes = emptyList(),
22
+ libraryName = "testlib",
23
+ componentDescriptors = emptyList(),
24
+ cmakeListsPath = null,
25
+ cxxModuleCMakeListsPath = null,
26
+ cxxModuleCMakeListsModuleName = null,
27
+ cxxModuleHeaderName = null,
28
+ isPureCxxDependency = false,
29
+ )
30
+
31
+ assertThat(module.packageImportPath).isEqualTo("com.example.TestPackage")
32
+ assertThat(module.packageInstance).isEqualTo("new TestPackage()")
33
+ }
34
+
35
+ @Test
36
+ fun `validates packageImportPath and packageInstance must both be null`() {
37
+ // Both null - should succeed
38
+ val module = NativeModule(
39
+ name = "test-module",
40
+ packageImportPath = null,
41
+ packageInstance = null,
42
+ dependencyConfiguration = null,
43
+ buildTypes = emptyList(),
44
+ libraryName = "testlib",
45
+ componentDescriptors = emptyList(),
46
+ cmakeListsPath = null,
47
+ cxxModuleCMakeListsPath = null,
48
+ cxxModuleCMakeListsModuleName = null,
49
+ cxxModuleHeaderName = null,
50
+ isPureCxxDependency = false,
51
+ )
52
+
53
+ assertThat(module.packageImportPath).isNull()
54
+ assertThat(module.packageInstance).isNull()
55
+ }
56
+
57
+ @Test
58
+ fun `throws IllegalArgumentException when packageImportPath present but packageInstance null`() {
59
+ // Violation - should fail
60
+ assertThatThrownBy {
61
+ NativeModule(
62
+ name = "test-module",
63
+ packageImportPath = "com.example.TestPackage",
64
+ packageInstance = null,
65
+ dependencyConfiguration = null,
66
+ buildTypes = emptyList(),
67
+ libraryName = "testlib",
68
+ componentDescriptors = emptyList(),
69
+ cmakeListsPath = null,
70
+ cxxModuleCMakeListsPath = null,
71
+ cxxModuleCMakeListsModuleName = null,
72
+ cxxModuleHeaderName = null,
73
+ isPureCxxDependency = false,
74
+ )
75
+ }
76
+ .isInstanceOf(IllegalArgumentException::class.java)
77
+ .hasMessageContaining("test-module")
78
+ .hasMessageContaining("packageImportPath and packageInstance")
79
+ .hasMessageContaining("both be present or both be null")
80
+ }
81
+
82
+ @Test
83
+ fun `throws IllegalArgumentException when packageInstance present but packageImportPath null`() {
84
+ // Violation - should fail
85
+ assertThatThrownBy {
86
+ NativeModule(
87
+ name = "test-module",
88
+ packageImportPath = null,
89
+ packageInstance = "new TestPackage()",
90
+ dependencyConfiguration = null,
91
+ buildTypes = emptyList(),
92
+ libraryName = "testlib",
93
+ componentDescriptors = emptyList(),
94
+ cmakeListsPath = null,
95
+ cxxModuleCMakeListsPath = null,
96
+ cxxModuleCMakeListsModuleName = null,
97
+ cxxModuleHeaderName = null,
98
+ isPureCxxDependency = false,
99
+ )
100
+ }
101
+ .isInstanceOf(IllegalArgumentException::class.java)
102
+ .hasMessageContaining("test-module")
103
+ }
104
+
105
+ @Test
106
+ fun `hasJavaImplementation returns true when both packageImportPath and packageInstance present`() {
107
+ val module = NativeModule(
108
+ name = "test-module",
109
+ packageImportPath = "com.example.TestPackage",
110
+ packageInstance = "new TestPackage()",
111
+ dependencyConfiguration = null,
112
+ buildTypes = emptyList(),
113
+ libraryName = "testlib",
114
+ componentDescriptors = emptyList(),
115
+ cmakeListsPath = null,
116
+ cxxModuleCMakeListsPath = null,
117
+ cxxModuleCMakeListsModuleName = null,
118
+ cxxModuleHeaderName = null,
119
+ isPureCxxDependency = false,
120
+ )
121
+
122
+ assertThat(module.hasJavaImplementation).isTrue
123
+ }
124
+
125
+ @Test
126
+ fun `hasJavaImplementation returns false when both are null`() {
127
+ val module = NativeModule(
128
+ name = "test-module",
129
+ packageImportPath = null,
130
+ packageInstance = null,
131
+ dependencyConfiguration = null,
132
+ buildTypes = emptyList(),
133
+ libraryName = "testlib",
134
+ componentDescriptors = emptyList(),
135
+ cmakeListsPath = null,
136
+ cxxModuleCMakeListsPath = null,
137
+ cxxModuleCMakeListsModuleName = null,
138
+ cxxModuleHeaderName = null,
139
+ isPureCxxDependency = false,
140
+ )
141
+
142
+ assertThat(module.hasJavaImplementation).isFalse
143
+ }
144
+
145
+ @Test
146
+ fun `hasCxxImplementation returns true when cxxModuleHeaderName present`() {
147
+ val module = NativeModule(
148
+ name = "test-module",
149
+ packageImportPath = null,
150
+ packageInstance = null,
151
+ dependencyConfiguration = null,
152
+ buildTypes = emptyList(),
153
+ libraryName = "testlib",
154
+ componentDescriptors = emptyList(),
155
+ cmakeListsPath = null,
156
+ cxxModuleCMakeListsPath = null,
157
+ cxxModuleCMakeListsModuleName = null,
158
+ cxxModuleHeaderName = "TestModule",
159
+ isPureCxxDependency = true,
160
+ )
161
+
162
+ assertThat(module.hasCxxImplementation).isTrue
163
+ }
164
+
165
+ @Test
166
+ fun `hasCxxImplementation returns false when cxxModuleHeaderName null`() {
167
+ val module = NativeModule(
168
+ name = "test-module",
169
+ packageImportPath = null,
170
+ packageInstance = null,
171
+ dependencyConfiguration = null,
172
+ buildTypes = emptyList(),
173
+ libraryName = "testlib",
174
+ componentDescriptors = emptyList(),
175
+ cmakeListsPath = null,
176
+ cxxModuleCMakeListsPath = null,
177
+ cxxModuleCMakeListsModuleName = null,
178
+ cxxModuleHeaderName = null,
179
+ isPureCxxDependency = false,
180
+ )
181
+
182
+ assertThat(module.hasCxxImplementation).isFalse
183
+ }
184
+
185
+ @Test
186
+ fun `hasFabricComponents returns true when componentDescriptors not empty`() {
187
+ val module = NativeModule(
188
+ name = "test-module",
189
+ packageImportPath = null,
190
+ packageInstance = null,
191
+ dependencyConfiguration = null,
192
+ buildTypes = emptyList(),
193
+ libraryName = "testlib",
194
+ componentDescriptors = listOf("MyComponent", "AnotherComponent"),
195
+ cmakeListsPath = null,
196
+ cxxModuleCMakeListsPath = null,
197
+ cxxModuleCMakeListsModuleName = null,
198
+ cxxModuleHeaderName = null,
199
+ isPureCxxDependency = false,
200
+ )
201
+
202
+ assertThat(module.hasFabricComponents).isTrue
203
+ }
204
+
205
+ @Test
206
+ fun `hasFabricComponents returns false when componentDescriptors empty`() {
207
+ val module = NativeModule(
208
+ name = "test-module",
209
+ packageImportPath = null,
210
+ packageInstance = null,
211
+ dependencyConfiguration = null,
212
+ buildTypes = emptyList(),
213
+ libraryName = "testlib",
214
+ componentDescriptors = emptyList(),
215
+ cmakeListsPath = null,
216
+ cxxModuleCMakeListsPath = null,
217
+ cxxModuleCMakeListsModuleName = null,
218
+ cxxModuleHeaderName = null,
219
+ isPureCxxDependency = false,
220
+ )
221
+
222
+ assertThat(module.hasFabricComponents).isFalse
223
+ }
224
+
225
+ @Test
226
+ fun `hasCMakeConfiguration returns true when cmakeListsPath present`() {
227
+ val module = NativeModule(
228
+ name = "test-module",
229
+ packageImportPath = null,
230
+ packageInstance = null,
231
+ dependencyConfiguration = null,
232
+ buildTypes = emptyList(),
233
+ libraryName = "testlib",
234
+ componentDescriptors = emptyList(),
235
+ cmakeListsPath = "android/CMakeLists.txt",
236
+ cxxModuleCMakeListsPath = null,
237
+ cxxModuleCMakeListsModuleName = null,
238
+ cxxModuleHeaderName = null,
239
+ isPureCxxDependency = false,
240
+ )
241
+
242
+ assertThat(module.hasCMakeConfiguration).isTrue
243
+ }
244
+
245
+ @Test
246
+ fun `hasCMakeConfiguration returns true when cxxModuleCMakeListsPath present`() {
247
+ val module = NativeModule(
248
+ name = "test-module",
249
+ packageImportPath = null,
250
+ packageInstance = null,
251
+ dependencyConfiguration = null,
252
+ buildTypes = emptyList(),
253
+ libraryName = "testlib",
254
+ componentDescriptors = emptyList(),
255
+ cmakeListsPath = null,
256
+ cxxModuleCMakeListsPath = "android/cxx/CMakeLists.txt",
257
+ cxxModuleCMakeListsModuleName = "testmodule_cxx",
258
+ cxxModuleHeaderName = null,
259
+ isPureCxxDependency = false,
260
+ )
261
+
262
+ assertThat(module.hasCMakeConfiguration).isTrue
263
+ }
264
+
265
+ @Test
266
+ fun `hasCMakeConfiguration returns false when both paths null`() {
267
+ val module = NativeModule(
268
+ name = "test-module",
269
+ packageImportPath = null,
270
+ packageInstance = null,
271
+ dependencyConfiguration = null,
272
+ buildTypes = emptyList(),
273
+ libraryName = "testlib",
274
+ componentDescriptors = emptyList(),
275
+ cmakeListsPath = null,
276
+ cxxModuleCMakeListsPath = null,
277
+ cxxModuleCMakeListsModuleName = null,
278
+ cxxModuleHeaderName = null,
279
+ isPureCxxDependency = false,
280
+ )
281
+
282
+ assertThat(module.hasCMakeConfiguration).isFalse
283
+ }
284
+
285
+ @Test
286
+ fun `needsCppAutolinking returns true when hasCxxImplementation`() {
287
+ val module = NativeModule(
288
+ name = "test-module",
289
+ packageImportPath = null,
290
+ packageInstance = null,
291
+ dependencyConfiguration = null,
292
+ buildTypes = emptyList(),
293
+ libraryName = "testlib",
294
+ componentDescriptors = emptyList(),
295
+ cmakeListsPath = null,
296
+ cxxModuleCMakeListsPath = null,
297
+ cxxModuleCMakeListsModuleName = null,
298
+ cxxModuleHeaderName = "TestModule",
299
+ isPureCxxDependency = true,
300
+ )
301
+
302
+ assertThat(module.needsCppAutolinking).isTrue
303
+ }
304
+
305
+ @Test
306
+ fun `needsCppAutolinking returns true when hasFabricComponents`() {
307
+ val module = NativeModule(
308
+ name = "test-module",
309
+ packageImportPath = null,
310
+ packageInstance = null,
311
+ dependencyConfiguration = null,
312
+ buildTypes = emptyList(),
313
+ libraryName = "testlib",
314
+ componentDescriptors = listOf("MyComponent"),
315
+ cmakeListsPath = null,
316
+ cxxModuleCMakeListsPath = null,
317
+ cxxModuleCMakeListsModuleName = null,
318
+ cxxModuleHeaderName = null,
319
+ isPureCxxDependency = false,
320
+ )
321
+
322
+ assertThat(module.needsCppAutolinking).isTrue
323
+ }
324
+
325
+ @Test
326
+ fun `needsCppAutolinking returns false when neither C++ nor Fabric`() {
327
+ val module = NativeModule(
328
+ name = "test-module",
329
+ packageImportPath = "com.example.TestPackage",
330
+ packageInstance = "new TestPackage()",
331
+ dependencyConfiguration = null,
332
+ buildTypes = emptyList(),
333
+ libraryName = "testlib",
334
+ componentDescriptors = emptyList(),
335
+ cmakeListsPath = null,
336
+ cxxModuleCMakeListsPath = null,
337
+ cxxModuleCMakeListsModuleName = null,
338
+ cxxModuleHeaderName = null,
339
+ isPureCxxDependency = false,
340
+ )
341
+
342
+ assertThat(module.needsCppAutolinking).isFalse
343
+ }
344
+
345
+ @Test
346
+ fun `sanitizedName replaces spaces with underscores`() {
347
+ // / C++ identifier sanitization
348
+ val module = NativeModule(
349
+ name = "my test module",
350
+ packageImportPath = null,
351
+ packageInstance = null,
352
+ dependencyConfiguration = null,
353
+ buildTypes = emptyList(),
354
+ libraryName = "testlib",
355
+ componentDescriptors = emptyList(),
356
+ cmakeListsPath = null,
357
+ cxxModuleCMakeListsPath = null,
358
+ cxxModuleCMakeListsModuleName = null,
359
+ cxxModuleHeaderName = null,
360
+ isPureCxxDependency = false,
361
+ )
362
+
363
+ assertThat(module.sanitizedName()).isEqualTo("my_test_module")
364
+ }
365
+
366
+ @Test
367
+ fun `sanitizedName replaces hyphens with underscores`() {
368
+ // C++ identifier sanitization
369
+ val module = NativeModule(
370
+ name = "react-native-webview",
371
+ packageImportPath = null,
372
+ packageInstance = null,
373
+ dependencyConfiguration = null,
374
+ buildTypes = emptyList(),
375
+ libraryName = "testlib",
376
+ componentDescriptors = emptyList(),
377
+ cmakeListsPath = null,
378
+ cxxModuleCMakeListsPath = null,
379
+ cxxModuleCMakeListsModuleName = null,
380
+ cxxModuleHeaderName = null,
381
+ isPureCxxDependency = false,
382
+ )
383
+
384
+ assertThat(module.sanitizedName()).isEqualTo("react_native_webview")
385
+ }
386
+
387
+ @Test
388
+ fun `sanitizedName replaces special characters with underscores`() {
389
+ // C++ identifier sanitization
390
+ val module = NativeModule(
391
+ name = "module@2.0-beta!",
392
+ packageImportPath = null,
393
+ packageInstance = null,
394
+ dependencyConfiguration = null,
395
+ buildTypes = emptyList(),
396
+ libraryName = "testlib",
397
+ componentDescriptors = emptyList(),
398
+ cmakeListsPath = null,
399
+ cxxModuleCMakeListsPath = null,
400
+ cxxModuleCMakeListsModuleName = null,
401
+ cxxModuleHeaderName = null,
402
+ isPureCxxDependency = false,
403
+ )
404
+
405
+ assertThat(module.sanitizedName()).isEqualTo("module_2_0_beta_")
406
+ }
407
+
408
+ @Test
409
+ fun `cmakeEntries returns empty list when no CMake configuration`() {
410
+ val module = NativeModule(
411
+ name = "test-module",
412
+ packageImportPath = null,
413
+ packageInstance = null,
414
+ dependencyConfiguration = null,
415
+ buildTypes = emptyList(),
416
+ libraryName = "testlib",
417
+ componentDescriptors = emptyList(),
418
+ cmakeListsPath = null,
419
+ cxxModuleCMakeListsPath = null,
420
+ cxxModuleCMakeListsModuleName = null,
421
+ cxxModuleHeaderName = null,
422
+ isPureCxxDependency = false,
423
+ )
424
+
425
+ val entries = module.cmakeEntries()
426
+
427
+ assertThat(entries).isEmpty()
428
+ }
429
+
430
+ @Test
431
+ fun `cmakeEntries returns one entry when only standard CMakeLists txt`() {
432
+ // Handle standard CMakeLists.txt
433
+ val module = NativeModule(
434
+ name = "test-module",
435
+ packageImportPath = null,
436
+ packageInstance = null,
437
+ dependencyConfiguration = null,
438
+ buildTypes = emptyList(),
439
+ libraryName = "testlib",
440
+ componentDescriptors = emptyList(),
441
+ cmakeListsPath = "android/CMakeLists.txt",
442
+ cxxModuleCMakeListsPath = null,
443
+ cxxModuleCMakeListsModuleName = null,
444
+ cxxModuleHeaderName = null,
445
+ isPureCxxDependency = false,
446
+ )
447
+
448
+ val entries = module.cmakeEntries()
449
+
450
+ assertThat(entries).hasSize(1)
451
+ assertThat(entries[0].sourcePath).isEqualTo("android/CMakeLists.txt")
452
+ assertThat(entries[0].buildDirName).isEqualTo("testlib_autolinked_build")
453
+ assertThat(entries[0].libraryTargets).containsExactly("react_codegen_testlib")
454
+ }
455
+
456
+ @Test
457
+ fun `cmakeEntries returns one entry when only cxxModule CMakeLists txt`() {
458
+ // Handle cxxModule CMakeLists.txt
459
+ val module = NativeModule(
460
+ name = "test-module",
461
+ packageImportPath = null,
462
+ packageInstance = null,
463
+ dependencyConfiguration = null,
464
+ buildTypes = emptyList(),
465
+ libraryName = "testlib",
466
+ componentDescriptors = emptyList(),
467
+ cmakeListsPath = null,
468
+ cxxModuleCMakeListsPath = "android/cxx/CMakeLists.txt",
469
+ cxxModuleCMakeListsModuleName = "testmodule_cxx",
470
+ cxxModuleHeaderName = null,
471
+ isPureCxxDependency = false,
472
+ )
473
+
474
+ val entries = module.cmakeEntries()
475
+
476
+ assertThat(entries).hasSize(1)
477
+ assertThat(entries[0].sourcePath).isEqualTo("android/cxx/CMakeLists.txt")
478
+ assertThat(entries[0].buildDirName).isEqualTo("testlib_cxx_autolinked_build")
479
+ assertThat(entries[0].libraryTargets).containsExactly("testmodule_cxx")
480
+ }
481
+
482
+ @Test
483
+ fun `cmakeEntries returns two entries when both CMakeLists txt present`() {
484
+ // Handle both standard and cxxModule CMakeLists.txt
485
+ val module = NativeModule(
486
+ name = "test-module",
487
+ packageImportPath = null,
488
+ packageInstance = null,
489
+ dependencyConfiguration = null,
490
+ buildTypes = emptyList(),
491
+ libraryName = "testlib",
492
+ componentDescriptors = emptyList(),
493
+ cmakeListsPath = "android/CMakeLists.txt",
494
+ cxxModuleCMakeListsPath = "android/cxx/CMakeLists.txt",
495
+ cxxModuleCMakeListsModuleName = "testmodule_cxx",
496
+ cxxModuleHeaderName = null,
497
+ isPureCxxDependency = false,
498
+ )
499
+
500
+ val entries = module.cmakeEntries()
501
+
502
+ assertThat(entries).hasSize(2)
503
+ // First entry: standard CMakeLists.txt
504
+ assertThat(entries[0].sourcePath).isEqualTo("android/CMakeLists.txt")
505
+ assertThat(entries[0].buildDirName).isEqualTo("testlib_autolinked_build")
506
+ // Second entry: cxxModule CMakeLists.txt
507
+ assertThat(entries[1].sourcePath).isEqualTo("android/cxx/CMakeLists.txt")
508
+ assertThat(entries[1].buildDirName).isEqualTo("testlib_cxx_autolinked_build")
509
+ }
510
+
511
+ @Test
512
+ fun `from factory method creates module from AndroidDependencyConfig`() {
513
+ // Treat null and missing fields identically
514
+ val config = AndroidDependencyConfig(
515
+ sourceDir = "android",
516
+ packageImportPath = "com.example.TestPackage",
517
+ packageInstance = "new TestPackage()",
518
+ dependencyConfiguration = null,
519
+ buildTypes = listOf("debug", "release"),
520
+ libraryName = "testlib",
521
+ componentDescriptors = listOf("MyComponent"),
522
+ cmakeListsPath = "android/CMakeLists.txt",
523
+ cxxModuleCMakeListsPath = null,
524
+ cxxModuleCMakeListsModuleName = null,
525
+ cxxModuleHeaderName = null,
526
+ isPureCxxDependency = null, // null treated as false
527
+ )
528
+
529
+ val module = NativeModule.from("test-module", config)
530
+
531
+ assertThat(module.name).isEqualTo("test-module")
532
+ assertThat(module.packageImportPath).isEqualTo("com.example.TestPackage")
533
+ assertThat(module.packageInstance).isEqualTo("new TestPackage()")
534
+ assertThat(module.libraryName).isEqualTo("testlib")
535
+ assertThat(module.componentDescriptors).containsExactly("MyComponent")
536
+ assertThat(module.cmakeListsPath).isEqualTo("android/CMakeLists.txt")
537
+ assertThat(module.isPureCxxDependency).isFalse // null becomes false
538
+ }
539
+
540
+ @Test
541
+ fun `from factory method treats null componentDescriptors as empty list`() {
542
+ // Null treated as absent
543
+ val config = AndroidDependencyConfig(
544
+ sourceDir = "android",
545
+ packageImportPath = null,
546
+ packageInstance = null,
547
+ dependencyConfiguration = null,
548
+ buildTypes = null,
549
+ libraryName = "testlib",
550
+ componentDescriptors = null, // null
551
+ cmakeListsPath = null,
552
+ cxxModuleCMakeListsPath = null,
553
+ cxxModuleCMakeListsModuleName = null,
554
+ cxxModuleHeaderName = null,
555
+ isPureCxxDependency = false,
556
+ )
557
+
558
+ val module = NativeModule.from("test-module", config)
559
+
560
+ assertThat(module.componentDescriptors).isEmpty()
561
+ }
562
+ }