Skip to content

Commit 5ea678c

Browse files
committed
feat: add harness test for issue #3897
1 parent 31e2927 commit 5ea678c

5 files changed

Lines changed: 131 additions & 4 deletions

File tree

13.5 KB
Loading

apps/simple-camera/__tests__/visioncamera.camera-view.harness.tsx

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import { screen } from '@react-native-harness/ui'
12
import { createRef } from 'react'
2-
import { type LayoutChangeEvent, StyleSheet } from 'react-native'
3+
import {
4+
type LayoutChangeEvent,
5+
PixelRatio,
6+
Platform,
7+
StyleSheet,
8+
View,
9+
} from 'react-native'
310
import {
411
afterEach,
512
beforeAll,
@@ -55,6 +62,8 @@ function expectPreviewGeometry(camera: CameraRef, layout: Layout) {
5562
expect(meteringPoint.normalizedY).toBeLessThanOrEqual(1)
5663
}
5764

65+
const SPACER_HEIGHT = 200
66+
5867
describe('VisionCamera - Camera View', () => {
5968
let backDevice: CameraDevice
6069

@@ -72,6 +81,80 @@ describe('VisionCamera - Camera View', () => {
7281
cleanup()
7382
})
7483

84+
it('preserves preview position when laid out below a sibling spacer (issue #3897)', async (context) => {
85+
if (Platform.OS !== 'android') {
86+
return context.skip(
87+
'Preview spacer-positioned layout: Android-only regression',
88+
)
89+
}
90+
91+
const started = deferred()
92+
const previewStarted = deferred()
93+
let sessionError: Error | undefined
94+
const onError = (error: Error) => {
95+
sessionError = error
96+
started.reject(error)
97+
previewStarted.reject(error)
98+
}
99+
100+
await render(
101+
<View style={styles.spacerRoot}>
102+
<View style={styles.redSpacer} />
103+
<Camera
104+
device={backDevice}
105+
isActive={true}
106+
style={styles.spacerCamera}
107+
onStarted={started.resolve}
108+
onPreviewStarted={previewStarted.resolve}
109+
onError={onError}
110+
/>
111+
</View>,
112+
)
113+
114+
await withTimeout(started.promise, 15_000, 'spacer Camera onStarted')
115+
await withTimeout(
116+
previewStarted.promise,
117+
15_000,
118+
'spacer Camera onPreviewStarted',
119+
)
120+
expect(sessionError).toBe(undefined)
121+
122+
// Yield two frames so the fitter's layout commit reaches the render
123+
// pipeline before we snapshot. Matches the harness's own
124+
// waitForNativeViewHierarchy pattern.
125+
await new Promise<void>((resolve) =>
126+
requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
127+
)
128+
129+
const png = await screen.screenshot()
130+
if (png == null) throw new Error('screen.screenshot returned null')
131+
// ScreenshotResult.width/height come back as 0, so parse the PNG IHDR
132+
// (width @ byte 16, height @ byte 20, big-endian).
133+
const dv = new DataView(
134+
png.data.buffer,
135+
png.data.byteOffset,
136+
png.data.byteLength,
137+
)
138+
const fileW = dv.getUint32(16, false)
139+
const fileH = dv.getUint32(20, false)
140+
141+
// Compare only the spacer's pixel band; the rest is live camera feed
142+
// that legitimately varies between runs.
143+
const spacerPx = SPACER_HEIGHT * PixelRatio.get()
144+
await expect({
145+
data: png.data,
146+
width: fileW,
147+
height: fileH,
148+
}).toMatchImageSnapshot({
149+
name: 'spacer-region-stays-red',
150+
failureThresholdType: 'percent',
151+
failureThreshold: 0.02,
152+
ignoreRegions: [
153+
{ x: 0, y: spacerPx, width: fileW, height: fileH - spacerPx },
154+
],
155+
})
156+
})
157+
75158
it('starts the high-level Camera preview and exposes preview/controller ref methods', async () => {
76159
const cameraRef = createRef<CameraRef>()
77160
const layout = deferred<Layout>()
@@ -492,3 +575,17 @@ describe('VisionCamera - Camera View', () => {
492575
expect(sessionError).toBe(undefined)
493576
})
494577
})
578+
579+
const styles = StyleSheet.create({
580+
spacerRoot: {
581+
flex: 1,
582+
backgroundColor: 'black',
583+
},
584+
redSpacer: {
585+
height: SPACER_HEIGHT,
586+
backgroundColor: 'red',
587+
},
588+
spacerCamera: {
589+
flex: 1,
590+
},
591+
})

apps/simple-camera/ios/Podfile.lock

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,28 @@ PODS:
2323
- GoogleUtilities/Logger
2424
- GoogleUtilities/Privacy
2525
- GTMSessionFetcher/Core (3.5.0)
26+
- HarnessUI (1.3.0):
27+
- hermes-engine
28+
- RCTRequired
29+
- RCTTypeSafety
30+
- React-Core
31+
- React-Core-prebuilt
32+
- React-debug
33+
- React-Fabric
34+
- React-featureflags
35+
- React-graphics
36+
- React-ImageManager
37+
- React-jsi
38+
- React-NativeModulesApple
39+
- React-RCTFabric
40+
- React-renderercss
41+
- React-rendererdebug
42+
- React-utils
43+
- ReactCodegen
44+
- ReactCommon/turbomodule/bridging
45+
- ReactCommon/turbomodule/core
46+
- ReactNativeDependencies
47+
- Yoga
2648
- hermes-engine (250829098.0.10):
2749
- hermes-engine/Pre-built (= 250829098.0.10)
2850
- hermes-engine/Pre-built (250829098.0.10)
@@ -2453,6 +2475,7 @@ PODS:
24532475

24542476
DEPENDENCIES:
24552477
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
2478+
- "HarnessUI (from `../../../node_modules/@react-native-harness/ui`)"
24562479
- hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
24572480
- NitroImage (from `../../../node_modules/react-native-nitro-image`)
24582481
- NitroModules (from `../../../node_modules/react-native-nitro-modules`)
@@ -2563,6 +2586,8 @@ SPEC REPOS:
25632586
EXTERNAL SOURCES:
25642587
FBLazyVector:
25652588
:path: "../../../node_modules/react-native/Libraries/FBLazyVector"
2589+
HarnessUI:
2590+
:path: "../../../node_modules/@react-native-harness/ui"
25662591
hermes-engine:
25672592
:podspec: "../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
25682593
:tag: hermes-v250829098.0.10
@@ -2754,7 +2779,8 @@ SPEC CHECKSUMS:
27542779
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
27552780
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
27562781
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
2757-
hermes-engine: 691752261227b9de03faf08561f47f2e2b5b52e9
2782+
HarnessUI: f439596aec93ff76765451f834c93f966d54e53e
2783+
hermes-engine: e0e7de0611a16bc91b9cc6f8ac5a1c84600c06c5
27582784
MLImage: 0de5c6c2bf9e93b80ef752e2797f0836f03b58c0
27592785
MLKitBarcodeScanning: 39de223e7b1b8a8fbf10816a536dd292d8a39343
27602786
MLKitCommon: 47d47b50a031d00db62f1b0efe5a1d8b09a3b2e6
@@ -2771,7 +2797,7 @@ SPEC CHECKSUMS:
27712797
React: e2dc35338068bbd299c66f043ae0d7f25de8499e
27722798
React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48
27732799
React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0
2774-
React-Core-prebuilt: 0e292a9b21c7ff7a2d50d7db3141213b15222a2a
2800+
React-Core-prebuilt: b96a5ee807b097c569912aa525f09e673e2a6f6f
27752801
React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146
27762802
React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716
27772803
React-debug: 92944dc4d89f56d640e75498266cbde557a48189
@@ -2839,7 +2865,7 @@ SPEC CHECKSUMS:
28392865
ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2
28402866
ReactCodegen: dfe41c3f92bdf782bcf9b2c9d7c12cb9cfd82cb7
28412867
ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f
2842-
ReactNativeDependencies: 2eb4b828d36504e50dff4d7a9c3808068bbd4713
2868+
ReactNativeDependencies: 98fd3ddae59aabdb73ee38b20b6babf126044243
28432869
RNGestureHandler: ed28fea435eee1ea629ed8372c2e98138a73a472
28442870
RNReanimated: 0f1a1b11eddb9a66ebf1c1a70d0cdd230f4bb10f
28452871
RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9

apps/simple-camera/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@react-native-community/cli-platform-ios": "20.1.3",
5050
"@react-native-harness/platform-android": "1.3.0",
5151
"@react-native-harness/platform-apple": "1.3.0",
52+
"@react-native-harness/ui": "^1.3.0",
5253
"@react-native/babel-preset": "0.85.3",
5354
"@react-native/metro-config": "0.85.3",
5455
"@react-native/typescript-config": "0.85.3",

bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)