@symbiote-native/store-review 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
@@ -114,8 +114,9 @@ export class RateAppButton {
114
114
  }
115
115
  ```
116
116
 
117
- These snippets are not yet demoed in a real canary screen — treat them as realistic reference
118
- code, not a trimmed-down copy of a running demo.
117
+ These snippets mirror the real canary demo screens — `examples/expo-react/screens/StoreReviewScreen.tsx`,
118
+ `examples/expo-vue-sfc/screens/StoreReviewScreen.vue`, `examples/expo-vue-tsx/screens/StoreReviewScreen.tsx`,
119
+ `examples/expo-angular/src/screens/StoreReviewScreen.ts`.
119
120
 
120
121
  ## API
121
122
 
@@ -1,4 +1,4 @@
1
1
  // @symbiote-native/store-review/angular: the Angular entry over the framework-agnostic core.
2
2
  // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
3
  // service, so this is a plain re-export.
4
- export * from '../core';
4
+ export * from '../core/index.js';
@@ -1 +1 @@
1
- export { isAvailableAsync, requestReview, hasAction } from './store-review';
1
+ export { isAvailableAsync, requestReview, hasAction } from './store-review.js';
@@ -8,7 +8,7 @@
8
8
  // upstream's implicit `Constants` read.
9
9
  import { Platform } from 'expo-modules-core';
10
10
  import { Linking } from 'react-native';
11
- import { expoStoreReview } from './native-module';
11
+ import { expoStoreReview } from './native-module.js';
12
12
  /**
13
13
  * Determines if the platform has the capabilities to use `requestReview()`'s native flow.
14
14
  * @return
@@ -2,4 +2,4 @@
2
2
  // Upstream ships free async functions, no per-instance state and no event stream — there is
3
3
  // nothing for a hook to wrap, so this is a plain re-export, mirroring
4
4
  // @symbiote-native/device's/@symbiote-native/local-auth's React entry.
5
- export * from '../core';
5
+ export * from '../core/index.js';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/store-review/svelte: the Svelte entry over the framework-agnostic core.
2
+ // Upstream ships free async functions, no per-instance state and no event stream — there is
3
+ // nothing for a rune to wrap, so this is a plain re-export. Svelte's lifecycle bucket is
4
+ // `runes/` (adapters/svelte/src/runes — never React's `hooks` or Vue's `composables`), and this
5
+ // package has none to fill.
6
+ export * from '../core/index.js';
@@ -1,4 +1,4 @@
1
1
  // @symbiote-native/store-review/vue: the Vue entry over the framework-agnostic core. Same
2
2
  // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
3
  // reactivity, so this is a plain re-export.
4
- export * from '../core';
4
+ export * from '../core/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/store-review",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "expo-store-review wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Prompts the platform's native in-app review flow, with a caller-supplied store-URL fallback in place of upstream's expo-constants-based lookup (this project doesn't depend on expo-constants).",
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/react": "0.2.8",
60
- "@symbiote-native/angular": "0.6.1",
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/vue": "0.3.8",
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",
98
115
  "@symbiote-native/test-utils": "0.1.6",
99
- "@symbiote-native/engine": "0.1.7"
116
+ "@symbiote-native/vue": "0.4.0"
100
117
  },
101
118
  "scripts": {
102
119
  "typecheck": "tsc --build",
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/store-review/svelte: the Svelte entry over the framework-agnostic core.
2
+ // Upstream ships free async functions, no per-instance state and no event stream — there is
3
+ // nothing for a rune to wrap, so this is a plain re-export. Svelte's lifecycle bucket is
4
+ // `runes/` (adapters/svelte/src/runes — never React's `hooks` or Vue's `composables`), and this
5
+ // package has none to fill.
6
+ export * from '../core';
@@ -1,150 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
-
3
- const FAKE_NATIVE_STORE_REVIEW = {
4
- isAvailableAsync: vi.fn(async () => true),
5
- requestReview: vi.fn(async () => undefined),
6
- };
7
-
8
- const fakePlatform = { OS: 'ios' as 'ios' | 'android' | 'web' };
9
-
10
- const fakeLinking = {
11
- canOpenURL: vi.fn(async () => true),
12
- openURL: vi.fn(async () => undefined),
13
- };
14
-
15
- // The real ExpoStoreReview native module only exists on device — resolving it via
16
- // requireNativeModule() at import time would throw in this headless test run, so the
17
- // module-lookup file is faked in place of expo-modules-core's runtime resolution, the same
18
- // pattern packages/device/src/core/device.test.ts uses.
19
- vi.mock('./native-module', () => ({
20
- expoStoreReview: FAKE_NATIVE_STORE_REVIEW,
21
- }));
22
-
23
- // expo-modules-core's real entry transitively imports 'react-native' for Platform/
24
- // TurboModuleRegistry, whose Flow-typed source Vitest's Oxc transform can't parse — same fake
25
- // packages/application/src/core/application.test.ts uses.
26
- vi.mock('expo-modules-core', () => ({
27
- Platform: fakePlatform,
28
- }));
29
-
30
- // react-native's own Linking is only reached by the URL-fallback branch (no native review flow
31
- // available) — stubbed the same way packages/system-ui/src/core/system-ui.test.ts stubs
32
- // react-native's Platform/processColor.
33
- vi.mock('react-native', () => ({
34
- Linking: fakeLinking,
35
- }));
36
-
37
- const { isAvailableAsync, requestReview, hasAction } = await import('./store-review');
38
-
39
- afterEach(() => {
40
- fakePlatform.OS = 'ios';
41
- FAKE_NATIVE_STORE_REVIEW.isAvailableAsync = vi.fn(async () => true);
42
- FAKE_NATIVE_STORE_REVIEW.requestReview = vi.fn(async () => undefined);
43
- vi.clearAllMocks();
44
- });
45
-
46
- describe('isAvailableAsync', () => {
47
- it('delegates to the native module', async () => {
48
- await expect(isAvailableAsync()).resolves.toBe(true);
49
- });
50
-
51
- it('resolves false instead of throwing when the native method is absent', async () => {
52
- // @ts-expect-error -- simulating a platform where the native module has no such method
53
- FAKE_NATIVE_STORE_REVIEW.isAvailableAsync = undefined;
54
-
55
- await expect(isAvailableAsync()).resolves.toBe(false);
56
- });
57
- });
58
-
59
- describe('requestReview — native review path', () => {
60
- it('calls the native requestReview and never touches Linking', async () => {
61
- await requestReview({ iosAppStoreUrl: 'https://apps.apple.com/app/id123' });
62
-
63
- expect(FAKE_NATIVE_STORE_REVIEW.requestReview).toHaveBeenCalled();
64
- expect(fakeLinking.canOpenURL).not.toHaveBeenCalled();
65
- expect(fakeLinking.openURL).not.toHaveBeenCalled();
66
- });
67
- });
68
-
69
- describe('requestReview — URL-fallback path (no native review flow)', () => {
70
- it('opens the supplied iOS App Store URL when Linking can open it', async () => {
71
- // @ts-expect-error -- simulating a platform with no native review flow
72
- FAKE_NATIVE_STORE_REVIEW.requestReview = undefined;
73
-
74
- await requestReview({ iosAppStoreUrl: 'https://apps.apple.com/app/id123' });
75
-
76
- expect(fakeLinking.canOpenURL).toHaveBeenCalledWith('https://apps.apple.com/app/id123');
77
- expect(fakeLinking.openURL).toHaveBeenCalledWith('https://apps.apple.com/app/id123');
78
- });
79
-
80
- it('opens the supplied Android Play Store URL on android', async () => {
81
- // @ts-expect-error -- simulating a platform with no native review flow
82
- FAKE_NATIVE_STORE_REVIEW.requestReview = undefined;
83
- fakePlatform.OS = 'android';
84
-
85
- await requestReview({
86
- androidPlayStoreUrl: 'https://play.google.com/store/apps/details?id=com.app',
87
- });
88
-
89
- expect(fakeLinking.canOpenURL).toHaveBeenCalledWith(
90
- 'https://play.google.com/store/apps/details?id=com.app',
91
- );
92
- expect(fakeLinking.openURL).toHaveBeenCalledWith(
93
- 'https://play.google.com/store/apps/details?id=com.app',
94
- );
95
- });
96
-
97
- it('warns and skips openURL when Linking reports the URL cannot be opened', async () => {
98
- // @ts-expect-error -- simulating a platform with no native review flow
99
- FAKE_NATIVE_STORE_REVIEW.requestReview = undefined;
100
- fakeLinking.canOpenURL.mockResolvedValueOnce(false);
101
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
102
-
103
- await requestReview({ iosAppStoreUrl: 'https://apps.apple.com/app/id123' });
104
-
105
- expect(fakeLinking.openURL).not.toHaveBeenCalled();
106
- expect(warnSpy).toHaveBeenCalledWith(
107
- "StoreReview.requestReview(): Can't open store url: https://apps.apple.com/app/id123",
108
- );
109
-
110
- warnSpy.mockRestore();
111
- });
112
-
113
- it('warns and never touches Linking when no URL was provided', async () => {
114
- // @ts-expect-error -- simulating a platform with no native review flow
115
- FAKE_NATIVE_STORE_REVIEW.requestReview = undefined;
116
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
117
-
118
- await requestReview();
119
-
120
- expect(fakeLinking.canOpenURL).not.toHaveBeenCalled();
121
- expect(fakeLinking.openURL).not.toHaveBeenCalled();
122
- expect(warnSpy).toHaveBeenCalledWith(
123
- expect.stringContaining('no native review flow and no store URL was provided'),
124
- );
125
-
126
- warnSpy.mockRestore();
127
- });
128
- });
129
-
130
- describe('hasAction', () => {
131
- it('resolves true when a store URL was supplied, without checking native availability', async () => {
132
- // @ts-expect-error -- simulating a platform with no native review flow
133
- FAKE_NATIVE_STORE_REVIEW.isAvailableAsync = undefined;
134
-
135
- await expect(hasAction({ iosAppStoreUrl: 'https://apps.apple.com/app/id123' })).resolves.toBe(
136
- true,
137
- );
138
- });
139
-
140
- it('falls back to native availability when no store URL was supplied', async () => {
141
- await expect(hasAction()).resolves.toBe(true);
142
- });
143
-
144
- it('resolves false when there is neither a store URL nor a native review flow', async () => {
145
- // @ts-expect-error -- simulating a platform with no native review flow
146
- FAKE_NATIVE_STORE_REVIEW.isAvailableAsync = undefined;
147
-
148
- await expect(hasAction()).resolves.toBe(false);
149
- });
150
- });