@ankhorage/permissions 0.2.2 → 0.2.4
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/CHANGELOG.md +13 -0
- package/README.md +27 -3
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/expo/adapters/mediaLibrary.d.ts.map +1 -1
- package/dist/expo/adapters/mediaLibrary.js +15 -3
- package/dist/expo/adapters/mediaLibrary.js.map +1 -1
- package/dist/expo/adapters/notifications.d.ts.map +1 -1
- package/dist/expo/adapters/notifications.js +32 -3
- package/dist/expo/adapters/notifications.js.map +1 -1
- package/dist/expo/client.d.ts +14 -0
- package/dist/expo/client.d.ts.map +1 -1
- package/dist/expo/client.js +42 -13
- package/dist/expo/client.js.map +1 -1
- package/dist/expo/client.testSupport.d.ts +54 -0
- package/dist/expo/client.testSupport.d.ts.map +1 -0
- package/dist/expo/client.testSupport.js +183 -0
- package/dist/expo/client.testSupport.js.map +1 -0
- package/dist/expo/manifest.d.ts +5 -0
- package/dist/expo/manifest.d.ts.map +1 -1
- package/dist/expo/manifest.js +5 -0
- package/dist/expo/manifest.js.map +1 -1
- package/dist/expo/permissionResponse.d.ts.map +1 -1
- package/dist/expo/permissionResponse.js +16 -9
- package/dist/expo/permissionResponse.js.map +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +16 -11
- package/dist/react/index.js.map +1 -1
- package/dist/state/permissionState.d.ts +2 -0
- package/dist/state/permissionState.d.ts.map +1 -1
- package/dist/state/permissionState.js +3 -1
- package/dist/state/permissionState.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +27 -17
- package/dist/testing/index.js.map +1 -1
- package/dist/web/index.d.ts +1 -2
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +2 -76
- package/dist/web/index.js.map +1 -1
- package/dist/web/permissionStates.d.ts +10 -0
- package/dist/web/permissionStates.d.ts.map +1 -0
- package/dist/web/permissionStates.js +64 -0
- package/dist/web/permissionStates.js.map +1 -0
- package/package.json +22 -13
- package/src/cli/index.ts +1 -0
- package/src/expo/adapters/mediaLibrary.ts +28 -4
- package/src/expo/adapters/notifications.ts +73 -4
- package/src/expo/client.notifications.test.ts +106 -0
- package/src/expo/client.test.ts +109 -349
- package/src/expo/client.testSupport.ts +188 -0
- package/src/expo/client.ts +45 -13
- package/src/expo/manifest.test.ts +49 -47
- package/src/expo/manifest.ts +7 -5
- package/src/expo/modules.d.ts +18 -4
- package/src/expo/permissionResponse.ts +26 -9
- package/src/manager/createPermissionManager.test.ts +48 -50
- package/src/react/index.tsx +21 -13
- package/src/registry/permissions.test.ts +4 -3
- package/src/state/permissionState.test.ts +8 -1
- package/src/state/permissionState.ts +4 -7
- package/src/testing/index.ts +36 -25
- package/src/web/index.test.ts +149 -151
- package/src/web/index.ts +9 -102
- package/src/web/permissionStates.ts +83 -0
package/src/expo/client.test.ts
CHANGED
|
@@ -1,372 +1,132 @@
|
|
|
1
|
-
import { beforeEach,
|
|
1
|
+
import { beforeEach, expect, test } from 'bun:test';
|
|
2
2
|
|
|
3
3
|
import { Permission, PERMISSIONS } from '../registry/permissions';
|
|
4
4
|
import { createPermissionClient } from './client';
|
|
5
|
+
import {
|
|
6
|
+
audioMock,
|
|
7
|
+
cameraMock,
|
|
8
|
+
locationMock,
|
|
9
|
+
mediaLibraryMock,
|
|
10
|
+
notificationsMock,
|
|
11
|
+
permissionResponse,
|
|
12
|
+
resetExpoMocks,
|
|
13
|
+
} from './client.testSupport';
|
|
14
|
+
|
|
15
|
+
beforeEach(resetExpoMocks);
|
|
16
|
+
|
|
17
|
+
test('Expo client covers getStatus and request for every known permission', async () => {
|
|
18
|
+
const client = createPermissionClient();
|
|
19
|
+
for (const permission of PERMISSIONS) {
|
|
20
|
+
const statusState = await client.getStatus(permission);
|
|
21
|
+
const requestState = await client.request(permission);
|
|
22
|
+
expect(statusState).toMatchObject({ permission, status: 'granted', granted: true });
|
|
23
|
+
expect(requestState).toMatchObject({ permission, status: 'granted', granted: true });
|
|
24
|
+
}
|
|
25
|
+
expect(mediaLibraryMock.getCalls).toEqual([false, true]);
|
|
26
|
+
expect(mediaLibraryMock.requestCalls).toEqual([false, true]);
|
|
27
|
+
expect(locationMock.getCalls).toEqual(['foreground', 'background']);
|
|
28
|
+
expect(locationMock.requestCalls).toEqual(['foreground', 'background']);
|
|
29
|
+
});
|
|
5
30
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type AudioMockMode = 'topLevel' | 'audioModule' | 'missingExports';
|
|
13
|
-
|
|
14
|
-
function permissionResponse(status: string, canAskAgain = true): MockPermissionResponse {
|
|
15
|
-
return { status, canAskAgain };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const cameraMock = {
|
|
19
|
-
mode: 'topLevel' as CameraMockMode,
|
|
20
|
-
getResponse: permissionResponse('granted'),
|
|
21
|
-
requestResponse: permissionResponse('granted'),
|
|
22
|
-
getError: undefined as string | undefined,
|
|
23
|
-
requestError: undefined as string | undefined,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const audioMock = {
|
|
27
|
-
mode: 'topLevel' as AudioMockMode,
|
|
28
|
-
getResponse: permissionResponse('granted'),
|
|
29
|
-
requestResponse: permissionResponse('granted'),
|
|
30
|
-
getError: undefined as string | undefined,
|
|
31
|
-
requestError: undefined as string | undefined,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const mediaLibraryMock = {
|
|
35
|
-
getResponse: permissionResponse('granted'),
|
|
36
|
-
requestResponse: permissionResponse('granted'),
|
|
37
|
-
getError: undefined as string | undefined,
|
|
38
|
-
requestError: undefined as string | undefined,
|
|
39
|
-
getCalls: [] as boolean[],
|
|
40
|
-
requestCalls: [] as boolean[],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const locationMock = {
|
|
44
|
-
foregroundGetResponse: permissionResponse('granted'),
|
|
45
|
-
foregroundRequestResponse: permissionResponse('granted'),
|
|
46
|
-
backgroundGetResponse: permissionResponse('granted'),
|
|
47
|
-
backgroundRequestResponse: permissionResponse('granted'),
|
|
48
|
-
getCalls: [] as string[],
|
|
49
|
-
requestCalls: [] as string[],
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const notificationsMock = {
|
|
53
|
-
getResponse: permissionResponse('granted'),
|
|
54
|
-
requestResponse: permissionResponse('granted'),
|
|
55
|
-
getError: undefined as string | undefined,
|
|
56
|
-
requestError: undefined as string | undefined,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
void mock.module('expo-camera', () => ({
|
|
60
|
-
get getCameraPermissionsAsync() {
|
|
61
|
-
if (cameraMock.mode !== 'topLevel') {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return () => {
|
|
66
|
-
if (cameraMock.getError !== undefined) {
|
|
67
|
-
throw new Error(cameraMock.getError);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return Promise.resolve(cameraMock.getResponse);
|
|
71
|
-
};
|
|
72
|
-
},
|
|
73
|
-
get requestCameraPermissionsAsync() {
|
|
74
|
-
if (cameraMock.mode !== 'topLevel') {
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return () => {
|
|
79
|
-
if (cameraMock.requestError !== undefined) {
|
|
80
|
-
throw new Error(cameraMock.requestError);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return Promise.resolve(cameraMock.requestResponse);
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
get Camera() {
|
|
87
|
-
if (cameraMock.mode !== 'cameraNamespace') {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
getCameraPermissionsAsync: () => Promise.resolve(cameraMock.getResponse),
|
|
93
|
-
requestCameraPermissionsAsync: () => Promise.resolve(cameraMock.requestResponse),
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
}));
|
|
97
|
-
|
|
98
|
-
void mock.module('expo-audio', () => ({
|
|
99
|
-
get getRecordingPermissionsAsync() {
|
|
100
|
-
if (audioMock.mode !== 'topLevel') {
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return () => {
|
|
105
|
-
if (audioMock.getError !== undefined) {
|
|
106
|
-
throw new Error(audioMock.getError);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return Promise.resolve(audioMock.getResponse);
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
|
-
get requestRecordingPermissionsAsync() {
|
|
113
|
-
if (audioMock.mode !== 'topLevel') {
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return () => {
|
|
118
|
-
if (audioMock.requestError !== undefined) {
|
|
119
|
-
throw new Error(audioMock.requestError);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return Promise.resolve(audioMock.requestResponse);
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
get AudioModule() {
|
|
126
|
-
if (audioMock.mode !== 'audioModule') {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
getRecordingPermissionsAsync: () => Promise.resolve(audioMock.getResponse),
|
|
132
|
-
requestRecordingPermissionsAsync: () => Promise.resolve(audioMock.requestResponse),
|
|
133
|
-
};
|
|
134
|
-
},
|
|
135
|
-
}));
|
|
136
|
-
|
|
137
|
-
void mock.module('expo-media-library', () => ({
|
|
138
|
-
getPermissionsAsync: (writeOnly?: boolean) => {
|
|
139
|
-
mediaLibraryMock.getCalls.push(writeOnly === true);
|
|
140
|
-
|
|
141
|
-
if (mediaLibraryMock.getError !== undefined) {
|
|
142
|
-
throw new Error(mediaLibraryMock.getError);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return Promise.resolve(mediaLibraryMock.getResponse);
|
|
146
|
-
},
|
|
147
|
-
requestPermissionsAsync: (writeOnly?: boolean) => {
|
|
148
|
-
mediaLibraryMock.requestCalls.push(writeOnly === true);
|
|
149
|
-
|
|
150
|
-
if (mediaLibraryMock.requestError !== undefined) {
|
|
151
|
-
throw new Error(mediaLibraryMock.requestError);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return Promise.resolve(mediaLibraryMock.requestResponse);
|
|
155
|
-
},
|
|
156
|
-
}));
|
|
157
|
-
|
|
158
|
-
void mock.module('expo-location', () => ({
|
|
159
|
-
getForegroundPermissionsAsync: () => {
|
|
160
|
-
locationMock.getCalls.push('foreground');
|
|
161
|
-
|
|
162
|
-
return Promise.resolve(locationMock.foregroundGetResponse);
|
|
163
|
-
},
|
|
164
|
-
requestForegroundPermissionsAsync: () => {
|
|
165
|
-
locationMock.requestCalls.push('foreground');
|
|
166
|
-
|
|
167
|
-
return Promise.resolve(locationMock.foregroundRequestResponse);
|
|
168
|
-
},
|
|
169
|
-
getBackgroundPermissionsAsync: () => {
|
|
170
|
-
locationMock.getCalls.push('background');
|
|
171
|
-
|
|
172
|
-
return Promise.resolve(locationMock.backgroundGetResponse);
|
|
173
|
-
},
|
|
174
|
-
requestBackgroundPermissionsAsync: () => {
|
|
175
|
-
locationMock.requestCalls.push('background');
|
|
176
|
-
|
|
177
|
-
return Promise.resolve(locationMock.backgroundRequestResponse);
|
|
178
|
-
},
|
|
179
|
-
}));
|
|
180
|
-
|
|
181
|
-
void mock.module('expo-notifications', () => ({
|
|
182
|
-
getPermissionsAsync: () => {
|
|
183
|
-
if (notificationsMock.getError !== undefined) {
|
|
184
|
-
throw new Error(notificationsMock.getError);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return Promise.resolve(notificationsMock.getResponse);
|
|
188
|
-
},
|
|
189
|
-
requestPermissionsAsync: () => {
|
|
190
|
-
if (notificationsMock.requestError !== undefined) {
|
|
191
|
-
throw new Error(notificationsMock.requestError);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return Promise.resolve(notificationsMock.requestResponse);
|
|
195
|
-
},
|
|
196
|
-
}));
|
|
197
|
-
|
|
198
|
-
describe('createPermissionClient', () => {
|
|
199
|
-
beforeEach(() => {
|
|
200
|
-
resetExpoMocks();
|
|
31
|
+
test('Expo client treats clipboard as not requiring a native module', async () => {
|
|
32
|
+
const client = createPermissionClient();
|
|
33
|
+
expect(await client.getStatus(Permission.Clipboard)).toMatchObject({
|
|
34
|
+
permission: Permission.Clipboard,
|
|
35
|
+
status: 'granted',
|
|
36
|
+
granted: true,
|
|
201
37
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for (const permission of PERMISSIONS) {
|
|
207
|
-
const statusState = await client.getStatus(permission);
|
|
208
|
-
const requestState = await client.request(permission);
|
|
209
|
-
|
|
210
|
-
expect(statusState.permission).toBe(permission);
|
|
211
|
-
expect(statusState.status).toBe('granted');
|
|
212
|
-
expect(statusState.granted).toBe(true);
|
|
213
|
-
|
|
214
|
-
expect(requestState.permission).toBe(permission);
|
|
215
|
-
expect(requestState.status).toBe('granted');
|
|
216
|
-
expect(requestState.granted).toBe(true);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
expect(mediaLibraryMock.getCalls).toEqual([false, true]);
|
|
220
|
-
expect(mediaLibraryMock.requestCalls).toEqual([false, true]);
|
|
221
|
-
expect(locationMock.getCalls).toEqual(['foreground', 'background']);
|
|
222
|
-
expect(locationMock.requestCalls).toEqual(['foreground', 'background']);
|
|
38
|
+
expect(await client.request(Permission.Clipboard)).toMatchObject({
|
|
39
|
+
permission: Permission.Clipboard,
|
|
40
|
+
status: 'granted',
|
|
41
|
+
granted: true,
|
|
223
42
|
});
|
|
43
|
+
});
|
|
224
44
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
expect(requestState).toMatchObject({
|
|
237
|
-
permission: Permission.Clipboard,
|
|
238
|
-
status: 'granted',
|
|
239
|
-
granted: true,
|
|
240
|
-
});
|
|
45
|
+
test('Expo client falls back to the Camera namespace', async () => {
|
|
46
|
+
cameraMock.mode = 'cameraNamespace';
|
|
47
|
+
cameraMock.getResponse = permissionResponse('denied', false);
|
|
48
|
+
cameraMock.requestResponse = permissionResponse('granted');
|
|
49
|
+
const client = createPermissionClient();
|
|
50
|
+
expect(await client.getStatus(Permission.Camera)).toMatchObject({
|
|
51
|
+
permission: Permission.Camera,
|
|
52
|
+
status: 'blocked',
|
|
53
|
+
granted: false,
|
|
54
|
+
canAskAgain: false,
|
|
55
|
+
reason: 'Permission is blocked. Open system settings to change it.',
|
|
241
56
|
});
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
cameraMock.requestResponse = permissionResponse('granted');
|
|
247
|
-
|
|
248
|
-
const client = createPermissionClient();
|
|
249
|
-
|
|
250
|
-
expect(await client.getStatus(Permission.Camera)).toMatchObject({
|
|
251
|
-
permission: Permission.Camera,
|
|
252
|
-
status: 'denied',
|
|
253
|
-
granted: false,
|
|
254
|
-
canAskAgain: false,
|
|
255
|
-
});
|
|
256
|
-
expect(await client.request(Permission.Camera)).toMatchObject({
|
|
257
|
-
permission: Permission.Camera,
|
|
258
|
-
status: 'granted',
|
|
259
|
-
granted: true,
|
|
260
|
-
});
|
|
57
|
+
expect(await client.request(Permission.Camera)).toMatchObject({
|
|
58
|
+
permission: Permission.Camera,
|
|
59
|
+
status: 'granted',
|
|
60
|
+
granted: true,
|
|
261
61
|
});
|
|
62
|
+
});
|
|
262
63
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
canAskAgain: false,
|
|
275
|
-
});
|
|
276
|
-
expect(await client.request(Permission.Microphone)).toMatchObject({
|
|
277
|
-
permission: Permission.Microphone,
|
|
278
|
-
status: 'granted',
|
|
279
|
-
granted: true,
|
|
280
|
-
});
|
|
64
|
+
test('Expo client falls back to AudioModule recording permissions', async () => {
|
|
65
|
+
audioMock.mode = 'audioModule';
|
|
66
|
+
audioMock.getResponse = permissionResponse('denied', false);
|
|
67
|
+
audioMock.requestResponse = permissionResponse('granted');
|
|
68
|
+
const client = createPermissionClient();
|
|
69
|
+
expect(await client.getStatus(Permission.Microphone)).toMatchObject({
|
|
70
|
+
permission: Permission.Microphone,
|
|
71
|
+
status: 'blocked',
|
|
72
|
+
granted: false,
|
|
73
|
+
canAskAgain: false,
|
|
74
|
+
reason: 'Permission is blocked. Open system settings to change it.',
|
|
281
75
|
});
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const client = createPermissionClient();
|
|
288
|
-
const state = await client.getStatus(Permission.Notifications);
|
|
289
|
-
|
|
290
|
-
expect(state).toMatchObject({
|
|
291
|
-
permission: Permission.Notifications,
|
|
292
|
-
status: 'unknown',
|
|
293
|
-
granted: false,
|
|
294
|
-
canAskAgain: false,
|
|
295
|
-
});
|
|
76
|
+
expect(await client.request(Permission.Microphone)).toMatchObject({
|
|
77
|
+
permission: Permission.Microphone,
|
|
78
|
+
status: 'granted',
|
|
79
|
+
granted: true,
|
|
296
80
|
});
|
|
81
|
+
});
|
|
297
82
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
83
|
+
test('Expo client normalizes unknown adapter statuses', async () => {
|
|
84
|
+
notificationsMock.getResponse = permissionResponse('mystery-status', false);
|
|
85
|
+
const state = await createPermissionClient().getStatus(Permission.Notifications);
|
|
86
|
+
expect(state).toMatchObject({
|
|
87
|
+
permission: Permission.Notifications,
|
|
88
|
+
status: 'unknown',
|
|
89
|
+
granted: false,
|
|
90
|
+
canAskAgain: false,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
303
93
|
|
|
94
|
+
test('Expo client normalizes non-requestable denials to blocked', async () => {
|
|
95
|
+
notificationsMock.getResponse = permissionResponse('denied', false);
|
|
96
|
+
notificationsMock.requestResponse = permissionResponse('denied', false);
|
|
97
|
+
const client = createPermissionClient();
|
|
98
|
+
for (const state of [
|
|
99
|
+
await client.getStatus(Permission.Notifications),
|
|
100
|
+
await client.request(Permission.Notifications),
|
|
101
|
+
]) {
|
|
304
102
|
expect(state).toMatchObject({
|
|
305
103
|
permission: Permission.Notifications,
|
|
306
|
-
status: '
|
|
104
|
+
status: 'blocked',
|
|
307
105
|
granted: false,
|
|
308
106
|
canAskAgain: false,
|
|
107
|
+
reason: 'Permission is blocked. Open system settings to change it.',
|
|
309
108
|
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
test('returns unavailable when a camera permission call fails at runtime', async () => {
|
|
313
|
-
cameraMock.getError = 'camera bridge unavailable';
|
|
314
|
-
|
|
315
|
-
const client = createPermissionClient();
|
|
316
|
-
const state = await client.getStatus(Permission.Camera);
|
|
317
|
-
|
|
318
|
-
expect(state).toMatchObject({
|
|
319
|
-
permission: Permission.Camera,
|
|
320
|
-
status: 'unavailable',
|
|
321
|
-
granted: false,
|
|
322
|
-
reason: 'camera bridge unavailable',
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('returns unavailable when a request call throws at runtime', async () => {
|
|
327
|
-
audioMock.requestError = 'microphone bridge unavailable';
|
|
328
|
-
|
|
329
|
-
const client = createPermissionClient();
|
|
330
|
-
const state = await client.request(Permission.Microphone);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
331
111
|
|
|
112
|
+
test('Expo client normalizes limited media-library access', async () => {
|
|
113
|
+
mediaLibraryMock.getResponse = {
|
|
114
|
+
status: 'granted',
|
|
115
|
+
canAskAgain: false,
|
|
116
|
+
accessPrivileges: 'limited',
|
|
117
|
+
};
|
|
118
|
+
mediaLibraryMock.requestResponse = mediaLibraryMock.getResponse;
|
|
119
|
+
const client = createPermissionClient();
|
|
120
|
+
for (const state of [
|
|
121
|
+
await client.getStatus(Permission.MediaLibrary),
|
|
122
|
+
await client.request(Permission.MediaLibrary),
|
|
123
|
+
]) {
|
|
332
124
|
expect(state).toMatchObject({
|
|
333
|
-
permission: Permission.
|
|
334
|
-
status: '
|
|
335
|
-
granted:
|
|
336
|
-
|
|
125
|
+
permission: Permission.MediaLibrary,
|
|
126
|
+
status: 'limited',
|
|
127
|
+
granted: true,
|
|
128
|
+
canAskAgain: false,
|
|
129
|
+
reason: 'Media library access is limited to user-selected assets.',
|
|
337
130
|
});
|
|
338
|
-
}
|
|
131
|
+
}
|
|
339
132
|
});
|
|
340
|
-
|
|
341
|
-
function resetExpoMocks(): void {
|
|
342
|
-
cameraMock.mode = 'topLevel';
|
|
343
|
-
cameraMock.getResponse = permissionResponse('granted');
|
|
344
|
-
cameraMock.requestResponse = permissionResponse('granted');
|
|
345
|
-
cameraMock.getError = undefined;
|
|
346
|
-
cameraMock.requestError = undefined;
|
|
347
|
-
|
|
348
|
-
audioMock.mode = 'topLevel';
|
|
349
|
-
audioMock.getResponse = permissionResponse('granted');
|
|
350
|
-
audioMock.requestResponse = permissionResponse('granted');
|
|
351
|
-
audioMock.getError = undefined;
|
|
352
|
-
audioMock.requestError = undefined;
|
|
353
|
-
|
|
354
|
-
mediaLibraryMock.getResponse = permissionResponse('granted');
|
|
355
|
-
mediaLibraryMock.requestResponse = permissionResponse('granted');
|
|
356
|
-
mediaLibraryMock.getError = undefined;
|
|
357
|
-
mediaLibraryMock.requestError = undefined;
|
|
358
|
-
mediaLibraryMock.getCalls.length = 0;
|
|
359
|
-
mediaLibraryMock.requestCalls.length = 0;
|
|
360
|
-
|
|
361
|
-
locationMock.foregroundGetResponse = permissionResponse('granted');
|
|
362
|
-
locationMock.foregroundRequestResponse = permissionResponse('granted');
|
|
363
|
-
locationMock.backgroundGetResponse = permissionResponse('granted');
|
|
364
|
-
locationMock.backgroundRequestResponse = permissionResponse('granted');
|
|
365
|
-
locationMock.getCalls.length = 0;
|
|
366
|
-
locationMock.requestCalls.length = 0;
|
|
367
|
-
|
|
368
|
-
notificationsMock.getResponse = permissionResponse('granted');
|
|
369
|
-
notificationsMock.requestResponse = permissionResponse('granted');
|
|
370
|
-
notificationsMock.getError = undefined;
|
|
371
|
-
notificationsMock.requestError = undefined;
|
|
372
|
-
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { mock } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
export interface MockPermissionResponse {
|
|
4
|
+
readonly status: string;
|
|
5
|
+
readonly canAskAgain?: boolean;
|
|
6
|
+
readonly accessPrivileges?: 'all' | 'limited' | 'none';
|
|
7
|
+
readonly ios?: { readonly status?: number };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type CameraMockMode = 'topLevel' | 'cameraNamespace' | 'missingExports';
|
|
11
|
+
type AudioMockMode = 'topLevel' | 'audioModule' | 'missingExports';
|
|
12
|
+
|
|
13
|
+
export function permissionResponse(status: string, canAskAgain = true): MockPermissionResponse {
|
|
14
|
+
return { status, canAskAgain };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const cameraMock = {
|
|
18
|
+
mode: 'topLevel' as CameraMockMode,
|
|
19
|
+
getResponse: permissionResponse('granted'),
|
|
20
|
+
requestResponse: permissionResponse('granted'),
|
|
21
|
+
getError: undefined as string | undefined,
|
|
22
|
+
requestError: undefined as string | undefined,
|
|
23
|
+
};
|
|
24
|
+
export const audioMock = {
|
|
25
|
+
mode: 'topLevel' as AudioMockMode,
|
|
26
|
+
getResponse: permissionResponse('granted'),
|
|
27
|
+
requestResponse: permissionResponse('granted'),
|
|
28
|
+
getError: undefined as string | undefined,
|
|
29
|
+
requestError: undefined as string | undefined,
|
|
30
|
+
};
|
|
31
|
+
export const mediaLibraryMock = {
|
|
32
|
+
getResponse: permissionResponse('granted'),
|
|
33
|
+
requestResponse: permissionResponse('granted'),
|
|
34
|
+
getError: undefined as string | undefined,
|
|
35
|
+
requestError: undefined as string | undefined,
|
|
36
|
+
getCalls: [] as boolean[],
|
|
37
|
+
requestCalls: [] as boolean[],
|
|
38
|
+
};
|
|
39
|
+
export const locationMock = {
|
|
40
|
+
foregroundGetResponse: permissionResponse('granted'),
|
|
41
|
+
foregroundRequestResponse: permissionResponse('granted'),
|
|
42
|
+
backgroundGetResponse: permissionResponse('granted'),
|
|
43
|
+
backgroundRequestResponse: permissionResponse('granted'),
|
|
44
|
+
getCalls: [] as string[],
|
|
45
|
+
requestCalls: [] as string[],
|
|
46
|
+
};
|
|
47
|
+
export const notificationsMock = {
|
|
48
|
+
getResponse: permissionResponse('granted'),
|
|
49
|
+
requestResponse: permissionResponse('granted'),
|
|
50
|
+
getError: undefined as string | undefined,
|
|
51
|
+
requestError: undefined as string | undefined,
|
|
52
|
+
};
|
|
53
|
+
export const linkingMock = { calls: 0, error: undefined as Error | undefined };
|
|
54
|
+
|
|
55
|
+
void mock.module('expo-camera', () => ({
|
|
56
|
+
get getCameraPermissionsAsync() {
|
|
57
|
+
if (cameraMock.mode !== 'topLevel') return undefined;
|
|
58
|
+
return () => {
|
|
59
|
+
if (cameraMock.getError !== undefined) throw new Error(cameraMock.getError);
|
|
60
|
+
return Promise.resolve(cameraMock.getResponse);
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
get requestCameraPermissionsAsync() {
|
|
64
|
+
if (cameraMock.mode !== 'topLevel') return undefined;
|
|
65
|
+
return () => {
|
|
66
|
+
if (cameraMock.requestError !== undefined) throw new Error(cameraMock.requestError);
|
|
67
|
+
return Promise.resolve(cameraMock.requestResponse);
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
get Camera() {
|
|
71
|
+
if (cameraMock.mode !== 'cameraNamespace') return undefined;
|
|
72
|
+
return {
|
|
73
|
+
getCameraPermissionsAsync: () => Promise.resolve(cameraMock.getResponse),
|
|
74
|
+
requestCameraPermissionsAsync: () => Promise.resolve(cameraMock.requestResponse),
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
void mock.module('expo-audio', () => ({
|
|
80
|
+
get getRecordingPermissionsAsync() {
|
|
81
|
+
if (audioMock.mode !== 'topLevel') return undefined;
|
|
82
|
+
return () => {
|
|
83
|
+
if (audioMock.getError !== undefined) throw new Error(audioMock.getError);
|
|
84
|
+
return Promise.resolve(audioMock.getResponse);
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
get requestRecordingPermissionsAsync() {
|
|
88
|
+
if (audioMock.mode !== 'topLevel') return undefined;
|
|
89
|
+
return () => {
|
|
90
|
+
if (audioMock.requestError !== undefined) throw new Error(audioMock.requestError);
|
|
91
|
+
return Promise.resolve(audioMock.requestResponse);
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
get AudioModule() {
|
|
95
|
+
if (audioMock.mode !== 'audioModule') return undefined;
|
|
96
|
+
return {
|
|
97
|
+
getRecordingPermissionsAsync: () => Promise.resolve(audioMock.getResponse),
|
|
98
|
+
requestRecordingPermissionsAsync: () => Promise.resolve(audioMock.requestResponse),
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
void mock.module('expo-media-library', () => ({
|
|
104
|
+
getPermissionsAsync: (writeOnly?: boolean) => {
|
|
105
|
+
mediaLibraryMock.getCalls.push(writeOnly === true);
|
|
106
|
+
if (mediaLibraryMock.getError !== undefined) throw new Error(mediaLibraryMock.getError);
|
|
107
|
+
return Promise.resolve(mediaLibraryMock.getResponse);
|
|
108
|
+
},
|
|
109
|
+
requestPermissionsAsync: (writeOnly?: boolean) => {
|
|
110
|
+
mediaLibraryMock.requestCalls.push(writeOnly === true);
|
|
111
|
+
if (mediaLibraryMock.requestError !== undefined) throw new Error(mediaLibraryMock.requestError);
|
|
112
|
+
return Promise.resolve(mediaLibraryMock.requestResponse);
|
|
113
|
+
},
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
void mock.module('expo-location', () => ({
|
|
117
|
+
getForegroundPermissionsAsync: () => {
|
|
118
|
+
locationMock.getCalls.push('foreground');
|
|
119
|
+
return Promise.resolve(locationMock.foregroundGetResponse);
|
|
120
|
+
},
|
|
121
|
+
requestForegroundPermissionsAsync: () => {
|
|
122
|
+
locationMock.requestCalls.push('foreground');
|
|
123
|
+
return Promise.resolve(locationMock.foregroundRequestResponse);
|
|
124
|
+
},
|
|
125
|
+
getBackgroundPermissionsAsync: () => {
|
|
126
|
+
locationMock.getCalls.push('background');
|
|
127
|
+
return Promise.resolve(locationMock.backgroundGetResponse);
|
|
128
|
+
},
|
|
129
|
+
requestBackgroundPermissionsAsync: () => {
|
|
130
|
+
locationMock.requestCalls.push('background');
|
|
131
|
+
return Promise.resolve(locationMock.backgroundRequestResponse);
|
|
132
|
+
},
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
void mock.module('expo-notifications', () => ({
|
|
136
|
+
getPermissionsAsync: () => {
|
|
137
|
+
if (notificationsMock.getError !== undefined) throw new Error(notificationsMock.getError);
|
|
138
|
+
return Promise.resolve(notificationsMock.getResponse);
|
|
139
|
+
},
|
|
140
|
+
requestPermissionsAsync: () => {
|
|
141
|
+
if (notificationsMock.requestError !== undefined)
|
|
142
|
+
throw new Error(notificationsMock.requestError);
|
|
143
|
+
return Promise.resolve(notificationsMock.requestResponse);
|
|
144
|
+
},
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
void mock.module('expo-linking', () => ({
|
|
148
|
+
openSettings: () => {
|
|
149
|
+
linkingMock.calls += 1;
|
|
150
|
+
if (linkingMock.error !== undefined) throw linkingMock.error;
|
|
151
|
+
return Promise.resolve();
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
export function resetExpoMocks(): void {
|
|
156
|
+
cameraMock.mode = 'topLevel';
|
|
157
|
+
cameraMock.getResponse = permissionResponse('granted');
|
|
158
|
+
cameraMock.requestResponse = permissionResponse('granted');
|
|
159
|
+
cameraMock.getError = undefined;
|
|
160
|
+
cameraMock.requestError = undefined;
|
|
161
|
+
audioMock.mode = 'topLevel';
|
|
162
|
+
audioMock.getResponse = permissionResponse('granted');
|
|
163
|
+
audioMock.requestResponse = permissionResponse('granted');
|
|
164
|
+
audioMock.getError = undefined;
|
|
165
|
+
audioMock.requestError = undefined;
|
|
166
|
+
resetCollectionMocks();
|
|
167
|
+
notificationsMock.getResponse = permissionResponse('granted');
|
|
168
|
+
notificationsMock.requestResponse = permissionResponse('granted');
|
|
169
|
+
notificationsMock.getError = undefined;
|
|
170
|
+
notificationsMock.requestError = undefined;
|
|
171
|
+
linkingMock.calls = 0;
|
|
172
|
+
linkingMock.error = undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function resetCollectionMocks(): void {
|
|
176
|
+
mediaLibraryMock.getResponse = permissionResponse('granted');
|
|
177
|
+
mediaLibraryMock.requestResponse = permissionResponse('granted');
|
|
178
|
+
mediaLibraryMock.getError = undefined;
|
|
179
|
+
mediaLibraryMock.requestError = undefined;
|
|
180
|
+
mediaLibraryMock.getCalls.length = 0;
|
|
181
|
+
mediaLibraryMock.requestCalls.length = 0;
|
|
182
|
+
locationMock.foregroundGetResponse = permissionResponse('granted');
|
|
183
|
+
locationMock.foregroundRequestResponse = permissionResponse('granted');
|
|
184
|
+
locationMock.backgroundGetResponse = permissionResponse('granted');
|
|
185
|
+
locationMock.backgroundRequestResponse = permissionResponse('granted');
|
|
186
|
+
locationMock.getCalls.length = 0;
|
|
187
|
+
locationMock.requestCalls.length = 0;
|
|
188
|
+
}
|