Skip to content

Commit 500bcee

Browse files
committed
refactor: use built-in conditional compilation
1 parent 098abca commit 500bcee

23 files changed

Lines changed: 256 additions & 250 deletions

apps/playground/ios/Podfile.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ PODS:
55
- FBLazyVector (0.82.1)
66
- fmt (11.0.2)
77
- glog (0.3.5)
8-
- Harness (1.0.0-alpha.20):
8+
- HarnessUI (1.0.0-alpha.20):
99
- boost
1010
- DoubleConversion
1111
- fast_float
@@ -2359,7 +2359,7 @@ DEPENDENCIES:
23592359
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
23602360
- fmt (from `../../../node_modules/react-native/third-party-podspecs/fmt.podspec`)
23612361
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
2362-
- "Harness (from `../node_modules/@react-native-harness/runtime`)"
2362+
- "HarnessUI (from `../node_modules/@react-native-harness/ui`)"
23632363
- hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
23642364
- RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
23652365
- RCTDeprecation (from `../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
@@ -2447,8 +2447,8 @@ EXTERNAL SOURCES:
24472447
:podspec: "../../../node_modules/react-native/third-party-podspecs/fmt.podspec"
24482448
glog:
24492449
:podspec: "../../../node_modules/react-native/third-party-podspecs/glog.podspec"
2450-
Harness:
2451-
:path: "../node_modules/@react-native-harness/runtime"
2450+
HarnessUI:
2451+
:path: "../node_modules/@react-native-harness/ui"
24522452
hermes-engine:
24532453
:podspec: "../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
24542454
:tag: hermes-2025-09-01-RNv0.82.0-265ef62ff3eb7289d17e366664ac0da82303e101
@@ -2592,7 +2592,7 @@ SPEC CHECKSUMS:
25922592
FBLazyVector: 0aa6183b9afe3c31fc65b5d1eeef1f3c19b63bfa
25932593
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
25942594
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
2595-
Harness: f4ead6c1b60fa9e99c2dcf5df315588a8bee5f56
2595+
HarnessUI: 2957b94c9c4a7e6e54b636229f4aa5e3809936bf
25962596
hermes-engine: 273e30e7fb618279934b0b95ffab60ecedb7acf5
25972597
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
25982598
RCTDeprecation: f17e2ebc07876ca9ab8eb6e4b0a4e4647497ae3a

apps/playground/react-native.config.js

Lines changed: 0 additions & 14 deletions
This file was deleted.

apps/playground/tsconfig.app.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
{
4646
"path": "../../packages/jest/tsconfig.lib.json"
4747
},
48+
{
49+
"path": "../../packages/ui/tsconfig.lib.json"
50+
},
4851
{
4952
"path": "../../packages/runtime/tsconfig.lib.json"
5053
},

apps/playground/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
{
1616
"path": "../../packages/jest"
1717
},
18+
{
19+
"path": "../../packages/ui"
20+
},
1821
{
1922
"path": "../../packages/runtime"
2023
},

packages/ui/HarnessUI.podspec

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,5 @@ Pod::Spec.new do |s|
1616
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
1717
s.private_header_files = "ios/**/*.h"
1818

19-
# Conditional compilation - exclude opposite implementation based on build configuration
20-
# Debug builds: Exclude release stubs, include full implementation with private APIs
21-
# Release builds: Exclude debug implementation, include only no-op stubs
22-
s.pod_target_xcconfig = {
23-
'EXCLUDED_SOURCE_FILE_NAMES[config=Debug]' => '$(PODS_TARGET_SRCROOT)/ios/HarnessUIRelease.mm $(PODS_TARGET_SRCROOT)/ios/ViewQueryHelperRelease.mm',
24-
'EXCLUDED_SOURCE_FILE_NAMES[config=Release]' => '$(PODS_TARGET_SRCROOT)/ios/HarnessUIDebug.mm $(PODS_TARGET_SRCROOT)/ios/ViewQueryHelperDebug.mm'
25-
}
26-
2719
install_modules_dependencies(s)
2820
end

packages/ui/android/src/main/java/com/harnessui/HarnessUIModule.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@ package com.harnessui
22

33
import com.facebook.react.bridge.Promise
44
import com.facebook.react.bridge.ReactApplicationContext
5+
import com.facebook.react.bridge.ReadableMap
56
import com.facebook.react.bridge.WritableArray
67
import com.facebook.react.bridge.WritableMap
78
import com.facebook.react.module.annotations.ReactModule
89

910
/**
1011
* TurboModule for HarnessUI - provides UI testing capabilities.
11-
* Delegates to UIHelper interface which has different implementations:
12-
* - Debug: Full functionality (UIHelperImpl in src/debug/)
13-
* - Release: No-op stubs (UIHelperImpl in src/release/)
14-
*
15-
* Gradle automatically links the correct implementation based on build type.
12+
* Includes touch simulation and view querying.
1613
*/
1714
@ReactModule(name = HarnessUIModule.NAME)
1815
class HarnessUIModule(reactContext: ReactApplicationContext) :
@@ -22,8 +19,7 @@ class HarnessUIModule(reactContext: ReactApplicationContext) :
2219
const val NAME = "HarnessUI"
2320
}
2421

25-
// Gradle automatically links correct implementation (debug vs release)
26-
private val helper: UIHelper = UIHelperImpl(reactContext)
22+
private val helper = UIHelperImpl(reactContext)
2723

2824
override fun getName(): String = NAME
2925

@@ -42,4 +38,8 @@ class HarnessUIModule(reactContext: ReactApplicationContext) :
4238

4339
override fun queryAllByAccessibilityLabel(label: String): WritableArray =
4440
helper.queryAllByAccessibilityLabel(label)
41+
42+
override fun captureScreenshot(bounds: ReadableMap?, promise: Promise) {
43+
helper.captureScreenshot(bounds, promise)
44+
}
4545
}

packages/ui/android/src/main/java/com/harnessui/UIHelper.kt

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/ui/android/src/debug/java/com/harnessui/UIHelperImpl.kt renamed to packages/ui/android/src/main/java/com/harnessui/UIHelperImpl.kt

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.harnessui
22

3+
import android.graphics.Bitmap
4+
import android.graphics.Canvas
35
import android.os.Handler
46
import android.os.Looper
57
import android.os.SystemClock
@@ -8,17 +10,21 @@ import android.view.MotionEvent
810
import com.facebook.react.bridge.Arguments
911
import com.facebook.react.bridge.Promise
1012
import com.facebook.react.bridge.ReactApplicationContext
13+
import com.facebook.react.bridge.ReadableMap
1114
import com.facebook.react.bridge.UiThreadUtil
1215
import com.facebook.react.bridge.WritableArray
1316
import com.facebook.react.bridge.WritableMap
17+
import com.facebook.react.bridge.WritableNativeArray
18+
import java.io.ByteArrayOutputStream
19+
import java.nio.ByteBuffer
1420
import java.util.concurrent.CountDownLatch
1521
import java.util.concurrent.TimeUnit
1622

1723
/**
18-
* Debug implementation of UIHelper with full functionality.
24+
* UI helper implementation for HarnessUI.
1925
* Includes touch simulation and view querying capabilities.
2026
*/
21-
class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
27+
class UIHelperImpl(private val context: ReactApplicationContext) {
2228

2329
companion object {
2430
private const val TAG = "HarnessUI"
@@ -32,7 +38,7 @@ class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
3238
// Touch Simulation
3339
// =========================================================================
3440

35-
override fun simulateTap(x: Double, y: Double, promise: Promise) {
41+
fun simulateTap(x: Double, y: Double, promise: Promise) {
3642
Log.i(TAG, "simulateTap called with x:$x y:$y")
3743

3844
UiThreadUtil.runOnUiThread {
@@ -81,22 +87,22 @@ class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
8187
// Query API
8288
// =========================================================================
8389

84-
override fun queryByTestId(testId: String): WritableMap? {
90+
fun queryByTestId(testId: String): WritableMap? {
8591
Log.i(TAG, "queryByTestId called with: $testId")
8692
return executeQuery(ViewQueryType.TEST_ID, testId)
8793
}
8894

89-
override fun queryByAccessibilityLabel(label: String): WritableMap? {
95+
fun queryByAccessibilityLabel(label: String): WritableMap? {
9096
Log.i(TAG, "queryByAccessibilityLabel called with: $label")
9197
return executeQuery(ViewQueryType.ACCESSIBILITY_LABEL, label)
9298
}
9399

94-
override fun queryAllByTestId(testId: String): WritableArray {
100+
fun queryAllByTestId(testId: String): WritableArray {
95101
Log.i(TAG, "queryAllByTestId called with: $testId")
96102
return executeQueryAll(ViewQueryType.TEST_ID, testId)
97103
}
98104

99-
override fun queryAllByAccessibilityLabel(label: String): WritableArray {
105+
fun queryAllByAccessibilityLabel(label: String): WritableArray {
100106
Log.i(TAG, "queryAllByAccessibilityLabel called with: $label")
101107
return executeQueryAll(ViewQueryType.ACCESSIBILITY_LABEL, label)
102108
}
@@ -182,4 +188,72 @@ class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
182188
Log.i(TAG, "QueryAll result count: ${result.size()}")
183189
return result
184190
}
191+
192+
// =========================================================================
193+
// Screenshot Capture
194+
// =========================================================================
195+
196+
fun captureScreenshot(bounds: ReadableMap?, promise: Promise) {
197+
Log.i(TAG, "captureScreenshot called")
198+
199+
UiThreadUtil.runOnUiThread {
200+
val activity = context.currentActivity ?: run {
201+
Log.w(TAG, "No current activity")
202+
promise.resolve(null)
203+
return@runOnUiThread
204+
}
205+
206+
val root = activity.window.decorView.rootView
207+
val density = root.resources.displayMetrics.density
208+
209+
try {
210+
// Determine capture dimensions
211+
val captureX: Int
212+
val captureY: Int
213+
val captureWidth: Int
214+
val captureHeight: Int
215+
216+
if (bounds != null) {
217+
// Convert dp to px for bounds
218+
captureX = (bounds.getDouble("x") * density).toInt()
219+
captureY = (bounds.getDouble("y") * density).toInt()
220+
captureWidth = (bounds.getDouble("width") * density).toInt()
221+
captureHeight = (bounds.getDouble("height") * density).toInt()
222+
} else {
223+
captureX = 0
224+
captureY = 0
225+
captureWidth = root.width
226+
captureHeight = root.height
227+
}
228+
229+
// Create bitmap and canvas
230+
val bitmap = Bitmap.createBitmap(captureWidth, captureHeight, Bitmap.Config.ARGB_8888)
231+
val canvas = Canvas(bitmap)
232+
233+
// Translate canvas if capturing a specific region
234+
canvas.translate(-captureX.toFloat(), -captureY.toFloat())
235+
236+
// Draw the view hierarchy
237+
root.draw(canvas)
238+
239+
// Convert to PNG bytes
240+
val outputStream = ByteArrayOutputStream()
241+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
242+
val pngBytes = outputStream.toByteArray()
243+
244+
// Clean up
245+
bitmap.recycle()
246+
247+
// Return as WritableArray (converted to ArrayBuffer by TurboModule bridge)
248+
val byteArray = WritableNativeArray()
249+
for (byte in pngBytes) {
250+
byteArray.pushInt(byte.toInt() and 0xFF)
251+
}
252+
promise.resolve(byteArray)
253+
} catch (e: Exception) {
254+
Log.e(TAG, "Screenshot capture failed", e)
255+
promise.resolve(null)
256+
}
257+
}
258+
}
185259
}

packages/ui/android/src/debug/java/com/harnessui/ViewQueryHelper.kt renamed to packages/ui/android/src/main/java/com/harnessui/ViewQueryHelper.kt

File renamed without changes.

packages/ui/android/src/release/java/com/harnessui/UIHelperImpl.kt

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)