@symbiote-native/network 0.0.1 → 0.2.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/README.md CHANGED
@@ -40,8 +40,8 @@ with zero further changes:
40
40
 
41
41
  Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
42
42
  peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
43
- implementation: `.examples/expo-react/ios/Podfile` and
44
- `.examples/expo-react/android/app/src/main/java/com/canary/MainApplication.kt`.
43
+ implementation: `examples/expo-react/ios/Podfile` and
44
+ `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
45
45
 
46
46
  No platform permission string is needed for network's surface — connection state, IP address, and
47
47
  airplane-mode detection all read system state with no runtime permission prompt on either
@@ -1,2 +1,2 @@
1
1
  export { NetworkStateService } from './services/network-state.service';
2
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, type NetworkState, type NetworkStateEvent, type EventSubscription, } from '../core';
2
+ export * from '../core';
@@ -1,4 +1,4 @@
1
1
  // NetworkStateService is the Angular-only lifecycle half; the free functions and event
2
2
  // subscription plumbing all live in core, shared with React/Vue.
3
3
  export { NetworkStateService } from './services/network-state.service/index.js';
4
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, } from '../core/index.js';
4
+ export * from '../core/index.js';
@@ -1,2 +1,2 @@
1
1
  export { useNetworkState } from './hooks/use-network-state';
2
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, type NetworkState, type NetworkStateEvent, type EventSubscription, } from '../core';
2
+ export * from '../core';
@@ -1,3 +1,3 @@
1
1
  // @symbiote-native/network/react: the React entry over the framework-agnostic core.
2
2
  export { useNetworkState } from './hooks/use-network-state/index.js';
3
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, } from '../core/index.js';
3
+ export * from '../core/index.js';
@@ -0,0 +1,2 @@
1
+ export { useNetworkState } from './runes/use-network-state.svelte';
2
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/network/svelte: the Svelte entry over the framework-agnostic core — mirrors the
2
+ // lifecycle-bucket naming convention of adapters/svelte/src/runes (never `hooks`/`composables`,
3
+ // those are React's and Vue's terms).
4
+ export { useNetworkState } from './runes/use-network-state.svelte';
5
+ export * from '../core/index.js';
@@ -0,0 +1,4 @@
1
+ import { type NetworkState } from '../../core';
2
+ export declare function useNetworkState(): {
3
+ readonly current: NetworkState;
4
+ };
@@ -0,0 +1,29 @@
1
+ // Svelte lifecycle wiring over the framework-agnostic core (core/network.ts). Seeds the initial
2
+ // value with a one-shot getNetworkStateAsync() call before the first native event fires, matching
3
+ // upstream's own useNetworkState and the Vue composable's onMounted half.
4
+ //
5
+ // `.svelte.ts` extension: runes ($state/$effect) only work there outside a `.svelte` component;
6
+ // `runes/` is Svelte's name for the lifecycle bucket (React's `hooks/`, Vue's `composables/`).
7
+ // Returns a boxed getter, not a bare `$state`: Svelte 5 reactivity is lexically scoped to the
8
+ // declaring module and doesn't survive being returned raw from a plain function, so the caller
9
+ // reads `.current` like unwrapping Vue's `Ref` via `.value`.
10
+ import { addNetworkStateListener, getNetworkStateAsync, } from '../../core/index.js';
11
+ export function useNetworkState() {
12
+ let networkState = $state({});
13
+ $effect(() => {
14
+ // Write-only touches of `networkState`, so the effect has no dependency on it and runs once
15
+ // per mount - the twin of Vue's onMounted/onUnmounted pair.
16
+ getNetworkStateAsync().then(state => {
17
+ networkState = state;
18
+ });
19
+ const subscription = addNetworkStateListener(event => {
20
+ networkState = event;
21
+ });
22
+ return () => subscription.remove();
23
+ });
24
+ return {
25
+ get current() {
26
+ return networkState;
27
+ },
28
+ };
29
+ }
@@ -1,2 +1,2 @@
1
1
  export { useNetworkState } from './composables/use-network-state';
2
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, type NetworkState, type NetworkStateEvent, type EventSubscription, } from '../core';
2
+ export * from '../core';
@@ -1,3 +1,3 @@
1
1
  // @symbiote-native/network/vue: the Vue entry over the framework-agnostic core.
2
2
  export { useNetworkState } from './composables/use-network-state/index.js';
3
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, } from '../core/index.js';
3
+ export * from '../core/index.js';
@@ -1,2 +1,2 @@
1
1
  export { NetworkStateService } from './services/network-state.service';
2
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, type NetworkState, type NetworkStateEvent, type EventSubscription, } from '../core';
2
+ export * from '../core';
@@ -1,5 +1,5 @@
1
1
  // NetworkStateService is the Angular-only lifecycle half; the free functions and event
2
2
  // subscription plumbing all live in core, shared with React/Vue.
3
3
  export { NetworkStateService } from './services/network-state.service';
4
- export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, NetworkStateType, } from '../core';
4
+ export * from '../core';
5
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,iEAAiE;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,EACvB,gBAAgB,GAIjB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,iEAAiE;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,cAAc,SAAS,CAAC"}
@@ -17,10 +17,10 @@ export class NetworkStateService {
17
17
  }, { injector: this.injector });
18
18
  return networkState.asReadonly();
19
19
  }
20
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NetworkStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
21
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NetworkStateService, providedIn: 'root' });
20
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NetworkStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
21
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NetworkStateService, providedIn: 'root' });
22
22
  }
23
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NetworkStateService, decorators: [{
23
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NetworkStateService, decorators: [{
24
24
  type: Injectable,
25
25
  args: [{ providedIn: 'root' }]
26
26
  }] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/network",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "expo-network wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Network state (type/connected/internet-reachable) with a change listener, device IP address, and airplane-mode detection.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,6 +30,10 @@
30
30
  "types": "./build/react/index.d.ts",
31
31
  "default": "./build/react/index.js"
32
32
  },
33
+ "./svelte": {
34
+ "types": "./build/svelte/index.d.ts",
35
+ "default": "./build/svelte/index.js"
36
+ },
33
37
  "./angular": {
34
38
  "types": "./build-ngc/angular/index.d.ts",
35
39
  "react-native": "./build-ngc/angular/index.js",
@@ -40,7 +44,10 @@
40
44
  "src",
41
45
  "build",
42
46
  "build-ngc",
43
- "native-link.json"
47
+ "native-link.json",
48
+ "!src/**/*.test.*",
49
+ "!src/**/*.spec.*",
50
+ "!src/**/*.detox.*"
44
51
  ],
45
52
  "publishConfig": {
46
53
  "access": "public"
@@ -50,15 +57,17 @@
50
57
  "expo-modules-core": "57.0.5"
51
58
  },
52
59
  "peerDependencies": {
60
+ "@symbiote-native/angular": ">=0.6.2",
53
61
  "@symbiote-native/engine": ">=0.1.7",
62
+ "@symbiote-native/react": ">=0.2.8",
63
+ "@symbiote-native/svelte": ">=0.1.0",
64
+ "@symbiote-native/vue": ">=0.3.8",
54
65
  "@angular/core": ">=20",
55
66
  "@vue/runtime-core": "^3.5.13",
56
67
  "react": ">=19.0.0",
57
68
  "react-native": ">=0.86",
58
- "vue": ">=3.5.0",
59
- "@symbiote-native/angular": "0.6.1",
60
- "@symbiote-native/react": "0.2.8",
61
- "@symbiote-native/vue": "0.3.8"
69
+ "svelte": ">=5.56.0",
70
+ "vue": ">=3.5.0"
62
71
  },
63
72
  "peerDependenciesMeta": {
64
73
  "@symbiote-native/angular": {
@@ -67,6 +76,9 @@
67
76
  "@symbiote-native/react": {
68
77
  "optional": true
69
78
  },
79
+ "@symbiote-native/svelte": {
80
+ "optional": true
81
+ },
70
82
  "@symbiote-native/vue": {
71
83
  "optional": true
72
84
  },
@@ -79,24 +91,29 @@
79
91
  "react": {
80
92
  "optional": true
81
93
  },
94
+ "svelte": {
95
+ "optional": true
96
+ },
82
97
  "vue": {
83
98
  "optional": true
84
99
  }
85
100
  },
86
101
  "devDependencies": {
87
- "@angular/compiler": "^22",
88
- "@angular/compiler-cli": "^22",
89
- "@angular/core": "^22",
102
+ "@angular/compiler": "~22.0.8",
103
+ "@angular/compiler-cli": "~22.0.8",
104
+ "@angular/core": "~22.0.8",
90
105
  "@types/node": "^26.0.0",
91
106
  "@types/react": "^19.2.0",
92
107
  "@vue/runtime-core": "^3.5.13",
93
108
  "react": "19.2.3",
109
+ "svelte": "^5.56.0",
94
110
  "typescript": "~6.0.0",
95
- "@symbiote-native/angular": "0.6.1",
96
- "@symbiote-native/react": "0.2.8",
97
- "@symbiote-native/engine": "0.1.7",
98
- "@symbiote-native/vue": "0.3.8",
99
- "@symbiote-native/test-utils": "0.1.6"
111
+ "@symbiote-native/angular": "0.7.0",
112
+ "@symbiote-native/engine": "0.2.0",
113
+ "@symbiote-native/react": "0.3.0",
114
+ "@symbiote-native/svelte": "0.2.0",
115
+ "@symbiote-native/test-utils": "0.1.6",
116
+ "@symbiote-native/vue": "0.4.0"
100
117
  },
101
118
  "scripts": {
102
119
  "typecheck": "tsc --build",
@@ -1,13 +1,4 @@
1
1
  // NetworkStateService is the Angular-only lifecycle half; the free functions and event
2
2
  // subscription plumbing all live in core, shared with React/Vue.
3
3
  export { NetworkStateService } from './services/network-state.service';
4
- export {
5
- getNetworkStateAsync,
6
- getIpAddressAsync,
7
- isAirplaneModeEnabledAsync,
8
- addNetworkStateListener,
9
- NetworkStateType,
10
- type NetworkState,
11
- type NetworkStateEvent,
12
- type EventSubscription,
13
- } from '../core';
4
+ export * from '../core';
@@ -1,13 +1,4 @@
1
1
  // @symbiote-native/network/react: the React entry over the framework-agnostic core.
2
2
 
3
3
  export { useNetworkState } from './hooks/use-network-state';
4
- export {
5
- getNetworkStateAsync,
6
- getIpAddressAsync,
7
- isAirplaneModeEnabledAsync,
8
- addNetworkStateListener,
9
- NetworkStateType,
10
- type NetworkState,
11
- type NetworkStateEvent,
12
- type EventSubscription,
13
- } from '../core';
4
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/network/svelte: the Svelte entry over the framework-agnostic core — mirrors the
2
+ // lifecycle-bucket naming convention of adapters/svelte/src/runes (never `hooks`/`composables`,
3
+ // those are React's and Vue's terms).
4
+
5
+ export { useNetworkState } from './runes/use-network-state.svelte';
6
+ export * from '../core';
@@ -0,0 +1,37 @@
1
+ // Svelte lifecycle wiring over the framework-agnostic core (core/network.ts). Seeds the initial
2
+ // value with a one-shot getNetworkStateAsync() call before the first native event fires, matching
3
+ // upstream's own useNetworkState and the Vue composable's onMounted half.
4
+ //
5
+ // `.svelte.ts` extension: runes ($state/$effect) only work there outside a `.svelte` component;
6
+ // `runes/` is Svelte's name for the lifecycle bucket (React's `hooks/`, Vue's `composables/`).
7
+ // Returns a boxed getter, not a bare `$state`: Svelte 5 reactivity is lexically scoped to the
8
+ // declaring module and doesn't survive being returned raw from a plain function, so the caller
9
+ // reads `.current` like unwrapping Vue's `Ref` via `.value`.
10
+ import {
11
+ addNetworkStateListener,
12
+ getNetworkStateAsync,
13
+ type EventSubscription,
14
+ type NetworkState,
15
+ } from '../../core';
16
+
17
+ export function useNetworkState(): { readonly current: NetworkState } {
18
+ let networkState = $state<NetworkState>({});
19
+
20
+ $effect(() => {
21
+ // Write-only touches of `networkState`, so the effect has no dependency on it and runs once
22
+ // per mount - the twin of Vue's onMounted/onUnmounted pair.
23
+ getNetworkStateAsync().then(state => {
24
+ networkState = state;
25
+ });
26
+ const subscription: EventSubscription = addNetworkStateListener(event => {
27
+ networkState = event;
28
+ });
29
+ return () => subscription.remove();
30
+ });
31
+
32
+ return {
33
+ get current(): NetworkState {
34
+ return networkState;
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,6 @@
1
+ // Pulls in svelte's ambient global declarations ($state/$effect/...) for this package's tsc
2
+ // program. adapters/svelte gets these for free because render.ts does a real value import from
3
+ // 'svelte' (any file in a program importing a module loads its ambient globals for the whole
4
+ // program); this package has no such import, so a triple-slash reference does the same job
5
+ // explicitly rather than relying on an incidental side-effect.
6
+ /// <reference types="svelte" />
package/src/vue/index.ts CHANGED
@@ -1,13 +1,4 @@
1
1
  // @symbiote-native/network/vue: the Vue entry over the framework-agnostic core.
2
2
 
3
3
  export { useNetworkState } from './composables/use-network-state';
4
- export {
5
- getNetworkStateAsync,
6
- getIpAddressAsync,
7
- isAirplaneModeEnabledAsync,
8
- addNetworkStateListener,
9
- NetworkStateType,
10
- type NetworkState,
11
- type NetworkStateEvent,
12
- type EventSubscription,
13
- } from '../core';
4
+ export * from '../core';
@@ -1,103 +0,0 @@
1
- // Co-located Angular-driven test (ADR 0025) for NetworkStateService. See battery's
2
- // battery-state.service.test.ts for the shared rationale.
3
-
4
- import '@angular/compiler';
5
- import { Component, inject, type Signal } from '@angular/core';
6
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
- import { mount, unmount } from '@symbiote-native/angular';
8
- import { installFabric } from '@symbiote-native/test-utils';
9
- import { NetworkStateService } from './index';
10
-
11
- type INetworkState = { type?: string; isConnected?: boolean; isInternetReachable?: boolean };
12
-
13
- const addListenerMock = vi.fn();
14
- const removeMock = vi.fn();
15
- const getNetworkStateAsyncMock = vi.fn(async () => ({
16
- type: 'WIFI',
17
- isConnected: true,
18
- isInternetReachable: true,
19
- }));
20
-
21
- vi.mock('../../../core', () => ({
22
- addNetworkStateListener: (listener: (event: INetworkState) => void) => addListenerMock(listener),
23
- getNetworkStateAsync: () => getNetworkStateAsyncMock(),
24
- }));
25
-
26
- const ROOT_TAG = 973;
27
- const fabric = installFabric();
28
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
29
-
30
- let capturedResult: Signal<INetworkState> | undefined;
31
- let capturedListener: ((event: INetworkState) => void) | undefined;
32
-
33
- @Component({
34
- selector: 'symbiote-network-state-host',
35
- standalone: true,
36
- template: '',
37
- })
38
- class NetworkStateHost {
39
- readonly networkState = inject(NetworkStateService).connect();
40
-
41
- constructor() {
42
- capturedResult = this.networkState;
43
- }
44
- }
45
-
46
- beforeEach(() => {
47
- capturedResult = undefined;
48
- capturedListener = undefined;
49
- getNetworkStateAsyncMock.mockResolvedValue({
50
- type: 'WIFI',
51
- isConnected: true,
52
- isInternetReachable: true,
53
- });
54
- addListenerMock.mockImplementation(listener => {
55
- capturedListener = listener;
56
- return { remove: removeMock };
57
- });
58
- });
59
-
60
- afterEach(() => {
61
- unmount(ROOT_TAG);
62
- fabric.reset();
63
- vi.clearAllMocks();
64
- });
65
-
66
- describe('NetworkStateService.connect', () => {
67
- it('reports an empty object before the initial fetch resolves', async () => {
68
- mount(ROOT_TAG, NetworkStateHost);
69
-
70
- expect(capturedResult?.()).toEqual({});
71
- });
72
-
73
- it('reports the fetched state once getNetworkStateAsync() resolves', async () => {
74
- mount(ROOT_TAG, NetworkStateHost);
75
- await tick();
76
-
77
- expect(capturedResult?.()).toEqual({
78
- type: 'WIFI',
79
- isConnected: true,
80
- isInternetReachable: true,
81
- });
82
- });
83
-
84
- it('updates the signal when the registered listener fires', async () => {
85
- mount(ROOT_TAG, NetworkStateHost);
86
- await tick();
87
-
88
- if (capturedListener === undefined) throw new Error('addListener callback was not captured');
89
- capturedListener({ type: 'CELLULAR', isConnected: true, isInternetReachable: false });
90
-
91
- expect(capturedResult?.()?.type).toBe('CELLULAR');
92
- });
93
-
94
- it('removes the subscription when the host component is unmounted', async () => {
95
- mount(ROOT_TAG, NetworkStateHost);
96
- await tick();
97
-
98
- unmount(ROOT_TAG);
99
- await tick();
100
-
101
- expect(removeMock).toHaveBeenCalledOnce();
102
- });
103
- });
@@ -1,103 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
-
3
- const FAKE_NATIVE_NETWORK = {
4
- getNetworkStateAsync: vi.fn(async () => ({
5
- type: 'WIFI',
6
- isConnected: true,
7
- isInternetReachable: true,
8
- })),
9
- getIpAddressAsync: vi.fn(async () => '192.168.1.1'),
10
- isAirplaneModeEnabledAsync: vi.fn(async () => false),
11
- addListener: vi.fn(() => ({ remove: vi.fn() })),
12
- };
13
-
14
- // The real ExpoNetwork native module only exists on device — resolving it via
15
- // requireNativeModule() at import time would throw in this headless test run, so the
16
- // module-lookup file is faked in place of expo-modules-core's runtime resolution, same pattern
17
- // packages/battery/src/core/battery.test.ts uses for its native-module.
18
- vi.mock('./native-module', () => ({
19
- expoNetwork: FAKE_NATIVE_NETWORK,
20
- }));
21
-
22
- // expo-modules-core's real entry transitively imports 'react-native' for Platform/
23
- // TurboModuleRegistry, whose Flow-typed source Vitest's Oxc transform can't parse — same fake
24
- // packages/haptics/src/core/haptics.test.ts uses.
25
- vi.mock('expo-modules-core', () => ({
26
- UnavailabilityError: class UnavailabilityError extends Error {
27
- constructor(moduleName: string, propertyName: string) {
28
- super(`${propertyName} is not available on ${moduleName}`);
29
- }
30
- },
31
- }));
32
-
33
- const {
34
- getNetworkStateAsync,
35
- getIpAddressAsync,
36
- isAirplaneModeEnabledAsync,
37
- addNetworkStateListener,
38
- } = await import('./network');
39
-
40
- afterEach(() => {
41
- vi.clearAllMocks();
42
- });
43
-
44
- describe('getNetworkStateAsync', () => {
45
- it('delegates to the native module', async () => {
46
- await expect(getNetworkStateAsync()).resolves.toEqual({
47
- type: 'WIFI',
48
- isConnected: true,
49
- isInternetReachable: true,
50
- });
51
- });
52
-
53
- it('throws UnavailabilityError when the native method is absent', async () => {
54
- const { getNetworkStateAsync: native } = FAKE_NATIVE_NETWORK;
55
- // @ts-expect-error -- simulating a platform where the native module has no such method
56
- FAKE_NATIVE_NETWORK.getNetworkStateAsync = undefined;
57
-
58
- await expect(getNetworkStateAsync()).rejects.toThrow();
59
-
60
- FAKE_NATIVE_NETWORK.getNetworkStateAsync = native;
61
- });
62
- });
63
-
64
- describe('getIpAddressAsync', () => {
65
- it('delegates to the native module', async () => {
66
- await expect(getIpAddressAsync()).resolves.toBe('192.168.1.1');
67
- });
68
-
69
- it('throws UnavailabilityError when the native method is absent', async () => {
70
- const { getIpAddressAsync: native } = FAKE_NATIVE_NETWORK;
71
- // @ts-expect-error -- simulating a platform where the native module has no such method
72
- FAKE_NATIVE_NETWORK.getIpAddressAsync = undefined;
73
-
74
- await expect(getIpAddressAsync()).rejects.toThrow();
75
-
76
- FAKE_NATIVE_NETWORK.getIpAddressAsync = native;
77
- });
78
- });
79
-
80
- describe('isAirplaneModeEnabledAsync', () => {
81
- it('delegates to the native module', async () => {
82
- await expect(isAirplaneModeEnabledAsync()).resolves.toBe(false);
83
- });
84
-
85
- it('throws UnavailabilityError when the native method is absent', async () => {
86
- const { isAirplaneModeEnabledAsync: native } = FAKE_NATIVE_NETWORK;
87
- // @ts-expect-error -- simulating a platform where the native module has no such method
88
- FAKE_NATIVE_NETWORK.isAirplaneModeEnabledAsync = undefined;
89
-
90
- await expect(isAirplaneModeEnabledAsync()).rejects.toThrow();
91
-
92
- FAKE_NATIVE_NETWORK.isAirplaneModeEnabledAsync = native;
93
- });
94
- });
95
-
96
- describe('addNetworkStateListener', () => {
97
- it('subscribes through the onNetworkStateChanged event name', () => {
98
- const listener = vi.fn();
99
- addNetworkStateListener(listener);
100
-
101
- expect(FAKE_NATIVE_NETWORK.addListener).toHaveBeenCalledWith('onNetworkStateChanged', listener);
102
- });
103
- });
@@ -1,91 +0,0 @@
1
- // Co-located React-driven test (ADR 0025) for useNetworkState. See battery's
2
- // use-battery-state.test.tsx for the shared rationale (mocks `core`, not expo-modules-core
3
- // internals).
4
-
5
- import { createElement, type ReactElement } from 'react';
6
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
- import { mount, unmount, View } from '@symbiote-native/react';
8
- import { installFabric } from '@symbiote-native/test-utils';
9
- import { useNetworkState } from './index';
10
-
11
- type INetworkState = { type?: string; isConnected?: boolean; isInternetReachable?: boolean };
12
-
13
- const { addListener, getNetworkStateAsync, remove } = vi.hoisted(() => {
14
- const remove = vi.fn();
15
- return {
16
- addListener: vi.fn((_listener: (event: INetworkState) => void) => ({ remove })),
17
- getNetworkStateAsync: vi.fn(async () => ({
18
- type: 'WIFI',
19
- isConnected: true,
20
- isInternetReachable: true,
21
- })),
22
- remove,
23
- };
24
- });
25
-
26
- vi.mock('../../../core', () => ({
27
- addNetworkStateListener: addListener,
28
- getNetworkStateAsync,
29
- }));
30
-
31
- const ROOT_TAG = 953;
32
-
33
- const results: INetworkState[] = [];
34
-
35
- function Probe(): ReactElement {
36
- results.push(useNetworkState());
37
- return createElement(View);
38
- }
39
-
40
- const fabric = installFabric();
41
-
42
- beforeEach(() => {
43
- fabric.reset();
44
- results.length = 0;
45
- vi.clearAllMocks();
46
- getNetworkStateAsync.mockResolvedValue({
47
- type: 'WIFI',
48
- isConnected: true,
49
- isInternetReachable: true,
50
- });
51
- });
52
-
53
- afterEach(() => unmount(ROOT_TAG));
54
-
55
- describe('useNetworkState', () => {
56
- it('reports an empty object before the initial fetch resolves', () => {
57
- mount(ROOT_TAG, createElement(Probe));
58
-
59
- expect(results[results.length - 1]).toEqual({});
60
- });
61
-
62
- it('reports the fetched state once the initial getNetworkStateAsync() resolves', async () => {
63
- mount(ROOT_TAG, createElement(Probe));
64
-
65
- await vi.waitFor(() =>
66
- expect(results[results.length - 1]).toEqual({
67
- type: 'WIFI',
68
- isConnected: true,
69
- isInternetReachable: true,
70
- }),
71
- );
72
- });
73
-
74
- it('updates when the native listener fires', async () => {
75
- mount(ROOT_TAG, createElement(Probe));
76
- await vi.waitFor(() => expect(results[results.length - 1].type).toBe('WIFI'));
77
-
78
- const listener = addListener.mock.calls[0][0];
79
- listener({ type: 'CELLULAR', isConnected: true, isInternetReachable: false });
80
-
81
- await vi.waitFor(() => expect(results[results.length - 1].type).toBe('CELLULAR'));
82
- });
83
-
84
- it('unsubscribes from the native listener on unmount', () => {
85
- mount(ROOT_TAG, createElement(Probe));
86
-
87
- unmount(ROOT_TAG);
88
-
89
- expect(remove).toHaveBeenCalledTimes(1);
90
- });
91
- });
@@ -1,100 +0,0 @@
1
- // Co-located Vue-driven test (ADR 0025) for useNetworkState. See battery's
2
- // use-battery-state.test.ts for the shared rationale.
3
-
4
- import { defineComponent, h, type Ref } from '@vue/runtime-core';
5
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
- import { mount, unmount } from '@symbiote-native/vue';
7
- import { installFabric } from '@symbiote-native/test-utils';
8
- import { useNetworkState } from './index';
9
-
10
- const ROOT_TAG = 9953;
11
-
12
- type INetworkState = { type?: string; isConnected?: boolean; isInternetReachable?: boolean };
13
- type IListener = (event: INetworkState) => void;
14
-
15
- let registeredListener: IListener | undefined;
16
- const removeMock = vi.fn();
17
- const addListenerMock = vi.fn((listener: IListener) => {
18
- registeredListener = listener;
19
- return { remove: removeMock };
20
- });
21
- const getNetworkStateAsyncMock = vi.fn(async () => ({
22
- type: 'WIFI',
23
- isConnected: true,
24
- isInternetReachable: true,
25
- }));
26
-
27
- vi.mock('../../../core', () => ({
28
- addNetworkStateListener: (listener: IListener) => addListenerMock(listener),
29
- getNetworkStateAsync: () => getNetworkStateAsyncMock(),
30
- }));
31
-
32
- const fabric = installFabric();
33
-
34
- beforeEach(() => {
35
- fabric.reset();
36
- registeredListener = undefined;
37
- addListenerMock.mockClear();
38
- removeMock.mockClear();
39
- getNetworkStateAsyncMock.mockClear();
40
- getNetworkStateAsyncMock.mockResolvedValue({
41
- type: 'WIFI',
42
- isConnected: true,
43
- isInternetReachable: true,
44
- });
45
- });
46
-
47
- afterEach(() => unmount(ROOT_TAG));
48
-
49
- function mountNetworkState(): Ref<INetworkState> {
50
- let networkState: Ref<INetworkState> | undefined;
51
- mount(
52
- ROOT_TAG,
53
- defineComponent({
54
- setup: () => {
55
- networkState = useNetworkState();
56
- return () => h('symbiote-text', {}, 'network');
57
- },
58
- }),
59
- );
60
- if (networkState === undefined) {
61
- throw new Error('setup() did not run');
62
- }
63
- return networkState;
64
- }
65
-
66
- describe('useNetworkState (Vue)', () => {
67
- it('starts at an empty object before the initial fetch resolves', () => {
68
- const networkState = mountNetworkState();
69
-
70
- expect(networkState.value).toEqual({});
71
- });
72
-
73
- it('updates to the fetched value once getNetworkStateAsync() resolves', async () => {
74
- const networkState = mountNetworkState();
75
-
76
- await vi.waitFor(() =>
77
- expect(networkState.value).toEqual({
78
- type: 'WIFI',
79
- isConnected: true,
80
- isInternetReachable: true,
81
- }),
82
- );
83
- });
84
-
85
- it('updates the ref when the native listener fires', async () => {
86
- const networkState = mountNetworkState();
87
- await vi.waitFor(() => expect(networkState.value.type).toBe('WIFI'));
88
-
89
- registeredListener?.({ type: 'CELLULAR', isConnected: true, isInternetReachable: false });
90
-
91
- expect(networkState.value.type).toBe('CELLULAR');
92
- });
93
-
94
- it('removes the subscription on unmount', () => {
95
- mountNetworkState();
96
- unmount(ROOT_TAG);
97
-
98
- expect(removeMock).toHaveBeenCalledTimes(1);
99
- });
100
- });