Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { screen } from '@react-native-harness/ui'
import { createRef } from 'react'
import { type LayoutChangeEvent, StyleSheet } from 'react-native'
import {
type LayoutChangeEvent,
PixelRatio,
Platform,
StyleSheet,
View,
} from 'react-native'
import {
afterEach,
beforeAll,
Expand Down Expand Up @@ -55,6 +62,8 @@ function expectPreviewGeometry(camera: CameraRef, layout: Layout) {
expect(meteringPoint.normalizedY).toBeLessThanOrEqual(1)
}

const SPACER_HEIGHT = 200

describe('VisionCamera - Camera View', () => {
let backDevice: CameraDevice

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

it('preserves preview position when laid out below a sibling spacer (issue #3897)', async (context) => {
if (Platform.OS !== 'android') {
return context.skip(
'Preview spacer-positioned layout: Android-only regression',
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test should run on all platforms. No need at making the test narrower - what benefit would that provide us?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial Github issue stated the bug happened on Android.

)
}

const started = deferred()
const previewStarted = deferred()
let sessionError: Error | undefined
const onError = (error: Error) => {
sessionError = error
started.reject(error)
previewStarted.reject(error)
}

await render(
<View style={styles.spacerRoot}>
<View style={styles.redSpacer} />
<Camera
device={backDevice}
isActive={true}
style={styles.spacerCamera}
onStarted={started.resolve}
onPreviewStarted={previewStarted.resolve}
onError={onError}
/>
</View>,
)

await withTimeout(started.promise, 15_000, 'spacer Camera onStarted')
await withTimeout(
previewStarted.promise,
15_000,
'spacer Camera onPreviewStarted',
)
expect(sessionError).toBe(undefined)

// Yield two frames so the fitter's layout commit reaches the render
// pipeline before we snapshot. Matches the harness's own
// waitForNativeViewHierarchy pattern.
await new Promise<void>((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
)

const png = await screen.screenshot()
if (png == null) throw new Error('screen.screenshot returned null')
// ScreenshotResult.width/height come back as 0, so parse the PNG IHDR
// (width @ byte 16, height @ byte 20, big-endian).
const dv = new DataView(
png.data.buffer,
png.data.byteOffset,
png.data.byteLength,
)
const fileW = dv.getUint32(16, false)
const fileH = dv.getUint32(20, false)

// Compare only the spacer's pixel band; the rest is live camera feed
// that legitimately varies between runs.
const spacerPx = SPACER_HEIGHT * PixelRatio.get()
await expect({
data: png.data,
width: fileW,
height: fileH,
}).toMatchImageSnapshot({
name: 'spacer-region-stays-red',
failureThresholdType: 'percent',
failureThreshold: 0.02,
ignoreRegions: [
{ x: 0, y: spacerPx, width: fileW, height: fileH - spacerPx },
],
})
})

it('starts the high-level Camera preview and exposes preview/controller ref methods', async () => {
const cameraRef = createRef<CameraRef>()
const layout = deferred<Layout>()
Expand Down Expand Up @@ -492,3 +575,17 @@ describe('VisionCamera - Camera View', () => {
expect(sessionError).toBe(undefined)
})
})

const styles = StyleSheet.create({
spacerRoot: {
flex: 1,
backgroundColor: 'black',
},
redSpacer: {
height: SPACER_HEIGHT,
backgroundColor: 'red',
},
spacerCamera: {
flex: 1,
},
})
32 changes: 29 additions & 3 deletions apps/simple-camera/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ PODS:
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMSessionFetcher/Core (3.5.0)
- HarnessUI (1.3.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- hermes-engine (250829098.0.10):
- hermes-engine/Pre-built (= 250829098.0.10)
- hermes-engine/Pre-built (250829098.0.10)
Expand Down Expand Up @@ -2454,6 +2476,7 @@ PODS:

DEPENDENCIES:
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
- "HarnessUI (from `../../../node_modules/@react-native-harness/ui`)"
- hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- NitroImage (from `../../../node_modules/react-native-nitro-image`)
- NitroModules (from `../../../node_modules/react-native-nitro-modules`)
Expand Down Expand Up @@ -2564,6 +2587,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
FBLazyVector:
:path: "../../../node_modules/react-native/Libraries/FBLazyVector"
HarnessUI:
:path: "../../../node_modules/@react-native-harness/ui"
hermes-engine:
:podspec: "../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-v250829098.0.10
Expand Down Expand Up @@ -2755,7 +2780,8 @@ SPEC CHECKSUMS:
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
hermes-engine: 691752261227b9de03faf08561f47f2e2b5b52e9
HarnessUI: f439596aec93ff76765451f834c93f966d54e53e
hermes-engine: e0e7de0611a16bc91b9cc6f8ac5a1c84600c06c5
MLImage: 0de5c6c2bf9e93b80ef752e2797f0836f03b58c0
MLKitBarcodeScanning: 39de223e7b1b8a8fbf10816a536dd292d8a39343
MLKitCommon: 47d47b50a031d00db62f1b0efe5a1d8b09a3b2e6
Expand All @@ -2772,7 +2798,7 @@ SPEC CHECKSUMS:
React: e2dc35338068bbd299c66f043ae0d7f25de8499e
React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48
React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0
React-Core-prebuilt: 0e292a9b21c7ff7a2d50d7db3141213b15222a2a
React-Core-prebuilt: b96a5ee807b097c569912aa525f09e673e2a6f6f
React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146
React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716
React-debug: 92944dc4d89f56d640e75498266cbde557a48189
Expand Down Expand Up @@ -2840,7 +2866,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2
ReactCodegen: dfe41c3f92bdf782bcf9b2c9d7c12cb9cfd82cb7
ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f
ReactNativeDependencies: 2eb4b828d36504e50dff4d7a9c3808068bbd4713
ReactNativeDependencies: 98fd3ddae59aabdb73ee38b20b6babf126044243
RNGestureHandler: ed28fea435eee1ea629ed8372c2e98138a73a472
RNReanimated: 0f1a1b11eddb9a66ebf1c1a70d0cdd230f4bb10f
RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9
Expand Down
1 change: 1 addition & 0 deletions apps/simple-camera/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@react-native-community/cli-platform-ios": "20.1.3",
"@react-native-harness/platform-android": "1.4.0-rc.1",
"@react-native-harness/platform-apple": "1.4.0-rc.1",
"@react-native-harness/ui": "1.4.0-rc.1",
"@react-native/babel-preset": "0.85.3",
"@react-native/metro-config": "0.85.3",
"@react-native/typescript-config": "0.85.3",
Expand Down
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading