The plugin's on-device proof (Mob.Plugin.SelfTest), run by
mix mob.selftest and mob_ci for every activated plugin.
One native call, no camera session, no UI: camera_stop_preview/0, which
is a no-op while nothing previews.
- iOS: the Objective-C NIF queues the teardown on the plugin's serial
camera queue (dropping a session that does not exist) and answers
:ok. The answer provesmob_camera_nif.mis linked and its NIF table registered; noAVCaptureDeviceis touched, so the simulator, which has no camera, passes the same way a phone does. - Android: the zig NIF calls the static
MobCameraBridge.camera_stop_previewthrough JNI, which clears the requested preview facing, and answers:ok. That proves the zig NIF is linked and the Kotlin bridge registered (nativeRegistercached the class and method IDs). An emulator without a camera passes: CameraX is not involved.{:error, :bridge_not_registered}means the host's plugin bootstrap never calledMobCameraBridge.register()or a method-ID lookup failed;:errormeans the NIF could not attach to the JVM. Both fail.
There is no {:skip, :needs_hardware} branch: the plugin's NIF exposes no
read-only "is there a camera" query, and the only call that would find out
(camera_snap/1, which reports :no_camera) opens a capture session,
which a self-test must not do. Capturing a frame is the feature, not the
proof.
The host stub's nif_not_loaded is a failure. Run it while the host is not
previewing: camera_stop_preview/0 ends a running preview on both
platforms, and on iOS also a frame stream (it drops the shared session).