@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,67 @@
1
+ package run.granite.gradle.fixtures
2
+
3
+ import run.granite.gradle.models.NativeModule
4
+
5
+ /**
6
+ * Test fixtures for NativeModule instances.
7
+ * Used for testing autolinking generation with various module configurations.
8
+ */
9
+ object NativeModuleFixtures {
10
+
11
+ /**
12
+ * Creates a module with includesGeneratedCode: true.
13
+ *
14
+ * This represents the brick-module package with:
15
+ * - libraryName: "BrickModuleSpec" (from package.json codegenConfig)
16
+ * - Java TurboModule implementation
17
+ * - No C++ implementation (Java only)
18
+ * - PackageList integration
19
+ * - includesGeneratedCode: true (uses brick-codegen instead of react-native-codegen)
20
+ */
21
+ fun createModuleWithCustomCodegen(): NativeModule = NativeModule(
22
+ name = "custom-codegen-module",
23
+ packageImportPath = "com.example.CustomCodegenPackage",
24
+ packageInstance = "new CustomCodegenPackage()",
25
+ dependencyConfiguration = null,
26
+ buildTypes = emptyList(),
27
+ libraryName = "CustomCodegenSpec",
28
+ componentDescriptors = emptyList(),
29
+ cmakeListsPath = "node_modules/custom-codegen-module/android/build/generated/source/codegen/jni/CMakeLists.txt",
30
+ cxxModuleCMakeListsPath = null,
31
+ cxxModuleCMakeListsModuleName = null,
32
+ cxxModuleHeaderName = null,
33
+ isPureCxxDependency = false,
34
+ includesGeneratedCode = true, // brick-module uses brick-codegen
35
+ )
36
+
37
+ /**
38
+ * Creates a standard module that uses react-native-codegen.
39
+ *
40
+ * This represents a typical React Native module that should be included in autolinking.
41
+ */
42
+ fun createStandardModule(): NativeModule = NativeModule(
43
+ name = "react-native-example",
44
+ packageImportPath = "com.example.ExamplePackage",
45
+ packageInstance = "new ExamplePackage()",
46
+ dependencyConfiguration = null,
47
+ buildTypes = emptyList(),
48
+ libraryName = "ExampleSpec",
49
+ componentDescriptors = emptyList(),
50
+ cmakeListsPath = "node_modules/react-native-example/android/build/generated/source/codegen/jni/CMakeLists.txt",
51
+ cxxModuleCMakeListsPath = null,
52
+ cxxModuleCMakeListsModuleName = null,
53
+ cxxModuleHeaderName = null,
54
+ isPureCxxDependency = false,
55
+ includesGeneratedCode = false,
56
+ )
57
+
58
+ /**
59
+ * Creates a list with both standard and custom codegen modules.
60
+ *
61
+ * Useful for testing filtering behavior.
62
+ */
63
+ fun createMixedModules(): List<NativeModule> = listOf(
64
+ createModuleWithCustomCodegen(),
65
+ createStandardModule(),
66
+ )
67
+ }
@@ -0,0 +1,71 @@
1
+ package run.granite.gradle.generators
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 run.granite.gradle.models.NativeModule
7
+ import java.io.File
8
+
9
+ class CMakeGeneratorTest {
10
+
11
+ @TempDir
12
+ lateinit var tempDir: File
13
+
14
+ @Test
15
+ fun `generate with CMake modules creates REACTNATIVE_MERGED_SO and add_subdirectory commands`() {
16
+ // Create actual directory for the test
17
+ val androidDir = File(tempDir, "android")
18
+ androidDir.mkdirs()
19
+
20
+ val module = createModuleWithCMake("test-module", "TestLib", "android/CMakeLists.txt")
21
+
22
+ val generated = CMakeGenerator.generate(listOf(module), tempDir)
23
+
24
+ assertThat(generated).contains("set(REACTNATIVE_MERGED_SO true)")
25
+ assertThat(generated).contains("add_subdirectory(")
26
+ assertThat(generated).contains("TestLib_autolinked_build")
27
+ assertThat(generated).contains("set(AUTOLINKED_LIBRARIES")
28
+ assertThat(generated).contains("react_codegen_TestLib")
29
+ }
30
+
31
+ @Test
32
+ fun `generate with no CMake modules creates empty file`() {
33
+ val module = createJavaOnlyModule()
34
+
35
+ val generated = CMakeGenerator.generate(listOf(module), tempDir)
36
+
37
+ assertThat(generated).contains("# No native modules with CMake configuration found")
38
+ assertThat(generated).contains("set(REACTNATIVE_MERGED_SO true)")
39
+ assertThat(generated).contains("set(AUTOLINKED_LIBRARIES)")
40
+ }
41
+
42
+ private fun createModuleWithCMake(name: String, libraryName: String, cmakePath: String): NativeModule = NativeModule(
43
+ name = name,
44
+ packageImportPath = null,
45
+ packageInstance = null,
46
+ dependencyConfiguration = null,
47
+ buildTypes = emptyList(),
48
+ libraryName = libraryName,
49
+ componentDescriptors = emptyList(),
50
+ cmakeListsPath = cmakePath,
51
+ cxxModuleCMakeListsPath = null,
52
+ cxxModuleCMakeListsModuleName = null,
53
+ cxxModuleHeaderName = null,
54
+ isPureCxxDependency = false,
55
+ )
56
+
57
+ private fun createJavaOnlyModule(): NativeModule = NativeModule(
58
+ name = "java-module",
59
+ packageImportPath = "com.example.Package",
60
+ packageInstance = "new Package()",
61
+ dependencyConfiguration = null,
62
+ buildTypes = emptyList(),
63
+ libraryName = "JavaLib",
64
+ componentDescriptors = emptyList(),
65
+ cmakeListsPath = null,
66
+ cxxModuleCMakeListsPath = null,
67
+ cxxModuleCMakeListsModuleName = null,
68
+ cxxModuleHeaderName = null,
69
+ isPureCxxDependency = false,
70
+ )
71
+ }
@@ -0,0 +1,344 @@
1
+ package run.granite.gradle.generators
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.jupiter.api.Test
5
+ import run.granite.gradle.fixtures.NativeModuleFixtures
6
+ import run.granite.gradle.models.NativeModule
7
+
8
+ /**
9
+ * Unit tests for CppAutolinkingGenerator core generation logic.
10
+ * Tests C++ code generation with various module configurations.
11
+ */
12
+ class CppAutolinkingGeneratorTest {
13
+
14
+ @Test
15
+ fun `generate with no modules creates empty implementations`() {
16
+ val modules = emptyList<NativeModule>()
17
+
18
+ val generated = CppAutolinkingGenerator.generate(modules)
19
+
20
+ // Should contain auto-generated comment
21
+ assertThat(generated).contains("Generated by Granite Gradle Plugin - DO NOT EDIT")
22
+
23
+ // Should include autolinking.h
24
+ assertThat(generated).contains("#include <autolinking.h>")
25
+
26
+ // Should have namespace
27
+ assertThat(generated).contains("namespace facebook::react {")
28
+
29
+ // Should have registerProviders with "no Fabric components" comment
30
+ assertThat(generated).contains("void autolinking_registerProviders(")
31
+ assertThat(generated).contains("// No autolinked Fabric components found")
32
+
33
+ // Should have cxxModuleProvider returning nullptr
34
+ assertThat(generated).contains("std::shared_ptr<TurboModule> autolinking_cxxModuleProvider(")
35
+ assertThat(generated).contains("// No autolinked C++ TurboModules found")
36
+ assertThat(generated).contains("return nullptr;")
37
+
38
+ // Should have ModuleProvider returning nullptr
39
+ assertThat(generated).contains("std::shared_ptr<TurboModule> autolinking_ModuleProvider(")
40
+ assertThat(generated).contains("// No autolinked Java TurboModules found")
41
+ }
42
+
43
+ @Test
44
+ fun `generate with Java-only module includes module provider call`() {
45
+ val modules = listOf(
46
+ NativeModule(
47
+ name = "react-native-webview",
48
+ packageImportPath = "com.example.Package",
49
+ packageInstance = "new Package()",
50
+ dependencyConfiguration = null,
51
+ buildTypes = emptyList(),
52
+ libraryName = "RNCWebView",
53
+ componentDescriptors = emptyList(),
54
+ cmakeListsPath = null,
55
+ cxxModuleCMakeListsPath = null,
56
+ cxxModuleCMakeListsModuleName = null,
57
+ cxxModuleHeaderName = null,
58
+ isPureCxxDependency = false,
59
+ ),
60
+ )
61
+
62
+ val generated = CppAutolinkingGenerator.generate(modules)
63
+
64
+ // Should have Java module provider call
65
+ assertThat(generated).contains("auto module_react_native_webview = RNCWebView_ModuleProvider(moduleName, params);")
66
+ assertThat(generated).contains("if (module_react_native_webview != nullptr) {")
67
+ assertThat(generated).contains("return module_react_native_webview;")
68
+
69
+ // Should not have C++ module headers or checks
70
+ assertThat(generated).doesNotContain("// C++ TurboModule headers")
71
+ assertThat(generated).contains("// No autolinked C++ TurboModules found")
72
+
73
+ // Should have "no Fabric components" comment
74
+ assertThat(generated).contains("// No autolinked Fabric components found")
75
+ }
76
+
77
+ @Test
78
+ fun `generate with C++ module includes header and provider check`() {
79
+ val modules = listOf(
80
+ NativeModule(
81
+ name = "cxx-module",
82
+ packageImportPath = null,
83
+ packageInstance = null,
84
+ dependencyConfiguration = null,
85
+ buildTypes = emptyList(),
86
+ libraryName = "CxxModule",
87
+ componentDescriptors = emptyList(),
88
+ cmakeListsPath = null,
89
+ cxxModuleCMakeListsPath = null,
90
+ cxxModuleCMakeListsModuleName = null,
91
+ cxxModuleHeaderName = "CxxModule",
92
+ isPureCxxDependency = true,
93
+ ),
94
+ )
95
+
96
+ val generated = CppAutolinkingGenerator.generate(modules)
97
+
98
+ // Should include C++ module header
99
+ assertThat(generated).contains("// C++ TurboModule headers")
100
+ assertThat(generated).contains("#include <CxxModule.h>")
101
+
102
+ // Should have C++ module provider check
103
+ assertThat(generated).contains("// Check C++ TurboModules (early-return pattern)")
104
+ assertThat(generated).contains("if (moduleName == CxxModule::kModuleName) {")
105
+ assertThat(generated).contains("return std::make_shared<CxxModule>(jsInvoker);")
106
+
107
+ // Should not have Java module provider
108
+ assertThat(generated).contains("// No autolinked Java TurboModules found")
109
+ }
110
+
111
+ @Test
112
+ fun `generate with Fabric module includes component descriptor header and registration`() {
113
+ val modules = listOf(
114
+ NativeModule(
115
+ name = "fabric-module",
116
+ packageImportPath = null,
117
+ packageInstance = null,
118
+ dependencyConfiguration = null,
119
+ buildTypes = emptyList(),
120
+ libraryName = "FabricModule",
121
+ componentDescriptors = listOf("MyComponentDescriptor", "AnotherComponentDescriptor"),
122
+ cmakeListsPath = null,
123
+ cxxModuleCMakeListsPath = null,
124
+ cxxModuleCMakeListsModuleName = null,
125
+ cxxModuleHeaderName = null,
126
+ isPureCxxDependency = false,
127
+ ),
128
+ )
129
+
130
+ val generated = CppAutolinkingGenerator.generate(modules)
131
+
132
+ // Should include Fabric headers
133
+ assertThat(generated).contains("// Fabric component descriptor headers")
134
+ assertThat(generated).contains("<react/renderer/components/FabricModule/ComponentDescriptors.h>")
135
+ assertThat(generated).contains("<react/renderer/components/FabricModule/MyComponentDescriptor.h>")
136
+
137
+ // Should register codegen components + custom component descriptors
138
+ assertThat(generated).contains("FabricModule_registerComponentDescriptorsFromCodegen(registry);")
139
+ assertThat(generated).contains("registry->add(concreteComponentDescriptorProvider<MyComponentDescriptor>());")
140
+ assertThat(generated).contains("registry->add(concreteComponentDescriptorProvider<AnotherComponentDescriptor>());")
141
+ }
142
+
143
+ @Test
144
+ fun `generate with mixed modules includes TurboModule providers and Fabric registration`() {
145
+ // Handle modules with both Java and C++ implementations
146
+ val modules = listOf(
147
+ createJavaModule("java-module", "JavaLib"),
148
+ createCxxModule("cxx-module", "CxxLib", "CxxModule"),
149
+ createFabricModule("fabric-module", "FabricLib", listOf("FabricComponent")),
150
+ createMixedModule("mixed-module", "MixedLib", "MixedModule", listOf("MixedComponent")),
151
+ )
152
+
153
+ val generated = CppAutolinkingGenerator.generate(modules)
154
+
155
+ // Should have C++ TurboModule headers
156
+ assertThat(generated).contains("// C++ TurboModule headers")
157
+
158
+ // Should have Fabric component headers or registration
159
+ assertThat(generated).contains("// Fabric component descriptor headers")
160
+ assertThat(generated).contains("registry->add(concreteComponentDescriptorProvider<FabricComponent>());")
161
+ assertThat(generated).contains("registry->add(concreteComponentDescriptorProvider<MixedComponent>());")
162
+
163
+ // Should have C++ provider checks
164
+ assertThat(generated).contains("if (moduleName == CxxModule::kModuleName) {")
165
+ assertThat(generated).contains("if (moduleName == MixedModule::kModuleName) {")
166
+
167
+ // Should have Java provider calls
168
+ assertThat(generated).contains("auto module_java_module = JavaLib_ModuleProvider(moduleName, params);")
169
+ assertThat(generated).contains("auto module_mixed_module = MixedLib_ModuleProvider(moduleName, params);")
170
+ assertThat(generated).contains("FabricLib_registerComponentDescriptorsFromCodegen(registry);")
171
+ assertThat(generated).contains("MixedLib_registerComponentDescriptorsFromCodegen(registry);")
172
+ }
173
+
174
+ @Test
175
+ fun `generate uses sanitized names for variables`() {
176
+ // C++ identifier sanitization
177
+ val modules = listOf(
178
+ NativeModule(
179
+ name = "module-with-hyphens",
180
+ packageImportPath = "com.example.Package",
181
+ packageInstance = "new Package()",
182
+ dependencyConfiguration = null,
183
+ buildTypes = emptyList(),
184
+ libraryName = "ModuleLib",
185
+ componentDescriptors = emptyList(),
186
+ cmakeListsPath = null,
187
+ cxxModuleCMakeListsPath = null,
188
+ cxxModuleCMakeListsModuleName = null,
189
+ cxxModuleHeaderName = null,
190
+ isPureCxxDependency = false,
191
+ ),
192
+ )
193
+
194
+ val generated = CppAutolinkingGenerator.generate(modules)
195
+
196
+ // Should use sanitized name (hyphens -> underscores)
197
+ assertThat(generated).contains("auto module_module_with_hyphens = ModuleLib_ModuleProvider(moduleName, params);")
198
+ assertThat(generated).contains("if (module_module_with_hyphens != nullptr) {")
199
+ }
200
+
201
+ @Test
202
+ fun `generate follows early-return pattern for multiple Java modules`() {
203
+ // Early-return pattern
204
+ val modules = listOf(
205
+ createJavaModule("module1", "Lib1"),
206
+ createJavaModule("module2", "Lib2"),
207
+ createJavaModule("module3", "Lib3"),
208
+ )
209
+
210
+ val generated = CppAutolinkingGenerator.generate(modules)
211
+
212
+ // Should check each module and return early if found
213
+ assertThat(generated).contains("auto module_module1 = Lib1_ModuleProvider(moduleName, params);")
214
+ assertThat(generated).contains("if (module_module1 != nullptr) {")
215
+ assertThat(generated).contains("return module_module1;")
216
+
217
+ assertThat(generated).contains("auto module_module2 = Lib2_ModuleProvider(moduleName, params);")
218
+ assertThat(generated).contains("if (module_module2 != nullptr) {")
219
+ assertThat(generated).contains("return module_module2;")
220
+
221
+ assertThat(generated).contains("auto module_module3 = Lib3_ModuleProvider(moduleName, params);")
222
+
223
+ // Should return nullptr at the end
224
+ val lines = generated.lines()
225
+ val returnNullptrIndex = lines.indexOfLast { it.contains("return nullptr;") }
226
+ assertThat(returnNullptrIndex).isGreaterThan(-1)
227
+ }
228
+
229
+ @Test
230
+ fun `generate follows early-return pattern for multiple C++ modules`() {
231
+ // Early-return pattern
232
+ val modules = listOf(
233
+ createCxxModule("module1", "Lib1", "Module1"),
234
+ createCxxModule("module2", "Lib2", "Module2"),
235
+ )
236
+
237
+ val generated = CppAutolinkingGenerator.generate(modules)
238
+
239
+ // Should check each module and return early if found
240
+ assertThat(generated).contains("if (moduleName == Module1::kModuleName) {")
241
+ assertThat(generated).contains("return std::make_shared<Module1>(jsInvoker);")
242
+
243
+ assertThat(generated).contains("if (moduleName == Module2::kModuleName) {")
244
+ assertThat(generated).contains("return std::make_shared<Module2>(jsInvoker);")
245
+
246
+ // Should return nullptr at the end
247
+ assertThat(generated).contains("return nullptr;")
248
+ }
249
+
250
+ @Test
251
+ fun `generate closes namespace correctly`() {
252
+ val modules = emptyList<NativeModule>()
253
+
254
+ val generated = CppAutolinkingGenerator.generate(modules)
255
+
256
+ assertThat(generated).contains("} // namespace facebook::react")
257
+ }
258
+
259
+ @Test
260
+ fun `generate includes modules with includesGeneratedCode flag`() {
261
+ // includesGeneratedCode indicates the library ships generated files; it should still be autolinked.
262
+ val modules = listOf(NativeModuleFixtures.createModuleWithCustomCodegen())
263
+
264
+ val generated = CppAutolinkingGenerator.generate(modules)
265
+
266
+ // Should call *_ModuleProvider for the module
267
+ assertThat(generated).contains("CustomCodegenSpec_ModuleProvider")
268
+ }
269
+
270
+ @Test
271
+ fun `generate includes standard modules and includesGeneratedCode modules`() {
272
+ // Mixed list: one standard module, one that ships generated code
273
+ val modules = NativeModuleFixtures.createMixedModules()
274
+
275
+ val generated = CppAutolinkingGenerator.generate(modules)
276
+
277
+ // Standard module should be included
278
+ assertThat(generated).contains("ExampleSpec_ModuleProvider")
279
+
280
+ // includesGeneratedCode module should also be included
281
+ assertThat(generated).contains("CustomCodegenSpec_ModuleProvider")
282
+ }
283
+
284
+ // Helper methods for creating test modules
285
+ private fun createJavaModule(name: String, libraryName: String): NativeModule = NativeModule(
286
+ name = name,
287
+ packageImportPath = "com.example.$name.Package",
288
+ packageInstance = "new Package()",
289
+ dependencyConfiguration = null,
290
+ buildTypes = emptyList(),
291
+ libraryName = libraryName,
292
+ componentDescriptors = emptyList(),
293
+ cmakeListsPath = null,
294
+ cxxModuleCMakeListsPath = null,
295
+ cxxModuleCMakeListsModuleName = null,
296
+ cxxModuleHeaderName = null,
297
+ isPureCxxDependency = false,
298
+ )
299
+
300
+ private fun createCxxModule(name: String, libraryName: String, headerName: String): NativeModule = NativeModule(
301
+ name = name,
302
+ packageImportPath = null,
303
+ packageInstance = null,
304
+ dependencyConfiguration = null,
305
+ buildTypes = emptyList(),
306
+ libraryName = libraryName,
307
+ componentDescriptors = emptyList(),
308
+ cmakeListsPath = null,
309
+ cxxModuleCMakeListsPath = null,
310
+ cxxModuleCMakeListsModuleName = null,
311
+ cxxModuleHeaderName = headerName,
312
+ isPureCxxDependency = true,
313
+ )
314
+
315
+ private fun createFabricModule(name: String, libraryName: String, descriptors: List<String>): NativeModule = NativeModule(
316
+ name = name,
317
+ packageImportPath = null,
318
+ packageInstance = null,
319
+ dependencyConfiguration = null,
320
+ buildTypes = emptyList(),
321
+ libraryName = libraryName,
322
+ componentDescriptors = descriptors,
323
+ cmakeListsPath = null,
324
+ cxxModuleCMakeListsPath = null,
325
+ cxxModuleCMakeListsModuleName = null,
326
+ cxxModuleHeaderName = null,
327
+ isPureCxxDependency = false,
328
+ )
329
+
330
+ private fun createMixedModule(name: String, libraryName: String, headerName: String, descriptors: List<String>): NativeModule = NativeModule(
331
+ name = name,
332
+ packageImportPath = "com.example.$name.Package",
333
+ packageInstance = "new Package()",
334
+ dependencyConfiguration = null,
335
+ buildTypes = emptyList(),
336
+ libraryName = libraryName,
337
+ componentDescriptors = descriptors,
338
+ cmakeListsPath = null,
339
+ cxxModuleCMakeListsPath = null,
340
+ cxxModuleCMakeListsModuleName = null,
341
+ cxxModuleHeaderName = headerName,
342
+ isPureCxxDependency = false,
343
+ )
344
+ }
@@ -0,0 +1,40 @@
1
+ package run.granite.gradle.generators
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
+ class EntryPointGeneratorTest {
8
+
9
+ @Test
10
+ fun `generate creates ReactNativeApplicationEntryPoint class with loadReactNative method`() {
11
+ val generated = EntryPointGenerator.generate("com.example.test")
12
+
13
+ assertThat(generated).contains("package com.facebook.react")
14
+ assertThat(generated).contains("public class ReactNativeApplicationEntryPoint")
15
+ assertThat(generated).contains("public static void loadReactNative(Context context)")
16
+ assertThat(generated).contains("SoLoader.init(context, OpenSourceMergedSoMapping.INSTANCE)")
17
+ assertThat(generated).contains("if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED)")
18
+ assertThat(generated).contains("DefaultNewArchitectureEntryPoint.load()")
19
+ }
20
+
21
+ @Test
22
+ fun `generate throws IllegalArgumentException when packageName is null`() {
23
+ assertThatThrownBy {
24
+ EntryPointGenerator.generate(null)
25
+ }
26
+ .isInstanceOf(IllegalArgumentException::class.java)
27
+ .hasMessageContaining("Android package name not found")
28
+ .hasMessageContaining("Ensure project has valid android configuration")
29
+ }
30
+
31
+ @Test
32
+ fun `generate includes RuntimeException handling for SoLoader failure`() {
33
+ val generated = EntryPointGenerator.generate("com.example.test")
34
+
35
+ assertThat(generated).contains("try {")
36
+ assertThat(generated).contains("SoLoader.init")
37
+ assertThat(generated).contains("} catch (Exception e) {")
38
+ assertThat(generated).contains("throw new RuntimeException(\"Failed to initialize SoLoader\", e)")
39
+ }
40
+ }