@symbiote-native/sensors 0.1.0 → 0.2.1

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.
Files changed (36) hide show
  1. package/README.md +5 -5
  2. package/native-link.json +52 -0
  3. package/package.json +11 -7
  4. package/src/angular/services/accelerometer.service/accelerometer.service.test.ts +0 -118
  5. package/src/angular/services/barometer.service/barometer.service.test.ts +0 -122
  6. package/src/angular/services/device-motion.service/device-motion.service.test.ts +0 -125
  7. package/src/angular/services/gyroscope.service/gyroscope.service.test.ts +0 -118
  8. package/src/angular/services/light-sensor.service/light-sensor.service.test.ts +0 -118
  9. package/src/angular/services/magnetometer-uncalibrated.service/magnetometer-uncalibrated.service.test.ts +0 -123
  10. package/src/angular/services/magnetometer.service/magnetometer.service.test.ts +0 -118
  11. package/src/angular/services/pedometer.service/pedometer.service.test.ts +0 -86
  12. package/src/core/accelerometer.test.ts +0 -49
  13. package/src/core/barometer.test.ts +0 -46
  14. package/src/core/device-motion.test.ts +0 -60
  15. package/src/core/device-sensor.test.ts +0 -76
  16. package/src/core/gyroscope.test.ts +0 -46
  17. package/src/core/light-sensor.test.ts +0 -49
  18. package/src/core/magnetometer-uncalibrated.test.ts +0 -49
  19. package/src/core/magnetometer.test.ts +0 -49
  20. package/src/core/pedometer.test.ts +0 -118
  21. package/src/react/hooks/use-accelerometer/use-accelerometer.test.tsx +0 -81
  22. package/src/react/hooks/use-barometer/use-barometer.test.tsx +0 -83
  23. package/src/react/hooks/use-device-motion/use-device-motion.test.tsx +0 -88
  24. package/src/react/hooks/use-gyroscope/use-gyroscope.test.tsx +0 -79
  25. package/src/react/hooks/use-light-sensor/use-light-sensor.test.tsx +0 -79
  26. package/src/react/hooks/use-magnetometer/use-magnetometer.test.tsx +0 -81
  27. package/src/react/hooks/use-magnetometer-uncalibrated/use-magnetometer-uncalibrated.test.tsx +0 -88
  28. package/src/react/hooks/use-pedometer/use-pedometer.test.tsx +0 -69
  29. package/src/vue/composables/use-accelerometer/use-accelerometer.test.ts +0 -96
  30. package/src/vue/composables/use-barometer/use-barometer.test.ts +0 -100
  31. package/src/vue/composables/use-device-motion/use-device-motion.test.ts +0 -104
  32. package/src/vue/composables/use-gyroscope/use-gyroscope.test.ts +0 -96
  33. package/src/vue/composables/use-light-sensor/use-light-sensor.test.ts +0 -96
  34. package/src/vue/composables/use-magnetometer/use-magnetometer.test.ts +0 -96
  35. package/src/vue/composables/use-magnetometer-uncalibrated/use-magnetometer-uncalibrated.test.ts +0 -103
  36. package/src/vue/composables/use-pedometer/use-pedometer.test.ts +0 -77
package/README.md CHANGED
@@ -38,8 +38,8 @@ package with zero further changes:
38
38
 
39
39
  Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
40
40
  peer-dependency exclusion list, per-sensor permission strings — live in the
41
- `symbiote-expo-native-module` skill. Reference implementation: `.examples/react/ios/Podfile` and
42
- `.examples/react/android/app/src/main/java/com/canary/MainApplication.kt`.
41
+ `symbiote-expo-native-module` skill. Reference implementation: `examples/expo-react/ios/Podfile`
42
+ and `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
43
43
 
44
44
  Permissions ship with the native module itself — nothing to reimplement, just the platform
45
45
  permission string each sensor needs (e.g. DeviceMotion/Pedometer need `NSMotionUsageDescription`
@@ -99,9 +99,9 @@ export class SensorsScreen {
99
99
  }
100
100
  ```
101
101
 
