@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,338 @@
1
+ package run.granite.gradle.config
2
+
3
+ import org.junit.jupiter.api.Test
4
+ import org.junit.jupiter.api.io.TempDir
5
+ import java.io.File
6
+ import kotlin.test.assertEquals
7
+ import kotlin.test.assertFalse
8
+ import kotlin.test.assertNotNull
9
+ import kotlin.test.assertTrue
10
+
11
+ /**
12
+ * Unit tests for DependencyConfigurator.
13
+ *
14
+ * Tests the dependency substitution logic that replaces deprecated React Native artifacts
15
+ * with new ones and enforces version consistency across the project.
16
+ *
17
+ * Note: Custom Maven group tests are excluded (no real use case, official RN always uses com.facebook.react)
18
+ */
19
+ class DependencyConfiguratorTest {
20
+
21
+ @TempDir
22
+ lateinit var tempDir: File
23
+
24
+ @Test
25
+ fun `getDependencySubstitutions returns base substitutions for default group`() {
26
+ // Given
27
+ val version = "0.81.1"
28
+ val group = "com.facebook.react"
29
+
30
+ // When
31
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(version, group)
32
+
33
+ // Then
34
+ // When using default group, only react-native and hermes-engine substitutions are needed
35
+ assertEquals(2, substitutions.size)
36
+
37
+ // react-native -> react-android substitution
38
+ val reactSubstitution = substitutions.find { it.first == "com.facebook.react:react-native" }
39
+ assertEquals("com.facebook.react:react-android:0.81.1", reactSubstitution?.second)
40
+ assertTrue(reactSubstitution?.third?.contains("deprecated") == true)
41
+
42
+ // hermes-engine -> hermes-android substitution
43
+ val hermesSubstitution = substitutions.find { it.first == "com.facebook.react:hermes-engine" }
44
+ assertEquals("com.facebook.react:hermes-android:0.81.1", hermesSubstitution?.second)
45
+ assertTrue(hermesSubstitution?.third?.contains("deprecated") == true)
46
+ }
47
+
48
+ @Test
49
+ fun `getDependencySubstitutions handles SNAPSHOT versions`() {
50
+ // Given
51
+ val version = "0.0.0-20250123-1234-abc123-SNAPSHOT"
52
+ val group = "com.facebook.react"
53
+
54
+ // When
55
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(version, group)
56
+
57
+ // Then
58
+ assertEquals(2, substitutions.size)
59
+
60
+ val reactSubstitution = substitutions.find { it.first == "com.facebook.react:react-native" }
61
+ assertEquals("com.facebook.react:react-android:0.0.0-20250123-1234-abc123-SNAPSHOT", reactSubstitution?.second)
62
+
63
+ val hermesSubstitution = substitutions.find { it.first == "com.facebook.react:hermes-engine" }
64
+ assertEquals("com.facebook.react:hermes-android:0.0.0-20250123-1234-abc123-SNAPSHOT", hermesSubstitution?.second)
65
+ }
66
+
67
+ @Test
68
+ fun `getDependencySubstitutions includes reason for each substitution`() {
69
+ // Given
70
+ val version = "0.81.1"
71
+ val group = "com.facebook.react"
72
+
73
+ // When
74
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(version, group)
75
+
76
+ // Then
77
+ // All substitution rules should include a reason (third = reason)
78
+ assertTrue(substitutions.all { it.third.isNotBlank() })
79
+ }
80
+
81
+ @Test
82
+ fun `getDependencySubstitutions handles stable release versions`() {
83
+ // Given
84
+ val version = "0.81.6"
85
+ val group = "com.facebook.react"
86
+
87
+ // When
88
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(version, group)
89
+
90
+ // Then
91
+ assertEquals(2, substitutions.size)
92
+
93
+ val reactSubstitution = substitutions.find { it.first == "com.facebook.react:react-native" }
94
+ assertEquals("com.facebook.react:react-android:0.81.6", reactSubstitution?.second)
95
+ }
96
+
97
+ @Test
98
+ fun `getDependencySubstitutions handles rc versions`() {
99
+ // Given
100
+ val version = "0.82.0-rc.0"
101
+ val group = "com.facebook.react"
102
+
103
+ // When
104
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(version, group)
105
+
106
+ // Then
107
+ assertEquals(2, substitutions.size)
108
+
109
+ val reactSubstitution = substitutions.find { it.first == "com.facebook.react:react-native" }
110
+ assertEquals("com.facebook.react:react-android:0.82.0-rc.0", reactSubstitution?.second)
111
+ }
112
+
113
+ // === DependencyCoordinates-based tests ===
114
+
115
+ @Test
116
+ fun `getDependencySubstitutions with coordinates includes hermes-android to new group substitution`() {
117
+ // Given - RN 0.84 scenario
118
+ val coordinates = DependencyCoordinates(
119
+ reactVersion = "0.84.0",
120
+ hermesVersion = "0.15.0",
121
+ hermesV1Version = "250829098.0.6",
122
+ )
123
+
124
+ // When
125
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
126
+
127
+ // Then
128
+ // Verify new substitution rules
129
+ val hermesAndroidSubstitution = substitutions.find {
130
+ it.first == "com.facebook.react:hermes-android"
131
+ }
132
+ assertNotNull(hermesAndroidSubstitution)
133
+ assertEquals("com.facebook.hermes:hermes-android:250829098.0.6", hermesAndroidSubstitution.second)
134
+ assertTrue(hermesAndroidSubstitution.third.contains("moved"))
135
+ }
136
+
137
+ @Test
138
+ fun `getDependencySubstitutions with coordinates uses V1 hermes version`() {
139
+ // Given
140
+ val coordinates = DependencyCoordinates(
141
+ reactVersion = "0.84.0",
142
+ hermesVersion = "0.15.0",
143
+ hermesV1Version = "250829098.0.6",
144
+ )
145
+
146
+ // When
147
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
148
+
149
+ // Then
150
+ val hermesEngineSubstitution = substitutions.find {
151
+ it.first == "com.facebook.react:hermes-engine"
152
+ }
153
+ // V1 version should be used
154
+ assertEquals("com.facebook.hermes:hermes-android:250829098.0.6", hermesEngineSubstitution?.second)
155
+ }
156
+
157
+ @Test
158
+ fun `getDependencySubstitutions with coordinates falls back to classic hermes when V1 empty`() {
159
+ // Given
160
+ val coordinates = DependencyCoordinates(
161
+ reactVersion = "0.84.0",
162
+ hermesVersion = "0.15.0",
163
+ hermesV1Version = "", // No V1
164
+ )
165
+
166
+ // When
167
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
168
+
169
+ // Then
170
+ val hermesEngineSubstitution = substitutions.find {
171
+ it.first == "com.facebook.react:hermes-engine"
172
+ }
173
+ assertEquals("com.facebook.hermes:hermes-android:0.15.0", hermesEngineSubstitution?.second)
174
+ }
175
+
176
+ @Test
177
+ fun `getDependencySubstitutions with coordinates returns 3 base substitutions for default groups`() {
178
+ // Given
179
+ val coordinates = DependencyCoordinates(
180
+ reactVersion = "0.84.0",
181
+ hermesVersion = "0.15.0",
182
+ hermesV1Version = "250829098.0.6",
183
+ )
184
+
185
+ // When
186
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
187
+
188
+ // Then
189
+ assertEquals(3, substitutions.size) // react-native, hermes-engine, hermes-android
190
+ }
191
+
192
+ @Test
193
+ fun `getDependencySubstitutions with custom react group adds react-android substitution`() {
194
+ // Given
195
+ val coordinates = DependencyCoordinates(
196
+ reactVersion = "0.84.0",
197
+ hermesVersion = "0.15.0",
198
+ hermesV1Version = "250829098.0.6",
199
+ reactGroup = "io.github.custom",
200
+ )
201
+
202
+ // When
203
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
204
+
205
+ // Then
206
+ assertEquals(4, substitutions.size)
207
+ val reactAndroidSubstitution = substitutions.find {
208
+ it.first == "com.facebook.react:react-android"
209
+ }
210
+ assertEquals("io.github.custom:react-android:0.84.0", reactAndroidSubstitution?.second)
211
+ }
212
+
213
+ @Test
214
+ fun `getDependencySubstitutions with custom hermes group adds hermes-android substitution`() {
215
+ // Given
216
+ val coordinates = DependencyCoordinates(
217
+ reactVersion = "0.84.0",
218
+ hermesVersion = "0.15.0",
219
+ hermesV1Version = "250829098.0.6",
220
+ hermesGroup = "io.github.custom.hermes",
221
+ )
222
+
223
+ // When
224
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
225
+
226
+ // Then
227
+ assertEquals(4, substitutions.size) // base 3 + custom hermes group
228
+ val hermesSubstitution = substitutions.find {
229
+ it.first == "com.facebook.hermes:hermes-android"
230
+ }
231
+ assertEquals("io.github.custom.hermes:hermes-android:250829098.0.6", hermesSubstitution?.second)
232
+ }
233
+
234
+ @Test
235
+ fun `getDependencySubstitutions with both custom groups adds all substitutions`() {
236
+ // Given
237
+ val coordinates = DependencyCoordinates(
238
+ reactVersion = "0.84.0",
239
+ hermesVersion = "0.15.0",
240
+ hermesV1Version = "250829098.0.6",
241
+ reactGroup = "io.github.custom",
242
+ hermesGroup = "io.github.custom.hermes",
243
+ )
244
+
245
+ // When
246
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
247
+
248
+ // Then
249
+ assertEquals(5, substitutions.size) // base 3 + custom react + custom hermes
250
+ }
251
+
252
+ @Test
253
+ fun `getDependencySubstitutions handles Hermes V1 version format correctly`() {
254
+ // Given - Hermes V1 build number format
255
+ val coordinates = DependencyCoordinates(
256
+ reactVersion = "0.84.0",
257
+ hermesVersion = "0.15.0",
258
+ hermesV1Version = "250829098.0.6", // Build number-based
259
+ )
260
+
261
+ // When
262
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
263
+
264
+ // Then
265
+ val allHermesSubstitutions = substitutions.filter {
266
+ it.second.contains("hermes-android")
267
+ }
268
+ assertTrue(allHermesSubstitutions.all { it.second.contains("250829098.0.6") })
269
+ }
270
+
271
+ // === Tests for existing deprecated API (additional) ===
272
+
273
+ @Test
274
+ fun `getDependencySubstitutions with custom group returns 4 substitutions`() {
275
+ // Given
276
+ val version = "0.81.1"
277
+ val group = "io.github.custom"
278
+
279
+ // When
280
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(version, group)
281
+
282
+ // Then
283
+ assertEquals(4, substitutions.size) // base 2 + custom group 2
284
+ }
285
+
286
+ // === Dynamic version substitution tests ===
287
+
288
+ @Test
289
+ fun `substitution rules use version-less module selectors for dynamic version matching`() {
290
+ // Given
291
+ val coordinates = DependencyCoordinates(
292
+ reactVersion = "0.84.0",
293
+ hermesVersion = "0.15.0",
294
+ hermesV1Version = "250829098.0.6",
295
+ )
296
+
297
+ // When
298
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
299
+
300
+ // Then - All source coordinates should be version-less for dynamic version matching
301
+ val reactNativeSubstitution = substitutions.find {
302
+ it.first == "com.facebook.react:react-native"
303
+ }
304
+ assertNotNull(reactNativeSubstitution)
305
+ // Source has no version (group:artifact format without version)
306
+ assertFalse(reactNativeSubstitution.first.contains(":0."))
307
+ assertFalse(reactNativeSubstitution.first.contains(":+"))
308
+ // Target has version
309
+ assertTrue(reactNativeSubstitution.second.contains(":0.84.0"))
310
+ }
311
+
312
+ @Test
313
+ fun `substitution rules match all versions including dynamic versions`() {
314
+ // Given - Scenario where dynamic versions are used
315
+ // When com.facebook.react:react-native:+ is declared
316
+ val coordinates = DependencyCoordinates(
317
+ reactVersion = "0.84.0",
318
+ hermesVersion = "0.15.0",
319
+ hermesV1Version = "250829098.0.6",
320
+ )
321
+
322
+ // When
323
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
324
+
325
+ // Then - Substitution source should be declared without version
326
+ // Gradle's substitute(module("group:artifact")) matches all versions
327
+ // (+, latest.release, 1.+, 0.84.0, etc.)
328
+ for ((source, target, _) in substitutions) {
329
+ // Source is "group:artifact" format (no version)
330
+ val parts = source.split(":")
331
+ assertEquals(2, parts.size, "Source '$source' should be version-less (group:artifact)")
332
+
333
+ // Target is "group:artifact:version" format (with version)
334
+ val targetParts = target.split(":")
335
+ assertEquals(3, targetParts.size, "Target '$target' should have version (group:artifact:version)")
336
+ }
337
+ }
338
+ }
@@ -0,0 +1,205 @@
1
+ package run.granite.gradle.config
2
+
3
+ import org.gradle.api.Project
4
+ import org.gradle.testfixtures.ProjectBuilder
5
+ import org.junit.jupiter.api.AfterEach
6
+ import org.junit.jupiter.api.BeforeEach
7
+ import org.junit.jupiter.api.Test
8
+ import run.granite.gradle.GraniteExtension
9
+ import java.io.File
10
+ import kotlin.test.assertFalse
11
+ import kotlin.test.assertNotNull
12
+ import kotlin.test.assertTrue
13
+
14
+ /**
15
+ * Unit tests for DevServerResourceConfigurator.
16
+ *
17
+ * Tests the generation of Android resources for React Native development server configuration.
18
+ */
19
+ class DevServerResourceConfiguratorTest {
20
+
21
+ private lateinit var project: Project
22
+ private lateinit var extension: GraniteExtension
23
+ private lateinit var configurator: DevServerResourceConfigurator
24
+ private lateinit var testResDir: File
25
+
26
+ @BeforeEach
27
+ fun setup() {
28
+ project = ProjectBuilder.builder().build()
29
+ project.pluginManager.apply("com.android.library")
30
+
31
+ extension = project.extensions.create("granite", GraniteExtension::class.java, project)
32
+ configurator = DevServerResourceConfigurator(project, extension)
33
+
34
+ testResDir = File(project.projectDir, "src/debug/res/values")
35
+ }
36
+
37
+ @AfterEach
38
+ fun cleanup() {
39
+ // Clean up generated test files
40
+ val debugSrcDir = File(project.projectDir, "src/debug")
41
+ if (debugSrcDir.exists()) {
42
+ debugSrcDir.deleteRecursively()
43
+ }
44
+ }
45
+
46
+ @Test
47
+ fun `configurator is created successfully`() {
48
+ assertNotNull(configurator, "DevServerResourceConfigurator should be created")
49
+ }
50
+
51
+ @Test
52
+ fun `configure does not generate files when dev server not configured`() {
53
+ // Don't set devServerHost or devServerPort
54
+ configurator.configure()
55
+
56
+ // Verify no resources were generated
57
+ assertFalse(
58
+ testResDir.exists(),
59
+ "Resource directory should not be created when dev server is not configured",
60
+ )
61
+ }
62
+
63
+ @Test
64
+ fun `configure generates strings xml when host is set`() {
65
+ extension.devServerHost.set("localhost")
66
+
67
+ configurator.configure()
68
+
69
+ val stringsFile = File(testResDir, "strings.xml")
70
+ assertTrue(stringsFile.exists(), "strings.xml should be generated")
71
+ assertTrue(
72
+ stringsFile.readText().contains("react_native_dev_server_host"),
73
+ "strings.xml should contain dev server host",
74
+ )
75
+ assertTrue(
76
+ stringsFile.readText().contains("localhost"),
77
+ "strings.xml should contain the configured host",
78
+ )
79
+ }
80
+
81
+ @Test
82
+ fun `configure generates integers xml when port is set`() {
83
+ extension.devServerPort.set(8081)
84
+
85
+ configurator.configure()
86
+
87
+ val integersFile = File(testResDir, "integers.xml")
88
+ assertTrue(integersFile.exists(), "integers.xml should be generated")
89
+ assertTrue(
90
+ integersFile.readText().contains("react_native_dev_server_port"),
91
+ "integers.xml should contain dev server port",
92
+ )
93
+ assertTrue(
94
+ integersFile.readText().contains("8081"),
95
+ "integers.xml should contain the configured port",
96
+ )
97
+ }
98
+
99
+ @Test
100
+ fun `configure generates both strings and integers when both are set`() {
101
+ extension.devServerHost.set("10.0.2.2")
102
+ extension.devServerPort.set(8082)
103
+
104
+ configurator.configure()
105
+
106
+ val stringsFile = File(testResDir, "strings.xml")
107
+ val integersFile = File(testResDir, "integers.xml")
108
+
109
+ assertTrue(stringsFile.exists(), "strings.xml should be generated")
110
+ assertTrue(integersFile.exists(), "integers.xml should be generated")
111
+
112
+ assertTrue(
113
+ stringsFile.readText().contains("10.0.2.2"),
114
+ "strings.xml should contain the configured host",
115
+ )
116
+ assertTrue(
117
+ integersFile.readText().contains("8082"),
118
+ "integers.xml should contain the configured port",
119
+ )
120
+ }
121
+
122
+ @Test
123
+ fun `configure generates valid xml format for strings`() {
124
+ extension.devServerHost.set("192.168.1.100")
125
+
126
+ configurator.configure()
127
+
128
+ val stringsFile = File(testResDir, "strings.xml")
129
+ val content = stringsFile.readText()
130
+
131
+ assertTrue(content.contains("<?xml version=\"1.0\" encoding=\"utf-8\"?>"), "Should have XML declaration")
132
+ assertTrue(content.contains("<resources>"), "Should have resources tag")
133
+ assertTrue(content.contains("</resources>"), "Should close resources tag")
134
+ assertTrue(content.contains("translatable=\"false\""), "Should mark as non-translatable")
135
+ }
136
+
137
+ @Test
138
+ fun `configure generates valid xml format for integers`() {
139
+ extension.devServerPort.set(9999)
140
+
141
+ configurator.configure()
142
+
143
+ val integersFile = File(testResDir, "integers.xml")
144
+ val content = integersFile.readText()
145
+
146
+ assertTrue(content.contains("<?xml version=\"1.0\" encoding=\"utf-8\"?>"), "Should have XML declaration")
147
+ assertTrue(content.contains("<resources>"), "Should have resources tag")
148
+ assertTrue(content.contains("</resources>"), "Should close resources tag")
149
+ assertTrue(content.contains("<integer name=\"react_native_dev_server_port\">"), "Should have integer tag")
150
+ }
151
+
152
+ @Test
153
+ fun `configure can be called multiple times`() {
154
+ extension.devServerHost.set("localhost")
155
+ extension.devServerPort.set(8081)
156
+
157
+ // Should not throw when called multiple times
158
+ configurator.configure()
159
+ configurator.configure()
160
+
161
+ // Files should still exist
162
+ assertTrue(File(testResDir, "strings.xml").exists())
163
+ assertTrue(File(testResDir, "integers.xml").exists())
164
+ }
165
+
166
+ @Test
167
+ fun `configure handles emulator android host`() {
168
+ extension.devServerHost.set("10.0.2.2") // Standard Android emulator host
169
+
170
+ configurator.configure()
171
+
172
+ val stringsFile = File(testResDir, "strings.xml")
173
+ assertTrue(
174
+ stringsFile.readText().contains("10.0.2.2"),
175
+ "Should handle Android emulator host address",
176
+ )
177
+ }
178
+
179
+ @Test
180
+ fun `configure handles custom ports`() {
181
+ extension.devServerPort.set(19000) // Custom Expo port
182
+
183
+ configurator.configure()
184
+
185
+ val integersFile = File(testResDir, "integers.xml")
186
+ assertTrue(
187
+ integersFile.readText().contains("19000"),
188
+ "Should handle custom port numbers",
189
+ )
190
+ }
191
+
192
+ @Test
193
+ fun `configure creates directory structure if not exists`() {
194
+ // Ensure directory doesn't exist
195
+ val debugSrcDir = File(project.projectDir, "src/debug")
196
+ if (debugSrcDir.exists()) {
197
+ debugSrcDir.deleteRecursively()
198
+ }
199
+
200
+ extension.devServerHost.set("localhost")
201
+ configurator.configure()
202
+
203
+ assertTrue(testResDir.exists(), "Should create directory structure")
204
+ }
205
+ }
@@ -0,0 +1,131 @@
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 ResourceConfigurator.
14
+ *
15
+ * Tests the configuration of Android resource packaging options,
16
+ * particularly bundle compression settings.
17
+ */
18
+ class ResourceConfiguratorTest {
19
+
20
+ private lateinit var project: Project
21
+ private lateinit var extension: GraniteExtension
22
+ private lateinit var androidExtension: LibraryExtension
23
+ private lateinit var configurator: ResourceConfigurator
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 = ResourceConfigurator(project, extension)
34
+ }
35
+
36
+ @Test
37
+ fun `configurator is created successfully`() {
38
+ assertNotNull(configurator, "ResourceConfigurator should be created")
39
+ }
40
+
41
+ @Test
42
+ fun `configure method executes without errors when compression is enabled`() {
43
+ extension.bundleCompressionEnabled.set(true)
44
+
45
+ // Should not throw
46
+ configurator.configure(androidExtension)
47
+ }
48
+
49
+ @Test
50
+ fun `configure method executes without errors when compression is disabled`() {
51
+ extension.bundleCompressionEnabled.set(false)
52
+
53
+ // Should not throw
54
+ configurator.configure(androidExtension)
55
+ }
56
+
57
+ @Test
58
+ fun `packaging configuration is applied to android extension`() {
59
+ extension.bundleCompressionEnabled.set(false)
60
+
61
+ configurator.configure(androidExtension)
62
+
63
+ // Verify packaging configuration exists
64
+ assertNotNull(androidExtension.packaging, "Packaging should be configured")
65
+ }
66
+
67
+ @Test
68
+ fun `map files are always excluded from compression`() {
69
+ extension.bundleCompressionEnabled.set(true)
70
+
71
+ configurator.configure(androidExtension)
72
+
73
+ // Verify packaging resources exist
74
+ assertNotNull(androidExtension.packaging.resources, "Packaging resources should be configured")
75
+ }
76
+
77
+ @Test
78
+ fun `bundle compression disabled excludes bundle and hbc files`() {
79
+ extension.bundleCompressionEnabled.set(false)
80
+
81
+ configurator.configure(androidExtension)
82
+
83
+ val excludes = androidExtension.packaging.resources.excludes
84
+
85
+ // When compression is disabled, bundle files should be excluded from APK compression
86
+ assertTrue(
87
+ excludes.any { it.contains(".bundle") },
88
+ "Bundle files should be excluded when compression is disabled",
89
+ )
90
+ assertTrue(
91
+ excludes.any { it.contains(".hbc") },
92
+ "Hermes bytecode files should be excluded when compression is disabled",
93
+ )
94
+ assertTrue(
95
+ excludes.any { it.contains(".map") },
96
+ "Map files should always be excluded",
97
+ )
98
+ }
99
+
100
+ @Test
101
+ fun `bundle compression enabled only excludes map files`() {
102
+ extension.bundleCompressionEnabled.set(true)
103
+
104
+ configurator.configure(androidExtension)
105
+
106
+ val excludes = androidExtension.packaging.resources.excludes
107
+
108
+ // When compression is enabled, only map files should be excluded
109
+ assertTrue(
110
+ excludes.any { it.contains(".map") },
111
+ "Map files should always be excluded",
112
+ )
113
+ }
114
+
115
+ @Test
116
+ fun `configurator can be called multiple times`() {
117
+ extension.bundleCompressionEnabled.set(false)
118
+
119
+ // Should not throw when called multiple times
120
+ configurator.configure(androidExtension)
121
+ configurator.configure(androidExtension)
122
+ }
123
+
124
+ @Test
125
+ fun `configurator works with default extension values`() {
126
+ // Don't set any values explicitly, use defaults
127
+
128
+ // Should not throw with default values
129
+ configurator.configure(androidExtension)
130
+ }
131
+ }