@symbiote-native/sensors 0.1.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.
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/build/angular/index.d.ts +16 -0
- package/build/angular/index.js +12 -0
- package/build/angular/services/accelerometer.service/index.d.ts +6 -0
- package/build/angular/services/accelerometer.service/index.js +81 -0
- package/build/angular/services/barometer.service/index.d.ts +6 -0
- package/build/angular/services/barometer.service/index.js +81 -0
- package/build/angular/services/device-motion.service/index.d.ts +6 -0
- package/build/angular/services/device-motion.service/index.js +81 -0
- package/build/angular/services/gyroscope.service/index.d.ts +6 -0
- package/build/angular/services/gyroscope.service/index.js +81 -0
- package/build/angular/services/light-sensor.service/index.d.ts +6 -0
- package/build/angular/services/light-sensor.service/index.js +81 -0
- package/build/angular/services/magnetometer-uncalibrated.service/index.d.ts +6 -0
- package/build/angular/services/magnetometer-uncalibrated.service/index.js +81 -0
- package/build/angular/services/magnetometer.service/index.d.ts +6 -0
- package/build/angular/services/magnetometer.service/index.js +81 -0
- package/build/angular/services/pedometer.service/index.d.ts +6 -0
- package/build/angular/services/pedometer.service/index.js +76 -0
- package/build/core/accelerometer.d.ts +10 -0
- package/build/core/accelerometer.js +6 -0
- package/build/core/barometer.d.ts +9 -0
- package/build/core/barometer.js +6 -0
- package/build/core/device-motion.d.ts +40 -0
- package/build/core/device-motion.js +21 -0
- package/build/core/device-sensor.d.ts +27 -0
- package/build/core/device-sensor.js +61 -0
- package/build/core/gyroscope.d.ts +10 -0
- package/build/core/gyroscope.js +6 -0
- package/build/core/index.d.ts +11 -0
- package/build/core/index.js +10 -0
- package/build/core/light-sensor.d.ts +8 -0
- package/build/core/light-sensor.js +6 -0
- package/build/core/magnetometer-uncalibrated.d.ts +10 -0
- package/build/core/magnetometer-uncalibrated.js +6 -0
- package/build/core/magnetometer.d.ts +10 -0
- package/build/core/magnetometer.js +6 -0
- package/build/core/native/exponent-accelerometer.d.ts +3 -0
- package/build/core/native/exponent-accelerometer.js +3 -0
- package/build/core/native/exponent-barometer.d.ts +3 -0
- package/build/core/native/exponent-barometer.js +3 -0
- package/build/core/native/exponent-device-motion.d.ts +7 -0
- package/build/core/native/exponent-device-motion.js +3 -0
- package/build/core/native/exponent-gyroscope.d.ts +3 -0
- package/build/core/native/exponent-gyroscope.js +3 -0
- package/build/core/native/exponent-light-sensor/index.android.d.ts +3 -0
- package/build/core/native/exponent-light-sensor/index.android.js +3 -0
- package/build/core/native/exponent-light-sensor/index.d.ts +1 -0
- package/build/core/native/exponent-light-sensor/index.ios.d.ts +3 -0
- package/build/core/native/exponent-light-sensor/index.ios.js +6 -0
- package/build/core/native/exponent-light-sensor/index.js +5 -0
- package/build/core/native/exponent-magnetometer-uncalibrated.d.ts +3 -0
- package/build/core/native/exponent-magnetometer-uncalibrated.js +3 -0
- package/build/core/native/exponent-magnetometer.d.ts +3 -0
- package/build/core/native/exponent-magnetometer.js +3 -0
- package/build/core/native/exponent-pedometer.d.ts +10 -0
- package/build/core/native/exponent-pedometer.js +3 -0
- package/build/core/pedometer.d.ts +10 -0
- package/build/core/pedometer.js +44 -0
- package/build/react/hooks/use-accelerometer/index.d.ts +2 -0
- package/build/react/hooks/use-accelerometer/index.js +17 -0
- package/build/react/hooks/use-barometer/index.d.ts +2 -0
- package/build/react/hooks/use-barometer/index.js +17 -0
- package/build/react/hooks/use-device-motion/index.d.ts +2 -0
- package/build/react/hooks/use-device-motion/index.js +17 -0
- package/build/react/hooks/use-gyroscope/index.d.ts +2 -0
- package/build/react/hooks/use-gyroscope/index.js +17 -0
- package/build/react/hooks/use-light-sensor/index.d.ts +2 -0
- package/build/react/hooks/use-light-sensor/index.js +17 -0
- package/build/react/hooks/use-magnetometer/index.d.ts +2 -0
- package/build/react/hooks/use-magnetometer/index.js +17 -0
- package/build/react/hooks/use-magnetometer-uncalibrated/index.d.ts +2 -0
- package/build/react/hooks/use-magnetometer-uncalibrated/index.js +17 -0
- package/build/react/hooks/use-pedometer/index.d.ts +2 -0
- package/build/react/hooks/use-pedometer/index.js +14 -0
- package/build/react/index.d.ts +16 -0
- package/build/react/index.js +14 -0
- package/build/vue/composables/use-accelerometer/index.d.ts +3 -0
- package/build/vue/composables/use-accelerometer/index.js +25 -0
- package/build/vue/composables/use-barometer/index.d.ts +3 -0
- package/build/vue/composables/use-barometer/index.js +25 -0
- package/build/vue/composables/use-device-motion/index.d.ts +3 -0
- package/build/vue/composables/use-device-motion/index.js +25 -0
- package/build/vue/composables/use-gyroscope/index.d.ts +3 -0
- package/build/vue/composables/use-gyroscope/index.js +25 -0
- package/build/vue/composables/use-light-sensor/index.d.ts +3 -0
- package/build/vue/composables/use-light-sensor/index.js +25 -0
- package/build/vue/composables/use-magnetometer/index.d.ts +3 -0
- package/build/vue/composables/use-magnetometer/index.js +25 -0
- package/build/vue/composables/use-magnetometer-uncalibrated/index.d.ts +3 -0
- package/build/vue/composables/use-magnetometer-uncalibrated/index.js +25 -0
- package/build/vue/composables/use-pedometer/index.d.ts +3 -0
- package/build/vue/composables/use-pedometer/index.js +20 -0
- package/build/vue/index.d.ts +16 -0
- package/build/vue/index.js +14 -0
- package/build-ngc/angular/index.d.ts +16 -0
- package/build-ngc/angular/index.js +13 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/services/accelerometer.service/index.d.ts +9 -0
- package/build-ngc/angular/services/accelerometer.service/index.js +40 -0
- package/build-ngc/angular/services/accelerometer.service/index.js.map +1 -0
- package/build-ngc/angular/services/barometer.service/index.d.ts +9 -0
- package/build-ngc/angular/services/barometer.service/index.js +40 -0
- package/build-ngc/angular/services/barometer.service/index.js.map +1 -0
- package/build-ngc/angular/services/device-motion.service/index.d.ts +9 -0
- package/build-ngc/angular/services/device-motion.service/index.js +40 -0
- package/build-ngc/angular/services/device-motion.service/index.js.map +1 -0
- package/build-ngc/angular/services/gyroscope.service/index.d.ts +9 -0
- package/build-ngc/angular/services/gyroscope.service/index.js +40 -0
- package/build-ngc/angular/services/gyroscope.service/index.js.map +1 -0
- package/build-ngc/angular/services/light-sensor.service/index.d.ts +9 -0
- package/build-ngc/angular/services/light-sensor.service/index.js +40 -0
- package/build-ngc/angular/services/light-sensor.service/index.js.map +1 -0
- package/build-ngc/angular/services/magnetometer-uncalibrated.service/index.d.ts +9 -0
- package/build-ngc/angular/services/magnetometer-uncalibrated.service/index.js +40 -0
- package/build-ngc/angular/services/magnetometer-uncalibrated.service/index.js.map +1 -0
- package/build-ngc/angular/services/magnetometer.service/index.d.ts +9 -0
- package/build-ngc/angular/services/magnetometer.service/index.js +40 -0
- package/build-ngc/angular/services/magnetometer.service/index.js.map +1 -0
- package/build-ngc/angular/services/pedometer.service/index.d.ts +9 -0
- package/build-ngc/angular/services/pedometer.service/index.js +35 -0
- package/build-ngc/angular/services/pedometer.service/index.js.map +1 -0
- package/build-ngc/core/accelerometer.d.ts +10 -0
- package/build-ngc/core/accelerometer.js +7 -0
- package/build-ngc/core/accelerometer.js.map +1 -0
- package/build-ngc/core/barometer.d.ts +9 -0
- package/build-ngc/core/barometer.js +7 -0
- package/build-ngc/core/barometer.js.map +1 -0
- package/build-ngc/core/device-motion.d.ts +40 -0
- package/build-ngc/core/device-motion.js +22 -0
- package/build-ngc/core/device-motion.js.map +1 -0
- package/build-ngc/core/device-sensor.d.ts +27 -0
- package/build-ngc/core/device-sensor.js +62 -0
- package/build-ngc/core/device-sensor.js.map +1 -0
- package/build-ngc/core/gyroscope.d.ts +10 -0
- package/build-ngc/core/gyroscope.js +7 -0
- package/build-ngc/core/gyroscope.js.map +1 -0
- package/build-ngc/core/index.d.ts +11 -0
- package/build-ngc/core/index.js +11 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/light-sensor.d.ts +8 -0
- package/build-ngc/core/light-sensor.js +7 -0
- package/build-ngc/core/light-sensor.js.map +1 -0
- package/build-ngc/core/magnetometer-uncalibrated.d.ts +10 -0
- package/build-ngc/core/magnetometer-uncalibrated.js +7 -0
- package/build-ngc/core/magnetometer-uncalibrated.js.map +1 -0
- package/build-ngc/core/magnetometer.d.ts +10 -0
- package/build-ngc/core/magnetometer.js +7 -0
- package/build-ngc/core/magnetometer.js.map +1 -0
- package/build-ngc/core/native/exponent-accelerometer.d.ts +3 -0
- package/build-ngc/core/native/exponent-accelerometer.js +4 -0
- package/build-ngc/core/native/exponent-accelerometer.js.map +1 -0
- package/build-ngc/core/native/exponent-barometer.d.ts +3 -0
- package/build-ngc/core/native/exponent-barometer.js +4 -0
- package/build-ngc/core/native/exponent-barometer.js.map +1 -0
- package/build-ngc/core/native/exponent-device-motion.d.ts +7 -0
- package/build-ngc/core/native/exponent-device-motion.js +4 -0
- package/build-ngc/core/native/exponent-device-motion.js.map +1 -0
- package/build-ngc/core/native/exponent-gyroscope.d.ts +3 -0
- package/build-ngc/core/native/exponent-gyroscope.js +4 -0
- package/build-ngc/core/native/exponent-gyroscope.js.map +1 -0
- package/build-ngc/core/native/exponent-light-sensor/index.android.d.ts +3 -0
- package/build-ngc/core/native/exponent-light-sensor/index.android.js +4 -0
- package/build-ngc/core/native/exponent-light-sensor/index.android.js.map +1 -0
- package/build-ngc/core/native/exponent-light-sensor/index.d.ts +1 -0
- package/build-ngc/core/native/exponent-light-sensor/index.ios.d.ts +3 -0
- package/build-ngc/core/native/exponent-light-sensor/index.ios.js +7 -0
- package/build-ngc/core/native/exponent-light-sensor/index.ios.js.map +1 -0
- package/build-ngc/core/native/exponent-light-sensor/index.js +6 -0
- package/build-ngc/core/native/exponent-light-sensor/index.js.map +1 -0
- package/build-ngc/core/native/exponent-magnetometer-uncalibrated.d.ts +3 -0
- package/build-ngc/core/native/exponent-magnetometer-uncalibrated.js +4 -0
- package/build-ngc/core/native/exponent-magnetometer-uncalibrated.js.map +1 -0
- package/build-ngc/core/native/exponent-magnetometer.d.ts +3 -0
- package/build-ngc/core/native/exponent-magnetometer.js +4 -0
- package/build-ngc/core/native/exponent-magnetometer.js.map +1 -0
- package/build-ngc/core/native/exponent-pedometer.d.ts +10 -0
- package/build-ngc/core/native/exponent-pedometer.js +4 -0
- package/build-ngc/core/native/exponent-pedometer.js.map +1 -0
- package/build-ngc/core/pedometer.d.ts +10 -0
- package/build-ngc/core/pedometer.js +45 -0
- package/build-ngc/core/pedometer.js.map +1 -0
- package/package.json +106 -0
- package/src/angular/index.ts +26 -0
- package/src/angular/services/accelerometer.service/accelerometer.service.test.ts +118 -0
- package/src/angular/services/accelerometer.service/index.ts +39 -0
- package/src/angular/services/barometer.service/barometer.service.test.ts +122 -0
- package/src/angular/services/barometer.service/index.ts +39 -0
- package/src/angular/services/device-motion.service/device-motion.service.test.ts +125 -0
- package/src/angular/services/device-motion.service/index.ts +39 -0
- package/src/angular/services/gyroscope.service/gyroscope.service.test.ts +118 -0
- package/src/angular/services/gyroscope.service/index.ts +39 -0
- package/src/angular/services/light-sensor.service/index.ts +39 -0
- package/src/angular/services/light-sensor.service/light-sensor.service.test.ts +118 -0
- package/src/angular/services/magnetometer-uncalibrated.service/index.ts +39 -0
- package/src/angular/services/magnetometer-uncalibrated.service/magnetometer-uncalibrated.service.test.ts +123 -0
- package/src/angular/services/magnetometer.service/index.ts +39 -0
- package/src/angular/services/magnetometer.service/magnetometer.service.test.ts +118 -0
- package/src/angular/services/pedometer.service/index.ts +34 -0
- package/src/angular/services/pedometer.service/pedometer.service.test.ts +86 -0
- package/src/core/accelerometer.test.ts +49 -0
- package/src/core/accelerometer.ts +19 -0
- package/src/core/barometer.test.ts +46 -0
- package/src/core/barometer.ts +15 -0
- package/src/core/device-motion.test.ts +60 -0
- package/src/core/device-motion.ts +41 -0
- package/src/core/device-sensor.test.ts +76 -0
- package/src/core/device-sensor.ts +95 -0
- package/src/core/gyroscope.test.ts +46 -0
- package/src/core/gyroscope.ts +16 -0
- package/src/core/index.ts +37 -0
- package/src/core/light-sensor.test.ts +49 -0
- package/src/core/light-sensor.ts +18 -0
- package/src/core/magnetometer-uncalibrated.test.ts +49 -0
- package/src/core/magnetometer-uncalibrated.ts +20 -0
- package/src/core/magnetometer.test.ts +49 -0
- package/src/core/magnetometer.ts +19 -0
- package/src/core/native/exponent-accelerometer.ts +9 -0
- package/src/core/native/exponent-barometer.ts +9 -0
- package/src/core/native/exponent-device-motion.ts +15 -0
- package/src/core/native/exponent-gyroscope.ts +9 -0
- package/src/core/native/exponent-light-sensor/index.android.ts +9 -0
- package/src/core/native/exponent-light-sensor/index.ios.ts +12 -0
- package/src/core/native/exponent-light-sensor/index.ts +5 -0
- package/src/core/native/exponent-magnetometer-uncalibrated.ts +9 -0
- package/src/core/native/exponent-magnetometer.ts +9 -0
- package/src/core/native/exponent-pedometer.ts +17 -0
- package/src/core/pedometer.test.ts +118 -0
- package/src/core/pedometer.ts +62 -0
- package/src/react/hooks/use-accelerometer/index.ts +21 -0
- package/src/react/hooks/use-accelerometer/use-accelerometer.test.tsx +81 -0
- package/src/react/hooks/use-barometer/index.ts +21 -0
- package/src/react/hooks/use-barometer/use-barometer.test.tsx +83 -0
- package/src/react/hooks/use-device-motion/index.ts +21 -0
- package/src/react/hooks/use-device-motion/use-device-motion.test.tsx +88 -0
- package/src/react/hooks/use-gyroscope/index.ts +21 -0
- package/src/react/hooks/use-gyroscope/use-gyroscope.test.tsx +79 -0
- package/src/react/hooks/use-light-sensor/index.ts +21 -0
- package/src/react/hooks/use-light-sensor/use-light-sensor.test.tsx +79 -0
- package/src/react/hooks/use-magnetometer/index.ts +21 -0
- package/src/react/hooks/use-magnetometer/use-magnetometer.test.tsx +81 -0
- package/src/react/hooks/use-magnetometer-uncalibrated/index.ts +23 -0
- package/src/react/hooks/use-magnetometer-uncalibrated/use-magnetometer-uncalibrated.test.tsx +88 -0
- package/src/react/hooks/use-pedometer/index.ts +17 -0
- package/src/react/hooks/use-pedometer/use-pedometer.test.tsx +69 -0
- package/src/react/index.ts +29 -0
- package/src/vue/composables/use-accelerometer/index.ts +34 -0
- package/src/vue/composables/use-accelerometer/use-accelerometer.test.ts +96 -0
- package/src/vue/composables/use-barometer/index.ts +30 -0
- package/src/vue/composables/use-barometer/use-barometer.test.ts +100 -0
- package/src/vue/composables/use-device-motion/index.ts +30 -0
- package/src/vue/composables/use-device-motion/use-device-motion.test.ts +104 -0
- package/src/vue/composables/use-gyroscope/index.ts +30 -0
- package/src/vue/composables/use-gyroscope/use-gyroscope.test.ts +96 -0
- package/src/vue/composables/use-light-sensor/index.ts +30 -0
- package/src/vue/composables/use-light-sensor/use-light-sensor.test.ts +96 -0
- package/src/vue/composables/use-magnetometer/index.ts +30 -0
- package/src/vue/composables/use-magnetometer/use-magnetometer.test.ts +96 -0
- package/src/vue/composables/use-magnetometer-uncalibrated/index.ts +36 -0
- package/src/vue/composables/use-magnetometer-uncalibrated/use-magnetometer-uncalibrated.test.ts +103 -0
- package/src/vue/composables/use-pedometer/index.ts +25 -0
- package/src/vue/composables/use-pedometer/use-pedometer.test.ts +77 -0
- package/src/vue/index.ts +29 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The light sensor has no iOS native support (upstream ExpoLightSensor.ios.ts carries the
|
|
2
|
+
// same TODO) — this stub always reports unavailable so LightSensorSensor degrades gracefully
|
|
3
|
+
// instead of throwing when requireNativeModule() would fail to resolve a real module.
|
|
4
|
+
import type { INativeSensorModule } from '../../device-sensor';
|
|
5
|
+
import type { ILightSensorMeasurement } from '../../light-sensor';
|
|
6
|
+
|
|
7
|
+
export const exponentLightSensor: INativeSensorModule<ILightSensorMeasurement> = {
|
|
8
|
+
addListener: () => ({ remove: () => {} }),
|
|
9
|
+
listenerCount: () => 0,
|
|
10
|
+
removeAllListeners: () => {},
|
|
11
|
+
isAvailableAsync: async () => false,
|
|
12
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Base resolution for plain Node/vitest imports of './native/exponent-light-sensor', which
|
|
2
|
+
// don't go through Metro's platform-extension picking — re-exports the iOS stub so headless
|
|
3
|
+
// resolution stays side-effect-free and never calls requireNativeModule() outside a real RN
|
|
4
|
+
// runtime. Metro still picks index.ios.ts/index.android.ts per real platform at bundle time.
|
|
5
|
+
export { exponentLightSensor } from './index.ios';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { INativeSensorModule } from '../device-sensor';
|
|
3
|
+
import type { IMagnetometerUncalibratedMeasurement } from '../magnetometer-uncalibrated';
|
|
4
|
+
|
|
5
|
+
const EXPONENT_MAGNETOMETER_UNCALIBRATED_MODULE_NAME = 'ExponentMagnetometerUncalibrated';
|
|
6
|
+
|
|
7
|
+
export const exponentMagnetometerUncalibrated = requireNativeModule<
|
|
8
|
+
INativeSensorModule<IMagnetometerUncalibratedMeasurement>
|
|
9
|
+
>(EXPONENT_MAGNETOMETER_UNCALIBRATED_MODULE_NAME);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { INativeSensorModule } from '../device-sensor';
|
|
3
|
+
import type { IMagnetometerMeasurement } from '../magnetometer';
|
|
4
|
+
|
|
5
|
+
const EXPONENT_MAGNETOMETER_MODULE_NAME = 'ExponentMagnetometer';
|
|
6
|
+
|
|
7
|
+
export const exponentMagnetometer = requireNativeModule<
|
|
8
|
+
INativeSensorModule<IMagnetometerMeasurement>
|
|
9
|
+
>(EXPONENT_MAGNETOMETER_MODULE_NAME);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { EventSubscription, PermissionResponse } from 'expo-modules-core';
|
|
3
|
+
import type { IPedometerResult } from '../pedometer';
|
|
4
|
+
|
|
5
|
+
const EXPONENT_PEDOMETER_MODULE_NAME = 'ExponentPedometer';
|
|
6
|
+
|
|
7
|
+
export type IExponentPedometerModule = {
|
|
8
|
+
addListener(eventName: string, listener: (result: IPedometerResult) => void): EventSubscription;
|
|
9
|
+
getStepCountAsync?(startMs: number, endMs: number): Promise<IPedometerResult>;
|
|
10
|
+
isAvailableAsync(): Promise<boolean>;
|
|
11
|
+
getPermissionsAsync?(): Promise<PermissionResponse>;
|
|
12
|
+
requestPermissionsAsync?(): Promise<PermissionResponse>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const exponentPedometer = requireNativeModule<IExponentPedometerModule>(
|
|
16
|
+
EXPONENT_PEDOMETER_MODULE_NAME,
|
|
17
|
+
);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const FAKE_NATIVE_PEDOMETER = {
|
|
4
|
+
addListener: vi.fn(() => ({ remove: vi.fn() })),
|
|
5
|
+
getStepCountAsync: vi.fn(async () => ({ steps: 42 })),
|
|
6
|
+
isAvailableAsync: vi.fn(async () => true),
|
|
7
|
+
getPermissionsAsync: vi.fn(),
|
|
8
|
+
requestPermissionsAsync: vi.fn(),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// The real ExponentPedometer native module only exists on device — resolving it via
|
|
12
|
+
// requireNativeModule() at import time would throw in this headless test run, so the
|
|
13
|
+
// module-lookup file is faked in place of `expo-modules-core`'s runtime resolution.
|
|
14
|
+
vi.mock('./native/exponent-pedometer', () => ({
|
|
15
|
+
exponentPedometer: FAKE_NATIVE_PEDOMETER,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// pedometer.ts pulls PermissionStatus/UnavailabilityError from expo-modules-core, whose real
|
|
19
|
+
// entry transitively drags in the Flow-typed 'react-native' source that Vitest's Oxc transform
|
|
20
|
+
// can't parse — same fake as device-sensor.test.ts / accelerometer.test.ts.
|
|
21
|
+
vi.mock('expo-modules-core', () => ({
|
|
22
|
+
PermissionStatus: { GRANTED: 'granted', UNDETERMINED: 'undetermined', DENIED: 'denied' },
|
|
23
|
+
UnavailabilityError: class UnavailabilityError extends Error {
|
|
24
|
+
constructor(moduleName: string, propertyName: string) {
|
|
25
|
+
super(`${propertyName} is not available on ${moduleName}`);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
watchStepCount,
|
|
32
|
+
getStepCountAsync,
|
|
33
|
+
isAvailableAsync,
|
|
34
|
+
getPermissionsAsync,
|
|
35
|
+
requestPermissionsAsync,
|
|
36
|
+
} = await import('./pedometer');
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('Pedometer', () => {
|
|
43
|
+
it('watchStepCount subscribes through the dotted Exponent.pedometerUpdate event name', () => {
|
|
44
|
+
const listener = vi.fn();
|
|
45
|
+
watchStepCount(listener);
|
|
46
|
+
|
|
47
|
+
expect(FAKE_NATIVE_PEDOMETER.addListener).toHaveBeenCalledWith(
|
|
48
|
+
'Exponent.pedometerUpdate',
|
|
49
|
+
listener,
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('getStepCountAsync throws when the start date is after the end date', async () => {
|
|
54
|
+
const start = new Date('2026-07-15T00:00:00Z');
|
|
55
|
+
const end = new Date('2026-07-01T00:00:00Z');
|
|
56
|
+
|
|
57
|
+
await expect(getStepCountAsync(start, end)).rejects.toThrow(
|
|
58
|
+
'Pedometer: the start date must precede the end date.',
|
|
59
|
+
);
|
|
60
|
+
expect(FAKE_NATIVE_PEDOMETER.getStepCountAsync).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('getStepCountAsync throws an UnavailabilityError-shaped error when the native method is absent', async () => {
|
|
64
|
+
const { getStepCountAsync: nativeGetStepCountAsync } = FAKE_NATIVE_PEDOMETER;
|
|
65
|
+
// @ts-expect-error -- simulating a platform where the native module has no such method
|
|
66
|
+
FAKE_NATIVE_PEDOMETER.getStepCountAsync = undefined;
|
|
67
|
+
|
|
68
|
+
const start = new Date('2026-07-01T00:00:00Z');
|
|
69
|
+
const end = new Date('2026-07-15T00:00:00Z');
|
|
70
|
+
|
|
71
|
+
await expect(getStepCountAsync(start, end)).rejects.toThrow(
|
|
72
|
+
'getStepCountAsync is not available on ExponentPedometer',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
FAKE_NATIVE_PEDOMETER.getStepCountAsync = nativeGetStepCountAsync;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('getStepCountAsync calls through with millisecond values from each Date', async () => {
|
|
79
|
+
const start = new Date('2026-07-01T00:00:00Z');
|
|
80
|
+
const end = new Date('2026-07-15T00:00:00Z');
|
|
81
|
+
|
|
82
|
+
const result = await getStepCountAsync(start, end);
|
|
83
|
+
|
|
84
|
+
expect(FAKE_NATIVE_PEDOMETER.getStepCountAsync).toHaveBeenCalledWith(
|
|
85
|
+
start.getTime(),
|
|
86
|
+
end.getTime(),
|
|
87
|
+
);
|
|
88
|
+
expect(result).toEqual({ steps: 42 });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('isAvailableAsync delegates to the native module', async () => {
|
|
92
|
+
await expect(isAvailableAsync()).resolves.toBe(true);
|
|
93
|
+
expect(FAKE_NATIVE_PEDOMETER.isAvailableAsync).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('falls back to a granted default permission response when the native module has none', async () => {
|
|
97
|
+
const {
|
|
98
|
+
getPermissionsAsync: nativeGetPermissionsAsync,
|
|
99
|
+
requestPermissionsAsync: nativeRequestPermissionsAsync,
|
|
100
|
+
} = FAKE_NATIVE_PEDOMETER;
|
|
101
|
+
// @ts-expect-error -- simulating a platform where the native module exposes no permission methods
|
|
102
|
+
FAKE_NATIVE_PEDOMETER.getPermissionsAsync = undefined;
|
|
103
|
+
// @ts-expect-error -- simulating a platform where the native module exposes no permission methods
|
|
104
|
+
FAKE_NATIVE_PEDOMETER.requestPermissionsAsync = undefined;
|
|
105
|
+
|
|
106
|
+
const expectedDefault = {
|
|
107
|
+
granted: true,
|
|
108
|
+
expires: 'never',
|
|
109
|
+
canAskAgain: true,
|
|
110
|
+
status: 'granted',
|
|
111
|
+
};
|
|
112
|
+
await expect(getPermissionsAsync()).resolves.toEqual(expectedDefault);
|
|
113
|
+
await expect(requestPermissionsAsync()).resolves.toEqual(expectedDefault);
|
|
114
|
+
|
|
115
|
+
FAKE_NATIVE_PEDOMETER.getPermissionsAsync = nativeGetPermissionsAsync;
|
|
116
|
+
FAKE_NATIVE_PEDOMETER.requestPermissionsAsync = nativeRequestPermissionsAsync;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Pedometer's upstream API (.vendors/expo/packages/expo-sensors/src/Pedometer.ts) is a set of
|
|
2
|
+
// free functions, not a class — unlike every other sensor here it has no shared instance to hang
|
|
3
|
+
// addListener/removeAllListeners/setUpdateInterval off, so it intentionally does NOT extend
|
|
4
|
+
// DeviceSensor (see device-sensor.ts for that shared shape).
|
|
5
|
+
import {
|
|
6
|
+
PermissionStatus,
|
|
7
|
+
UnavailabilityError,
|
|
8
|
+
type EventSubscription,
|
|
9
|
+
type PermissionResponse,
|
|
10
|
+
} from 'expo-modules-core';
|
|
11
|
+
import { exponentPedometer } from './native/exponent-pedometer';
|
|
12
|
+
|
|
13
|
+
export type IPedometerResult = {
|
|
14
|
+
steps: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type IPedometerUpdateCallback = (result: IPedometerResult) => void;
|
|
18
|
+
|
|
19
|
+
// Upstream event name — the only sensor event in this package carrying a dot, because it is
|
|
20
|
+
// Exponent's original (pre-Expo-rename) native module identifier.
|
|
21
|
+
const PEDOMETER_UPDATE_EVENT_NAME = 'Exponent.pedometerUpdate';
|
|
22
|
+
|
|
23
|
+
const DEFAULT_PERMISSION_RESPONSE: PermissionResponse = {
|
|
24
|
+
granted: true,
|
|
25
|
+
expires: 'never',
|
|
26
|
+
canAskAgain: true,
|
|
27
|
+
status: PermissionStatus.GRANTED,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function watchStepCount(callback: IPedometerUpdateCallback): EventSubscription {
|
|
31
|
+
return exponentPedometer.addListener(PEDOMETER_UPDATE_EVENT_NAME, callback);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// iOS only in practice (Android has no native getStepCountAsync) — proven by the same
|
|
35
|
+
// optional-native-method check upstream uses, not a Platform.OS branch.
|
|
36
|
+
export async function getStepCountAsync(start: Date, end: Date): Promise<IPedometerResult> {
|
|
37
|
+
if (!exponentPedometer.getStepCountAsync) {
|
|
38
|
+
throw new UnavailabilityError('ExponentPedometer', 'getStepCountAsync');
|
|
39
|
+
}
|
|
40
|
+
if (start > end) {
|
|
41
|
+
throw new Error('Pedometer: the start date must precede the end date.');
|
|
42
|
+
}
|
|
43
|
+
return exponentPedometer.getStepCountAsync(start.getTime(), end.getTime());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function isAvailableAsync(): Promise<boolean> {
|
|
47
|
+
return exponentPedometer.isAvailableAsync();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getPermissionsAsync(): Promise<PermissionResponse> {
|
|
51
|
+
if (!exponentPedometer.getPermissionsAsync) {
|
|
52
|
+
return DEFAULT_PERMISSION_RESPONSE;
|
|
53
|
+
}
|
|
54
|
+
return exponentPedometer.getPermissionsAsync();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function requestPermissionsAsync(): Promise<PermissionResponse> {
|
|
58
|
+
if (!exponentPedometer.requestPermissionsAsync) {
|
|
59
|
+
return DEFAULT_PERMISSION_RESPONSE;
|
|
60
|
+
}
|
|
61
|
+
return exponentPedometer.requestPermissionsAsync();
|
|
62
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic Accelerometer singleton (core/) —
|
|
2
|
+
// mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Accelerometer, type IAccelerometerMeasurement } from '../../../core';
|
|
5
|
+
|
|
6
|
+
export function useAccelerometer(updateIntervalMs?: number): IAccelerometerMeasurement | null {
|
|
7
|
+
const [measurement, setMeasurement] = useState<IAccelerometerMeasurement | null>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
// Re-subscribing on interval change keeps the native update rate in sync with the
|
|
11
|
+
// caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
|
|
12
|
+
if (updateIntervalMs !== undefined) {
|
|
13
|
+
Accelerometer.setUpdateInterval(updateIntervalMs);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const subscription = Accelerometer.addListener(setMeasurement);
|
|
17
|
+
return () => subscription.remove();
|
|
18
|
+
}, [updateIntervalMs]);
|
|
19
|
+
|
|
20
|
+
return measurement;
|
|
21
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Co-located React-driven test (ADR 0025) for useAccelerometer. Mocks the whole `core`
|
|
2
|
+
// module rather than expo-modules-core internals — this hook's own lifecycle wiring
|
|
3
|
+
// (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
|
|
4
|
+
// already has its own coverage in packages/sensors/src/core/*.test.ts.
|
|
5
|
+
|
|
6
|
+
import { createElement, type ReactElement } from 'react';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { mount, unmount, View } from '@symbiote-native/react';
|
|
9
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
10
|
+
import { useAccelerometer } from './index';
|
|
11
|
+
import type { IAccelerometerMeasurement } from '../../../core';
|
|
12
|
+
|
|
13
|
+
const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
|
|
14
|
+
const remove = vi.fn();
|
|
15
|
+
return {
|
|
16
|
+
addListener: vi.fn((_listener: (measurement: IAccelerometerMeasurement) => void) => ({
|
|
17
|
+
remove,
|
|
18
|
+
})),
|
|
19
|
+
removeAllListeners: vi.fn(),
|
|
20
|
+
setUpdateInterval: vi.fn(),
|
|
21
|
+
remove,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
vi.mock('../../../core', () => ({
|
|
26
|
+
Accelerometer: { addListener, removeAllListeners, setUpdateInterval },
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const ROOT_TAG = 901;
|
|
30
|
+
|
|
31
|
+
const results: Array<IAccelerometerMeasurement | null> = [];
|
|
32
|
+
|
|
33
|
+
function Probe(props: { updateIntervalMs?: number }): ReactElement {
|
|
34
|
+
results.push(useAccelerometer(props.updateIntervalMs));
|
|
35
|
+
return createElement(View);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const fabric = installFabric();
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
fabric.reset();
|
|
42
|
+
results.length = 0;
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
47
|
+
|
|
48
|
+
describe('useAccelerometer', () => {
|
|
49
|
+
it('reports null before any native measurement arrives', () => {
|
|
50
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
51
|
+
|
|
52
|
+
expect(results[results.length - 1]).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('updates to the latest measurement once the native listener fires', async () => {
|
|
56
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
57
|
+
|
|
58
|
+
const measurement: IAccelerometerMeasurement = { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 };
|
|
59
|
+
const listener = addListener.mock.calls[0][0];
|
|
60
|
+
listener(measurement);
|
|
61
|
+
|
|
62
|
+
// The mock invokes the listener directly, outside the engine's event dispatcher
|
|
63
|
+
// (setEventDispatcher in render.ts), which is what normally flushes a native-driven
|
|
64
|
+
// setState synchronously — so the resulting re-render lands on a later microtask here.
|
|
65
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('unsubscribes from the native listener on unmount', () => {
|
|
69
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
70
|
+
|
|
71
|
+
unmount(ROOT_TAG);
|
|
72
|
+
|
|
73
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('sets the native update interval when updateIntervalMs is provided', () => {
|
|
77
|
+
mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
|
|
78
|
+
|
|
79
|
+
expect(setUpdateInterval).toHaveBeenCalledWith(100);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic Barometer singleton (core/) —
|
|
2
|
+
// mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Barometer, type IBarometerMeasurement } from '../../../core';
|
|
5
|
+
|
|
6
|
+
export function useBarometer(updateIntervalMs?: number): IBarometerMeasurement | null {
|
|
7
|
+
const [measurement, setMeasurement] = useState<IBarometerMeasurement | null>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
// Re-subscribing on interval change keeps the native update rate in sync with the
|
|
11
|
+
// caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
|
|
12
|
+
if (updateIntervalMs !== undefined) {
|
|
13
|
+
Barometer.setUpdateInterval(updateIntervalMs);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const subscription = Barometer.addListener(setMeasurement);
|
|
17
|
+
return () => subscription.remove();
|
|
18
|
+
}, [updateIntervalMs]);
|
|
19
|
+
|
|
20
|
+
return measurement;
|
|
21
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Co-located React-driven test (ADR 0025) for useBarometer. Mocks the whole `core`
|
|
2
|
+
// module rather than expo-modules-core internals — this hook's own lifecycle wiring
|
|
3
|
+
// (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
|
|
4
|
+
// already has its own coverage in packages/sensors/src/core/*.test.ts.
|
|
5
|
+
|
|
6
|
+
import { createElement, type ReactElement } from 'react';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { mount, unmount, View } from '@symbiote-native/react';
|
|
9
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
10
|
+
import { useBarometer } from './index';
|
|
11
|
+
import type { IBarometerMeasurement } from '../../../core';
|
|
12
|
+
|
|
13
|
+
const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
|
|
14
|
+
const remove = vi.fn();
|
|
15
|
+
return {
|
|
16
|
+
addListener: vi.fn((_listener: (measurement: IBarometerMeasurement) => void) => ({ remove })),
|
|
17
|
+
removeAllListeners: vi.fn(),
|
|
18
|
+
setUpdateInterval: vi.fn(),
|
|
19
|
+
remove,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock('../../../core', () => ({
|
|
24
|
+
Barometer: { addListener, removeAllListeners, setUpdateInterval },
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const ROOT_TAG = 902;
|
|
28
|
+
|
|
29
|
+
const results: Array<IBarometerMeasurement | null> = [];
|
|
30
|
+
|
|
31
|
+
function Probe(props: { updateIntervalMs?: number }): ReactElement {
|
|
32
|
+
results.push(useBarometer(props.updateIntervalMs));
|
|
33
|
+
return createElement(View);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fabric = installFabric();
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
fabric.reset();
|
|
40
|
+
results.length = 0;
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
45
|
+
|
|
46
|
+
describe('useBarometer', () => {
|
|
47
|
+
it('reports null before any native measurement arrives', () => {
|
|
48
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
49
|
+
|
|
50
|
+
expect(results[results.length - 1]).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('updates to the latest measurement once the native listener fires', async () => {
|
|
54
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
55
|
+
|
|
56
|
+
const measurement: IBarometerMeasurement = {
|
|
57
|
+
pressure: 1013.25,
|
|
58
|
+
relativeAltitude: 12.3,
|
|
59
|
+
timestamp: 123,
|
|
60
|
+
};
|
|
61
|
+
const listener = addListener.mock.calls[0][0];
|
|
62
|
+
listener(measurement);
|
|
63
|
+
|
|
64
|
+
// The mock invokes the listener directly, outside the engine's event dispatcher
|
|
65
|
+
// (setEventDispatcher in render.ts), which is what normally flushes a native-driven
|
|
66
|
+
// setState synchronously — so the resulting re-render lands on a later microtask here.
|
|
67
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('unsubscribes from the native listener on unmount', () => {
|
|
71
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
72
|
+
|
|
73
|
+
unmount(ROOT_TAG);
|
|
74
|
+
|
|
75
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('sets the native update interval when updateIntervalMs is provided', () => {
|
|
79
|
+
mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
|
|
80
|
+
|
|
81
|
+
expect(setUpdateInterval).toHaveBeenCalledWith(100);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic DeviceMotion singleton (core/) —
|
|
2
|
+
// mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { DeviceMotion, type IDeviceMotionMeasurement } from '../../../core';
|
|
5
|
+
|
|
6
|
+
export function useDeviceMotion(updateIntervalMs?: number): IDeviceMotionMeasurement | null {
|
|
7
|
+
const [measurement, setMeasurement] = useState<IDeviceMotionMeasurement | null>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
// Re-subscribing on interval change keeps the native update rate in sync with the
|
|
11
|
+
// caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
|
|
12
|
+
if (updateIntervalMs !== undefined) {
|
|
13
|
+
DeviceMotion.setUpdateInterval(updateIntervalMs);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const subscription = DeviceMotion.addListener(setMeasurement);
|
|
17
|
+
return () => subscription.remove();
|
|
18
|
+
}, [updateIntervalMs]);
|
|
19
|
+
|
|
20
|
+
return measurement;
|
|
21
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Co-located React-driven test (ADR 0025) for useDeviceMotion. Mocks the whole `core`
|
|
2
|
+
// module rather than expo-modules-core internals — this hook's own lifecycle wiring
|
|
3
|
+
// (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
|
|
4
|
+
// already has its own coverage in packages/sensors/src/core/*.test.ts.
|
|
5
|
+
|
|
6
|
+
import { createElement, type ReactElement } from 'react';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { mount, unmount, View } from '@symbiote-native/react';
|
|
9
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
10
|
+
import { useDeviceMotion } from './index';
|
|
11
|
+
import type { IDeviceMotionMeasurement } from '../../../core';
|
|
12
|
+
|
|
13
|
+
const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
|
|
14
|
+
const remove = vi.fn();
|
|
15
|
+
return {
|
|
16
|
+
addListener: vi.fn((_listener: (measurement: IDeviceMotionMeasurement) => void) => ({
|
|
17
|
+
remove,
|
|
18
|
+
})),
|
|
19
|
+
removeAllListeners: vi.fn(),
|
|
20
|
+
setUpdateInterval: vi.fn(),
|
|
21
|
+
remove,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
vi.mock('../../../core', () => ({
|
|
26
|
+
DeviceMotion: { addListener, removeAllListeners, setUpdateInterval },
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const ROOT_TAG = 902;
|
|
30
|
+
|
|
31
|
+
const results: Array<IDeviceMotionMeasurement | null> = [];
|
|
32
|
+
|
|
33
|
+
function Probe(props: { updateIntervalMs?: number }): ReactElement {
|
|
34
|
+
results.push(useDeviceMotion(props.updateIntervalMs));
|
|
35
|
+
return createElement(View);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const fabric = installFabric();
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
fabric.reset();
|
|
42
|
+
results.length = 0;
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
47
|
+
|
|
48
|
+
describe('useDeviceMotion', () => {
|
|
49
|
+
it('reports null before any native measurement arrives', () => {
|
|
50
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
51
|
+
|
|
52
|
+
expect(results[results.length - 1]).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('updates to the latest measurement once the native listener fires', async () => {
|
|
56
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
57
|
+
|
|
58
|
+
const measurement: IDeviceMotionMeasurement = {
|
|
59
|
+
acceleration: { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 },
|
|
60
|
+
accelerationIncludingGravity: { x: 0.1, y: 0.2, z: 9.8, timestamp: 123 },
|
|
61
|
+
rotation: { alpha: 1, beta: 2, gamma: 3, timestamp: 123 },
|
|
62
|
+
rotationRate: { alpha: 0.1, beta: 0.2, gamma: 0.3, timestamp: 123 },
|
|
63
|
+
interval: 16,
|
|
64
|
+
orientation: 0, // DeviceMotionOrientation.Portrait
|
|
65
|
+
};
|
|
66
|
+
const listener = addListener.mock.calls[0][0];
|
|
67
|
+
listener(measurement);
|
|
68
|
+
|
|
69
|
+
// The mock invokes the listener directly, outside the engine's event dispatcher
|
|
70
|
+
// (setEventDispatcher in render.ts), which is what normally flushes a native-driven
|
|
71
|
+
// setState synchronously — so the resulting re-render lands on a later microtask here.
|
|
72
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('unsubscribes from the native listener on unmount', () => {
|
|
76
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
77
|
+
|
|
78
|
+
unmount(ROOT_TAG);
|
|
79
|
+
|
|
80
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('sets the native update interval when updateIntervalMs is provided', () => {
|
|
84
|
+
mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
|
|
85
|
+
|
|
86
|
+
expect(setUpdateInterval).toHaveBeenCalledWith(100);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic Gyroscope singleton (core/) —
|
|
2
|
+
// mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Gyroscope, type IGyroscopeMeasurement } from '../../../core';
|
|
5
|
+
|
|
6
|
+
export function useGyroscope(updateIntervalMs?: number): IGyroscopeMeasurement | null {
|
|
7
|
+
const [measurement, setMeasurement] = useState<IGyroscopeMeasurement | null>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
// Re-subscribing on interval change keeps the native update rate in sync with the
|
|
11
|
+
// caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
|
|
12
|
+
if (updateIntervalMs !== undefined) {
|
|
13
|
+
Gyroscope.setUpdateInterval(updateIntervalMs);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const subscription = Gyroscope.addListener(setMeasurement);
|
|
17
|
+
return () => subscription.remove();
|
|
18
|
+
}, [updateIntervalMs]);
|
|
19
|
+
|
|
20
|
+
return measurement;
|
|
21
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Co-located React-driven test (ADR 0025) for useGyroscope. Mocks the whole `core`
|
|
2
|
+
// module rather than expo-modules-core internals — this hook's own lifecycle wiring
|
|
3
|
+
// (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
|
|
4
|
+
// already has its own coverage in packages/sensors/src/core/*.test.ts.
|
|
5
|
+
|
|
6
|
+
import { createElement, type ReactElement } from 'react';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { mount, unmount, View } from '@symbiote-native/react';
|
|
9
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
10
|
+
import { useGyroscope } from './index';
|
|
11
|
+
import type { IGyroscopeMeasurement } from '../../../core';
|
|
12
|
+
|
|
13
|
+
const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
|
|
14
|
+
const remove = vi.fn();
|
|
15
|
+
return {
|
|
16
|
+
addListener: vi.fn((_listener: (measurement: IGyroscopeMeasurement) => void) => ({ remove })),
|
|
17
|
+
removeAllListeners: vi.fn(),
|
|
18
|
+
setUpdateInterval: vi.fn(),
|
|
19
|
+
remove,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock('../../../core', () => ({
|
|
24
|
+
Gyroscope: { addListener, removeAllListeners, setUpdateInterval },
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const ROOT_TAG = 902;
|
|
28
|
+
|
|
29
|
+
const results: Array<IGyroscopeMeasurement | null> = [];
|
|
30
|
+
|
|
31
|
+
function Probe(props: { updateIntervalMs?: number }): ReactElement {
|
|
32
|
+
results.push(useGyroscope(props.updateIntervalMs));
|
|
33
|
+
return createElement(View);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fabric = installFabric();
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
fabric.reset();
|
|
40
|
+
results.length = 0;
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
45
|
+
|
|
46
|
+
describe('useGyroscope', () => {
|
|
47
|
+
it('reports null before any native measurement arrives', () => {
|
|
48
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
49
|
+
|
|
50
|
+
expect(results[results.length - 1]).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('updates to the latest measurement once the native listener fires', async () => {
|
|
54
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
55
|
+
|
|
56
|
+
const measurement: IGyroscopeMeasurement = { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 };
|
|
57
|
+
const listener = addListener.mock.calls[0][0];
|
|
58
|
+
listener(measurement);
|
|
59
|
+
|
|
60
|
+
// The mock invokes the listener directly, outside the engine's event dispatcher
|
|
61
|
+
// (setEventDispatcher in render.ts), which is what normally flushes a native-driven
|
|
62
|
+
// setState synchronously — so the resulting re-render lands on a later microtask here.
|
|
63
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('unsubscribes from the native listener on unmount', () => {
|
|
67
|
+
mount(ROOT_TAG, createElement(Probe, {}));
|
|
68
|
+
|
|
69
|
+
unmount(ROOT_TAG);
|
|
70
|
+
|
|
71
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('sets the native update interval when updateIntervalMs is provided', () => {
|
|
75
|
+
mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
|
|
76
|
+
|
|
77
|
+
expect(setUpdateInterval).toHaveBeenCalledWith(100);
|
|
78
|
+
});
|
|
79
|
+
});
|