102
- The React example above mirrors the real demo screen, `.examples/react/screens/SensorsScreen.tsx`
103
- — the Vue and Angular snippets are illustrative; those adapters' own demo screens aren't wired up
104
- yet (React is this repo's "prove the pattern first" adapter for this package).
102
+ All three examples above mirror the real canary demo screens —
103
+ `examples/expo-react/screens/SensorsScreen.tsx`, `examples/expo-vue-sfc/screens/SensorsScreen.vue`,
104
+ `examples/expo-vue-tsx/screens/SensorsScreen.tsx`, `examples/expo-angular/src/screens/SensorsScreen.ts`.
105
105
 
106
106
  Every hook/composable/`connect()` takes an optional `updateIntervalMs` and returns `null` until the
107
107
  first native reading arrives — check `isAvailableAsync()` separately if you need to distinguish
@@ -0,0 +1,52 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-sensors",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.sensors.modules.AccelerometerModule",
7
+ "className": "AccelerometerModule",
8
+ "nativeName": "ExponentAccelerometer"
9
+ },
10
+ {
11
+ "importPath": "expo.modules.sensors.modules.BarometerModule",
12
+ "className": "BarometerModule",
13
+ "nativeName": "ExpoBarometer"
14
+ },
15
+ {
16
+ "importPath": "expo.modules.sensors.modules.DeviceMotionModule",
17
+ "className": "DeviceMotionModule",
18
+ "nativeName": "ExponentDeviceMotion"
19
+ },
20
+ {
21
+ "importPath": "expo.modules.sensors.modules.GyroscopeModule",
22
+ "className": "GyroscopeModule",
23
+ "nativeName": "ExponentGyroscope"
24
+ },
25
+ {
26
+ "importPath": "expo.modules.sensors.modules.LightSensorModule",
27
+ "className": "LightSensorModule",
28
+ "nativeName": "ExpoLightSensor"
29
+ },
30
+ {
31
+ "importPath": "expo.modules.sensors.modules.MagnetometerModule",
32
+ "className": "MagnetometerModule",
33
+ "nativeName": "ExponentMagnetometer"
34
+ },
35
+ {
36
+ "importPath": "expo.modules.sensors.modules.MagnetometerUncalibratedModule",
37
+ "className": "MagnetometerUncalibratedModule",
38
+ "nativeName": "ExponentMagnetometerUncalibrated"
39
+ },
40
+ {
41
+ "importPath": "expo.modules.sensors.modules.PedometerModule",
42
+ "className": "PedometerModule",
43
+ "nativeName": "ExponentPedometer"
44
+ }
45
+ ]
46
+ },
47
+ "ios": {
48
+ "infoPlistKeys": {
49
+ "NSMotionUsageDescription": "This app uses motion sensor data."
50
+ }
51
+ }
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/sensors",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "expo-sensors wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Accelerometer, Barometer, DeviceMotion, Gyroscope, LightSensor, Magnetometer, MagnetometerUncalibrated, Pedometer.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,11 @@
39
39
  "files": [
40
40
  "src",
41
41
  "build",
42
- "build-ngc"
42
+ "build-ngc",
43
+ "native-link.json",
44
+ "!src/**/*.test.*",
45
+ "!src/**/*.spec.*",
46
+ "!src/**/*.detox.*"
43
47
  ],
44
48
  "publishConfig": {
45
49
  "access": "public"
@@ -55,7 +59,7 @@
55
59
  "react": ">=19.0.0",
56
60
  "react-native": ">=0.86",
57
61
  "vue": ">=3.5.0",
58
- "@symbiote-native/angular": "0.6.1",
62
+ "@symbiote-native/angular": "0.6.2",
59
63
  "@symbiote-native/react": "0.2.8",
60
64
  "@symbiote-native/vue": "0.3.8"
61
65
  },
@@ -91,11 +95,11 @@
91
95
  "@vue/runtime-core": "^3.5.13",
92
96
  "react": "19.2.3",
93
97
  "typescript": "~6.0.0",
94
- "@symbiote-native/angular": "0.6.1",
95
- "@symbiote-native/vue": "0.3.8",
96
- "@symbiote-native/test-utils": "0.1.6",
98
+ "@symbiote-native/angular": "0.6.2",
97
99
  "@symbiote-native/engine": "0.1.7",
98
- "@symbiote-native/react": "0.2.8"
100
+ "@symbiote-native/react": "0.2.8",
101
+ "@symbiote-native/test-utils": "0.1.6",
102
+ "@symbiote-native/vue": "0.3.8"
99
103
  },
