@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,191 @@
1
+ package run.granite.gradle
2
+
3
+ import org.gradle.testfixtures.ProjectBuilder
4
+ import org.junit.jupiter.api.BeforeEach
5
+ import org.junit.jupiter.api.Test
6
+ import org.junit.jupiter.api.assertThrows
7
+ import org.junit.jupiter.api.io.TempDir
8
+ import java.io.File
9
+ import kotlin.test.assertEquals
10
+ import kotlin.test.assertTrue
11
+
12
+ /**
13
+ * Unit tests for GraniteExtension.
14
+ */
15
+ class GraniteExtensionTest {
16
+
17
+ @TempDir
18
+ lateinit var testProjectDir: File
19
+
20
+ private lateinit var project: org.gradle.api.Project
21
+ private lateinit var extension: GraniteExtension
22
+
23
+ @BeforeEach
24
+ fun setup() {
25
+ project = ProjectBuilder.builder()
26
+ .withProjectDir(testProjectDir)
27
+ .build()
28
+
29
+ // Apply plugins to create extension
30
+ project.pluginManager.apply("com.android.library")
31
+ project.pluginManager.apply(GranitePlugin::class.java)
32
+
33
+ extension = project.extensions.getByType(GraniteExtension::class.java)
34
+ }
35
+
36
+ @Test
37
+ fun `extension has correct default values`() {
38
+ // Then
39
+ assertEquals("src/main/js/index.js", extension.entryFile.get())
40
+ assertEquals("index.android.bundle", extension.bundleAssetName.get())
41
+ assertTrue(extension.bundleCompressionEnabled.get())
42
+ assertEquals(4, extension.nativeArchitectures.get().size)
43
+ assertTrue(extension.nativeArchitectures.get().contains("arm64-v8a"))
44
+ assertTrue(extension.nativeArchitectures.get().contains("armeabi-v7a"))
45
+ }
46
+
47
+ @Test
48
+ fun `extension allows customization of properties`() {
49
+ // When
50
+ extension.entryFile.set("custom/entry.js")
51
+ extension.bundleAssetName.set("custom.bundle")
52
+ extension.bundleCompressionEnabled.set(false)
53
+ extension.nativeArchitectures.set(listOf("arm64-v8a"))
54
+
55
+ // Then
56
+ assertEquals("custom/entry.js", extension.entryFile.get())
57
+ assertEquals("custom.bundle", extension.bundleAssetName.get())
58
+ assertEquals(false, extension.bundleCompressionEnabled.get())
59
+ assertEquals(1, extension.nativeArchitectures.get().size)
60
+ }
61
+
62
+ // Test removed: entryFile validation moved to BundleTask execution time
63
+
64
+ @Test
65
+ fun `validate succeeds when all required files exist`() {
66
+ // Given
67
+ setupValidProject()
68
+
69
+ // When/Then - Should not throw
70
+ extension.validate()
71
+ }
72
+
73
+ @Test
74
+ fun `validate succeeds even without entry file`() {
75
+ // Given - Only React Native directory setup, no entry file
76
+ val nodeModules = File(testProjectDir, "node_modules")
77
+ val reactNativeDir = File(nodeModules, "react-native")
78
+ reactNativeDir.mkdirs()
79
+
80
+ val packageJson = File(reactNativeDir, "package.json")
81
+ packageJson.writeText("""{"version":"0.81.6"}""")
82
+
83
+ extension.reactNativeDir.set(reactNativeDir)
84
+ extension.nodeModulesDir.set(nodeModules)
85
+
86
+ // When/Then - Should not throw (entryFile validation deferred to BundleTask)
87
+ extension.validate()
88
+ }
89
+
90
+ @Test
91
+ fun `validate fails when React Native directory does not exist`() {
92
+ // Given - React Native dir not created
93
+
94
+ // When/Then
95
+ assertThrows<IllegalStateException> {
96
+ extension.validate()
97
+ }
98
+ }
99
+
100
+ // Test removed: Hermes is now always enabled, cannot be disabled
101
+
102
+ @Test
103
+ fun `validate fails with invalid ABI`() {
104
+ // Given
105
+ setupValidProject()
106
+ extension.nativeArchitectures.set(listOf("invalid-abi"))
107
+
108
+ // When/Then
109
+ val exception = assertThrows<IllegalStateException> {
110
+ extension.validate()
111
+ }
112
+
113
+ assertTrue(exception.message?.contains("Invalid") == true)
114
+ assertTrue(exception.message?.contains("ABI") == true)
115
+ }
116
+
117
+ @Test
118
+ fun `validate accepts all valid ABIs`() {
119
+ // Given
120
+ setupValidProject()
121
+ val validAbis = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
122
+ extension.nativeArchitectures.set(validAbis)
123
+
124
+ // When/Then - Should not throw
125
+ extension.validate()
126
+ }
127
+
128
+ @Test
129
+ fun `getEntryFileResolved returns correct file`() {
130
+ // Given
131
+ extension.entryFile.set("custom/entry.js")
132
+
133
+ // When
134
+ val resolved = extension.getEntryFileResolved()
135
+
136
+ // Then - Use canonical paths to handle symlinks (e.g., /var vs /private/var on macOS)
137
+ assertEquals(
138
+ File(testProjectDir, "custom/entry.js").canonicalPath,
139
+ resolved.canonicalPath,
140
+ )
141
+ }
142
+
143
+ @Test
144
+ fun `getReactNativeDirResolved returns correct directory`() {
145
+ // Given
146
+ val customRnDir = File(testProjectDir, "custom/react-native")
147
+ extension.reactNativeDir.set(customRnDir)
148
+
149
+ // When
150
+ val resolved = extension.getReactNativeDirResolved()
151
+
152
+ // Then - Use canonical paths to handle symlinks
153
+ assertEquals(customRnDir.canonicalPath, resolved.canonicalPath)
154
+ }
155
+
156
+ @Test
157
+ fun `getNodeModulesDirResolved returns correct directory`() {
158
+ // Given
159
+ val customNodeModules = File(testProjectDir, "custom/node_modules")
160
+ extension.nodeModulesDir.set(customNodeModules)
161
+
162
+ // When
163
+ val resolved = extension.getNodeModulesDirResolved()
164
+
165
+ // Then - Use canonical paths to handle symlinks
166
+ assertEquals(customNodeModules.canonicalPath, resolved.canonicalPath)
167
+ }
168
+
169
+ /**
170
+ * Sets up a valid project structure for testing.
171
+ */
172
+ private fun setupValidProject() {
173
+ // Create entry file
174
+ val entryFile = File(testProjectDir, "src/main/js/index.js")
175
+ entryFile.parentFile.mkdirs()
176
+ entryFile.writeText("// Entry file")
177
+
178
+ // Create React Native directory structure
179
+ val nodeModules = File(testProjectDir, "node_modules")
180
+ val reactNativeDir = File(nodeModules, "react-native")
181
+ reactNativeDir.mkdirs()
182
+
183
+ // Create package.json
184
+ val packageJson = File(reactNativeDir, "package.json")
185
+ packageJson.writeText("""{"version":"0.81.6"}""")
186
+
187
+ // Update extension to point to test directories
188
+ extension.reactNativeDir.set(reactNativeDir)
189
+ extension.nodeModulesDir.set(nodeModules)
190
+ }
191
+ }
@@ -0,0 +1,156 @@
1
+ package run.granite.gradle
2
+
3
+ import org.gradle.testfixtures.ProjectBuilder
4
+ import org.junit.jupiter.api.BeforeEach
5
+ import org.junit.jupiter.api.Test
6
+ import org.junit.jupiter.api.assertThrows
7
+ import org.junit.jupiter.api.io.TempDir
8
+ import java.io.File
9
+ import kotlin.test.assertEquals
10
+ import kotlin.test.assertNotNull
11
+ import kotlin.test.assertTrue
12
+
13
+ /**
14
+ * Unit tests for GranitePlugin.
15
+ */
16
+ class GranitePluginTest {
17
+
18
+ @TempDir
19
+ lateinit var testProjectDir: File
20
+
21
+ private lateinit var buildFile: File
22
+ private lateinit var settingsFile: File
23
+
24
+ @BeforeEach
25
+ fun setup() {
26
+ buildFile = File(testProjectDir, "build.gradle.kts")
27
+ settingsFile = File(testProjectDir, "settings.gradle.kts")
28
+ }
29
+
30
+ @Test
31
+ fun `plugin applies successfully to library project`() {
32
+ // Given
33
+ val project = ProjectBuilder.builder()
34
+ .withProjectDir(testProjectDir)
35
+ .build()
36
+
37
+ // When
38
+ project.pluginManager.apply("com.android.library")
39
+ project.pluginManager.apply(GranitePlugin::class.java)
40
+
41
+ // Then
42
+ assertTrue(project.plugins.hasPlugin(GranitePlugin::class.java))
43
+ assertNotNull(project.extensions.findByName("granite"))
44
+ }
45
+
46
+ @Test
47
+ fun `plugin creates granite extension with correct type`() {
48
+ // Given
49
+ val project = ProjectBuilder.builder()
50
+ .withProjectDir(testProjectDir)
51
+ .build()
52
+
53
+ // When
54
+ project.pluginManager.apply("com.android.library")
55
+ project.pluginManager.apply(GranitePlugin::class.java)
56
+
57
+ // Then
58
+ val extension = project.extensions.findByName("granite")
59
+ assertNotNull(extension)
60
+ assertTrue(extension is GraniteExtension)
61
+ }
62
+
63
+ @Test
64
+ fun `plugin registers autolinking task`() {
65
+ // Given
66
+ val project = ProjectBuilder.builder()
67
+ .withProjectDir(testProjectDir)
68
+ .build()
69
+
70
+ setupMinimalProject(project)
71
+
72
+ // When
73
+ project.pluginManager.apply("com.android.library")
74
+ project.pluginManager.apply(GranitePlugin::class.java)
75
+
76
+ // Force evaluation of afterEvaluate blocks by accessing the task container
77
+ // Note: This is a simplified test. Full task registration should be tested with GradleRunner.
78
+ // For now, we just verify the plugin applies without errors
79
+ assertTrue(project.plugins.hasPlugin(GranitePlugin::class.java))
80
+ }
81
+
82
+ @Test
83
+ fun `plugin registers codegen tasks`() {
84
+ // Given
85
+ val project = ProjectBuilder.builder()
86
+ .withProjectDir(testProjectDir)
87
+ .build()
88
+
89
+ setupMinimalProject(project)
90
+
91
+ // When
92
+ project.pluginManager.apply("com.android.library")
93
+ project.pluginManager.apply(GranitePlugin::class.java)
94
+
95
+ // Note: Task registration happens in afterEvaluate, which doesn't execute with ProjectBuilder.
96
+ // Full task registration should be tested with GradleRunner integration tests.
97
+ // For now, we verify the plugin and extension are configured correctly
98
+ val extension = project.extensions.findByName("granite")
99
+ assertNotNull(extension)
100
+ assertTrue(extension is GraniteExtension)
101
+ }
102
+
103
+ @Test
104
+ fun `plugin fails when applied to application project`() {
105
+ // Given
106
+ val project = ProjectBuilder.builder()
107
+ .withProjectDir(testProjectDir)
108
+ .build()
109
+
110
+ // When/Then
111
+ project.pluginManager.apply("com.android.application")
112
+
113
+ // Gradle wraps exceptions in PluginApplicationException
114
+ val exception = assertThrows<org.gradle.api.internal.plugins.PluginApplicationException> {
115
+ project.pluginManager.apply(GranitePlugin::class.java)
116
+ }
117
+
118
+ // Verify the cause is our expected IllegalStateException
119
+ assertTrue(exception.cause is IllegalStateException)
120
+ }
121
+
122
+ @Test
123
+ fun `plugin constants are correct`() {
124
+ assertEquals("run.granite.library", GranitePlugin.PLUGIN_ID)
125
+ assertEquals("granite", GranitePlugin.EXTENSION_NAME)
126
+ assertEquals("granite", GranitePlugin.PLUGIN_GROUP)
127
+ }
128
+
129
+ /**
130
+ * Sets up minimal project structure for testing.
131
+ */
132
+ private fun setupMinimalProject(project: org.gradle.api.Project) {
133
+ // Create minimal required directories
134
+ File(testProjectDir, "src/main/js").mkdirs()
135
+ File(testProjectDir, "src/main/java").mkdirs()
136
+
137
+ // Create minimal entry file
138
+ val entryFile = File(testProjectDir, "src/main/js/index.js")
139
+ entryFile.parentFile.mkdirs()
140
+ entryFile.writeText("// Empty entry file")
141
+
142
+ // Create fake node_modules structure
143
+ val nodeModules = File(testProjectDir, "node_modules")
144
+ val reactNativeDir = File(nodeModules, "react-native")
145
+ val androidDir = File(reactNativeDir, "android")
146
+ androidDir.mkdirs()
147
+
148
+ // Create package.json
149
+ val packageJson = File(reactNativeDir, "package.json")
150
+ packageJson.writeText("""{"version":"0.81.6"}""")
151
+
152
+ // Create cli.js
153
+ val cliJs = File(reactNativeDir, "cli.js")
154
+ cliJs.writeText("// Mock CLI")
155
+ }
156
+ }
@@ -0,0 +1,87 @@
1
+ package run.granite.gradle
2
+
3
+ import org.gradle.testfixtures.ProjectBuilder
4
+ import org.junit.jupiter.api.Test
5
+ import org.junit.jupiter.api.assertThrows
6
+ import kotlin.test.assertEquals
7
+ import kotlin.test.assertNotNull
8
+ import kotlin.test.assertTrue
9
+
10
+ /**
11
+ * Unit tests for GraniteRootProjectPlugin.
12
+ */
13
+ class GraniteRootProjectPluginTest {
14
+
15
+ @Test
16
+ fun `plugin constants are correct`() {
17
+ assertEquals("run.granite.rootproject", GraniteRootProjectPlugin.PLUGIN_ID)
18
+ assertEquals("graniteRoot", GraniteRootProjectPlugin.EXTENSION_NAME)
19
+ }
20
+
21
+ @Test
22
+ fun `plugin applies successfully to root project`() {
23
+ // Given
24
+ val project = ProjectBuilder.builder().build()
25
+
26
+ // When
27
+ project.pluginManager.apply(GraniteRootProjectPlugin::class.java)
28
+
29
+ // Then
30
+ assertTrue(project.plugins.hasPlugin(GraniteRootProjectPlugin::class.java))
31
+ }
32
+
33
+ @Test
34
+ fun `plugin creates graniteRoot extension`() {
35
+ // Given
36
+ val project = ProjectBuilder.builder().build()
37
+
38
+ // When
39
+ project.pluginManager.apply(GraniteRootProjectPlugin::class.java)
40
+
41
+ // Then
42
+ val extension = project.extensions.findByName("graniteRoot")
43
+ assertNotNull(extension)
44
+ assertTrue(extension is GraniteRootExtension)
45
+ }
46
+
47
+ @Test
48
+ fun `extension has correct default values`() {
49
+ // Given
50
+ val project = ProjectBuilder.builder().build()
51
+ project.pluginManager.apply(GraniteRootProjectPlugin::class.java)
52
+
53
+ // When
54
+ val extension = project.extensions.getByType(GraniteRootExtension::class.java)
55
+
56
+ // Then
57
+ assertEquals("com.facebook.react", extension.reactGroup.get())
58
+ assertEquals("com.facebook.hermes", extension.hermesGroup.get())
59
+ assertEquals(project.file("node_modules"), extension.nodeModulesDir.get())
60
+ // reactNativeVersion and hermesVersion should not have conventions (are not present)
61
+ assertTrue(!extension.reactNativeVersion.isPresent)
62
+ assertTrue(!extension.hermesVersion.isPresent)
63
+ }
64
+
65
+ @Test
66
+ fun `plugin fails when applied to non-root project`() {
67
+ // Given
68
+ val rootProject = ProjectBuilder.builder()
69
+ .withName("root")
70
+ .build()
71
+ val subProject = ProjectBuilder.builder()
72
+ .withName("sub")
73
+ .withParent(rootProject)
74
+ .build()
75
+
76
+ // When/Then
77
+ // Gradle wraps exceptions in PluginApplicationException
78
+ val exception = assertThrows<org.gradle.api.internal.plugins.PluginApplicationException> {
79
+ subProject.pluginManager.apply(GraniteRootProjectPlugin::class.java)
80
+ }
81
+
82
+ // Verify the cause is our expected IllegalStateException
83
+ assertTrue(exception.cause is IllegalStateException)
84
+ assertTrue(exception.cause!!.message!!.contains("can only be applied to the root project"))
85
+ assertTrue(exception.cause!!.message!!.contains("Current project: :sub"))
86
+ }
87
+ }
@@ -0,0 +1,115 @@
1
+ package run.granite.gradle.config
2
+
3
+ import com.android.build.gradle.LibraryExtension
4
+ import org.gradle.api.Project
5
+ import org.gradle.testfixtures.ProjectBuilder
6
+ import org.junit.jupiter.api.BeforeEach
7
+ import org.junit.jupiter.api.Test
8
+ import run.granite.gradle.GraniteExtension
9
+ import kotlin.test.assertNotNull
10
+ import kotlin.test.assertTrue
11
+
12
+ /**
13
+ * Unit tests for BuildConfigConfigurator.
14
+ *
15
+ * Tests the configuration of Android BuildConfig field generation for
16
+ * feature flags and React Native configuration.
17
+ */
18
+ class BuildConfigConfiguratorTest {
19
+
20
+ private lateinit var project: Project
21
+ private lateinit var extension: GraniteExtension
22
+ private lateinit var androidExtension: LibraryExtension
23
+ private lateinit var configurator: BuildConfigConfigurator
24
+
25
+ @BeforeEach
26
+ fun setup() {
27
+ project = ProjectBuilder.builder().build()
28
+ project.pluginManager.apply("com.android.library")
29
+
30
+ extension = project.extensions.create("granite", GraniteExtension::class.java, project)
31
+ androidExtension = project.extensions.getByType(LibraryExtension::class.java)
32
+
33
+ configurator = BuildConfigConfigurator(project, extension)
34
+ }
35
+
36
+ @Test
37
+ fun `configurator is created successfully`() {
38
+ assertNotNull(configurator, "BuildConfigConfigurator should be created")
39
+ }
40
+
41
+ @Test
42
+ fun `configure method executes without errors`() {
43
+ // Hermes is always enabled
44
+ extension.reactNativeVersion.set("0.81.0")
45
+ extension.bundleAssetName.set("index.android")
46
+
47
+ // Should not throw
48
+ configurator.configure(androidExtension)
49
+ }
50
+
51
+ @Test
52
+ fun `buildConfig feature is enabled`() {
53
+ // Hermes is always enabled
54
+ extension.reactNativeVersion.set("0.81.0")
55
+ extension.bundleAssetName.set("index.android")
56
+
57
+ configurator.configure(androidExtension)
58
+
59
+ // Verify BuildConfig is enabled
60
+ assertTrue(
61
+ androidExtension.buildFeatures.buildConfig == true,
62
+ "BuildConfig feature should be enabled",
63
+ )
64
+ }
65
+
66
+ @Test
67
+ fun `configure works with default extension values`() {
68
+ // Should not throw with default values
69
+ configurator.configure(androidExtension)
70
+
71
+ assertTrue(
72
+ androidExtension.buildFeatures.buildConfig == true,
73
+ "BuildConfig feature should be enabled even with defaults",
74
+ )
75
+ }
76
+
77
+ // Test removed: Hermes is now always enabled, cannot be disabled
78
+
79
+ @Test
80
+ fun `configure works with new architecture enabled`() {
81
+ // Both Hermes and New Architecture are always enabled
82
+ extension.reactNativeVersion.set("0.81.0")
83
+ extension.bundleAssetName.set("index.android")
84
+
85
+ // Should not throw
86
+ configurator.configure(androidExtension)
87
+ }
88
+
89
+ @Test
90
+ fun `configure can be called multiple times`() {
91
+ // Hermes is always enabled
92
+ extension.reactNativeVersion.set("0.81.0")
93
+ extension.bundleAssetName.set("index.android")
94
+
95
+ // Should not throw when called multiple times
96
+ configurator.configure(androidExtension)
97
+ configurator.configure(androidExtension)
98
+ }
99
+
100
+ @Test
101
+ fun `configure with different react native versions`() {
102
+ extension.reactNativeVersion.set("0.82.0")
103
+
104
+ // Should not throw
105
+ configurator.configure(androidExtension)
106
+ }
107
+
108
+ @Test
109
+ fun `configure with custom bundle asset name`() {
110
+ extension.bundleAssetName.set("custom.bundle")
111
+
112
+ // Should not throw
113
+ configurator.configure(androidExtension)
114
+ }
115
+ }