@lowdefy/shell 3.23.3 → 7.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/LICENSE +183 -183
- package/README.md +364 -2
- package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
- package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
- package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
- package/app/+native-intent.js +39 -0
- package/app/_layout.js +36 -0
- package/app.config.ts +183 -0
- package/appLinks.js +65 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +172 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native/LowdefyAppShortcuts.swift.template +84 -0
- package/native/withAppShortcuts.js +60 -0
- package/native-fingerprint.json +16 -0
- package/package.json +120 -40
- package/profiles.js +607 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +112 -0
- package/scripts/checkReleaseBundle.mjs +358 -0
- package/scripts/createDayjsLocales.mjs +67 -0
- package/scripts/createShellAssets.mjs +54 -0
- package/scripts/createShellVersion.mjs +83 -0
- package/scripts/createTypeManifest.mjs +205 -0
- package/shell.dev.json +11 -0
- package/shellVariant.js +126 -0
- package/src/ShellContext.js +32 -0
- package/src/actions/CopyToClipboard.js +29 -0
- package/src/actions/CopyToClipboard.test.js +33 -0
- package/src/actions/GeolocationCurrentPosition.js +61 -0
- package/src/actions/GeolocationCurrentPosition.test.js +79 -0
- package/src/actions/GetLocalStorage.js +36 -0
- package/src/actions/RemoveLocalStorage.js +31 -0
- package/src/actions/ScrollTo.js +66 -0
- package/src/actions/ScrollTo.test.js +83 -0
- package/src/actions/SetFocus.js +33 -0
- package/src/actions/SetFocus.test.js +49 -0
- package/src/actions/SetLocalStorage.js +34 -0
- package/src/actions/localStorage.test.js +68 -0
- package/src/actions/measureInScrollView.js +36 -0
- package/src/actions/nativeActions.js +41 -0
- package/src/auth/AuthButton.js +44 -0
- package/src/auth/AuthGate.js +57 -0
- package/src/auth/AuthLink.js +43 -0
- package/src/auth/LockScreen.js +67 -0
- package/src/auth/LockScreen.test.js +37 -0
- package/src/auth/SignInScreen.js +268 -0
- package/src/auth/SignInScreen.test.js +187 -0
- package/src/auth/createAppAuth.js +325 -0
- package/src/auth/createAppAuth.test.js +424 -0
- package/src/auth/createAuthApi.js +115 -0
- package/src/auth/createAuthApi.test.js +138 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createDeviceKey.js +62 -0
- package/src/auth/createDeviceKey.test.js +54 -0
- package/src/auth/createSessionStores.js +45 -0
- package/src/auth/createTokenStore.js +103 -0
- package/src/auth/createTokenStore.test.js +116 -0
- package/src/auth/getAuthSettings.js +24 -0
- package/src/auth/getAuthView.js +39 -0
- package/src/auth/getAuthView.test.js +52 -0
- package/src/auth/getSheetReturnUrl.js +35 -0
- package/src/auth/getTokenStoreOptions.js +46 -0
- package/src/auth/isBackgroundShell.js +26 -0
- package/src/auth/isSheetReturnLink.js +35 -0
- package/src/auth/parseQuery.js +39 -0
- package/src/auth/purgeAppData.js +80 -0
- package/src/auth/purgeAppData.test.js +104 -0
- package/src/auth/shouldLock.js +26 -0
- package/src/auth/signInWithSheet.js +97 -0
- package/src/auth/signInWithSheet.test.js +205 -0
- package/src/auth/styles.js +78 -0
- package/src/auth/unregisterPushToken.js +62 -0
- package/src/auth/unregisterPushToken.test.js +67 -0
- package/src/auth/usesBiometricUnlock.js +23 -0
- package/src/auth/watchSession.js +47 -0
- package/src/auth/withoutCookies.js +26 -0
- package/src/cache/createSqliteCacheStore.js +113 -0
- package/src/cache/createSqliteCacheStore.test.js +93 -0
- package/src/cache/getCacheKey.js +25 -0
- package/src/cache/getLastCacheKeyName.js +22 -0
- package/src/components/AppProvider.js +118 -0
- package/src/components/Frame.js +116 -0
- package/src/components/Frame.test.js +101 -0
- package/src/components/LoadingScreen.js +35 -0
- package/src/components/MenuButton.js +121 -0
- package/src/components/MenuButton.test.js +74 -0
- package/src/components/MessageScreen.js +51 -0
- package/src/components/NavigationTheme.js +33 -0
- package/src/components/NoConnectionBanner.js +51 -0
- package/src/components/NoConnectionBanner.test.js +73 -0
- package/src/components/OtherPageScreen.js +95 -0
- package/src/components/OtherPageScreen.test.js +74 -0
- package/src/components/PageScreen.js +254 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/RouteErrorScreen.js +47 -0
- package/src/components/ShellRoot.js +34 -0
- package/src/components/ShellRoot.test.js +62 -0
- package/src/components/ShellThemeProvider.js +45 -0
- package/src/components/SwitchToTab.js +36 -0
- package/src/components/SwitchToTab.test.js +63 -0
- package/src/components/TabIconImages.js +72 -0
- package/src/components/UpdateScreen.js +40 -0
- package/src/components/getNavigationTheme.js +44 -0
- package/src/components/getNavigationTheme.test.js +41 -0
- package/src/components/styles.js +50 -0
- package/src/components/useSurfaceTokens.js +47 -0
- package/src/config/getAppApiBase.js +34 -0
- package/src/config/getShellConfig.js +37 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +126 -0
- package/src/dev/DevShell.js +239 -0
- package/src/dev/DevShell.test.js +233 -0
- package/src/dev/connect/ConfirmSheet.js +71 -0
- package/src/dev/connect/ConnectScreen.js +117 -0
- package/src/dev/connect/QrScanner.js +89 -0
- package/src/dev/connection/createDevShellConfig.js +47 -0
- package/src/dev/connection/createDevTransport.js +51 -0
- package/src/dev/connection/createDevTransport.test.js +87 -0
- package/src/dev/connection/createServerList.js +48 -0
- package/src/dev/connection/isLoopbackHost.js +25 -0
- package/src/dev/connection/pairedShellApp.test.js +104 -0
- package/src/dev/connection/parseDevLink.js +66 -0
- package/src/dev/connection/parseDevLink.test.js +97 -0
- package/src/dev/connection/parseQuery.js +33 -0
- package/src/dev/connection/parseServerInput.js +40 -0
- package/src/dev/connection/parseServerUrl.js +45 -0
- package/src/dev/devStyles.js +97 -0
- package/src/dev/index.js +21 -0
- package/src/dev/inject/createDeviceInjections.js +188 -0
- package/src/dev/inject/createDeviceInjections.test.js +224 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/inject/injectPush.js +60 -0
- package/src/dev/inject/injectShare.js +55 -0
- package/src/dev/inject/withInjectedDevice.js +98 -0
- package/src/dev/menu/BlockGallery.js +33 -0
- package/src/dev/menu/DevMenu.js +121 -0
- package/src/dev/menu/createLogBuffer.js +83 -0
- package/src/dev/menu/watchShake.js +52 -0
- package/src/dev/menu/watchShake.test.js +61 -0
- package/src/dev/recorder/createNativeRecorder.js +204 -0
- package/src/dev/recorder/createNativeRecorder.test.js +307 -0
- package/src/dev/redirectDevLink.js +30 -0
- package/src/dev/redirectDevLink.test.js +49 -0
- package/src/dev/steps/createStepRunner.js +184 -0
- package/src/dev/steps/createStepRunner.test.js +262 -0
- package/src/dev/steps/findBlock.js +78 -0
- package/src/dev/steps/isContextSettled.js +37 -0
- package/src/dev/steps/waitForSettle.js +39 -0
- package/src/dev/steps/waitUntil.js +40 -0
- package/src/dev/tab/DevTabClient.js +97 -0
- package/src/dev/tab/answerEval.js +39 -0
- package/src/dev/tab/answerInspect.js +45 -0
- package/src/dev/tab/createDevTabClient.js +272 -0
- package/src/dev/tab/createDevTabClient.test.js +317 -0
- package/src/dev/tab/describeTabError.js +35 -0
- package/src/dev/tab/devClientMarker.js +22 -0
- package/src/dev/tab/devClientMarker.test.js +23 -0
- package/src/dev/tab/findPageContext.js +43 -0
- package/src/dev/tab/getShownPage.js +42 -0
- package/src/dev/tab/readEventStream.js +68 -0
- package/src/dev/tab/reloadApp.js +42 -0
- package/src/dev/tab/reloadApp.test.js +115 -0
- package/src/device/DeviceHosts.js +32 -0
- package/src/device/basic/createFilesFeature.js +41 -0
- package/src/device/basic/createHapticsFeature.js +46 -0
- package/src/device/basic/createKeepAwakeFeature.js +37 -0
- package/src/device/basic/createPhotosFeature.js +40 -0
- package/src/device/basic/createRequestQueueFeature.js +37 -0
- package/src/device/basic/createShareFeature.js +63 -0
- package/src/device/basic/createShareFeature.test.js +47 -0
- package/src/device/basic/createSpeechFeature.js +43 -0
- package/src/device/basic/createUploadFeature.js +41 -0
- package/src/device/basic/toImagePickerOptions.js +41 -0
- package/src/device/basic/toPickedFile.js +54 -0
- package/src/device/calendar/index.js +68 -0
- package/src/device/contacts/index.js +67 -0
- package/src/device/createDevice.js +65 -0
- package/src/device/device.js +27 -0
- package/src/device/field/MapView.js +93 -0
- package/src/device/field/ScanCodeSheet.js +86 -0
- package/src/device/field/ScannerView.js +52 -0
- package/src/device/field/createAudioFeature.js +70 -0
- package/src/device/field/createBrightnessFeature.js +40 -0
- package/src/device/field/createCameraFeature.js +56 -0
- package/src/device/field/createDocumentScannerFeature.js +51 -0
- package/src/device/field/createLocationFeature.js +39 -0
- package/src/device/field/createScanRequests.js +55 -0
- package/src/device/field/index.js +39 -0
- package/src/device/field/scanRequests.js +22 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/device/nfc/index.js +89 -0
- package/src/device/nfc/toNfcRecord.js +44 -0
- package/src/device/nfc/toNfcRecord.test.js +48 -0
- package/src/device/tracking/createBacklog.js +63 -0
- package/src/device/tracking/createBacklog.test.js +47 -0
- package/src/device/tracking/index.js +211 -0
- package/src/device/tracking/index.test.js +110 -0
- package/src/host/createDeviceStore.js +32 -0
- package/src/host/createMemoryStore.js +32 -0
- package/src/host/createNetworkStatus.js +94 -0
- package/src/host/createNetworkStatus.test.js +94 -0
- package/src/host/createRouterAdapter.js +33 -0
- package/src/host/createShellFetch.js +62 -0
- package/src/host/createShellFetch.test.js +165 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +83 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +31 -0
- package/src/host/isNetworkError.js +24 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/readServerError.js +45 -0
- package/src/host/readServerError.test.js +85 -0
- package/src/host/reportShellError.js +47 -0
- package/src/host/reportShellError.test.js +79 -0
- package/src/host/withNetworkErrors.js +38 -0
- package/src/host/withNetworkErrors.test.js +61 -0
- package/src/host/withShellHeader.js +25 -0
- package/src/host/withShellHeader.test.js +28 -0
- package/src/js/evaluateJsEntries.js +43 -0
- package/src/js/evaluateJsEntries.test.js +48 -0
- package/src/locale/createShellLocale.js +112 -0
- package/src/locale/createShellLocale.test.js +74 -0
- package/src/locale/dayjsLocales.js +165 -0
- package/src/locale/getDeviceLocales.js +23 -0
- package/src/locale/loadDayjsLocale.js +32 -0
- package/src/locale/resolveActiveLocale.js +46 -0
- package/src/locale/resolveActiveLocale.test.js +40 -0
- package/src/native/backgroundFetch.js +97 -0
- package/src/native/callEndpoint.js +63 -0
- package/src/native/createAppEventTrigger.js +70 -0
- package/src/native/currentApp.js +49 -0
- package/src/native/getPagePath.js +32 -0
- package/src/native/openPath.js +55 -0
- package/src/native/openPath.test.js +39 -0
- package/src/native/parseShellLink.js +38 -0
- package/src/native/parseShellLink.test.js +29 -0
- package/src/native/startAppServices.js +94 -0
- package/src/native/startAppServices.test.js +97 -0
- package/src/native/startShareIn.js +59 -0
- package/src/native/startShortcuts.js +88 -0
- package/src/native/startShortcuts.test.js +59 -0
- package/src/native/toSharePayload.js +47 -0
- package/src/native/toSharePayload.test.js +47 -0
- package/src/native/whenAppReady.js +48 -0
- package/src/navigation/SlotContext.js +24 -0
- package/src/navigation/frameSlots.js +27 -0
- package/src/navigation/getFrame.js +30 -0
- package/src/navigation/getInitialScreenOptions.js +63 -0
- package/src/navigation/getInitialScreenOptions.test.js +66 -0
- package/src/navigation/getPresentation.js +28 -0
- package/src/navigation/getSlotRootPath.js +27 -0
- package/src/navigation/getStackOptions.js +185 -0
- package/src/navigation/getTabHref.js +45 -0
- package/src/navigation/getTabIcons.js +43 -0
- package/src/navigation/isPageUnderHeader.js +46 -0
- package/src/navigation/navigation.test.js +342 -0
- package/src/navigation/toAppLinkPath.js +43 -0
- package/src/navigation/toAppLinkPath.test.js +53 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/notifications/createNotificationsFeature.js +61 -0
- package/src/notifications/getCategoryAction.js +31 -0
- package/src/notifications/handleNotificationResponse.js +82 -0
- package/src/notifications/registerPushToken.js +71 -0
- package/src/notifications/startNotifications.js +114 -0
- package/src/notifications/toCategoryActions.js +42 -0
- package/src/notifications/toNotificationEvent.js +36 -0
- package/src/notifications/toScheduleRequest.js +62 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +15 -0
- package/src/plugins/typeManifest.js +789 -0
- package/src/plugins/types.js +64 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/queue/createRequestQueue.js +198 -0
- package/src/queue/createRequestQueue.test.js +215 -0
- package/src/queue/createSqliteQueueStore.js +121 -0
- package/src/queue/summarizeQueue.js +39 -0
- package/src/session/createAppSession.js +323 -0
- package/src/session/createAppSession.test.js +391 -0
- package/src/session/getPageView.js +59 -0
- package/src/session/getPageView.test.js +137 -0
- package/src/sync/encodePagePath.js +27 -0
- package/src/sync/fetchPage.js +72 -0
- package/src/sync/fetchPage.test.js +84 -0
- package/src/sync/fetchRoot.js +43 -0
- package/src/sync/fetchUser.js +33 -0
- package/src/sync/runLimited.js +36 -0
- package/src/sync/toRootConfig.js +35 -0
- package/src/test/createFakeNetwork.js +40 -0
- package/src/test/createFakeSecureStore.js +40 -0
- package/src/test/createFakeServer.js +98 -0
- package/src/test/createMemoryCacheStore.js +59 -0
- package/src/test/createNetworkError.js +24 -0
- package/src/test/expoImage.js +42 -0
- package/src/test/expoNotifications.js +40 -0
- package/src/test/expoQuickActions.js +22 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoTaskManager.js +25 -0
- package/src/test/expoUi.js +55 -0
- package/src/test/expoUiBottomSheet.js +64 -0
- package/src/test/expoUiDateTimePicker.js +26 -0
- package/src/test/expoUiMenu.js +46 -0
- package/src/test/expoUiSegmentedControl.js +47 -0
- package/src/test/expoUiSwiftUi.js +62 -0
- package/src/test/expoUiSwiftUiModifiers.js +23 -0
- package/src/test/expoWebBrowser.js +23 -0
- package/src/test/fixtures.js +98 -0
- package/src/test/nativeModule.js +33 -0
- package/src/test/renderPageScreen.js +109 -0
- package/src/types/checkTypeSet.js +39 -0
- package/src/types/collectPageTypes.js +49 -0
- package/src/types/collectPageTypes.test.js +90 -0
- package/src/types/findMissingPageTypes.js +40 -0
- package/src/version/compareVersions.js +38 -0
- package/src/version/isShellTooOld.js +30 -0
- package/src/version/shellVersion.js +21 -0
- package/src/version/shellVersion.test.js +32 -0
- package/src/widgets/fetchWidgetData.js +59 -0
- package/src/widgets/index.js +156 -0
- package/src/widgets/refreshWidgets.js +31 -0
- package/withExcludedNativeModules.js +93 -0
- package/dist/location.js +0 -33
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/icon-32.png +0 -0
- package/dist/public/icon-512.png +0 -0
- package/dist/public/icon.svg +0 -17
- package/dist/public/logo-dark-theme.png +0 -0
- package/dist/public/logo-light-theme.png +0 -0
- package/dist/public/logo-square-dark-theme.png +0 -0
- package/dist/public/logo-square-light-theme.png +0 -0
- package/dist/public/manifest.webmanifest +0 -16
- package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
- package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
- package/dist/shell/454.b597ca21366afda000af.js +0 -2
- package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
- package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
- package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
- package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
- package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
- package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
- package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
- package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
- package/dist/shell/index.html +0 -57
- package/dist/shell/main.f01501a75909f38ccc32.js +0 -1
- package/dist/shell/runtime.e50f6c3a5f7c74a0a717.js +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
import { LowdefyInternalError, RequestError, UserError } from '@lowdefy/errors';
|
|
19
|
+
|
|
20
|
+
import fetchPage from './fetchPage.js';
|
|
21
|
+
|
|
22
|
+
const apiBase = 'https://field.example.com';
|
|
23
|
+
|
|
24
|
+
function answer({ status = 200, body, headers = {} }) {
|
|
25
|
+
return {
|
|
26
|
+
ok: status >= 200 && status < 300,
|
|
27
|
+
status,
|
|
28
|
+
headers: { get: (name) => headers[name] ?? null },
|
|
29
|
+
json: async () => body,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fetchWith(res) {
|
|
34
|
+
return jest.fn(async () => res);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test('a page answer is returned with its build and etag', async () => {
|
|
38
|
+
const pageConfig = { id: 'jobs', _buildId: 'b1' };
|
|
39
|
+
const result = await fetchPage({
|
|
40
|
+
apiBase,
|
|
41
|
+
fetchWithAuth: fetchWith(answer({ body: { pageConfig }, headers: { etag: '"p1"' } })),
|
|
42
|
+
path: 'jobs',
|
|
43
|
+
});
|
|
44
|
+
expect(result).toEqual({ page: { buildId: 'b1', etag: '"p1"', pageConfig } });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('lowdefy dev installing plugins is an expected outcome that names them, never a page', async () => {
|
|
48
|
+
const error = await fetchPage({
|
|
49
|
+
apiBase,
|
|
50
|
+
fetchWithAuth: fetchWith(answer({ body: { installing: true, packages: ['@acme/blocks'] } })),
|
|
51
|
+
path: 'jobs',
|
|
52
|
+
}).catch((caught) => caught);
|
|
53
|
+
expect(error).toBeInstanceOf(UserError);
|
|
54
|
+
expect(error.message).toBe(
|
|
55
|
+
'Lowdefy dev is installing @acme/blocks for "/jobs". Try again once the install finishes.'
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('an answer with no page config is a fault, never a page', async () => {
|
|
60
|
+
const error = await fetchPage({
|
|
61
|
+
apiBase,
|
|
62
|
+
fetchWithAuth: fetchWith(answer({ body: { pageId: 'jobs' } })),
|
|
63
|
+
path: 'jobs',
|
|
64
|
+
}).catch((caught) => caught);
|
|
65
|
+
expect(error).toBeInstanceOf(LowdefyInternalError);
|
|
66
|
+
expect(error.message).toBe(
|
|
67
|
+
'The app\'s server answered the page request for "/jobs" with no page config.'
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('a server error keeps its class and status', async () => {
|
|
72
|
+
const error = await fetchPage({
|
|
73
|
+
apiBase,
|
|
74
|
+
fetchWithAuth: fetchWith(
|
|
75
|
+
answer({
|
|
76
|
+
status: 500,
|
|
77
|
+
body: { '~e': { name: 'RequestError', message: 'Page failed.', isLowdefyError: true } },
|
|
78
|
+
})
|
|
79
|
+
),
|
|
80
|
+
path: 'jobs',
|
|
81
|
+
}).catch((caught) => caught);
|
|
82
|
+
expect(error).toBeInstanceOf(RequestError);
|
|
83
|
+
expect(error.status).toBe(500);
|
|
84
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { parseCallerStamp } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import readServerError from '../host/readServerError.js';
|
|
20
|
+
|
|
21
|
+
// GET /api/mobile/root, revalidated with the ETag of the root the shell holds. Returns
|
|
22
|
+
// { notModified, root, etag, stamp }: on a 304 the held root is current and root is null. The
|
|
23
|
+
// stamp is the caller the server answered as, whichever the status.
|
|
24
|
+
async function fetchRoot({ apiBase, etag, fetchWithAuth }) {
|
|
25
|
+
const headers = etag ? { 'If-None-Match': etag } : {};
|
|
26
|
+
const res = await fetchWithAuth(`${apiBase}/api/mobile/root`, { method: 'GET', headers });
|
|
27
|
+
const stamp = parseCallerStamp(res.headers.get('x-lowdefy-caller'));
|
|
28
|
+
if (res.status === 304) {
|
|
29
|
+
return { notModified: true, root: null, etag, stamp };
|
|
30
|
+
}
|
|
31
|
+
if (res.status === 404) {
|
|
32
|
+
throw new Error('This app has no mobile pages.');
|
|
33
|
+
}
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw await readServerError({
|
|
36
|
+
res,
|
|
37
|
+
message: `The app's server answered ${res.status} to the mobile root request.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return { notModified: false, root: await res.json(), etag: res.headers.get('etag'), stamp };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default fetchRoot;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import readServerError from '../host/readServerError.js';
|
|
18
|
+
|
|
19
|
+
// GET /api/user: the caller the pages render (the _user operator, and the caller the core checks
|
|
20
|
+
// each response's stamp against).
|
|
21
|
+
async function fetchUser({ apiBase, fetchWithAuth }) {
|
|
22
|
+
const res = await fetchWithAuth(`${apiBase}/api/user`, { method: 'GET', headers: {} });
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw await readServerError({
|
|
25
|
+
res,
|
|
26
|
+
message: `The app's server answered ${res.status} to the user request.`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const body = await res.json();
|
|
30
|
+
return body.user ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default fetchUser;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Runs fn over every item, at most `limit` at a time. Resolves once all have settled; a failed
|
|
18
|
+
// item does not stop the others, and the failures are returned.
|
|
19
|
+
async function runLimited({ items, limit, fn }) {
|
|
20
|
+
const queue = [...items];
|
|
21
|
+
const failures = [];
|
|
22
|
+
async function worker() {
|
|
23
|
+
while (queue.length > 0) {
|
|
24
|
+
const item = queue.shift();
|
|
25
|
+
try {
|
|
26
|
+
await fn(item);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
failures.push({ item, error });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, () => worker()));
|
|
33
|
+
return failures;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default runLimited;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The root config @lowdefy/client-native renders pages with, from the mobile root. The web menus
|
|
18
|
+
// (_menu) have no mobile counterpart; the mobile frame is the shell's. The theme
|
|
19
|
+
// ({ darkMode, light, dark }) and the Tailwind class map style the blocks. A root cached from a
|
|
20
|
+
// server built before they were served has no class map or i18n.
|
|
21
|
+
function toRootConfig(root) {
|
|
22
|
+
return {
|
|
23
|
+
classMap: root.classMap ?? {},
|
|
24
|
+
events: root.events,
|
|
25
|
+
home: { configured: true, pageId: root.homePageId },
|
|
26
|
+
i18n: root.i18n ?? {},
|
|
27
|
+
lowdefyApp: root.lowdefyApp ?? {},
|
|
28
|
+
lowdefyGlobal: root.lowdefyGlobal ?? {},
|
|
29
|
+
menus: [],
|
|
30
|
+
pagePaths: Object.fromEntries(root.pageIndex.map((entry) => [entry.pageId, entry.path])),
|
|
31
|
+
theme: root.theme ?? {},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default toRootConfig;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const ONLINE = { type: 'WIFI', isConnected: true, isInternetReachable: true };
|
|
18
|
+
|
|
19
|
+
// expo-network as createNetworkStatus reads it, with the phone's network state set by the test.
|
|
20
|
+
function createFakeNetwork({ state: initial = ONLINE } = {}) {
|
|
21
|
+
const listeners = new Set();
|
|
22
|
+
let state = initial;
|
|
23
|
+
return {
|
|
24
|
+
addNetworkStateListener(listener) {
|
|
25
|
+
listeners.add(listener);
|
|
26
|
+
return { remove: () => listeners.delete(listener) };
|
|
27
|
+
},
|
|
28
|
+
getNetworkStateAsync: async () => state,
|
|
29
|
+
// The phone's network changes, as expo-network reports it.
|
|
30
|
+
change(next) {
|
|
31
|
+
state = next;
|
|
32
|
+
listeners.forEach((listener) => listener(next));
|
|
33
|
+
},
|
|
34
|
+
listening: () => listeners.size > 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const OFFLINE = { type: 'NONE', isConnected: false, isInternetReachable: false };
|
|
39
|
+
export { ONLINE };
|
|
40
|
+
export default createFakeNetwork;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
|
|
19
|
+
// expo-secure-store over a Map, keyed as the native module keys items: by key and by whether the
|
|
20
|
+
// item is behind biometrics (a protected item is a different Keychain service on iOS).
|
|
21
|
+
function createFakeSecureStore() {
|
|
22
|
+
const items = new Map();
|
|
23
|
+
function itemKey(key, options = {}) {
|
|
24
|
+
return `${key}|${options.requireAuthentication === true ? 'auth' : 'no-auth'}`;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
items,
|
|
28
|
+
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 'AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY',
|
|
29
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'WHEN_UNLOCKED_THIS_DEVICE_ONLY',
|
|
30
|
+
getItemAsync: jest.fn(async (key, options) => items.get(itemKey(key, options))?.value ?? null),
|
|
31
|
+
setItemAsync: jest.fn(async (key, value, options) => {
|
|
32
|
+
items.set(itemKey(key, options), { value, options });
|
|
33
|
+
}),
|
|
34
|
+
deleteItemAsync: jest.fn(async (key, options) => {
|
|
35
|
+
items.delete(itemKey(key, options));
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default createFakeSecureStore;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
|
|
19
|
+
function createResponse({ body = null, headers = {}, status = 200 }) {
|
|
20
|
+
const lowerHeaders = Object.fromEntries(
|
|
21
|
+
Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value])
|
|
22
|
+
);
|
|
23
|
+
return {
|
|
24
|
+
ok: status >= 200 && status < 300,
|
|
25
|
+
status,
|
|
26
|
+
headers: { get: (name) => lowerHeaders[name.toLowerCase()] ?? null },
|
|
27
|
+
json: async () => structuredClone(body),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function header(init, name) {
|
|
32
|
+
const entry = Object.entries(init?.headers ?? {}).find(
|
|
33
|
+
([key]) => key.toLowerCase() === name.toLowerCase()
|
|
34
|
+
);
|
|
35
|
+
return entry?.[1] ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A Lowdefy server for the shell's tests. `state` holds what it serves and may be changed between
|
|
40
|
+
* calls: `caller` (the x-lowdefy-caller stamp), `user`, `root` and its `rootEtag`, `pages`
|
|
41
|
+
* ({ [path]: { pageConfig, etag } }), `buildId`, `requests` (a function answering
|
|
42
|
+
* /api/request/* calls) and `auth` (a function of { path, init } answering /api/auth/* calls with
|
|
43
|
+
* { body, headers, status }). A matching If-None-Match gets a 304. Every call is in `fetch.mock.calls`.
|
|
44
|
+
*/
|
|
45
|
+
function createFakeServer(initial) {
|
|
46
|
+
const state = { buildId: 'build-1', caller: '', user: null, ...initial };
|
|
47
|
+
const fetch = jest.fn(async (url, init) => {
|
|
48
|
+
const { pathname } = new URL(url);
|
|
49
|
+
const callerHeaders = { 'x-lowdefy-caller': state.caller };
|
|
50
|
+
if (pathname === '/api/mobile/root') {
|
|
51
|
+
if (header(init, 'if-none-match') === state.rootEtag) {
|
|
52
|
+
return createResponse({ status: 304, headers: { ...callerHeaders, etag: state.rootEtag } });
|
|
53
|
+
}
|
|
54
|
+
return createResponse({
|
|
55
|
+
body: state.root,
|
|
56
|
+
headers: { ...callerHeaders, etag: state.rootEtag },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (pathname === '/api/user') {
|
|
60
|
+
return createResponse({ body: { user: state.user } });
|
|
61
|
+
}
|
|
62
|
+
if (pathname.startsWith('/api/page/')) {
|
|
63
|
+
const path = decodeURIComponent(pathname.slice('/api/page/'.length));
|
|
64
|
+
const page = state.pages[path];
|
|
65
|
+
if (page === undefined) {
|
|
66
|
+
return createResponse({
|
|
67
|
+
status: 404,
|
|
68
|
+
body: state.otherSurfaces?.[path] ?? { pageConfig: null },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (header(init, 'if-none-match') === page.etag) {
|
|
72
|
+
return createResponse({ status: 304, headers: { ...callerHeaders, etag: page.etag } });
|
|
73
|
+
}
|
|
74
|
+
return createResponse({
|
|
75
|
+
body: { buildId: state.buildId, pageConfig: page.pageConfig },
|
|
76
|
+
headers: { ...callerHeaders, etag: page.etag },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (pathname.startsWith('/api/request/')) {
|
|
80
|
+
const build = header(init, 'x-lowdefy-build');
|
|
81
|
+
if (build !== null && build !== state.buildId) {
|
|
82
|
+
return createResponse({
|
|
83
|
+
status: 409,
|
|
84
|
+
body: { name: 'UserError', message: 'Other build.', buildId: state.buildId },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return createResponse({ body: state.requests?.(url, init) ?? { success: true } });
|
|
88
|
+
}
|
|
89
|
+
if (pathname.startsWith('/api/auth/') && state.auth !== undefined) {
|
|
90
|
+
return createResponse(state.auth({ path: pathname.slice('/api/auth'.length), init }));
|
|
91
|
+
}
|
|
92
|
+
return createResponse({ status: 404, body: {} });
|
|
93
|
+
});
|
|
94
|
+
return { fetch, state };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { header };
|
|
98
|
+
export default createFakeServer;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The page cache held in memory, with the same methods as createSqliteCacheStore.
|
|
18
|
+
function createMemoryCacheStore() {
|
|
19
|
+
const roots = new Map();
|
|
20
|
+
const pages = new Map();
|
|
21
|
+
function pagesOf(cacheKey) {
|
|
22
|
+
if (!pages.has(cacheKey)) pages.set(cacheKey, new Map());
|
|
23
|
+
return pages.get(cacheKey);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
roots,
|
|
27
|
+
pages,
|
|
28
|
+
async read(cacheKey) {
|
|
29
|
+
if (!roots.has(cacheKey)) return null;
|
|
30
|
+
return {
|
|
31
|
+
...structuredClone(roots.get(cacheKey)),
|
|
32
|
+
pages: Object.fromEntries(structuredClone([...pagesOf(cacheKey).entries()])),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
async writeRoot({ cacheKey, entry }) {
|
|
36
|
+
roots.set(cacheKey, structuredClone(entry));
|
|
37
|
+
},
|
|
38
|
+
async writePage({ cacheKey, pageId, page }) {
|
|
39
|
+
pagesOf(cacheKey).set(pageId, structuredClone(page));
|
|
40
|
+
},
|
|
41
|
+
async removeEntry({ cacheKey }) {
|
|
42
|
+
roots.delete(cacheKey);
|
|
43
|
+
pages.delete(cacheKey);
|
|
44
|
+
},
|
|
45
|
+
async removeApp({ appId }) {
|
|
46
|
+
[...roots.keys(), ...pages.keys()]
|
|
47
|
+
.filter((cacheKey) => cacheKey.startsWith(`${appId}:`))
|
|
48
|
+
.forEach((cacheKey) => {
|
|
49
|
+
roots.delete(cacheKey);
|
|
50
|
+
pages.delete(cacheKey);
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
async removePages({ cacheKey, pageIds }) {
|
|
54
|
+
pageIds.forEach((pageId) => pagesOf(cacheKey).delete(pageId));
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default createMemoryCacheStore;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// A call that never reached the server, as the shell's fetch rejects with it (withNetworkErrors).
|
|
18
|
+
function createNetworkError() {
|
|
19
|
+
const error = new TypeError('Network request failed');
|
|
20
|
+
error.isNetworkError = true;
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default createNetworkError;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import { View } from 'react-native';
|
|
19
|
+
|
|
20
|
+
// A stand-in for expo-image's Image, whose view needs a native module Jest does not have. It keeps
|
|
21
|
+
// its props, so a test reads the source and fires the load and error events:
|
|
22
|
+
// fireEvent(image, 'load', { cacheType: 'none' }) or fireEvent(image, 'error', { error: '' }).
|
|
23
|
+
function Image({ cachePolicy, contentFit, onError, onLoad, recyclingKey, source, style, testID }) {
|
|
24
|
+
return (
|
|
25
|
+
<View
|
|
26
|
+
cachePolicy={cachePolicy}
|
|
27
|
+
contentFit={contentFit}
|
|
28
|
+
onError={onError}
|
|
29
|
+
onLoad={onLoad}
|
|
30
|
+
recyclingKey={recyclingKey}
|
|
31
|
+
source={source}
|
|
32
|
+
style={style}
|
|
33
|
+
testID={testID ?? 'expo-image'}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The caches sign-out clears; a test spies on them.
|
|
39
|
+
Image.clearMemoryCache = async () => true;
|
|
40
|
+
Image.clearDiskCache = async () => true;
|
|
41
|
+
|
|
42
|
+
export { Image };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// A stand-in for expo-notifications: nothing is delivered, no response launched the app, and
|
|
18
|
+
// permission is not granted, so no push token is registered in tests.
|
|
19
|
+
const subscription = () => ({ remove: () => undefined });
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
DEFAULT_ACTION_IDENTIFIER: 'expo.modules.notifications.actions.DEFAULT',
|
|
23
|
+
SchedulableTriggerInputTypes: { DATE: 'date', TIME_INTERVAL: 'timeInterval' },
|
|
24
|
+
addNotificationReceivedListener: subscription,
|
|
25
|
+
addNotificationResponseReceivedListener: subscription,
|
|
26
|
+
addPushTokenListener: subscription,
|
|
27
|
+
cancelAllScheduledNotificationsAsync: async () => undefined,
|
|
28
|
+
cancelScheduledNotificationAsync: async () => undefined,
|
|
29
|
+
clearLastNotificationResponse: () => undefined,
|
|
30
|
+
dismissNotificationAsync: async () => undefined,
|
|
31
|
+
getDevicePushTokenAsync: async () => ({ data: 'device-token' }),
|
|
32
|
+
getExpoPushTokenAsync: async () => ({ data: 'ExponentPushToken[test]' }),
|
|
33
|
+
getLastNotificationResponse: () => null,
|
|
34
|
+
getPermissionsAsync: async () => ({ granted: false, status: 'undetermined' }),
|
|
35
|
+
requestPermissionsAsync: async () => ({ granted: false, status: 'denied' }),
|
|
36
|
+
scheduleNotificationAsync: async (request) => request.identifier ?? 'notification-id',
|
|
37
|
+
setBadgeCountAsync: async () => true,
|
|
38
|
+
setNotificationCategoryAsync: async () => null,
|
|
39
|
+
setNotificationHandler: () => undefined,
|
|
40
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// A stand-in for expo-quick-actions: no quick action launched the app.
|
|
18
|
+
module.exports = {
|
|
19
|
+
initial: undefined,
|
|
20
|
+
setItems: async () => undefined,
|
|
21
|
+
addListener: () => ({ remove: () => undefined }),
|
|
22
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// A stand-in for expo-speech-recognition, whose recogniser is a native module Jest does not have.
|
|
18
|
+
// The native blocks' dictation hook asks it whether the phone can recognise speech; here it
|
|
19
|
+
// cannot, so screens render without a mic. blocks-native's own tests drive a fuller stand-in.
|
|
20
|
+
const ExpoSpeechRecognitionModule = {
|
|
21
|
+
abort: () => {},
|
|
22
|
+
addListener: () => ({ remove: () => {} }),
|
|
23
|
+
getSupportedLocales: async () => ({ locales: [], installedLocales: [] }),
|
|
24
|
+
isRecognitionAvailable: () => false,
|
|
25
|
+
requestPermissionsAsync: async () => ({ granted: false, status: 'denied' }),
|
|
26
|
+
start: () => {},
|
|
27
|
+
stop: () => {},
|
|
28
|
+
supportsOnDeviceRecognition: () => false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { ExpoSpeechRecognitionModule };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// A stand-in for expo-task-manager and expo-background-task: tasks are defined and registered on
|
|
18
|
+
// nothing.
|
|
19
|
+
module.exports = {
|
|
20
|
+
BackgroundTaskResult: { Success: 1, Failed: 2 },
|
|
21
|
+
defineTask: () => undefined,
|
|
22
|
+
isTaskRegisteredAsync: async () => false,
|
|
23
|
+
registerTaskAsync: async () => undefined,
|
|
24
|
+
unregisterTaskAsync: async () => undefined,
|
|
25
|
+
};
|