100
104
  "scripts": {
101
105
  "typecheck": "tsc --build",
@@ -1,118 +0,0 @@
1
- // Co-located Angular-driven test (ADR 0025) for AccelerometerService. Mounts a real host
2
- // component through @symbiote-native/angular so `connect()` runs the same way an app would call
3
- // it — inside the component's own injection context — and drives the returned signal through a
4
- // full mount/unmount lifecycle, because `effect()`'s injector-scoped cleanup only fires
5
- // correctly when torn down through a real Angular injection context.
6
-
7
- import '@angular/compiler';
8
- import { Component, inject, type Signal } from '@angular/core';
9
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
10
- import { mount, unmount } from '@symbiote-native/angular';
11
- import { installFabric } from '@symbiote-native/test-utils';
12
- import type { IAccelerometerMeasurement } from '../../../core';
13
- import { AccelerometerService } from './index';
14
-
15
- const addListenerMock = vi.fn();
16
- const removeMock = vi.fn();
17
- const setUpdateIntervalMock = vi.fn();
18
-
19
- vi.mock('../../../core', () => ({
20
- Accelerometer: {
21
- addListener: (listener: (measurement: IAccelerometerMeasurement) => void) =>
22
- addListenerMock(listener),
23
- setUpdateInterval: (intervalMs: number) => setUpdateIntervalMock(intervalMs),
24
- },
25
- }));
26
-
27
- const ROOT_TAG = 941;
28
- const fabric = installFabric();
29
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
30
-
31
- const MEASUREMENT: IAccelerometerMeasurement = { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 };
32
-
33
- let capturedResult: Signal<IAccelerometerMeasurement | null> | undefined;
34
- let capturedListener: ((measurement: IAccelerometerMeasurement) => void) | undefined;
35
-
36
- @Component({
37
- selector: 'symbiote-accelerometer-host',
38
- standalone: true,
39
- template: '',
40
- })
41
- class AccelerometerHost {
42
- readonly measurement = inject(AccelerometerService).connect();
43
-
44
- constructor() {
45
- capturedResult = this.measurement;
46
- }
47
- }
48
-
49
- @Component({
50
- selector: 'symbiote-accelerometer-interval-host',
51
- standalone: true,
52
- template: '',
53
- })
54
- class AccelerometerIntervalHost {
55
- readonly measurement = inject(AccelerometerService).connect(500);
56
-
57
- constructor() {
58
- capturedResult = this.measurement;
59
- }
60
- }
61
-
62
- beforeEach(() => {
63
- capturedResult = undefined;
64
- capturedListener = undefined;
65
- addListenerMock.mockImplementation(listener => {
66
- capturedListener = listener;
67
- return { remove: removeMock };
68
- });
69
- });
70
-
71
- afterEach(() => {
72
- unmount(ROOT_TAG);
73
- fabric.reset();
74
- vi.clearAllMocks();
75
- });
76
-
77
- describe('AccelerometerService.connect', () => {
78
- it('reports null before any measurement event fires', async () => {
79
- mount(ROOT_TAG, AccelerometerHost);
80
- await tick();
81
-
82
- expect(capturedResult?.()).toBeNull();
83
- });
84
-
85
- it('updates the signal when the registered listener fires with a measurement', async () => {
86
- mount(ROOT_TAG, AccelerometerHost);
87
- await tick();
88
-
89
- if (capturedListener === undefined) throw new Error('addListener callback was not captured');
90
- capturedListener(MEASUREMENT);
91
-
92
- expect(capturedResult?.()).toEqual(MEASUREMENT);
93
- });
94
-
95
- it('removes the subscription when the host component is unmounted', async () => {
96
- mount(ROOT_TAG, AccelerometerHost);
97
- await tick();
98
-
99
- unmount(ROOT_TAG);
100
- await tick();
101
-
102
- expect(removeMock).toHaveBeenCalledOnce();
103
- });
104
-
105
- it('calls setUpdateInterval with the given interval when one is passed', async () => {
106
- mount(ROOT_TAG, AccelerometerIntervalHost);
107
- await tick();
108
-
109
- expect(setUpdateIntervalMock).toHaveBeenCalledWith(500);
110
- });
111
-
112
- it('does not call setUpdateInterval when no interval is passed', async () => {
113
- mount(ROOT_TAG, AccelerometerHost);
114
- await tick();
115
-
116
- expect(setUpdateIntervalMock).not.toHaveBeenCalled();
117
- });
118
- });
@@ -1,122 +0,0 @@
1
- // Co-located Angular-driven test (ADR 0025) for BarometerService. Mounts a real host
2
- // component through @symbiote-native/angular so `connect()` runs the same way an app would call
3
- // it — inside the component's own injection context — and drives the returned signal through a
4
- // full mount/unmount lifecycle, because `effect()`'s injector-scoped cleanup only fires
5
- // correctly when torn down through a real Angular injection context.
6
-
7
- import '@angular/compiler';
8
- import { Component, inject, type Signal } from '@angular/core';
9
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
10
- import { mount, unmount } from '@symbiote-native/angular';
11
- import { installFabric } from '@symbiote-native/test-utils';
12
- import type { IBarometerMeasurement } from '../../../core';
13
- import { BarometerService } from './index';
14
-
15
- const addListenerMock = vi.fn();
16
- const removeMock = vi.fn();
17
- const setUpdateIntervalMock = vi.fn();
18
-
19
- vi.mock('../../../core', () => ({
20
- Barometer: {
21
- addListener: (listener: (measurement: IBarometerMeasurement) => void) =>
22
- addListenerMock(listener),
23
- setUpdateInterval: (intervalMs: number) => setUpdateIntervalMock(intervalMs),
24
- },
25
- }));
26
-
27
- const ROOT_TAG = 942;
28
- const fabric = installFabric();
29
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
30
-
31
- const MEASUREMENT: IBarometerMeasurement = {
32
- pressure: 1013.25,
33
- relativeAltitude: 12.3,
34
- timestamp: 123,
35
- };
36
-
37
- let capturedResult: Signal<IBarometerMeasurement | null> | undefined;
38
- let capturedListener: ((measurement: IBarometerMeasurement) => void) | undefined;
39
-
40
- @Component({
41
- selector: 'symbiote-barometer-host',
42
- standalone: true,
43
- template: '',
44
- })
45
- class BarometerHost {
46
- readonly measurement = inject(BarometerService).connect();
47
-
48
- constructor() {
49
- capturedResult = this.measurement;
50
- }
51
- }
52
-
53
- @Component({
54
- selector: 'symbiote-barometer-interval-host',
55
- standalone: true,
56
- template: '',
57
- })
58
- class BarometerIntervalHost {
59
- readonly measurement = inject(BarometerService).connect(500);
60
-
61
- constructor() {
62
- capturedResult = this.measurement;
63
- }
64
- }
65
-
66
- beforeEach(() => {
67
- capturedResult = undefined;
68
- capturedListener = undefined;
69
- addListenerMock.mockImplementation(listener => {
70
- capturedListener = listener;
71
- return { remove: removeMock };
72
- });
73
- });
74
-
75
- afterEach(() => {
76
- unmount(ROOT_TAG);
77
- fabric.reset();
78
- vi.clearAllMocks();
79
- });
80
-
81
- describe('BarometerService.connect', () => {
82
- it('reports null before any measurement event fires', async () => {
83
- mount(ROOT_TAG, BarometerHost);
84
- await tick();
85
-
86
- expect(capturedResult?.()).toBeNull();
87
- });
88
-
89
- it('updates the signal when the registered listener fires with a measurement', async () => {
90
- mount(ROOT_TAG, BarometerHost);
91
- await tick();
92
-
93
- if (capturedListener === undefined) throw new Error('addListener callback was not captured');
94
- capturedListener(MEASUREMENT);
95
-
96
- expect(capturedResult?.()).toEqual(MEASUREMENT);
97
- });
98
-
99
- it('removes the subscription when the host component is unmounted', async () => {
100
- mount(ROOT_TAG, BarometerHost);
101
- await tick();
102
-
103
- unmount(ROOT_TAG);
104
- await tick();
105
-
106
- expect(removeMock).toHaveBeenCalledOnce();
107
- });
108
-
109
- it('calls setUpdateInterval with the given interval when one is passed', async () => {
110
- mount(ROOT_TAG, BarometerIntervalHost);
111
- await tick();
112
-
113
- expect(setUpdateIntervalMock).toHaveBeenCalledWith(500);
114
- });
115
-
116
- it('does not call setUpdateInterval when no interval is passed', async () => {
117
- mount(ROOT_TAG, BarometerHost);
118
- await tick();
119
-
120
- expect(setUpdateIntervalMock).not.toHaveBeenCalled();
121
- });
122
- });
@@ -1,125 +0,0 @@
1
- // Co-located Angular-driven test (ADR 0025) for DeviceMotionService. Mounts a real host
2
- // component through @symbiote-native/angular so `connect()` runs the same way an app would call
3
- // it — inside the component's own injection context — and drives the returned signal through a
4
- // full mount/unmount lifecycle, because `effect()`'s injector-scoped cleanup only fires
5
- // correctly when torn down through a real Angular injection context.
6
-
7
- import '@angular/compiler';
8
- import { Component, inject, type Signal } from '@angular/core';
9
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
10
- import { mount, unmount } from '@symbiote-native/angular';
11
- import { installFabric } from '@symbiote-native/test-utils';
12
- import type { IDeviceMotionMeasurement } from '../../../core';
13
- import { DeviceMotionService } from './index';
14
-
15
- const addListenerMock = vi.fn();
16
- const removeMock = vi.fn();
17
- const setUpdateIntervalMock = vi.fn();
18
-
19
- vi.mock('../../../core', () => ({
20
- DeviceMotion: {
21
- addListener: (listener: (measurement: IDeviceMotionMeasurement) => void) =>
22
- addListenerMock(listener),
23
- setUpdateInterval: (intervalMs: number) => setUpdateIntervalMock(intervalMs),
24
- },
25
- }));
26
-
27
- const ROOT_TAG = 942;
28
- const fabric = installFabric();
29
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
30
-
31
- const MEASUREMENT: IDeviceMotionMeasurement = {
32
- acceleration: { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 },
33
- accelerationIncludingGravity: { x: 0.1, y: 0.2, z: 9.8, timestamp: 123 },
34
- rotation: { alpha: 1, beta: 2, gamma: 3, timestamp: 123 },
35
- rotationRate: { alpha: 0.1, beta: 0.2, gamma: 0.3, timestamp: 123 },
36
- interval: 16,
37
- orientation: 0, // DeviceMotionOrientation.Portrait
38
- };
39
-
40
- let capturedResult: Signal<IDeviceMotionMeasurement | null> | undefined;
41
- let capturedListener: ((measurement: IDeviceMotionMeasurement) => void) | undefined;
42
-
43
- @Component({
44
- selector: 'symbiote-device-motion-host',
45
- standalone: true,
46
- template: '',
47
- })
48
- class DeviceMotionHost {
49
- readonly measurement = inject(DeviceMotionService).connect();
50
-
51
- constructor() {
52
- capturedResult = this.measurement;
53
- }
54
- }
55
-
56
- @Component({
57
- selector: 'symbiote-device-motion-interval-host',
58
- standalone: true,
59
- template: '',
60
- })
61
- class DeviceMotionIntervalHost {
62
- readonly measurement = inject(DeviceMotionService).connect(500);
63
-
64
- constructor() {
65
- capturedResult = this.measurement;
66
- }
67
- }
68
-
69
- beforeEach(() => {
70
- capturedResult = undefined;
71
- capturedListener = undefined;
72
- addListenerMock.mockImplementation(listener => {
73
- capturedListener = listener;
74
- return { remove: removeMock };
75
- });
76
- });
77
-
78
- afterEach(() => {
79
- unmount(ROOT_TAG);
80
- fabric.reset();
81
- vi.clearAllMocks();
82
- });
83
-
84
- describe('DeviceMotionService.connect', () => {
85
- it('reports null before any measurement event fires', async () => {
86
- mount(ROOT_TAG, DeviceMotionHost);
87
- await tick();
88
-
89
- expect(capturedResult?.()).toBeNull();
90
- });
91
-
92
- it('updates the signal when the registered listener fires with a measurement', async () => {
93
- mount(ROOT_TAG, DeviceMotionHost);
94
- await tick();
95
-
96
- if (capturedListener === undefined) throw new Error('addListener callback was not captured');
97
- capturedListener(MEASUREMENT);
98
-
99
- expect(capturedResult?.()).toEqual(MEASUREMENT);
100
- });
101
-
102
- it('removes the subscription when the host component is unmounted', async () => {
103
- mount(ROOT_TAG, DeviceMotionHost);
104
- await tick();
105
-
106
- unmount(ROOT_TAG);
107
- await tick();
108
-
109
- expect(removeMock).toHaveBeenCalledOnce();
110
- });
111
-
112
- it('calls setUpdateInterval with the given interval when one is passed', async () => {
113
- mount(ROOT_TAG, DeviceMotionIntervalHost);
114
- await tick();
115
-
116
- expect(setUpdateIntervalMock).toHaveBeenCalledWith(500);
117
- });
118
-
119
- it('does not call setUpdateInterval when no interval is passed', async () => {
120
- mount(ROOT_TAG, DeviceMotionHost);
121
- await tick();
122
-
123
- expect(setUpdateIntervalMock).not.toHaveBeenCalled();
124
- });
125
- });
@@ -1,118 +0,0 @@
1
- // Co-located Angular-driven test (ADR 0025) for GyroscopeService. Mounts a real host
2
- // component through @symbiote-native/angular so `connect()` runs the same way an app would call
3
- // it — inside the component's own injection context — and drives the returned signal through a
4
- // full mount/unmount lifecycle, because `effect()`'s injector-scoped cleanup only fires
5
- // correctly when torn down through a real Angular injection context.
6
-
7
- import '@angular/compiler';
8
- import { Component, inject, type Signal } from '@angular/core';
9
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
10
- import { mount, unmount } from '@symbiote-native/angular';
11
- import { installFabric } from '@symbiote-native/test-utils';
12
- import type { IGyroscopeMeasurement } from '../../../core';
13
- import { GyroscopeService } from './index';
14
-
15
- const addListenerMock = vi.fn();
16
- const removeMock = vi.fn();
17
- const setUpdateIntervalMock = vi.fn();
18
-
19
- vi.mock('../../../core', () => ({
20
- Gyroscope: {
21
- addListener: (listener: (measurement: IGyroscopeMeasurement) => void) =>
22
- addListenerMock(listener),
23
- setUpdateInterval: (intervalMs: number) => setUpdateIntervalMock(intervalMs),
24
- },
25
- }));
26
-
27
- const ROOT_TAG = 942;
28
- const fabric = installFabric();
29
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
30
-
31
- const MEASUREMENT: IGyroscopeMeasurement = { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 };
32
-
33
- let capturedResult: Signal<IGyroscopeMeasurement | null> | undefined;
34
- let capturedListener: ((measurement: IGyroscopeMeasurement) => void) | undefined;
35
-
36
- @Component({
37
- selector: 'symbiote-gyroscope-host',
38
- standalone: true,
39
- template: '',
40
- })
41
- class GyroscopeHost {
42
- readonly measurement = inject(GyroscopeService).connect();
43
-
44
- constructor() {
45
- capturedResult = this.measurement;
46
- }
47
- }
48
-
49
- @Component({
50
- selector: 'symbiote-gyroscope-interval-host',
51
- standalone: true,
52
- template: '',
53
- })
54
- class GyroscopeIntervalHost {
55
- readonly measurement = inject(GyroscopeService).connect(500);
56
-
57
- constructor() {
58
- capturedResult = this.measurement;
59
- }
60
- }
61
-
62
- beforeEach(() => {
63
- capturedResult = undefined;
64
- capturedListener = undefined;
65
- addListenerMock.mockImplementation(listener => {
66
- capturedListener = listener;
67
- return { remove: removeMock };
68
- });
69
- });
70
-
71
- afterEach(() => {
72
- unmount(ROOT_TAG);
73
- fabric.reset();
74
- vi.clearAllMocks();
75
- });
76
-
77
- describe('GyroscopeService.connect', () => {
78
- it('reports null before any measurement event fires', async () => {
79
- mount(ROOT_TAG, GyroscopeHost);
80
- await tick();
81
-
82
- expect(capturedResult?.()).toBeNull();
83
- });
84
-
85
- it('updates the signal when the registered listener fires with a measurement', async () => {
86
- mount(ROOT_TAG, GyroscopeHost);
87
- await tick();
88
-
89
- if (capturedListener === undefined) throw new Error('addListener callback was not captured');
90
- capturedListener(MEASUREMENT);
91
-
92
- expect(capturedResult?.()).toEqual(MEASUREMENT);
93
- });
94
-
95
- it('removes the subscription when the host component is unmounted', async () => {
96
- mount(ROOT_TAG, GyroscopeHost);
97
- await tick();
98
-
99
- unmount(ROOT_TAG);
100
- await tick();
101
-
102
- expect(removeMock).toHaveBeenCalledOnce();
103
- });
104
-
105
- it('calls setUpdateInterval with the given interval when one is passed', async () => {
106
- mount(ROOT_TAG, GyroscopeIntervalHost);
107
- await tick();
108
-
109
- expect(setUpdateIntervalMock).toHaveBeenCalledWith(500);
110
- });
111
-
112
- it('does not call setUpdateInterval when no interval is passed', async () => {
113
- mount(ROOT_TAG, GyroscopeHost);
114
- await tick();
115
-
116
- expect(setUpdateIntervalMock).not.toHaveBeenCalled();
117
- });
118
- });