@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,79 @@
|
|
|
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 { RequestError, UserError } from '@lowdefy/errors';
|
|
19
|
+
|
|
20
|
+
import createNetworkError from '../test/createNetworkError.js';
|
|
21
|
+
import reportShellError from './reportShellError.js';
|
|
22
|
+
|
|
23
|
+
function createSession(handleError) {
|
|
24
|
+
return { getState: () => ({ lowdefy: { _internal: { handleError } } }) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let consoleError;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
consoleError.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('a fault goes to the core handleError once a page has made one', () => {
|
|
38
|
+
const handleError = jest.fn(async () => {});
|
|
39
|
+
const error = new RequestError('Something went wrong.');
|
|
40
|
+
reportShellError({ error, session: createSession(handleError) });
|
|
41
|
+
expect(handleError).toHaveBeenCalledWith(error);
|
|
42
|
+
expect(consoleError).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('a fault goes to the device console before any page made a core', () => {
|
|
46
|
+
const error = new Error('Push token registration failed.');
|
|
47
|
+
reportShellError({ error, session: { getState: () => ({ lowdefy: { buildId: 'b1' } }) } });
|
|
48
|
+
expect(consoleError).toHaveBeenCalledWith(error);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('a fault goes to the device console when no app is open', () => {
|
|
52
|
+
const error = new Error('The shell could not open its database.');
|
|
53
|
+
reportShellError({ error });
|
|
54
|
+
expect(consoleError).toHaveBeenCalledWith(error);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('no network and expected outcomes are not reported', () => {
|
|
58
|
+
const handleError = jest.fn(async () => {});
|
|
59
|
+
const session = createSession(handleError);
|
|
60
|
+
reportShellError({ error: createNetworkError(), session });
|
|
61
|
+
reportShellError({ error: new UserError('Camera permission was denied.'), session });
|
|
62
|
+
const refusal = new Error('Not allowed.');
|
|
63
|
+
refusal.name = 'AuthorizationError';
|
|
64
|
+
reportShellError({ error: refusal, session });
|
|
65
|
+
expect(handleError).not.toHaveBeenCalled();
|
|
66
|
+
expect(consoleError).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('a handleError that fails falls back to the device console', async () => {
|
|
70
|
+
const error = new Error('Background fetch failed.');
|
|
71
|
+
reportShellError({
|
|
72
|
+
error,
|
|
73
|
+
session: createSession(async () => {
|
|
74
|
+
throw new Error('Logger gone.');
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
78
|
+
expect(consoleError).toHaveBeenCalledWith(error);
|
|
79
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Marks a rejection of the platform's fetch as a network failure (isNetworkError): the request never
|
|
19
|
+
* reached the server, so the offline queue keeps it and the screens say there is no connection.
|
|
20
|
+
* Marked where the platform answers, so a programming fault later in the send path (a TypeError
|
|
21
|
+
* reading the response) is never taken for "offline". A call the caller aborted is not marked.
|
|
22
|
+
*
|
|
23
|
+
* @param {Function} fetch The platform's fetch (React Native's, expo/fetch, or Lowdefy Dev's).
|
|
24
|
+
*/
|
|
25
|
+
function withNetworkErrors(fetch) {
|
|
26
|
+
return async function networkErrorFetch(url, init) {
|
|
27
|
+
try {
|
|
28
|
+
return await fetch(url, init);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (init?.signal?.aborted !== true && error instanceof Error) {
|
|
31
|
+
error.isNetworkError = true;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default withNetworkErrors;
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
import isNetworkError from './isNetworkError.js';
|
|
20
|
+
import withNetworkErrors from './withNetworkErrors.js';
|
|
21
|
+
|
|
22
|
+
test('a call the platform fetch rejects is marked as a network failure', async () => {
|
|
23
|
+
const failure = new TypeError('Network request failed');
|
|
24
|
+
const fetch = withNetworkErrors(jest.fn(async () => Promise.reject(failure)));
|
|
25
|
+
const error = await fetch('https://app.example.com/api/user', {}).catch((caught) => caught);
|
|
26
|
+
expect(error).toBe(failure);
|
|
27
|
+
expect(isNetworkError(error)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('expo/fetch failures, which are not TypeErrors, are marked too', async () => {
|
|
31
|
+
const failure = new Error('fetch failed: The Internet connection appears to be offline.');
|
|
32
|
+
const fetch = withNetworkErrors(jest.fn(async () => Promise.reject(failure)));
|
|
33
|
+
const error = await fetch('https://app.example.com/api/agent', {}).catch((caught) => caught);
|
|
34
|
+
expect(isNetworkError(error)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('a call the caller aborted is not a network failure', async () => {
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
controller.abort();
|
|
40
|
+
const failure = new Error('The operation was aborted.');
|
|
41
|
+
const fetch = withNetworkErrors(jest.fn(async () => Promise.reject(failure)));
|
|
42
|
+
const error = await fetch('https://app.example.com/api/agent', {
|
|
43
|
+
signal: controller.signal,
|
|
44
|
+
}).catch((caught) => caught);
|
|
45
|
+
expect(isNetworkError(error)).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('a TypeError thrown after the answer arrived is not a network failure', async () => {
|
|
49
|
+
const response = { ok: true, status: 200 };
|
|
50
|
+
const fetch = withNetworkErrors(jest.fn(async () => response));
|
|
51
|
+
const res = await fetch('https://app.example.com/api/user', {});
|
|
52
|
+
expect(res).toBe(response);
|
|
53
|
+
let fault;
|
|
54
|
+
try {
|
|
55
|
+
res.headers.get('etag');
|
|
56
|
+
} catch (error) {
|
|
57
|
+
fault = error;
|
|
58
|
+
}
|
|
59
|
+
expect(fault).toBeInstanceOf(TypeError);
|
|
60
|
+
expect(isNetworkError(fault)).toBe(false);
|
|
61
|
+
});
|
|
@@ -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
|
+
// Every call a shell makes carries x-lowdefy-shell, its version: the server serves mobile pages
|
|
18
|
+
// only to shells and web pages only to browsers.
|
|
19
|
+
function withShellHeader({ fetch, shellVersion }) {
|
|
20
|
+
return function shellHeaderFetch(url, init = {}) {
|
|
21
|
+
return fetch(url, { ...init, headers: { ...init.headers, 'x-lowdefy-shell': shellVersion } });
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default withShellHeader;
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
import withShellHeader from './withShellHeader.js';
|
|
20
|
+
|
|
21
|
+
test('every call carries the shell version as x-lowdefy-shell', async () => {
|
|
22
|
+
const fetch = jest.fn(async () => ({ status: 200 }));
|
|
23
|
+
const shellFetch = withShellHeader({ fetch, shellVersion: '7.4.0' });
|
|
24
|
+
await shellFetch('https://app.example.com/api/page/home', { headers: { Accept: 'x' } });
|
|
25
|
+
await shellFetch('https://app.example.com/api/user');
|
|
26
|
+
expect(fetch.mock.calls[0][1].headers).toEqual({ Accept: 'x', 'x-lowdefy-shell': '7.4.0' });
|
|
27
|
+
expect(fetch.mock.calls[1][1].headers).toEqual({ 'x-lowdefy-shell': '7.4.0' });
|
|
28
|
+
});
|
|
@@ -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 { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The one place the shell turns `_js` source into functions. A shell is shared by every app, so
|
|
21
|
+
* it cannot carry an app's compiled clientJsMap: each mobile page, and the mobile root for the
|
|
22
|
+
* app events, carries its `_js` as `_jsEntries`, the module text the build generates
|
|
23
|
+
* (`export default { [hash]: fn }`). It is evaluated with `new Function`, as the dev server
|
|
24
|
+
* evaluates a page it builds on request (server-dev usePageConfig), and keeps the `volatile` flag
|
|
25
|
+
* the module sets for dependency tracking.
|
|
26
|
+
*
|
|
27
|
+
* Hermes must run `new Function` for this to work. Should it not, `_js` on mobile pages becomes a
|
|
28
|
+
* build error, and this function the one place that refuses it.
|
|
29
|
+
*
|
|
30
|
+
* @param {string|null|undefined} jsEntries The module text; none when the page has no `_js`.
|
|
31
|
+
* @returns {Object} `{ [hash]: fn }`, the jsMap the engine's `_js` operator reads.
|
|
32
|
+
*/
|
|
33
|
+
function evaluateJsEntries(jsEntries) {
|
|
34
|
+
if (type.isNone(jsEntries)) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
const define = new Function('exports', jsEntries.replace('export default', 'exports.default ='));
|
|
38
|
+
const module = {};
|
|
39
|
+
define(module);
|
|
40
|
+
return module.default;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default evaluateJsEntries;
|
|
@@ -0,0 +1,48 @@
|
|
|
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 evaluateJsEntries from './evaluateJsEntries.js';
|
|
18
|
+
import helloJsEntries from '../../fixtures/hello.jsEntries.json';
|
|
19
|
+
|
|
20
|
+
const accessors = {
|
|
21
|
+
lowdefyGlobal: (key) => ({ greeting: 'Hello from Lowdefy' })[key],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
test('evaluateJsEntries turns the hello page module text into its _js functions', () => {
|
|
25
|
+
const jsMap = evaluateJsEntries(helloJsEntries.today);
|
|
26
|
+
expect(Object.keys(jsMap)).toEqual([
|
|
27
|
+
'HorPchxU3cTNYhB6dbx9DFcepx4=',
|
|
28
|
+
'b+IYcPY3kW9J7YdZ4RyICtelfz8=',
|
|
29
|
+
]);
|
|
30
|
+
expect(jsMap['HorPchxU3cTNYhB6dbx9DFcepx4='](accessors)).toBe('Hello from Lowdefy');
|
|
31
|
+
expect(jsMap['b+IYcPY3kW9J7YdZ4RyICtelfz8='](accessors)).toBe(String(new Date().getFullYear()));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('evaluateJsEntries keeps the volatile flag dependency tracking reads', () => {
|
|
35
|
+
const jsMap = evaluateJsEntries(helloJsEntries.today);
|
|
36
|
+
expect(jsMap['b+IYcPY3kW9J7YdZ4RyICtelfz8='].volatile).toBe(true);
|
|
37
|
+
expect(jsMap['HorPchxU3cTNYhB6dbx9DFcepx4='].volatile).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('evaluateJsEntries evaluates the app events module text of the root', () => {
|
|
41
|
+
const jsMap = evaluateJsEntries(helloJsEntries.app);
|
|
42
|
+
expect(jsMap['yHZGzdxAuSQ8P5USi0AFFY81iNg='](accessors)).toBe('Hello from Lowdefy');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('evaluateJsEntries gives an empty map for a page without _js', () => {
|
|
46
|
+
expect(evaluateJsEntries(undefined)).toEqual({});
|
|
47
|
+
expect(evaluateJsEntries(null)).toEqual({});
|
|
48
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
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 { appStorageKey, type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import loadDayjsLocale from './loadDayjsLocale.js';
|
|
20
|
+
import resolveActiveLocale from './resolveActiveLocale.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The locale of one app in the shell, kept as the web client keeps it (useLocale): the app's
|
|
24
|
+
* i18n config from the mobile root, the person's choice that SetLocale sets and the device store
|
|
25
|
+
* keeps under the app's key, and the device's locale.
|
|
26
|
+
*
|
|
27
|
+
* `apply` points the app at its active locale: `lowdefy.i18n` (read by `_t`, `_locale` and
|
|
28
|
+
* translate), the window members the engine trace reads, and the dayjs locale `_dayjs` formats
|
|
29
|
+
* with. Screens call it as they render and re-render on `subscribe`.
|
|
30
|
+
*
|
|
31
|
+
* It sets `window.__lowdefy_setLocale`, which actions-core's SetLocale calls.
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} params
|
|
34
|
+
* @param {() => string[]} params.getDeviceLocales
|
|
35
|
+
* @param {Object} params.globals The app's host globals.
|
|
36
|
+
* @param {Object} params.session The app's session (createAppSession).
|
|
37
|
+
* @param {Object} params.storage The device store.
|
|
38
|
+
*/
|
|
39
|
+
function createShellLocale({ getDeviceLocales, globals, session, storage }) {
|
|
40
|
+
const listeners = new Set();
|
|
41
|
+
let version = 0;
|
|
42
|
+
|
|
43
|
+
function getStorageKey() {
|
|
44
|
+
const { rootConfig } = session.getState();
|
|
45
|
+
return appStorageKey({
|
|
46
|
+
basePath: '',
|
|
47
|
+
name: 'lowdefy_locale',
|
|
48
|
+
slug: rootConfig.lowdefyApp.slug,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A host store may throw when the platform blocks storage, as the core allows; the choice then
|
|
53
|
+
// lasts only while the app runs.
|
|
54
|
+
function readPreference() {
|
|
55
|
+
try {
|
|
56
|
+
return storage.get(getStorageKey()) ?? 'auto';
|
|
57
|
+
} catch {
|
|
58
|
+
return 'auto';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writePreference(preference) {
|
|
63
|
+
try {
|
|
64
|
+
if (preference === 'auto') {
|
|
65
|
+
storage.remove(getStorageKey());
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
storage.set(getStorageKey(), preference);
|
|
69
|
+
} catch {
|
|
70
|
+
// See readPreference.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function apply() {
|
|
75
|
+
const { lowdefy, rootConfig } = session.getState();
|
|
76
|
+
const { i18n } = rootConfig;
|
|
77
|
+
if (type.isNone(i18n.defaultLocale)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const active = resolveActiveLocale({
|
|
81
|
+
deviceLocales: getDeviceLocales(),
|
|
82
|
+
i18n,
|
|
83
|
+
preference: readPreference(),
|
|
84
|
+
});
|
|
85
|
+
lowdefy.i18n = { ...i18n, active };
|
|
86
|
+
globals.window.__lowdefy_locale = active;
|
|
87
|
+
globals.window.__lowdefy_supported_locales = i18n.locales;
|
|
88
|
+
const dayjsId = i18n.locales.find((locale) => locale.code === active)?.dayjs;
|
|
89
|
+
if (type.isString(dayjsId)) {
|
|
90
|
+
loadDayjsLocale(dayjsId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Applied now, not on the next render: SetLocale updates the engine straight after.
|
|
95
|
+
function setPreference(code) {
|
|
96
|
+
writePreference(code ?? 'auto');
|
|
97
|
+
apply();
|
|
98
|
+
version += 1;
|
|
99
|
+
listeners.forEach((listener) => listener());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function subscribe(listener) {
|
|
103
|
+
listeners.add(listener);
|
|
104
|
+
return () => listeners.delete(listener);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
globals.window.__lowdefy_setLocale = setPreference;
|
|
108
|
+
|
|
109
|
+
return { apply, getVersion: () => version, setPreference, subscribe };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default createShellLocale;
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
import createMemoryStore from '../host/createMemoryStore.js';
|
|
20
|
+
import createShellLocale from './createShellLocale.js';
|
|
21
|
+
|
|
22
|
+
const i18n = {
|
|
23
|
+
defaultLocale: 'en-US',
|
|
24
|
+
locales: [
|
|
25
|
+
{ code: 'en-US', dayjs: 'en' },
|
|
26
|
+
{ code: 'af-ZA', dayjs: 'af' },
|
|
27
|
+
],
|
|
28
|
+
messages: {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function createLocale(rootI18n) {
|
|
32
|
+
const lowdefy = {};
|
|
33
|
+
const session = {
|
|
34
|
+
getState: () => ({ lowdefy, rootConfig: { i18n: rootI18n, lowdefyApp: { slug: 'field' } } }),
|
|
35
|
+
};
|
|
36
|
+
const globals = { window: {} };
|
|
37
|
+
const storage = createMemoryStore();
|
|
38
|
+
const locale = createShellLocale({
|
|
39
|
+
getDeviceLocales: () => ['en-ZA'],
|
|
40
|
+
globals,
|
|
41
|
+
session,
|
|
42
|
+
storage,
|
|
43
|
+
});
|
|
44
|
+
return { globals, locale, lowdefy, storage };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test('createShellLocale points lowdefy.i18n and the window at the active locale', () => {
|
|
48
|
+
const { globals, locale, lowdefy } = createLocale(i18n);
|
|
49
|
+
locale.apply();
|
|
50
|
+
expect(lowdefy.i18n).toEqual({ ...i18n, active: 'en-US' });
|
|
51
|
+
expect(globals.window.__lowdefy_locale).toBe('en-US');
|
|
52
|
+
expect(globals.window.__lowdefy_supported_locales).toBe(i18n.locales);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('SetLocale sets the choice through the window, keeps it and tells the screens', () => {
|
|
56
|
+
const { globals, locale, lowdefy, storage } = createLocale(i18n);
|
|
57
|
+
const listener = jest.fn();
|
|
58
|
+
locale.subscribe(listener);
|
|
59
|
+
globals.window.__lowdefy_setLocale('af-ZA');
|
|
60
|
+
expect(lowdefy.i18n.active).toBe('af-ZA');
|
|
61
|
+
expect(storage.get('lowdefy:field::lowdefy_locale')).toBe('af-ZA');
|
|
62
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(locale.getVersion()).toBe(1);
|
|
64
|
+
globals.window.__lowdefy_setLocale('auto');
|
|
65
|
+
expect(storage.keys()).toEqual([]);
|
|
66
|
+
expect(lowdefy.i18n.active).toBe('en-US');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('createShellLocale leaves an app without i18n alone', () => {
|
|
70
|
+
const { globals, locale, lowdefy } = createLocale({});
|
|
71
|
+
locale.apply();
|
|
72
|
+
expect(lowdefy.i18n).toBeUndefined();
|
|
73
|
+
expect(globals.window.__lowdefy_locale).toBeUndefined();
|
|
74
|
+
});
|